summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING661
-rw-r--r--README1162
-rw-r--r--actions/accesstoken.php42
-rw-r--r--actions/all.php93
-rw-r--r--actions/allrss.php77
-rw-r--r--actions/api.php200
-rw-r--r--actions/avatarbynickname.php68
-rw-r--r--actions/block.php145
-rw-r--r--actions/confirmaddress.php95
-rw-r--r--actions/deletenotice.php101
-rw-r--r--actions/deleteprofile.php277
-rw-r--r--actions/disfavor.php83
-rw-r--r--actions/doc.php38
-rw-r--r--actions/emailsettings.php330
-rw-r--r--actions/facebookhome.php132
-rw-r--r--actions/facebookinvite.php46
-rw-r--r--actions/facebookremove.php65
-rw-r--r--actions/facebooksettings.php52
-rw-r--r--actions/favor.php94
-rw-r--r--actions/favorited.php99
-rw-r--r--actions/favoritesrss.php73
-rw-r--r--actions/featured.php102
-rw-r--r--actions/finishaddopenid.php103
-rw-r--r--actions/finishimmediate.php65
-rw-r--r--actions/finishopenidlogin.php436
-rw-r--r--actions/finishremotesubscribe.php288
-rw-r--r--actions/foaf.php202
-rw-r--r--actions/imsettings.php270
-rw-r--r--actions/inbox.php55
-rw-r--r--actions/invite.php199
-rw-r--r--actions/login.php152
-rw-r--r--actions/logout.php41
-rw-r--r--actions/microsummary.php46
-rw-r--r--actions/newmessage.php135
-rw-r--r--actions/newnotice.php154
-rw-r--r--actions/noticesearch.php164
-rw-r--r--actions/noticesearchrss.php70
-rw-r--r--actions/nudge.php84
-rw-r--r--actions/openidlogin.php92
-rw-r--r--actions/openidsettings.php156
-rw-r--r--actions/opensearch.php59
-rw-r--r--actions/othersettings.php181
-rw-r--r--actions/outbox.php56
-rw-r--r--actions/peoplesearch.php84
-rw-r--r--actions/peopletag.php103
-rw-r--r--actions/postnotice.php88
-rw-r--r--actions/profilesettings.php439
-rw-r--r--actions/public.php99
-rw-r--r--actions/publicrss.php57
-rw-r--r--actions/publicxrds.php79
-rw-r--r--actions/recoverpassword.php331
-rw-r--r--actions/register.php262
-rw-r--r--actions/remotesubscribe.php386
-rw-r--r--actions/replies.php94
-rw-r--r--actions/repliesrss.php79
-rw-r--r--actions/requesttoken.php42
-rw-r--r--actions/showfavorites.php97
-rw-r--r--actions/showmessage.php100
-rw-r--r--actions/shownotice.php116
-rw-r--r--actions/showstream.php450
-rw-r--r--actions/smssettings.php331
-rw-r--r--actions/subedit.php89
-rw-r--r--actions/subscribe.php78
-rw-r--r--actions/subscribers.php61
-rw-r--r--actions/subscriptions.php78
-rw-r--r--actions/sup.php81
-rw-r--r--actions/tag.php165
-rw-r--r--actions/tagother.php193
-rw-r--r--actions/tagrss.php61
-rw-r--r--actions/twitapiaccount.php99
-rw-r--r--actions/twitapiblocks.php69
-rw-r--r--actions/twitapidirect_messages.php287
-rw-r--r--actions/twitapifavorites.php175
-rw-r--r--actions/twitapifriendships.php155
-rw-r--r--actions/twitapihelp.php52
-rw-r--r--actions/twitapinotifications.php37
-rw-r--r--actions/twitapistatuses.php563
-rw-r--r--actions/twitapiusers.php118
-rw-r--r--actions/twittersettings.php378
-rw-r--r--actions/unblock.php92
-rw-r--r--actions/unsubscribe.php80
-rw-r--r--actions/updateprofile.php174
-rw-r--r--actions/userauthorization.php579
-rw-r--r--actions/userbyid.php49
-rw-r--r--actions/userrss.php90
-rw-r--r--actions/xrds.php132
-rw-r--r--classes/Avatar.php95
-rw-r--r--classes/Channel.php200
-rw-r--r--classes/Command.php376
-rw-r--r--classes/CommandInterpreter.php196
-rw-r--r--classes/Confirm_address.php29
-rw-r--r--classes/Consumer.php23
-rw-r--r--classes/Fave.php37
-rw-r--r--classes/Foreign_link.php76
-rw-r--r--classes/Foreign_service.php24
-rw-r--r--classes/Foreign_subscription.php23
-rw-r--r--classes/Foreign_user.php70
-rw-r--r--classes/Invitation.php24
-rw-r--r--classes/Memcached_DataObject.php194
-rw-r--r--classes/Message.php68
-rw-r--r--classes/Nonce.php25
-rw-r--r--classes/Notice.php539
-rw-r--r--classes/NoticeWrapper.php59
-rw-r--r--classes/Notice_inbox.php40
-rw-r--r--classes/Notice_source.php24
-rw-r--r--classes/Notice_tag.php55
-rw-r--r--classes/Profile.php159
-rw-r--r--classes/Profile_block.php49
-rw-r--r--classes/Profile_tag.php101
-rw-r--r--classes/Queue_item.php55
-rw-r--r--classes/Remember_me.php24
-rw-r--r--classes/Remote_profile.php45
-rw-r--r--classes/Reply.php23
-rw-r--r--classes/Sms_carrier.php28
-rw-r--r--classes/Subscription.php51
-rw-r--r--classes/Token.php26
-rw-r--r--classes/User.php473
-rw-r--r--classes/User_openid.php24
-rw-r--r--classes/laconica.ini344
-rw-r--r--classes/laconica.links.ini43
-rw-r--r--config.php.sample144
-rw-r--r--db/carrier.sql61
-rw-r--r--db/foreign_services.sql8
-rw-r--r--db/laconica.sql370
-rw-r--r--db/laconica_pg.sql329
-rw-r--r--doc/about10
-rw-r--r--doc/contact24
-rw-r--r--doc/faq42
-rw-r--r--doc/help29
-rw-r--r--doc/im35
-rw-r--r--doc/openid11
-rw-r--r--doc/openmublog25
-rw-r--r--doc/privacy45
-rw-r--r--doc/source12
-rw-r--r--extlib/Apache2.0.txt202
-rw-r--r--extlib/Auth/OpenID.php552
-rw-r--r--extlib/Auth/OpenID/AX.php1023
-rw-r--r--extlib/Auth/OpenID/Association.php613
-rw-r--r--extlib/Auth/OpenID/BigMath.php471
-rw-r--r--extlib/Auth/OpenID/Consumer.php2229
-rw-r--r--extlib/Auth/OpenID/CryptUtil.php109
-rw-r--r--extlib/Auth/OpenID/DatabaseConnection.php131
-rw-r--r--extlib/Auth/OpenID/DiffieHellman.php113
-rw-r--r--extlib/Auth/OpenID/Discover.php548
-rw-r--r--extlib/Auth/OpenID/DumbStore.php100
-rw-r--r--extlib/Auth/OpenID/Extension.php62
-rw-r--r--extlib/Auth/OpenID/FileStore.php618
-rw-r--r--extlib/Auth/OpenID/HMAC.php99
-rw-r--r--extlib/Auth/OpenID/Interface.php197
-rw-r--r--extlib/Auth/OpenID/KVForm.php112
-rw-r--r--extlib/Auth/OpenID/MemcachedStore.php208
-rw-r--r--extlib/Auth/OpenID/Message.php915
-rw-r--r--extlib/Auth/OpenID/MySQLStore.php78
-rw-r--r--extlib/Auth/OpenID/Nonce.php109
-rw-r--r--extlib/Auth/OpenID/PAPE.php301
-rw-r--r--extlib/Auth/OpenID/Parse.php352
-rw-r--r--extlib/Auth/OpenID/PostgreSQLStore.php113
-rw-r--r--extlib/Auth/OpenID/SQLStore.php569
-rw-r--r--extlib/Auth/OpenID/SQLiteStore.php71
-rw-r--r--extlib/Auth/OpenID/SReg.php521
-rw-r--r--extlib/Auth/OpenID/Server.php1760
-rw-r--r--extlib/Auth/OpenID/ServerRequest.php37
-rw-r--r--extlib/Auth/OpenID/TrustRoot.php462
-rw-r--r--extlib/Auth/OpenID/URINorm.php249
-rw-r--r--extlib/Auth/Yadis/HTTPFetcher.php147
-rw-r--r--extlib/Auth/Yadis/Manager.php529
-rw-r--r--extlib/Auth/Yadis/Misc.php59
-rw-r--r--extlib/Auth/Yadis/ParanoidHTTPFetcher.php228
-rw-r--r--extlib/Auth/Yadis/ParseHTML.php259
-rw-r--r--extlib/Auth/Yadis/PlainHTTPFetcher.php251
-rw-r--r--extlib/Auth/Yadis/XML.php374
-rw-r--r--extlib/Auth/Yadis/XRDS.php478
-rw-r--r--extlib/Auth/Yadis/XRI.php234
-rw-r--r--extlib/Auth/Yadis/XRIRes.php72
-rw-r--r--extlib/Auth/Yadis/Yadis.php382
-rw-r--r--extlib/DB.php1489
-rw-r--r--extlib/DB/DataObject.php4152
-rw-r--r--extlib/DB/DataObject/Cast.php546
-rw-r--r--extlib/DB/DataObject/Error.php53
-rw-r--r--extlib/DB/DataObject/Generator.php1553
-rwxr-xr-xextlib/DB/DataObject/createTables.php59
-rw-r--r--extlib/DB/common.php2262
-rw-r--r--extlib/DB/dbase.php510
-rw-r--r--extlib/DB/fbsql.php769
-rw-r--r--extlib/DB/ibase.php1082
-rw-r--r--extlib/DB/ifx.php683
-rw-r--r--extlib/DB/msql.php831
-rw-r--r--extlib/DB/mssql.php963
-rw-r--r--extlib/DB/mysql.php1045
-rw-r--r--extlib/DB/mysqli.php1092
-rw-r--r--extlib/DB/oci8.php1156
-rw-r--r--extlib/DB/odbc.php883
-rw-r--r--extlib/DB/pgsql.php1135
-rw-r--r--extlib/DB/sqlite.php960
-rw-r--r--extlib/DB/storage.php506
-rw-r--r--extlib/DB/sybase.php942
-rw-r--r--extlib/Mail.php238
-rw-r--r--extlib/Mail/RFC822.php940
-rw-r--r--extlib/Mail/mail.php143
-rw-r--r--extlib/Mail/mock.php119
-rw-r--r--extlib/Mail/null.php60
-rw-r--r--extlib/Mail/sendmail.php170
-rw-r--r--extlib/Mail/smtp.php407
-rw-r--r--extlib/Mail/smtpmx.php478
-rw-r--r--extlib/Net/SMTP.php1082
-rw-r--r--extlib/Net/Socket.php592
-rw-r--r--extlib/OAuth.php755
-rw-r--r--extlib/OAuth_LICENSE.txt22
-rw-r--r--extlib/PEAR.php1118
-rw-r--r--extlib/PHP_License_2_02.txt75
-rw-r--r--extlib/PHP_License_3.01.txt68
-rw-r--r--extlib/PHP_Markdown_License.text36
-rw-r--r--extlib/Validate.php1051
-rw-r--r--extlib/XMPPHP/BOSH.php188
-rw-r--r--extlib/XMPPHP/Exception.php41
-rw-r--r--extlib/XMPPHP/Log.php119
-rw-r--r--extlib/XMPPHP/Roster.php163
-rw-r--r--extlib/XMPPHP/XMLObj.php158
-rw-r--r--extlib/XMPPHP/XMLStream.php763
-rw-r--r--extlib/XMPPHP/XMPP.php423
-rw-r--r--extlib/XMPPHP/XMPP_Old.php114
-rw-r--r--extlib/facebook/facebook.php499
-rw-r--r--extlib/facebook/facebook_desktop.php104
-rw-r--r--extlib/facebook/facebookapi_php5_restlib.php2632
-rw-r--r--extlib/facebook/jsonwrapper/JSON/JSON.php806
-rw-r--r--extlib/facebook/jsonwrapper/JSON/LICENSE21
-rw-r--r--extlib/facebook/jsonwrapper/jsonwrapper.php6
-rw-r--r--extlib/facebook/jsonwrapper/jsonwrapper_inner.php23
-rw-r--r--extlib/get_temp_dir.php14
-rw-r--r--extlib/gpl-2.0.txt339
-rw-r--r--extlib/markdown.php1710
-rw-r--r--htaccess.sample150
-rw-r--r--index.php70
-rw-r--r--js/jquery.form.js632
-rw-r--r--js/jquery.js3549
-rw-r--r--js/jquery.min.js32
-rw-r--r--js/util.js179
-rw-r--r--js/xbImportNode.js46
-rw-r--r--lib/Shorturl_api.php121
-rw-r--r--lib/action.php142
-rw-r--r--lib/common.php172
-rw-r--r--lib/daemon.php133
-rw-r--r--lib/deleteaction.php61
-rw-r--r--lib/facebookaction.php283
-rw-r--r--lib/gallery.php320
-rw-r--r--lib/jabber.php300
-rw-r--r--lib/language.php88
-rw-r--r--lib/mail.php309
-rw-r--r--lib/mailbox.php172
-rw-r--r--lib/noticelist.php224
-rw-r--r--lib/oauthstore.php144
-rw-r--r--lib/omb.php299
-rw-r--r--lib/openid.php242
-rw-r--r--lib/personal.php206
-rw-r--r--lib/profilelist.php169
-rw-r--r--lib/queuehandler.php132
-rw-r--r--lib/rssaction.php189
-rw-r--r--lib/search_engines.php116
-rw-r--r--lib/searchaction.php110
-rw-r--r--lib/settingsaction.php119
-rw-r--r--lib/stream.php55
-rw-r--r--lib/subs.php140
-rw-r--r--lib/theme.php35
-rw-r--r--lib/twitter.php202
-rw-r--r--lib/twitterapi.php583
-rw-r--r--lib/util.php2227
-rw-r--r--lib/xmppqueuehandler.php91
-rw-r--r--locale/bg_BG/LC_MESSAGES/laconica.mobin0 -> 71649 bytes
-rw-r--r--locale/bg_BG/LC_MESSAGES/laconica.po2873
-rw-r--r--locale/ca_ES/LC_MESSAGES/laconica.mobin0 -> 31026 bytes
-rw-r--r--locale/ca_ES/LC_MESSAGES/laconica.po2831
-rw-r--r--locale/cs_CZ/LC_MESSAGES/laconica.mobin0 -> 29415 bytes
-rw-r--r--locale/cs_CZ/LC_MESSAGES/laconica.po2838
-rw-r--r--locale/de_DE/LC_MESSAGES/laconica.mobin0 -> 33822 bytes
-rw-r--r--locale/de_DE/LC_MESSAGES/laconica.po2854
-rw-r--r--locale/el/LC_MESSAGES/laconica.mobin0 -> 7230 bytes
-rw-r--r--locale/el/LC_MESSAGES/laconica.po2872
-rw-r--r--locale/en_GB/LC_MESSAGES/laconica.mobin0 -> 18206 bytes
-rw-r--r--locale/en_GB/LC_MESSAGES/laconica.po2875
-rw-r--r--locale/es/LC_MESSAGES/laconica.mobin0 -> 31092 bytes
-rw-r--r--locale/es/LC_MESSAGES/laconica.po2839
-rw-r--r--locale/fr_FR/LC_MESSAGES/laconica.mobin0 -> 15135 bytes
-rw-r--r--locale/fr_FR/LC_MESSAGES/laconica.po3207
-rw-r--r--locale/he_IL/LC_MESSAGES/laconica.po2819
-rw-r--r--locale/it_IT/LC_MESSAGES/laconica.mobin0 -> 59354 bytes
-rw-r--r--locale/it_IT/LC_MESSAGES/laconica.po2968
-rw-r--r--locale/ja_JP/LC_MESSAGES/laconica.mobin0 -> 32408 bytes
-rw-r--r--locale/ja_JP/LC_MESSAGES/laconica.po2774
-rw-r--r--locale/ko/LC_MESSAGES/laconica.mobin0 -> 2611 bytes
-rw-r--r--locale/ko/LC_MESSAGES/laconica.po2998
-rw-r--r--locale/laconica.pot2858
-rw-r--r--locale/mk_MK/LC_MESSAGES/laconica.mobin0 -> 38953 bytes
-rw-r--r--locale/mk_MK/LC_MESSAGES/laconica.po2827
-rw-r--r--locale/nl_NL/LC_MESSAGES/laconica.mobin0 -> 29725 bytes
-rw-r--r--locale/nl_NL/LC_MESSAGES/laconica.po2850
-rw-r--r--locale/no_NB/LC_MESSAGES/laconica.mobin0 -> 9659 bytes
-rw-r--r--locale/no_NB/LC_MESSAGES/laconica.po2914
-rw-r--r--locale/pl_PL/LC_MESSAGES/laconica.mobin0 -> 31573 bytes
-rw-r--r--locale/pl_PL/LC_MESSAGES/laconica.po2848
-rw-r--r--locale/pt/LC_MESSAGES/laconica.mobin0 -> 367 bytes
-rw-r--r--locale/pt/LC_MESSAGES/laconica.po2865
-rw-r--r--locale/pt_BR/LC_MESSAGES/laconica.mobin0 -> 48136 bytes
-rw-r--r--locale/pt_BR/LC_MESSAGES/laconica.po2877
-rw-r--r--locale/ru_RU/LC_MESSAGES/laconica.mobin0 -> 12622 bytes
-rw-r--r--locale/ru_RU/LC_MESSAGES/laconica.po2862
-rw-r--r--locale/sv_SE/LC_MESSAGES/laconica.mobin0 -> 55323 bytes
-rw-r--r--locale/sv_SE/LC_MESSAGES/laconica.po2914
-rw-r--r--locale/te_IN/LC_MESSAGES/laconica.mobin0 -> 30123 bytes
-rw-r--r--locale/te_IN/LC_MESSAGES/laconica.po2870
-rw-r--r--locale/tr_TR/LC_MESSAGES/laconica.mobin0 -> 28610 bytes
-rw-r--r--locale/tr_TR/LC_MESSAGES/laconica.po2842
-rw-r--r--locale/uk_UA/LC_MESSAGES/laconica.mobin0 -> 54241 bytes
-rw-r--r--locale/uk_UA/LC_MESSAGES/laconica.po2898
-rw-r--r--locale/vi_VN/LC_MESSAGES/laconica.mobin0 -> 73406 bytes
-rw-r--r--locale/vi_VN/LC_MESSAGES/laconica.po3157
-rw-r--r--locale/zh_CN/LC_MESSAGES/laconica.po3026
-rw-r--r--locale/zh_hant/LC_MESSAGES/laconica.po2985
-rwxr-xr-xscripts/cleardb.sh11
-rwxr-xr-xscripts/enjitqueuehandler.php128
-rwxr-xr-xscripts/fixup_hashtags.php46
-rwxr-xr-xscripts/fixup_inboxes.php80
-rwxr-xr-xscripts/fixup_notices_rendered.php50
-rwxr-xr-xscripts/fixup_replies.php40
-rwxr-xr-xscripts/getpiddir.php32
-rwxr-xr-xscripts/inbox_users.php109
-rwxr-xr-xscripts/jabberqueuehandler.php63
-rwxr-xr-xscripts/maildaemon.php215
-rwxr-xr-xscripts/ombqueuehandler.php74
-rwxr-xr-xscripts/publicqueuehandler.php61
-rwxr-xr-xscripts/rebuilddb.sh14
-rwxr-xr-xscripts/setpassword.php68
-rwxr-xr-xscripts/sitemap.php376
-rwxr-xr-xscripts/smsqueuehandler.php64
-rwxr-xr-xscripts/sphinx-cron.sh24
-rwxr-xr-xscripts/sphinx-indexer.sh24
-rwxr-xr-xscripts/sphinx.sh15
-rwxr-xr-xscripts/startdaemons.sh31
-rwxr-xr-xscripts/stopdaemons.sh43
-rwxr-xr-xscripts/synctwitterfriends.php58
-rwxr-xr-xscripts/update_pot.sh3
-rwxr-xr-xscripts/update_translations.php66
-rwxr-xr-xscripts/xmppconfirmhandler.php148
-rwxr-xr-xscripts/xmppdaemon.php312
-rw-r--r--sphinx.conf.sample71
-rw-r--r--theme/default/bg-body.gifbin0 -> 154 bytes
-rw-r--r--theme/default/bg-header.gifbin0 -> 2463 bytes
-rw-r--r--theme/default/default-avatar-mini.pngbin0 -> 646 bytes
-rw-r--r--theme/default/default-avatar-profile.pngbin0 -> 2853 bytes
-rw-r--r--theme/default/default-avatar-stream.pngbin0 -> 1487 bytes
-rw-r--r--theme/default/display.css1081
-rw-r--r--theme/default/icon_feed.jpgbin0 -> 521 bytes
-rw-r--r--theme/default/icon_foaf.gifbin0 -> 1144 bytes
-rw-r--r--theme/default/icon_heart-01.gifbin0 -> 67 bytes
-rw-r--r--theme/default/icon_heart-02.gifbin0 -> 74 bytes
-rw-r--r--theme/default/icon_tag-01.gifbin0 -> 586 bytes
-rw-r--r--theme/default/ie6.css73
-rw-r--r--theme/default/ie7.css39
-rw-r--r--theme/default/login-bg.gifbin0 -> 328 bytes
-rw-r--r--theme/identica/bg-body.gifbin0 -> 136 bytes
-rw-r--r--theme/identica/bg-header.gifbin0 -> 2469 bytes
-rw-r--r--theme/identica/default-avatar-mini.pngbin0 -> 646 bytes
-rw-r--r--theme/identica/default-avatar-profile.pngbin0 -> 2853 bytes
-rw-r--r--theme/identica/default-avatar-stream.pngbin0 -> 1487 bytes
-rw-r--r--theme/identica/display.css187
-rw-r--r--theme/identica/facebookapp.css161
-rw-r--r--theme/identica/ie6.css14
-rw-r--r--theme/identica/ie7.css3
-rw-r--r--theme/identica/login-bg.gifbin0 -> 328 bytes
-rw-r--r--theme/identica/logo.pngbin0 -> 818 bytes
-rw-r--r--theme/iphone/bg-body.gifbin0 -> 136 bytes
-rw-r--r--theme/iphone/bg-header.gifbin0 -> 2469 bytes
-rw-r--r--theme/iphone/default-avatar-mini.pngbin0 -> 646 bytes
-rw-r--r--theme/iphone/default-avatar-profile.pngbin0 -> 2853 bytes
-rw-r--r--theme/iphone/default-avatar-stream.pngbin0 -> 1487 bytes
-rw-r--r--theme/iphone/display.css700
-rw-r--r--theme/iphone/display.css.dist686
-rw-r--r--theme/iphone/ie6.css.dist63
-rw-r--r--theme/iphone/ie7.css.dist20
-rw-r--r--theme/iphone/login-bg.gifbin0 -> 328 bytes
-rw-r--r--theme/iphone/logo.pngbin0 -> 818 bytes
380 files changed, 168703 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000..dba13ed2d
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/README b/README
new file mode 100644
index 000000000..1ef31a8c1
--- /dev/null
+++ b/README
@@ -0,0 +1,1162 @@
+------
+README
+------
+
+Laconica 0.6.4 ("Catapult")
+11 December 2008
+
+This is the README file for Laconica, the Open Source microblogging
+platform. It includes installation instructions, descriptions of
+options you can set, warnings, tips, and general info for
+administrators. Information on using Laconica can be found in the
+"doc" subdirectory or in the "help" section on-line.
+
+About
+=====
+
+Laconica (pronounced "luh-KAWN-ih-kuh") is a Free and Open Source
+microblogging platform. It helps people in a community, company or
+group to exchange short (140 character) messages over the Web. Users
+can choose which people to "follow" and receive only their friends' or
+colleagues' status messages. It provides a similar service to sites
+like Twitter, Jaiku, Pownce and Plurk.
+
+With a little work, status messages can be sent to mobile phones,
+instant messenger programs (GTalk/Jabber), and specially-designed
+desktop clients that support the Twitter API.
+
+Laconica supports an open standard called OpenMicroBlogging
+(http://openmicroblogging.org/) that lets users on different Web sites
+or in different companies subscribe to each others' notices. It
+enables a distributed social network spread all across the Web.
+
+Laconica was originally developed for the Open Software Service,
+Identi.ca (http://identi.ca/). It is shared with you in hope that you
+too make an Open Software Service available to your users. To learn
+more, please see the Open Software Service Definition 1.0:
+
+ http://www.openknowledge.org/ossd
+
+License
+=======
+
+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, in the file "COPYING". If not, see
+<http://www.gnu.org/licenses/>.
+
+ IMPORTANT NOTE: The GNU Affero General Public License (AGPL) has
+ *different requirements* from the "regular" GPL. In particular, if
+ you make modifications to the Laconica source code on your server,
+ you *MUST MAKE AVAILABLE* the modified version of the source code
+ to your users under the same license. This is a legal requirement
+ of using the software, and if you do not wish to share your
+ modifications, *YOU MAY NOT INSTALL LACONICA*.
+
+Additional library software has been made available in the 'extlib'
+directory. All of it is Free Software and can be distributed under
+liberal terms, but those terms may differ in detail from the AGPL's
+particulars. See each package's license file in the extlib directory
+for additional terms.
+
+New this version
+================
+
+This is a minor feature and security improvement version from version
+0.6.3 (release 24 Nov 2008). Notable features of version 0.6.4 include:
+
+- "private" installs won't show any data to the outside world; redirect
+ non-logged-in users to login. (See "Private" below)
+- Ability to "block" a subscriber, which forces them to unsubscribe,
+ doesn't allow them to subscribe again, and doesn't allow them to send
+ @-replies
+- Fine-grained control of subscriptions; users can choose not to receive
+ notices from other users over SMS, or IM, or both
+- support for Mozilla microsummaries
+ (https://wiki.mozilla.org/Microsummaries)
+- more efficient support for blacklisting users from the public page
+- instructions on the public page for people who aren't logged in
+- better registration instructions
+- a check for license compatibility in receiving OMB notices
+- HTML output in RSS 1.0, 2.0, and Atom feeds
+- tuned and more reliable 'rememberme' cookies for username/password
+ and OpenID logins
+- a utility for setting user passwords
+- a "ban" configuration variable to ban certain users from posting
+ notices
+- an configurable posting throttle to keep any one user from flooding
+ the site with messages.
+- fine-tuned url-shortening: only shorten if it's needed, only expand
+ certain URLs, and handle failure of URL-shortening services reliably
+- disable Ajax input for notices, subscribe, nudge, while the
+ request is processing
+- early implementation of support for Last-Modified and ETag-based
+ caching
+- initial microformats support
+- redirect on bad nicknames in URLs
+- correctly send emails in recipient's, not sender's, language
+- correct email content type
+- Change "Most Favorited" page to "Popular"
+- properly support the "since" parameter in API calls
+- Fix for changes in validate_credentials API call for the Twitter
+ bridge
+- Fix for fatal error when sending email confirmation on registration
+- Better replies for commands sent through the Ajax channel
+- Add a User-Agent string for OMB requests
+- Upgrade upstream library XMPPHP
+- Upgrade upstream library JQuery Forms
+- Code cleanup: checkboxes have proper <label> elements
+- Code cleanup: consolidated various notice-listing code in one place
+- Better support for unsubscribing from a remote user
+- Stump of experimental Facebook application (not ready for use! code
+ review only!)
+- Stump of experimental user account deletion (not ready for use! code
+ review only!)
+
+Prerequisites
+=============
+
+The following software packages are *required* for this software to
+run correctly.
+
+- PHP 5.2.x. It may be possible to run this software on earlier
+ versions of PHP, but many of the functions used are only available
+ in PHP 5.2 or above.
+- MySQL 5.x. The Laconica database is stored, by default, in a MySQL
+ server. It has been primarily tested on 5.x servers, although it may
+ be possible to install on earlier (or later!) versions. The server
+ *must* support the MyISAM storage engine -- the default for most
+ MySQL servers -- *and* the InnoDB storage engine.
+- A Web server. Preferably, you should have Apache 2.2.x with the
+ mod_rewrite extension installed and enabled.
+
+Your PHP installation must include the following PHP extensions:
+
+- Curl. This is for fetching files by HTTP.
+- XMLWriter. This is for formatting XML and HTML output.
+- MySQL. For accessing the database.
+- GD. For scaling down avatar images.
+- mbstring. For handling Unicode (UTF-8) encoded strings.
+- gettext. For multiple languages. Default on many PHP installs.
+
+For some functionality, you will also need the following extensions:
+
+- Memcache. A client for the memcached server, which caches database
+ information in volatile memory. This is important for adequate
+ performance on high-traffic sites. You will also need a memcached
+ server to store the data in.
+- Mailparse. Efficient parsing of email requires this extension.
+ Submission by email or SMS-over-email uses this extension.
+- Sphinx Search. A client for the sphinx server, an alternative
+ to MySQL or Postgresql fulltext search. You will also need a
+ Sphinx server to serve the search queries.
+
+You will almost definitely get 2-3 times better performance from your
+site if you install a PHP bytecode cache/accelerator. Some well-known
+examples are: eaccelerator, Turck mmcache, xcache, apc. Zend Optimizer
+is a proprietary accelerator installed on some hosting sites.
+
+External libraries
+------------------
+
+A number of external PHP libraries are used to provide basic
+functionality and optional functionality for your system. For your
+convenience, they are available in the "extlib" directory of this
+package, and you do not have to download and install them. However,
+you may want to keep them up-to-date with the latest upstream version,
+and the URLs are listed here for your convenience.
+
+- DB_DataObject http://pear.php.net/package/DB_DataObject
+- Validate http://pear.php.net/package/Validate
+- OpenID from OpenIDEnabled (not the PEAR version!). We decided
+ to use the openidenabled.com version since it's more widely
+ implemented, and seems to be better supported.
+ http://openidenabled.com/php-openid/
+- PEAR DB. Although this is an older data access system (new
+ packages should probably use PHP DBO), the OpenID libraries
+ depend on PEAR DB so we use it here, too. DB_DataObject can
+ also use PEAR MDB2, which may give you better performance
+ but won't work with OpenID.
+ http://pear.php.net/package/DB
+- OAuth.php from http://oauth.googlecode.com/svn/code/php/
+- markdown.php from http://michelf.com/projects/php-markdown/
+- PEAR Mail, for sending out mail notifications
+ http://pear.php.net/package/Mail
+- PEAR Net_SMTP, if you use the SMTP factory for notifications
+ http://pear.php.net/package/Net_SMTP
+- PEAR Net_Socket, if you use the SMTP factory for notifications
+ http://pear.php.net/package/Net_Socket
+- XMPPHP, the follow-up to Class.Jabber.php. Probably the best XMPP
+ library available for PHP. http://xmpphp.googlecode.com/. Note that
+ as of this writing the version of this library that is available in
+ the extlib directory is *significantly different* from the upstream
+ version (patches have been submitted). Upgrading to the upstream
+ version may render your Laconica site unable to send or receive XMPP
+ messages.
+
+A design goal of Laconica is that the basic Web functionality should
+work on even the most restrictive commercial hosting services.
+However, additional functionality, such as receiving messages by
+Jabber/GTalk, require that you be able to run long-running processes
+on your account. In addition, posting by email or from SMS require
+that you be able to install a mail filter in your mail server.
+
+Installation
+============
+
+Installing the basic Laconica Web component is relatively easy,
+especially if you've previously installed PHP/MySQL packages.
+
+1. Unpack the tarball you downloaded on your Web server. Usually a
+ command like this will work:
+
+ tar zxf laconica-0.6.4.tar.gz
+
+ ...which will make a laconica-0.6.4 subdirectory in your current
+ directory. (If you don't have shell access on your Web server, you
+ may have to unpack the tarball on your local computer and FTP the
+ files to the server.)
+
+2. Move the tarball to a directory of your choosing in your Web root
+ directory. Usually something like this will work:
+
+ mv laconica-0.6.4 /var/www/mublog
+
+ This will make your Laconica instance available in the mublog path of
+ your server, like "http://example.net/mublog". "microblog" or
+ "laconica" might also be good path names. If you know how to
+ configure virtual hosts on your web server, you can try setting up
+ "http://micro.example.net/" or the like.
+
+3. You should also take this moment to make your avatar subdirectory
+ writeable by the Web server. An insecure way to do this is:
+
+ chmod a+w /var/www/mublog/avatar
+
+ On some systems, this will probably work:
+
+ chgrp www-data /var/www/mublog/avatar
+ chmod g+w /var/www/mublog/avatar
+
+ If your Web server runs as another user besides "www-data", try
+ that user's default group instead. As a last resort, you can create
+ a new group like "avatar" and add the Web server's user to the group.
+
+4. Create a database to hold your microblog data. Something like this
+ should work:
+
+ mysqladmin -u "username" --password="password" create laconica
+
+ Note that Laconica must have its own database; you can't share the
+ database with another program. You can name it whatever you want,
+ though.
+
+ (If you don't have shell access to your server, you may need to use
+ a tool like PHPAdmin to create a database. Check your hosting
+ service's documentation for how to create a new MySQL database.)
+
+5. Run the laconica.sql SQL script in the db subdirectory to create
+ the database tables in the database. A typical system would work
+ like this:
+
+ mysql -u "username" --password="password" laconica < /var/www/mublog/db/laconica.sql
+
+ You may want to test by logging into the database and checking that
+ the tables were created. Here's an example:
+
+ SHOW TABLES;
+
+6. Create a new database account that Laconica will use to access the
+ database. If you have shell access, this will probably work from the
+ MySQL shell:
+
+ GRANT SELECT,INSERT,DELETE,UPDATE on laconica.*
+ TO 'lacuser'@'localhost'
+ IDENTIFIED BY 'lacpassword';
+
+ You should change 'lacuser' and 'lacpassword' to your preferred new
+ username and password. You may want to test logging in as this new
+ user and testing that you can SELECT from some of the tables in the
+ DB (use SHOW TABLES to see which ones are there).
+
+7. Copy the config.php.sample in the Laconica directory to config.php.
+
+8. Edit config.php to set the basic configuration for your system.
+ (See descriptions below for basic config options.) Note that there
+ are lots of options and if you try to do them all at once, you will
+ have a hard time making sure what's working and what's not. So,
+ stick with the basics at first. In particular, customizing the
+ 'site' and 'db' settings will almost definitely be needed.
+
+9. At this point, you should be able to navigate in a browser to your
+ microblog's main directory and see the "Public Timeline", which
+ will be empty. If not, magic has happened! You can now register a
+ new user, post some notices, edit your profile, etc. However, you
+ may want to wait to do that stuff if you think you can set up
+ "fancy URLs" (see below), since some URLs are stored in the database.
+
+Fancy URLs
+----------
+
+By default, Laconica will have big long sloppy URLs that are hard for
+people to remember or use. For example, a user's home profile might be
+found at:
+
+ http://example.org/mublog/index.php?action=showstream&nickname=fred
+
+It's possible to configure the software so it looks like this instead:
+
+ http://example.org/mublog/fred
+
+These "fancy URLs" are more readable and memorable for users. To use
+fancy URLs, you must either have Apache 2.2.x with .htaccess enabled
+and mod_redirect enabled, -OR- know how to configure "url redirection"
+in your server.
+
+1. Copy the htaccess.sample file to .htaccess in your Laconica
+ directory. Note: if you have control of your server's httpd.conf or
+ similar configuration files, it can greatly improve performance to
+ import the .htaccess file into your conf file instead. If you're
+ not sure how to do it, you may save yourself a lot of headache by
+ just leaving the .htaccess file.
+
+2. Change the "RewriteBase" in the new .htaccess file to be the URL path
+ to your Laconica installation on your server. Typically this will
+ be the path to your Laconica directory relative to your Web root.
+
+3. Add or uncomment or change a line in your config.php file so it says:
+
+ $config['site']['fancy'] = true;
+
+You should now be able to navigate to a "fancy" URL on your server,
+like:
+
+ http://example.net/mublog/main/register
+
+If you changed your HTTP server configuration, you may need to restart
+the server first.
+
+If you have problems with the .htaccess file on versions of Apache
+earlier than 2.2.x, try changing the regular expressions in the
+htaccess.sample file that use "\w" to just use ".".
+
+Sphinx
+------
+
+To use a Sphinx server to search users and notices, you also need
+to install, compile and enable the sphinx pecl extension for php on the
+client side, which itself depends on the sphinx development files.
+"pecl install sphinx" should take care of that. Add "extension=sphinx.so"
+to your php.ini and reload apache to enable it.
+
+You can update your MySQL or Postgresql databases to drop their fulltext
+search indexes, since they're now provided by sphinx.
+
+On the sphinx server side, a script reads the main database and build
+the keyword index. A cron job reads the database and keeps the sphinx
+indexes up to date. scripts/sphinx-cron.sh should be called by cron
+every 5 minutes, for example. scripts/sphinx.sh is an init.d script
+to start and stop the sphinx search daemon.
+
+SMS
+---
+
+Laconica supports a cheap-and-dirty system for sending update messages
+to mobile phones and for receiving updates from the mobile. Instead of
+sending through the SMS network itself, which is costly and requires
+buy-in from the wireless carriers, it simply piggybacks on the email
+gateways that many carriers provide to their customers. So, SMS
+configuration is essentially email configuration.
+
+Each user sends to a made-up email address, which they keep a secret.
+Incoming email that is "From" the user's SMS email address, and "To"
+the users' secret email address on the site's domain, will be
+converted to a message and stored in the DB.
+
+For this to work, there *must* be a domain or sub-domain for which all
+(or most) incoming email can pass through the incoming mail filter.
+
+1. Run the SQL script carrier.sql in your Laconica database. This will
+ usually work:
+
+ mysql -u "lacuser" --password="lacpassword" laconica < db/carrier.sql
+
+ This will populate your database with a list of wireless carriers
+ that support email SMS gateways.
+
+2. Make sure the maildaemon.php file is executable:
+
+ chmod +x scripts/maildaemon.php
+
+ Note that "daemon" is kind of a misnomer here; the script is more
+ of a filter than a daemon.
+
+2. Edit /etc/aliases on your mail server and add the following line:
+
+ *: /path/to/laconica/scripts/maildaemon.php
+
+3. Run whatever code you need to to update your aliases database. For
+ many mail servers (Postfix, Exim, Sendmail), this should work:
+
+ newaliases
+
+ You may need to restart your mail server for the new database to
+ take effect.
+
+4. Set the following in your config.php file:
+
+ $config['mail']['domain'] = 'yourdomain.example.net';
+
+At this point, post-by-email and post-by-SMS-gateway should work. Note
+that if your mail server is on a different computer from your email
+server, you'll need to have a full installation of Laconica, a working
+config.php, and access to the Laconica database from the mail server.
+
+XMPP
+----
+
+XMPP (eXtended Message and Presence Protocol, http://xmpp.org/) is the
+instant-messenger protocol that drives Jabber and GTalk IM. You can
+distribute messages via XMPP using the system below; however, you
+need to run the XMPP incoming daemon to allow incoming messages as
+well.
+
+1. You may want to strongly consider setting up your own XMPP server.
+ Ejabberd, OpenFire, and JabberD are all Open Source servers.
+ Jabber, Inc. provides a high-performance commercial server.
+
+2. You must register a Jabber ID (JID) with your new server. It helps
+ to choose a name like "update@example.com" or "notice" or something
+ similar. Alternately, your "update JID" can be registered on a
+ publicly-available XMPP service, like jabber.org or GTalk.
+
+ Laconica will not register the JID with your chosen XMPP server;
+ you need to do this manually, with an XMPP client like Gajim,
+ Telepathy, or Pidgin.im.
+
+3. Configure your site's XMPP variables, as described below in the
+ configuration section.
+
+On a default installation, your site can broadcast messages using
+XMPP. Users won't be able to post messages using XMPP unless you've
+got the XMPP daemon running. See 'Queues and daemons' below for how
+to set that up. Also, once you have a sizable number of users, sending
+a lot of SMS, OMB, and XMPP messages whenever someone posts a message
+can really slow down your site; it may cause posting to timeout.
+
+NOTE: stream_select(), a crucial function for network programming, is
+broken on PHP 5.2.x less than 5.2.6 on amd64-based servers. We don't
+work around this bug in Laconica; current recommendation is to move
+off of amd64 to another server.
+
+Public feed
+-----------
+
+You can send *all* messages from your microblogging site to a
+third-party service using XMPP. This can be useful for providing
+search, indexing, bridging, or other cool services.
+
+To configure a downstream site to receive your public stream, add
+their "JID" (Jabber ID) to your config.php as follows:
+
+ $config['xmpp']['public'][] = 'downstream@example.net';
+
+(Don't miss those square brackets at the end.) Note that your XMPP
+broadcasting must be configured as mentioned above. Although you can
+send out messages at "Web time", high-volume sites should strongly
+consider setting up queues and daemons.
+
+Queues and daemons
+------------------
+
+Some activities that Laconica needs to do, like broadcast OMB, SMS,
+and XMPP messages, can be 'queued' and done by off-line bots instead.
+For this to work, you must be able to run long-running offline
+processes, either on your main Web server or on another server you
+control. (Your other server will still need all the above
+prerequisites, with the exception of Apache.) Installing on a separate
+server is probably a good idea for high-volume sites.
+
+1. You'll need the "CLI" (command-line interface) version of PHP
+ installed on whatever server you use.
+
+2. If you're using a separate server for queues, install Laconica
+ somewhere on the server. You don't need to worry about the
+ .htaccess file, but make sure that your config.php file is close
+ to, or identical to, your Web server's version.
+
+3. In your config.php files (both the Web server and the queues
+ server!), set the following variable:
+
+ $config['queue']['enabled'] = true;
+
+ You may also want to look at the 'daemon' section of this file for
+ more daemon options. Note that if you set the 'user' and/or 'group'
+ options, you'll need to create that user and/or group by hand.
+ They're not created automatically.
+
+4. On the queues server, run the command scripts/startdaemons.sh. It
+ needs as a parameter the install path; if you run it from the
+ Laconica dir, "." should suffice.
+
+This will run six (for now) queue handlers:
+
+* xmppdaemon.php - listens for new XMPP messages from users and stores
+ them as notices in the database.
+* jabberqueuehandler.php - sends queued notices in the database to
+ registered users who should receive them.
+* publicqueuehandler.php - sends queued notices in the database to
+ public feed listeners.
+* ombqueuehandler.php - sends queued notices to OpenMicroBlogging
+ recipients on foreign servers.
+* smsqueuehandler.php - sends queued notices to SMS-over-email addresses
+ of registered users.
+* xmppconfirmhandler.php - sends confirmation messages to registered
+ users.
+
+Note that these queue daemons are pretty raw, and need your care. In
+particular, they leak memory, and you may want to restart them on a
+regular (daily or so) basis with a cron job. Also, if they lose
+the connection to the XMPP server for too long, they'll simply die. It
+may be a good idea to use a daemon-monitoring service, like 'monit',
+to check their status and keep them running.
+
+All the daemons write their process IDs (pids) to /var/run/ by
+default. This can be useful for starting, stopping, and monitoring the
+daemons.
+
+Twitter Friends Syncing
+-----------------------
+
+As of Laconica 0.6.3, users may set a flag in their settings ("Subscribe
+to my Twitter friends here" under the Twitter tab) to have Laconica
+attempt to locate and subscribe to "friends" (people they "follow") on
+Twitter who also have accounts on your Laconica system, and who have
+previously set up a link for automatically posting notices to Twitter.
+
+Optionally, there is a script (./scripts/synctwitterfriends.php), meant
+to be run periodically from a job scheduler (e.g.: cron under Unix), to
+look for new additions to users' friends lists. Note that the friends
+syncing only subscribes users to each other, it does not unsubscribe
+users when they stop following each other on Twitter.
+
+Sample cron job:
+
+# Update Twitter friends subscriptions every half hour
+0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null
+
+Sitemaps
+--------
+
+Sitemap files (http://sitemaps.org/) are a very nice way of telling
+search engines and other interested bots what's available on your site
+and what's changed recently. You can generate sitemap files for your
+Laconica instance.
+
+1. Choose your sitemap URL layout. Laconica creates a number of
+ sitemap XML files for different parts of your site. You may want to
+ put these in a sub-directory of your Laconica directory to avoid
+ clutter. The sitemap index file tells the search engines and other
+ bots where to find all the sitemap files; it *must* be in the main
+ installation directory or higher. Both types of file must be
+ available through HTTP.
+
+2. To generate your sitemaps, run the following command on your server:
+
+ php scripts/sitemap.php -f index-file-path -d sitemap-directory -u URL-prefix-for-sitemaps
+
+ Here, index-file-path is the full path to the sitemap index file,
+ like './sitemapindex.xml'. sitemap-directory is the directory where
+ you want the sitemaps stored, like './sitemaps/' (make sure the dir
+ exists). URL-prefix-for-sitemaps is the full URL for the sitemap dir,
+ typically something like 'http://example.net/mublog/sitemaps/'.
+
+You can use several methods for submitting your sitemap index to
+search engines to get your site indexed. One is to add a line like the
+following to your robots.txt file:
+
+ Sitemap: /mublog/sitemapindex.xml
+
+This is a good idea for letting *all* Web spiders know about your
+sitemap. You can also submit sitemap files to major search engines
+using their respective "Webmaster centres"; see sitemaps.org for links
+to these resources.
+
+Themes
+------
+
+There are two themes shipped with this version of Laconica: "stoica",
+which is what the Identi.ca site uses, and "default", which is a good
+basis for other sites.
+
+As of right now, your ability to change the theme is site-wide; users
+can't choose their own theme. Additionally, the only thing you can
+change in the theme is CSS stylesheets and some image files; you can't
+change the HTML output, like adding or removing menu items.
+
+You can choose a theme using the $config['site']['theme'] element in
+the config.php file. See below for details.
+
+You can add your own theme by making a sub-directory of the 'theme'
+subdirectory with the name of your theme. Each theme can have the
+following files:
+
+display.css: a CSS2 file for "default" styling for all browsers.
+ie6.css: a CSS2 file for override styling for fixing up Internet
+ Explorer 6.
+ie7.css: a CSS2 file for override styling for fixing up Internet
+ Explorer 7.
+logo.png: a logo image for the site.
+default-avatar-profile.png: a 96x96 pixel image to use as the avatar for
+ users who don't upload their own.
+default-avatar-stream.png: Ditto, but 48x48. For streams of notices.
+default-avatar-mini.png: Ditto ditto, but 24x24. For subscriptions
+ listing on profile pages.
+
+You may want to start by copying the files from the default theme to
+your own directory.
+
+Translation
+-----------
+
+Translations in Laconica use the gettext system (http://www.gnu.org/software/gettext/).
+Theoretically, you can add your own sub-directory to the locale/
+subdirectory to add a new language to your system. You'll need to
+compile the ".po" files into ".mo" files, however.
+
+Contributions of translation information to Laconica are very easy:
+you can use the Web interface at http://laconi.ca/entrans/ to add one
+or a few or lots of new translations -- or even new languages. You can
+also download more up-to-date .po files there, if you so desire.
+
+Backups
+-------
+
+There is no built-in system for doing backups in Laconica. You can make
+backups of a working Laconica system by backing up the database and
+the Web directory. To backup the database use mysqldump (http://ur1.ca/7xo)
+and to backup the Web directory, try tar.
+
+Private
+-------
+
+The administrator can set the "private" flag for a site so that it's
+not visible to non-logged-in users. This might be useful for
+workgroups who want to share a microblogging site for project
+management, but host it on a public server.
+
+Note that this is an experimental feature; total privacy is not
+guaranteed or ensured. Also, privacy is all-or-nothing for a site; you
+can't have some accounts or notices private, and others public.
+Finally, the interaction of private sites with OpenMicroBlogging is
+undefined. Remote users won't be able to subscribe to users on a
+private site, but users of the private site may be able to subscribe
+to users on a remote site. (Or not... it's not well tested.) The
+"proper behaviour" hasn't been defined here, so handle with care.
+
+Upgrading
+=========
+
+If you've been using Laconica 0.6, 0.5 or lower, or if you've been
+tracking the "git" version of the software, you will probably want
+to upgrade and keep your existing data. There is no automated upgrade
+procedure in Laconica 0.6.4. Try these step-by-step instructions; read
+to the end first before trying them.
+
+0. Download Laconica and set up all the prerequisites as if you were
+ doing a new install.
+1. Make backups of both your database and your Web directory. UNDER NO
+ CIRCUMSTANCES should you try to do an upgrade without a known-good
+ backup. You have been warned.
+2. Shut down Web access to your site, either by turning off your Web
+ server or by redirecting all pages to a "sorry, under maintenance"
+ page.
+3. Shut down XMPP access to your site, typically by shutting down the
+ xmppdaemon.php process and all other daemons that you're running.
+ If you've got "monit" or "cron" automatically restarting your
+ daemons, make sure to turn that off, too.
+4. Shut down SMS and email access to your site. The easy way to do
+ this is to comment out the line piping incoming email to your
+ maildaemon.php file, and running something like "newaliases".
+5. Once all writing processes to your site are turned off, make a
+ final backup of the Web directory and database.
+6. Move your Laconica directory to a backup spot, like "mublog.bak".
+7. Unpack your Laconica 0.6 tarball and move it to "mublog" or
+ wherever your code used to be.
+8. Copy the config.php file and avatar directory from your old
+ directory to your new directory.
+9. Copy htaccess.sample to .htaccess in the new directory. Change the
+ RewriteBase to use the correct path.
+10. Rebuild the database. Go to your Laconica directory and run the
+ rebuilddb.sh script like this:
+
+ ./scripts/rebuilddb.sh rootuser rootpassword database db/laconica.sql
+
+ Here, rootuser and rootpassword are the username and password for a
+ user who can drop and create databases as well as tables; typically
+ that's _not_ the user Laconica runs as.
+11. Use mysql client to log into your database and make sure that the
+ notice, user, profile, subscription etc. tables are non-empty.
+12. Turn back on the Web server, and check that things still work.
+13. Turn back on XMPP bots and email maildaemon. Note that the XMPP
+ bots have changed since version 0.5; see above for details.
+
+If you're upgrading from very old versions, you may want to look at
+the fixup_* scripts in the scripts directories. These will store some
+precooked data in the DB. All upgraders should check out the inboxes
+options below.
+
+NOTE: the database definition file, stoica.ini, has been renamed to
+laconica.ini (since this is the recommended database name). If you
+have a line in your config.php pointing to the old name, you'll need
+to update it.
+
+Notice inboxes
+--------------
+
+Before version 0.6.2, the page showing all notices from people the
+user is subscribed to ("so-and-so with friends") was calculated at run
+time. Starting with 0.6.2, we have a new data structure for holding a
+user's "notice inbox". (Note: distinct from the "message inbox", which
+is the "inbox" tab in the UI. The notice inbox appears under the
+"Personal" tab.)
+
+Notices are added to the inbox when they're created. This speeds up
+the query considerably, and also allows us the opportunity, in the
+future, to add different kind of notices to an inbox -- like @-replies
+or subscriptions to search terms or hashtags.
+
+Notice inboxes are enabled by default for new installations. If you
+are upgrading an existing site, this means that your users will see
+empty "Personal" pages. The following steps will help you fix the
+problem.
+
+0. $config['inboxes']['enabled'] can be set to one of three values. If
+ you set it to 'false', the site will work as before. Support for this
+ will probably be dropped in future versions.
+1. Setting the flag to 'transitional' means that you're in transition.
+ In this mode, the code will run the "new query" or the "old query"
+ based on whether the user's inbox has been updated.
+2. After setting the flag to "transitional", you can run the
+ fixup_inboxes.php script to create the inboxes. You may want to set
+ the memory limit high. You can re-run it without ill effect.
+3. When fixup_inboxes is finished, you can set the enabled flag to
+ 'true'.
+
+Configuration options
+=====================
+
+The sole configuration file for Laconica (excepting configurations for
+dependency software) is config.php in your Laconica directory. If you
+edit any other file in the directory, like lib/common.php (where most
+of the defaults are defined), you will lose your configuration options
+in any upgrade, and you will wish that you had been more careful.
+
+Almost all configuration options are made through a two-dimensional
+associative array, cleverly named $config. A typical configuration
+line will be:
+
+ $config['section']['option'] = value;
+
+For brevity, the following documentation describes each section and
+option.
+
+site
+----
+
+This section is a catch-all for site-wide variables.
+
+name: the name of your site, like 'YourCompany Microblog'.
+server: the server part of your site's URLs, like 'example.net'.
+path: The path part of your site's URLs, like 'mublog' or '/'
+ (installed in root).
+fancy: whether or not your site uses fancy URLs (see Fancy URLs
+ section above). Default is false.
+logfile: full path to a file for Laconica to save logging
+ information to. You may want to use this if you don't have
+ access to syslog.
+locale_path: full path to the directory for locale data. Unless you
+ store all your locale data in one place, you probably
+ don't need to use this.
+language: default language for your site. Defaults to US English.
+languages: A list of languages supported on your site. Typically you'd
+ only change this if you wanted to disable support for one
+ or another language:
+ "unset($config['site']['languages']['de'])" will disable
+ support for German.
+theme: Theme for your site (see Theme section). Two themes are
+ provided by default: 'default' and 'stoica' (the one used by
+ Identi.ca). It's appreciated if you don't use the 'stoica' theme
+ except as the basis for your own.
+email: contact email address for your site. By default, it's extracted
+ from your Web server environment; you may want to customize it.
+broughtbyurl: name of an organization or individual who provides the
+ service. Each page will include a link to this name in the
+ footer. A good way to link to the blog, forum, wiki,
+ corporate portal, or whoever is making the service available.
+broughtby: text used for the "brought by" link.
+timezone: default timezone for message display. Users can set their
+ own time zone. Defaults to 'UTC', which is a pretty good default.
+closed: If set to 'true', will disallow registration on your site.
+ This is a cheap way to restrict accounts to only one
+ individual or group; just register the accounts you want on
+ the service, *then* set this variable to 'true'.
+inviteonly: If set to 'true', will only allow registration if the user
+ was invited by an existing user.
+private: If set to 'true', anonymous users will be redirected to the
+ 'login' page. Also, API methods that normally require no
+ authentication will require it. Note that this does not turn
+ off registration; use 'closed' or 'inviteonly' for the
+ behaviour you want.
+
+db
+--
+
+This section is a reference to the configuration options for
+DB_DataObject (see http://ur1.ca/7xp). The ones that you may want to
+set are listed below for clarity.
+
+database: a DSN (Data Source Name) for your Laconica database. This is
+ in the format 'protocol://username:password@hostname/databasename',
+ where 'protocol' is 'mysql' or 'mysqli' (or possibly 'postgresql', if you
+ really know what you're doing), 'username' is the username,
+ 'password' is the password, and etc.
+ini_yourdbname: if your database is not named 'laconica', you'll need
+ to set this to point to the location of the
+ laconica.ini file. Note that the real name of your database
+ should go in there, not literally 'yourdbname'.
+db_driver: You can try changing this to 'MDB2' to use the other driver
+ type for DB_DataObject, but note that it breaks the OpenID
+ libraries, which only support PEAR::DB.
+debug: On a database error, you may get a message saying to set this
+ value to 5 to see debug messages in the browser. This breaks
+ just about all pages, and will also expose the username and
+ password
+quote_identifiers: Set this to true if you're using postgresql.
+type: either 'mysql' or 'postgresql' (used for some bits of
+ database-type-specific SQL in the code). Defaults to mysql.
+mirror: you can set this to an array of DSNs, like the above
+ 'database' value. If it's set, certain read-only actions will
+ use a random value out of this array for the database, rather
+ than the one in 'database' (actually, 'database' is overwritten).
+ You can offload a busy DB server by setting up MySQL replication
+ and adding the slaves to this array. Note that if you want some
+ requests to go to the 'database' (master) server, you'll need
+ to include it in this array, too.
+
+syslog
+------
+
+By default, Laconica sites log error messages to the syslog facility.
+(You can override this using the 'logfile' parameter described above).
+
+appname: The name that Laconica uses to log messages. By default it's
+ "laconica", but if you have more than one installation on the
+ server, you may want to change the name for each instance so
+ you can track log messages more easily.
+
+queue
+-----
+
+You can configure the software to queue time-consuming tasks, like
+sending out SMS email or XMPP messages, for off-line processing. See
+'Queues and daemons' above for how to set this up.
+
+enabled: Whether to uses queues. Defaults to false.
+
+license
+-------
+
+The default license to use for your users notices. The default is the
+Creative Commons Attribution 3.0 license, which is probably the right
+choice for any public site. Note that some other servers will not
+accept notices if you apply a stricter license than this.
+
+url: URL of the license, used for links.
+title: Title for the license, like 'Creative Commons Attribution 3.0'.
+image: A button shown on each page for the license.
+
+mail
+----
+
+This is for configuring out-going email. We use PEAR's Mail module,
+see: http://pear.php.net/manual/en/package.mail.mail.factory.php
+
+backend: the backend to use for mail, one of 'mail', 'sendmail', and
+ 'smtp'. Defaults to PEAR's default, 'mail'.
+params: if the mail backend requires any parameters, you can provide
+ them in an associative array.
+
+nickname
+--------
+
+This is for configuring nicknames in the service.
+
+blacklist: an array of strings for usernames that may not be
+ registered. A default array exists for strings that are
+ used by Laconica (e.g. 'doc', 'main', 'avatar', 'theme')
+ but you may want to add others if you have other software
+ installed in a subdirectory of Laconica or if you just
+ don't want certain words used as usernames.
+featured: an array of nicknames of 'featured' users of the site.
+ Can be useful to draw attention to well-known users, or
+ interesting people, or whatever.
+
+avatar
+------
+
+For configuring avatar access.
+
+server: If set, defines another server where avatars are stored in the
+ root directory. Note that the 'avatar' subdir still has to be
+ writeable. You'd typically use this to split HTTP requests on
+ the client to speed up page loading, either with another
+ virtual server or with an NFS or SAMBA share. Clients
+ typically only make 2 connections to a single server at a
+ time (http://ur1.ca/6ih), so this can parallelize the job.
+ Defaults to null.
+
+public
+------
+
+For configuring the public stream.
+
+localonly: If set to true, only messages posted by users of this
+ service (rather than other services, filtered through OMB)
+ are shown in the public stream. Default true.
+blacklist: An array of IDs of users to hide from the public stream.
+ Useful if you have someone making excessive Twitterfeed posts
+ to the site, other kinds of automated posts, testing bots, etc.
+
+theme
+-----
+
+server: Like avatars, you can speed up page loading by pointing the
+ theme file lookup to another server (virtual or real). The
+ theme server's root path should map to the Laconica "theme"
+ subdirectory. Defaults to NULL.
+
+xmpp
+----
+
+For configuring the XMPP sub-system.
+
+enabled: Whether to accept and send messages by XMPP. Default false.
+server: server part of XMPP ID for update user.
+port: connection port for clients. Default 5222, which you probably
+ shouldn't need to change.
+user: username for the client connection. Users will receive messages
+ from 'user'@'server'.
+resource: a unique identifier for the connection to the server. This
+ is actually used as a prefix for each XMPP component in the system.
+password: password for the user account.
+host: some XMPP domains are served by machines with a different
+ hostname. (For example, @gmail.com GTalk users connect to
+ talk.google.com). Set this to the correct hostname if that's the
+ case with your server.
+encryption: Whether to encrypt the connection between Laconica and the
+ XMPP server. Defaults to true, but you can get
+ considerably better performance turning it off if you're
+ connecting to a server on the same machine or on a
+ protected network.
+debug: if turned on, this will make the XMPP library blurt out all of
+ the incoming and outgoing messages as XML stanzas. Use as a
+ last resort, and never turn it on if you don't have queues
+ enabled, since it will spit out sensitive data to the browser.
+public: an array of JIDs to send _all_ notices to. This is useful for
+ participating in third-party search and archiving services.
+
+tag
+---
+
+Miscellaneous tagging stuff.
+
+dropoff: Decay factor for tag listing, in seconds.
+ Defaults to exponential decay over ten days; you can twiddle
+ with it to try and get better results for your site.
+
+daemon
+------
+
+For daemon processes.
+
+piddir: directory that daemon processes should write their PID file
+ (process ID) to. Defaults to /var/run/, which is where this
+ stuff should usually go on Unix-ish systems.
+user: If set, the daemons will try to change their effective user ID
+ to this user before running. Probably a good idea, especially if
+ you start the daemons as root. Note: user name, like 'daemon',
+ not 1001.
+group: If set, the daemons will try to change their effective group ID
+ to this named group. Again, a name, not a numerical ID.
+
+memcached
+---------
+
+You can get a significant boost in performance by caching some
+database data in memcached (http://www.danga.com/memcached/).
+
+enabled: Set to true to enable. Default false.
+server: a string with the hostname of the memcached server. Can also
+ be an array of hostnames, if you've got more than one server.
+
+sphinx
+------
+
+You can get a significant boost in performance using Sphinx Search
+instead of your database server to search for users and notices.
+(http://sphinxsearch.com/).
+
+enabled: Set to true to enable. Default false.
+server: a string with the hostname of the sphinx server.
+port: an integer with the port number of the sphinx server.
+
+integration
+-----------
+
+A catch-all for integration with other systems.
+
+source: The name to use for the source of posts to Twitter. Defaults
+ to 'laconica', but if you request your own source name from
+ Twitter (http://twitter.com/help/request_source), you can use
+ that here instead. Status updates on Twitter will then have
+ links to your site.
+
+inboxes
+-------
+
+For notice inboxes.
+
+enabled: A three-valued flag for whether to use notice inboxes (see
+ upgrading info above for notes about this change). Can be
+ 'false', 'true', or '"transitional"'.
+
+throttle
+--------
+
+For notice-posting throttles.
+
+enabled: Whether to throttle posting. Defaults to false.
+count: Each user can make this many posts in 'timespan' seconds. So, if count
+ is 100 and timespan is 3600, then there can be only 100 posts
+ from a user every hour.
+timespan: see 'count'.
+
+profile
+-------
+
+Profile management.
+
+banned: an array of usernames and/or profile IDs of 'banned' profiles.
+ The site will reject any notices by these users -- they will
+ not be accepted at all. (Compare with blacklisted users above,
+ whose posts just won't show up in the public stream.)
+
+Troubleshooting
+===============
+
+The primary output for Laconica is syslog, unless you configured a
+separate logfile. This is probably the first place to look if you're
+getting weird behaviour from Laconica.
+
+If you're tracking the unstable version of Laconica in the git
+repository (see below), and you get a compilation error ("unexpected
+T_STRING") in the browser, check to see that you don't have any
+conflicts in your code.
+
+If you upgraded to Laconica 0.6.4 without reading the "Notice inboxes"
+section above, and all your users' 'Personal' tabs are empty, read the
+"Notice inboxes" section above.
+
+Myths
+=====
+
+These are some myths you may see on the Web about Laconica.
+Documentation from the core team about Laconica has been pretty
+sparse, so some backtracking and guesswork resulted in some incorrect
+assumptions.
+
+- "Set $config['db']['debug'] = 5 to debug the database." This is an
+ extremely bad idea. It's a tool built into DB_DataObject that will
+ emit oodles of print lines directly to the browser of your users.
+ Among these lines will be your database username and password. Do
+ not enable this option on a production Web site for any reason.
+
+- "Edit dataobject.ini with the following settings..." dataobject.ini
+ is a development file for the DB_DataObject framework and is not
+ used by the running software. It was removed from the Laconica
+ distribution because its presence was confusing. Do not bother
+ configuring dataobject.ini, and do not put your database username
+ and password into the file on a production Web server; unscrupulous
+ persons may try to read it to get your passwords.
+
+Unstable version
+================
+
+If you're adventurous or impatient, you may want to install the
+development version of Laconica. To get it, use the git version
+control tool (http://git-scm.com/) like so:
+
+ git clone http://laconi.ca/software/laconica.git
+
+To keep it up-to-date, use 'git pull'. Watch for conflicts!
+
+Further information
+===================
+
+There are several ways to get more information about Laconica.
+
+* There is a mailing list for Laconica developers and admins at
+ http://mail.laconi.ca/mailman/listinfo/laconica-dev
+* The #laconica IRC channel on freenode.net (http://www.freenode.net/).
+* The Laconica wiki, http://laconi.ca/trac/
+
+Feedback
+========
+
+* Microblogging messages to http://identi.ca/evan are very welcome.
+* Laconica's Trac server has a bug tracker for any defects you may find,
+ or ideas for making things better. http://laconi.ca/trac/
+* e-mail to evan@identi.ca will usually be read and responded to very
+ quickly, unless the question is really hard.
+
+Credits
+=======
+
+The following is an incomplete list of developers who've worked on
+Laconi.ca. Apologies for any oversight; please let evan@identi.ca know
+if anyone's been overlooked in error.
+
+* Evan Prodromou, founder and lead developer, Control Yourself, Inc.
+* Zach Copley, Control Yourself, Inc.
+* Earle Martin, Control Yourself, Inc.
+* Marie-Claude Doyon, designer, Control Yourself, Inc.
+* Sarven Capadisli, Control Yourself, Inc.
+* Robin Millette, Control Yourself, Inc.
+* Ciaran Gultnieks
+* Michael Landers
+* Ori Avtalion
+* Garret Buell
+* Mike Cochrane
+* Matthew Gregg
+* Florian Biree
+* Erik Stambaugh
+* 'drry'
+* Gina Haeussge
+* Ken Sheppardson (Trac server, man-about-town)
+* Tiago 'gouki' Faria (entrans)
+* Tryggvi Björgvinsson
+
+Thanks also to the developers of our upstream library code and to the
+thousands of people who have tried out Identi.ca, installed Laconi.ca,
+told their friends, and built the Open Microblogging network to what
+it is today.
diff --git a/actions/accesstoken.php b/actions/accesstoken.php
new file mode 100644
index 000000000..4907749ce
--- /dev/null
+++ b/actions/accesstoken.php
@@ -0,0 +1,42 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class AccesstokenAction extends Action {
+ function handle($args) {
+ parent::handle($args);
+ try {
+ common_debug('getting request from env variables', __FILE__);
+ common_remove_magic_from_request();
+ $req = OAuthRequest::from_request();
+ common_debug('getting a server', __FILE__);
+ $server = omb_oauth_server();
+ common_debug('fetching the access token', __FILE__);
+ $token = $server->fetch_access_token($req);
+ common_debug('got this token: "'.print_r($token,TRUE).'"', __FILE__);
+ common_debug('printing the access token', __FILE__);
+ print $token;
+ } catch (OAuthException $e) {
+ common_server_error($e->getMessage());
+ }
+ }
+}
diff --git a/actions/all.php b/actions/all.php
new file mode 100644
index 000000000..2a26e48d4
--- /dev/null
+++ b/actions/all.php
@@ -0,0 +1,93 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/actions/showstream.php');
+
+class AllAction extends StreamAction {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->client_error(_('No such user.'));
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ # Looks like we're good; show the header
+
+ common_show_header(sprintf(_("%s and friends"), $profile->nickname),
+ array($this, 'show_header'), $user,
+ array($this, 'show_top'));
+
+ $this->show_notices($user);
+
+ common_show_footer();
+ }
+
+ function show_header($user) {
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('allrss', array('nickname' =>
+ $user->nickname)),
+ 'type' => 'application/rss+xml',
+ 'title' => sprintf(_('Feed for friends of %s'), $user->nickname)));
+ }
+
+ function show_top($user) {
+ $cur = common_current_user();
+
+ if ($cur && $cur->id == $user->id) {
+ common_notice_form('all');
+ }
+
+ $this->views_menu();
+
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('allrss', array('nickname' => $user->nickname)),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'allrss')));
+ }
+
+ function show_notices($user) {
+
+ $page = $this->trimmed('page');
+ if (!$page) {
+ $page = 1;
+ }
+
+ $notice = $user->noticesWithFriends(($page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
+ $cnt = $this->show_notice_list($notice);
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'all', array('nickname' => $user->nickname));
+ }
+}
diff --git a/actions/allrss.php b/actions/allrss.php
new file mode 100644
index 000000000..e49ac5540
--- /dev/null
+++ b/actions/allrss.php
@@ -0,0 +1,77 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class AllrssAction extends Rss10Action {
+
+ var $user = NULL;
+
+ function init() {
+ $nickname = $this->trimmed('nickname');
+ $this->user = User::staticGet('nickname', $nickname);
+
+ if (!$this->user) {
+ common_user_error(_('No such user.'));
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function get_notices($limit=0) {
+
+ $user = $this->user;
+
+ $notice = $user->noticesWithFriends(0, $limit);
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ $user = $this->user;
+ $c = array('url' => common_local_url('allrss',
+ array('nickname' =>
+ $user->nickname)),
+ 'title' => sprintf(_('%s and friends'), $user->nickname),
+ 'link' => common_local_url('all',
+ array('nickname' =>
+ $user->nickname)),
+ 'description' => sprintf(_('Feed for friends of %s'), $user->nickname));
+ return $c;
+ }
+
+ function get_image() {
+ $user = $this->user;
+ $profile = $user->getProfile();
+ if (!$profile) {
+ return NULL;
+ }
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ return ($avatar) ? $avatar->url : NULL;
+ }
+} \ No newline at end of file
diff --git a/actions/api.php b/actions/api.php
new file mode 100644
index 000000000..ccebcd89e
--- /dev/null
+++ b/actions/api.php
@@ -0,0 +1,200 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class ApiAction extends Action {
+
+ var $user;
+ var $content_type;
+ var $api_arg;
+ var $api_method;
+ var $api_action;
+
+ function handle($args) {
+ parent::handle($args);
+
+ $this->api_action = $this->arg('apiaction');
+ $method = $this->arg('method');
+ $argument = $this->arg('argument');
+
+ if (isset($argument)) {
+ $cmdext = explode('.', $argument);
+ $this->api_arg = $cmdext[0];
+ $this->api_method = $method;
+ $this->content_type = strtolower($cmdext[1]);
+ } else {
+
+ # Requested format / content-type will be an extension on the method
+ $cmdext = explode('.', $method);
+ $this->api_method = $cmdext[0];
+ $this->content_type = strtolower($cmdext[1]);
+ }
+
+ if($this->requires_auth()) {
+ if (!isset($_SERVER['PHP_AUTH_USER'])) {
+
+ # This header makes basic auth go
+ header('WWW-Authenticate: Basic realm="Laconica API"');
+
+ # If the user hits cancel -- bam!
+ $this->show_basic_auth_error();
+ } else {
+ $nickname = $_SERVER['PHP_AUTH_USER'];
+ $password = $_SERVER['PHP_AUTH_PW'];
+ $user = common_check_user($nickname, $password);
+
+ if ($user) {
+ $this->user = $user;
+ $this->process_command();
+ } else {
+ # basic authentication failed
+ $this->show_basic_auth_error();
+ }
+ }
+ } else {
+
+ # Caller might give us a username even if not required
+ if (isset($_SERVER['PHP_AUTH_USER'])) {
+ $user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
+ if ($user) {
+ $this->user = $user;
+ }
+ # Twitter doesn't throw an error if the user isn't found
+ }
+
+ $this->process_command();
+ }
+ }
+
+ function process_command() {
+ $action = "twitapi$this->api_action";
+ $actionfile = INSTALLDIR."/actions/$action.php";
+
+ if (file_exists($actionfile)) {
+ require_once($actionfile);
+ $action_class = ucfirst($action)."Action";
+ $action_obj = new $action_class();
+
+ if (!$action_obj->prepare($this->args)) {
+ return;
+ }
+
+ if (method_exists($action_obj, $this->api_method)) {
+ $apidata = array( 'content-type' => $this->content_type,
+ 'api_method' => $this->api_method,
+ 'api_arg' => $this->api_arg,
+ 'user' => $this->user);
+
+ call_user_func(array($action_obj, $this->api_method), $_REQUEST, $apidata);
+ } else {
+ common_user_error("API method not found!", $code=404);
+ }
+ } else {
+ common_user_error("API method not found!", $code=404);
+ }
+ }
+
+ # Whitelist of API methods that don't need authentication
+ function requires_auth() {
+ static $noauth = array( 'statuses/public_timeline',
+ 'statuses/show',
+ 'users/show',
+ 'help/test',
+ 'help/downtime_schedule');
+
+ static $bareauth = array('statuses/user_timeline',
+ 'statuses/friends',
+ 'statuses/followers',
+ 'favorites/favorites');
+
+ # If the site is "private", all API methods need authentication
+
+ if (common_config('site', 'private')) {
+ return true;
+ }
+
+ $fullname = "$this->api_action/$this->api_method";
+
+ if (in_array($fullname, $bareauth)) {
+ # bareauth: only needs auth if without an argument
+ if ($this->api_arg) {
+ return false;
+ } else {
+ return true;
+ }
+ } else if (in_array($fullname, $noauth)) {
+ # noauth: never needs auth
+ return false;
+ } else {
+ # everybody else needs auth
+ return true;
+ }
+ }
+
+ function show_basic_auth_error() {
+ header('HTTP/1.1 401 Unauthorized');
+ $msg = 'Could not authenticate you.';
+
+ if ($this->content_type == 'xml') {
+ header('Content-Type: application/xml; charset=utf-8');
+ common_start_xml();
+ common_element_start('hash');
+ common_element('error', NULL, $msg);
+ common_element('request', NULL, $_SERVER['REQUEST_URI']);
+ common_element_end('hash');
+ common_end_xml();
+ } else if ($this->content_type == 'json') {
+ header('Content-Type: application/json; charset=utf-8');
+ $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+ print(json_encode($error_array));
+ } else {
+ header('Content-type: text/plain');
+ print "$msg\n";
+ }
+ }
+
+ function is_readonly() {
+ # NOTE: before handle(), can't use $this->arg
+ $apiaction = $_REQUEST['apiaction'];
+ $method = $_REQUEST['method'];
+ list($cmdtext, $fmt) = explode('.', $method);
+
+ static $write_methods = array(
+ 'account' => array('update_location', 'update_delivery_device', 'end_session'),
+ 'blocks' => array('create', 'destroy'),
+ 'direct_messages' => array('create', 'destroy'),
+ 'favorites' => array('create', 'destroy'),
+ 'friendships' => array('create', 'destroy'),
+ 'help' => array(),
+ 'notifications' => array('follow', 'leave'),
+ 'statuses' => array('update', 'destroy'),
+ 'users' => array()
+ );
+
+ if (array_key_exists($apiaction, $write_methods)) {
+ if (!in_array($cmdtext, $write_methods[$apiaction])) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/actions/avatarbynickname.php b/actions/avatarbynickname.php
new file mode 100644
index 000000000..b33cababf
--- /dev/null
+++ b/actions/avatarbynickname.php
@@ -0,0 +1,68 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class AvatarbynicknameAction extends Action {
+ function handle($args) {
+ parent::handle($args);
+ $nickname = $this->trimmed('nickname');
+ if (!$nickname) {
+ $this->client_error(_('No nickname.'));
+ return;
+ }
+ $size = $this->trimmed('size');
+ if (!$size) {
+ $this->client_error(_('No size.'));
+ return;
+ }
+ $size = strtolower($size);
+ if (!in_array($size, array('original', '96', '48', '24'))) {
+ $this->client_error(_('Invalid size.'));
+ return;
+ }
+
+ $user = User::staticGet('nickname', $nickname);
+ if (!$user) {
+ $this->client_error(_('No such user.'));
+ return;
+ }
+ $profile = $user->getProfile();
+ if (!$profile) {
+ $this->client_error(_('User has no profile.'));
+ return;
+ }
+ if ($size == 'original') {
+ $avatar = $profile->getOriginal();
+ } else {
+ $avatar = $profile->getAvatar($size+0);
+ }
+
+ if ($avatar) {
+ $url = $avatar->url;
+ } else {
+ if ($size == 'original') {
+ $url = common_default_avatar(AVATAR_PROFILE_SIZE);
+ } else {
+ $url = common_default_avatar($size+0);
+ }
+ }
+ common_redirect($url, 302);
+ }
+}
diff --git a/actions/block.php b/actions/block.php
new file mode 100644
index 000000000..e6d2b7e49
--- /dev/null
+++ b/actions/block.php
@@ -0,0 +1,145 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class BlockAction extends Action {
+
+ var $profile = NULL;
+
+ function prepare($args) {
+
+ parent::prepare($args);
+
+ if (!common_logged_in()) {
+ $this->client_error(_('Not logged in.'));
+ return false;
+ }
+
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $id = $this->trimmed('blockto');
+
+ if (!$id) {
+ $this->client_error(_('No profile specified.'));
+ return false;
+ }
+
+ $this->profile = Profile::staticGet('id', $id);
+
+ if (!$this->profile) {
+ $this->client_error(_('No profile with that ID.'));
+ return false;
+ }
+
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ if ($this->arg('block')) {
+ $this->are_you_sure_form();
+ } else if ($this->arg('no')) {
+ $cur = common_current_user();
+ common_redirect(common_local_url('subscribers',
+ array('nickname' => $cur->nickname)));
+ } else if ($this->arg('yes')) {
+ $this->block_profile();
+ }
+ }
+ }
+
+ function are_you_sure_form() {
+
+ $id = $this->profile->id;
+
+ common_show_header(_('Block user'));
+
+ common_element('p', NULL,
+ _('Are you sure you want to block this user? '.
+ 'Afterwards, they will be unsubscribed from you, '.
+ 'unable to subscribe to you in the future, and '.
+ 'you will not be notified of any @-replies from them.'));
+
+ common_element_start('form', array('id' => 'block-' . $id,
+ 'method' => 'post',
+ 'class' => 'block',
+ 'action' => common_local_url('block')));
+
+ common_hidden('token', common_session_token());
+
+ common_element('input', array('id' => 'blockto-' . $id,
+ 'name' => 'blockto',
+ 'type' => 'hidden',
+ 'value' => $id));
+
+ foreach ($this->args as $k => $v) {
+ if (substr($k, 0, 9) == 'returnto-') {
+ common_hidden($k, $v);
+ }
+ }
+
+ common_submit('no', _('No'));
+ common_submit('yes', _('Yes'));
+
+ common_element_end('form');
+
+ common_show_footer();
+ }
+
+ function block_profile() {
+
+ $cur = common_current_user();
+
+ if ($cur->hasBlocked($this->profile)) {
+ $this->client_error(_('You have already blocked this user.'));
+ return;
+ }
+
+ $result = $cur->block($this->profile);
+
+ if (!$result) {
+ $this->server_error(_('Failed to save block information.'));
+ return;
+ }
+
+ # Now, gotta figure where we go back to
+
+ foreach ($this->args as $k => $v) {
+ if ($k == 'returnto-action') {
+ $action = $v;
+ } else if (substr($k, 0, 9) == 'returnto-') {
+ $args[substr($k, 9)] = $v;
+ }
+ }
+
+ if ($action) {
+ common_redirect(common_local_url($action, $args));
+ } else {
+ common_redirect(common_local_url('subscriptions',
+ array('nickname' => $cur->nickname)));
+ }
+ }
+}
diff --git a/actions/confirmaddress.php b/actions/confirmaddress.php
new file mode 100644
index 000000000..44280e08a
--- /dev/null
+++ b/actions/confirmaddress.php
@@ -0,0 +1,95 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class ConfirmaddressAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ common_set_returnto($this->self_url());
+ common_redirect(common_local_url('login'));
+ return;
+ }
+ $code = $this->trimmed('code');
+ if (!$code) {
+ $this->client_error(_('No confirmation code.'));
+ return;
+ }
+ $confirm = Confirm_address::staticGet('code', $code);
+ if (!$confirm) {
+ $this->client_error(_('Confirmation code not found.'));
+ return;
+ }
+ $cur = common_current_user();
+ if ($cur->id != $confirm->user_id) {
+ $this->client_error(_('That confirmation code is not for you!'));
+ return;
+ }
+ $type = $confirm->address_type;
+ if (!in_array($type, array('email', 'jabber', 'sms'))) {
+ $this->server_error(sprintf(_('Unrecognized address type %s'), $type));
+ return;
+ }
+ if ($cur->$type == $confirm->address) {
+ $this->client_error(_('That address has already been confirmed.'));
+ return;
+ }
+
+ $cur->query('BEGIN');
+
+ $orig_user = clone($cur);
+
+ $cur->$type = $confirm->address;
+
+ if ($type == 'sms') {
+ $cur->carrier = ($confirm->address_extra)+0;
+ $carrier = Sms_carrier::staticGet($cur->carrier);
+ $cur->smsemail = $carrier->toEmailAddress($cur->sms);
+ }
+
+ $result = $cur->updateKeys($orig_user);
+
+ if (!$result) {
+ common_log_db_error($cur, 'UPDATE', __FILE__);
+ $this->server_error(_('Couldn\'t update user.'));
+ return;
+ }
+
+ if ($type == 'email') {
+ $cur->emailChanged();
+ }
+
+ $result = $confirm->delete();
+
+ if (!$result) {
+ common_log_db_error($confirm, 'DELETE', __FILE__);
+ $this->server_error(_('Couldn\'t delete email confirmation.'));
+ return;
+ }
+
+ $cur->query('COMMIT');
+
+ common_show_header(_('Confirm Address'));
+ common_element('p', NULL,
+ sprintf(_('The address "%s" has been confirmed for your account.'), $cur->$type));
+ common_show_footer();
+ }
+}
diff --git a/actions/deletenotice.php b/actions/deletenotice.php
new file mode 100644
index 000000000..64746283a
--- /dev/null
+++ b/actions/deletenotice.php
@@ -0,0 +1,101 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/deleteaction.php');
+
+class DeletenoticeAction extends DeleteAction {
+ function handle($args) {
+ parent::handle($args);
+ # XXX: Ajax!
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->delete_notice();
+ } else if ($_SERVER['REQUEST_METHOD'] == 'GET') {
+ $this->show_form();
+ }
+ }
+
+ function get_instructions() {
+ return _('You are about to permanently delete a notice. Once this is done, it cannot be undone.');
+ }
+
+ function get_title() {
+ return _('Delete notice');
+ }
+
+ function show_form($error=NULL) {
+ $user = common_current_user();
+
+ common_show_header($this->get_title(), array($this, 'show_header'), $error,
+ array($this, 'show_top'));
+ common_element_start('form', array('id' => 'notice_delete_form',
+ 'method' => 'post',
+ 'action' => common_local_url('deletenotice')));
+ common_hidden('token', common_session_token());
+ common_hidden('notice', $this->trimmed('notice'));
+ common_element_start('p');
+ common_element('span', array('id' => 'confirmation_text'), _('Are you sure you want to delete this notice?'));
+
+ common_element('input', array('id' => 'submit_no',
+ 'name' => 'submit',
+ 'type' => 'submit',
+ 'value' => _('No')));
+ common_element('input', array('id' => 'submit_yes',
+ 'name' => 'submit',
+ 'type' => 'submit',
+ 'value' => _('Yes')));
+ common_element_end('p');
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function delete_notice() {
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+ $url = common_get_returnto();
+ $confirmed = $this->trimmed('submit');
+ if ($confirmed == _('Yes')) {
+ $user = common_current_user();
+ $notice_id = $this->trimmed('notice');
+ $notice = Notice::staticGet($notice_id);
+ $replies = new Reply;
+ $replies->get('notice_id', $notice_id);
+
+ common_dequeue_notice($notice);
+ if (common_config('memcached', 'enabled')) {
+ $notice->blowSubsCache();
+ }
+ $replies->delete();
+ $notice->delete();
+ } else {
+ if ($url) {
+ common_set_returnto(NULL);
+ } else {
+ $url = common_local_url('public');
+ }
+ }
+ common_redirect($url);
+ }
+}
diff --git a/actions/deleteprofile.php b/actions/deleteprofile.php
new file mode 100644
index 000000000..418ac998d
--- /dev/null
+++ b/actions/deleteprofile.php
@@ -0,0 +1,277 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class DeleteprofileAction extends Action {
+ function handle($args) {
+ parent::handle($args);
+ $this->server_error(_('Code not yet ready.'));
+ return;
+ if ('POST' === $_SERVER['REQUEST_METHOD']) {
+ $this->handle_post();
+ }
+ else if ('GET' === $_SERVER['REQUEST_METHOD']) {
+ $this->show_form();
+ }
+ }
+
+ function get_instructions() {
+ return _('Export and delete your user information.');
+ }
+
+ function form_header($title, $msg=NULL, $success=false) {
+ common_show_header($title,
+ NULL,
+ array($msg, $success),
+ array($this, 'show_top'));
+ }
+
+ function show_feeds_list($feeds) {
+ common_element_start('div', array('class' => 'feedsdel'));
+ common_element('p', null, 'Feeds:');
+ common_element_start('ul', array('class' => 'xoxo'));
+
+ foreach ($feeds as $key => $value) {
+ $this->common_feed_item($feeds[$key]);
+ }
+ common_element_end('ul');
+ common_element_end('div');
+ }
+
+ //TODO move to common.php (and retrace its origin)
+ function common_feed_item($feed) {
+ $user = common_current_user();
+ $nickname = $user->nickname;
+
+ switch($feed['item']) {
+ case 'notices': default:
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's ".$feed['version']." notice feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'foaf':
+ $feed_classname = "foaf";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's FOAF file";
+ $feed['textContent'] = "FOAF";
+ break;
+ }
+ common_element_start('li');
+ common_element('a', array('href' => $feed['href'],
+ 'class' => $feed_classname,
+ 'type' => $feed_mimetype,
+ 'title' => $feed_title),
+ $feed['textContent']);
+ common_element_end('li');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $this->form_header(_('Delete my account'), $msg, $success);
+ common_element('h2', NULL, _('Delete my account confirmation'));
+ $this->show_confirm_delete_form();
+ common_show_footer();
+ }
+
+ function show_confirm_delete_form() {
+ $user = common_current_user();
+ $notices = DB_DataObject::factory('notice');
+ $notices->profile_id = $user->id;
+ $notice_count = (int) $notices->count();
+
+ common_element_start('form', array('method' => 'POST',
+ 'id' => 'delete',
+ 'action' =>
+ common_local_url('deleteprofile')));
+
+ common_hidden('token', common_session_token());
+ common_element('p', null, "Last chance to copy your notices and contacts by saving the two links below before deleting your account. Be careful, this operation cannot be undone.");
+
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('userrss', array('limit' => $notice_count, 'nickname' => $user->nickname)),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'notices'),
+ 1=>array('href'=>common_local_url('foaf',array('nickname' => $user->nickname)),
+ 'type' => 'rdf',
+ 'version' => 'FOAF',
+ 'item' => 'foaf')));
+
+ common_checkbox('confirmation', _('Check if you are sure you want to delete your account.'));
+
+ common_submit('deleteaccount', _('Delete my account'));
+ common_element_end('form');
+ }
+
+ function handle_post() {
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('deleteaccount') && $this->arg('confirmation')) {
+ $this->delete_account();
+ }
+ $this->show_form();
+ }
+
+ function delete_account() {
+ $user = common_current_user();
+ assert(!is_null($user)); # should already be checked
+
+ // deleted later through the profile
+ /*
+ $avatar = new Avatar;
+ $avatar->profile_id = $user->id;
+ $n_avatars_deleted = $avatar->delete();
+ */
+
+ $fave = new Fave;
+ $fave->user_id = $user->id;
+ $n_faves_deleted = $fave->delete();
+
+ $confirmation = new Confirm_address;
+ $confirmation->user_id = $user->id;
+ $n_confirmations_deleted = $confirmation->delete();
+
+ // TODO foreign stuff...
+
+ $invitation = new Invitation;
+ $invitation->user_id = $user->id;
+ $n_invitations_deleted = $invitation->delete();
+
+ $message_from = new Message;
+ $message_from->from_profile = $user->id;
+ $n_messages_from_deleted = $message_from->delete();
+
+ $message_to = new Message;
+ $message_to->to_profile = $user->id;
+ $n_messages_to_deleted = $message_to->delete();
+
+ $notice_inbox = new Notice_inbox;
+ $notice_inbox->user_id = $user->id;
+ $n_notices_inbox_deleted = $notice_inbox->delete();
+
+ $profile_tagger = new Profile_tag;
+ $profile_tagger->tagger = $user->id;
+ $n_profiles_tagger_deleted = $profile_tagger->delete();
+
+ $profile_tagged = new Profile_tag;
+ $profile_tagged->tagged = $user->id;
+ $n_profiles_tagged_deleted = $profile_tagged->delete();
+
+ $remember_me = new Remember_me;
+ $remember_me->user_id = $user->id;
+ $n_remember_mes_deleted = $remember_me->delete();
+
+ $reply= new Reply;
+ $reply->profile_id = $user->id;
+ $n_replies_deleted = $reply->delete();
+
+ // FIXME we're not removings replies to deleted notices.
+ // notices should take care of that themselves.
+
+ $notice = new Notice;
+ $notice->profile_id = $user->id;
+ $n_notices_deleted = $notice->delete();
+
+ $subscriber = new Subscription;
+ $subscriber->subscriber = $user->id;
+ $n_subscribers_deleted = $subscriber->delete();
+
+ $subscribed = new Subscription;
+ $subscribed->subscribed = $user->id;
+ $n_subscribeds_deleted = $subscribed->delete();
+
+ $user_openid = new User_openid;
+ $user_openid->user_id = $user->id;
+ $n_user_openids_deleted = $user_openid->delete();
+
+ $profile = new Profile;
+ $profile->id = $user->id;
+ $profile->delete_avatars();
+ $n_profiles_deleted = $profile->delete();
+ $n_users_deleted = $user->delete();
+
+ // logout and redirect to public
+ common_set_user(NULL);
+ common_real_login(false); # not logged in
+ common_forgetme(); # don't log back in!
+ common_redirect(common_local_url('public'));
+ }
+
+ function show_top($arr) {
+ $msg = $arr[0];
+ $success = $arr[1];
+ if ($msg) {
+ $this->message($msg, $success);
+ } else {
+ $inst = $this->get_instructions();
+ $output = common_markup_to_html($inst);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+ $this->settings_menu();
+ }
+
+ function settings_menu() {
+ # action => array('prompt', 'title')
+ $menu =
+ array('profilesettings' =>
+ array(_('Profile'),
+ _('Change your profile settings')),
+ 'emailsettings' =>
+ array(_('Email'),
+ _('Change email handling')),
+ 'openidsettings' =>
+ array(_('OpenID'),
+ _('Add or remove OpenIDs')),
+ 'smssettings' =>
+ array(_('SMS'),
+ _('Updates by SMS')),
+ 'imsettings' =>
+ array(_('IM'),
+ _('Updates by instant messenger (IM)')),
+ 'twittersettings' =>
+ array(_('Twitter'),
+ _('Twitter integration options')),
+ 'othersettings' =>
+ array(_('Other'),
+ _('Other options')));
+
+ $action = $this->trimmed('action');
+ common_element_start('ul', array('id' => 'nav_views'));
+ foreach ($menu as $menuaction => $menudesc) {
+ if ($menuaction == 'imsettings' &&
+ !common_config('xmpp', 'enabled')) {
+ continue;
+ }
+ common_menu_item(common_local_url($menuaction),
+ $menudesc[0],
+ $menudesc[1],
+ $action == $menuaction);
+ }
+ common_element_end('ul');
+ }
+}
+
diff --git a/actions/disfavor.php b/actions/disfavor.php
new file mode 100644
index 000000000..be208f65a
--- /dev/null
+++ b/actions/disfavor.php
@@ -0,0 +1,83 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class DisfavorAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ common_redirect(common_local_url('showfavorites', array('nickname' => $user->nickname)));
+ return;
+ }
+
+ $id = $this->trimmed('notice');
+
+ $notice = Notice::staticGet($id);
+
+ $token = $this->trimmed('token-'.$notice->id);
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_("There was a problem with your session token. Try again, please."));
+ return;
+ }
+
+ $fave = new Fave();
+ $fave->user_id = $this->id;
+ $fave->notice_id = $notice->id;
+ if (!$fave->find(true)) {
+ $this->client_error(_('This notice is not a favorite!'));
+ return;
+ }
+
+ $result = $fave->delete();
+
+ if (!$result) {
+ common_log_db_error($fave, 'DELETE', __FILE__);
+ $this->server_error(_('Could not delete favorite.'));
+ return;
+ }
+
+ $user->blowFavesCache();
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Add to favorites'));
+ common_element_end('head');
+ common_element_start('body');
+ common_favor_form($notice);
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ common_redirect(common_local_url('showfavorites',
+ array('nickname' => $user->nickname)));
+ }
+ }
+}
diff --git a/actions/doc.php b/actions/doc.php
new file mode 100644
index 000000000..f3327048f
--- /dev/null
+++ b/actions/doc.php
@@ -0,0 +1,38 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class DocAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ $title = $this->trimmed('title');
+ $filename = INSTALLDIR.'/doc/'.$title;
+ if (!file_exists($filename)) {
+ common_user_error(_('No such document.'));
+ return;
+ }
+ $c = file_get_contents($filename);
+ $output = common_markup_to_html($c);
+ common_show_header(_(ucfirst($title)));
+ common_raw($output);
+ common_show_footer();
+ }
+}
diff --git a/actions/emailsettings.php b/actions/emailsettings.php
new file mode 100644
index 000000000..b35b4d28e
--- /dev/null
+++ b/actions/emailsettings.php
@@ -0,0 +1,330 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+
+class EmailsettingsAction extends SettingsAction {
+
+ function get_instructions() {
+ return _('Manage how you get email from %%site.name%%.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $user = common_current_user();
+ $this->form_header(_('Email Settings'), $msg, $success);
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'emailsettings',
+ 'action' =>
+ common_local_url('emailsettings')));
+ common_hidden('token', common_session_token());
+
+ common_element('h2', NULL, _('Address'));
+
+ if ($user->email) {
+ common_element_start('p');
+ common_element('span', 'address confirmed', $user->email);
+ common_element('span', 'input_instructions',
+ _('Current confirmed email address.'));
+ common_hidden('email', $user->email);
+ common_element_end('p');
+ common_submit('remove', _('Remove'));
+ } else {
+ $confirm = $this->get_confirmation();
+ if ($confirm) {
+ common_element_start('p');
+ common_element('span', 'address unconfirmed', $confirm->address);
+ common_element('span', 'input_instructions',
+ _('Awaiting confirmation on this address. Check your inbox (and spam box!) for a message with further instructions.'));
+ common_hidden('email', $confirm->address);
+ common_element_end('p');
+ common_submit('cancel', _('Cancel'));
+ } else {
+ common_input('email', _('Email Address'),
+ ($this->arg('email')) ? $this->arg('email') : NULL,
+ _('Email address, like "UserName@example.org"'));
+ common_submit('add', _('Add'));
+ }
+ }
+
+ if ($user->email) {
+ common_element('h2', NULL, _('Incoming email'));
+
+ if ($user->incomingemail) {
+ common_element_start('p');
+ common_element('span', 'address', $user->incomingemail);
+ common_element('span', 'input_instructions',
+ _('Send email to this address to post new notices.'));
+ common_element_end('p');
+ common_submit('removeincoming', _('Remove'));
+ }
+
+ common_element_start('p');
+ common_element('span', 'input_instructions',
+ _('Make a new email address for posting to; cancels the old one.'));
+ common_element_end('p');
+ common_submit('newincoming', _('New'));
+ }
+
+ common_element('h2', NULL, _('Preferences'));
+
+ common_checkbox('emailnotifysub',
+ _('Send me notices of new subscriptions through email.'),
+ $user->emailnotifysub);
+ common_checkbox('emailnotifyfav',
+ _('Send me email when someone adds my notice as a favorite.'),
+ $user->emailnotifyfav);
+ common_checkbox('emailnotifymsg',
+ _('Send me email when someone sends me a private message.'),
+ $user->emailnotifymsg);
+ common_checkbox('emailnotifynudge',
+ _('Allow friends to nudge me and send me an email.'),
+ $user->emailnotifynudge);
+ common_checkbox('emailpost',
+ _('I want to post notices by email.'),
+ $user->emailpost);
+ common_checkbox('emailmicroid',
+ _('Publish a MicroID for my email address.'),
+ $user->emailmicroid);
+
+ common_submit('save', _('Save'));
+
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function get_confirmation() {
+ $user = common_current_user();
+ $confirm = new Confirm_address();
+ $confirm->user_id = $user->id;
+ $confirm->address_type = 'email';
+ if ($confirm->find(TRUE)) {
+ return $confirm;
+ } else {
+ return NULL;
+ }
+ }
+
+ function handle_post() {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->save_preferences();
+ } else if ($this->arg('add')) {
+ $this->add_address();
+ } else if ($this->arg('cancel')) {
+ $this->cancel_confirmation();
+ } else if ($this->arg('remove')) {
+ $this->remove_address();
+ } else if ($this->arg('removeincoming')) {
+ $this->remove_incoming();
+ } else if ($this->arg('newincoming')) {
+ $this->new_incoming();
+ } else {
+ $this->show_form(_('Unexpected form submission.'));
+ }
+ }
+
+ function save_preferences() {
+
+ $emailnotifysub = $this->boolean('emailnotifysub');
+ $emailnotifyfav = $this->boolean('emailnotifyfav');
+ $emailnotifymsg = $this->boolean('emailnotifymsg');
+ $emailnotifynudge = $this->boolean('emailnotifynudge');
+ $emailmicroid = $this->boolean('emailmicroid');
+ $emailpost = $this->boolean('emailpost');
+
+ $user = common_current_user();
+
+ assert(!is_null($user)); # should already be checked
+
+ $user->query('BEGIN');
+
+ $original = clone($user);
+
+ $user->emailnotifysub = $emailnotifysub;
+ $user->emailnotifyfav = $emailnotifyfav;
+ $user->emailnotifymsg = $emailnotifymsg;
+ $user->emailnotifynudge = $emailnotifynudge;
+ $user->emailmicroid = $emailmicroid;
+ $user->emailpost = $emailpost;
+
+ $result = $user->update($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+
+ $user->query('COMMIT');
+
+ $this->show_form(_('Preferences saved.'), true);
+ }
+
+ function add_address() {
+
+ $user = common_current_user();
+
+ $email = $this->trimmed('email');
+
+ # Some validation
+
+ if (!$email) {
+ $this->show_form(_('No email address.'));
+ return;
+ }
+
+ $email = common_canonical_email($email);
+
+ if (!$email) {
+ $this->show_form(_('Cannot normalize that email address'));
+ return;
+ }
+ if (!Validate::email($email, true)) {
+ $this->show_form(_('Not a valid email address'));
+ return;
+ } else if ($user->email == $email) {
+ $this->show_form(_('That is already your email address.'));
+ return;
+ } else if ($this->email_exists($email)) {
+ $this->show_form(_('That email address already belongs to another user.'));
+ return;
+ }
+
+ $confirm = new Confirm_address();
+ $confirm->address = $email;
+ $confirm->address_type = 'email';
+ $confirm->user_id = $user->id;
+ $confirm->code = common_confirmation_code(64);
+
+ $result = $confirm->insert();
+
+ if ($result === FALSE) {
+ common_log_db_error($confirm, 'INSERT', __FILE__);
+ common_server_error(_('Couldn\'t insert confirmation code.'));
+ return;
+ }
+
+ mail_confirm_address($user, $confirm->code, $user->nickname, $email);
+
+ $msg = _('A confirmation code was sent to the email address you added. Check your inbox (and spam box!) for the code and instructions on how to use it.');
+
+ $this->show_form($msg, TRUE);
+ }
+
+ function cancel_confirmation() {
+ $email = $this->arg('email');
+ $confirm = $this->get_confirmation();
+ if (!$confirm) {
+ $this->show_form(_('No pending confirmation to cancel.'));
+ return;
+ }
+ if ($confirm->address != $email) {
+ $this->show_form(_('That is the wrong IM address.'));
+ return;
+ }
+
+ $result = $confirm->delete();
+
+ if (!$result) {
+ common_log_db_error($confirm, 'DELETE', __FILE__);
+ $this->server_error(_('Couldn\'t delete email confirmation.'));
+ return;
+ }
+
+ $this->show_form(_('Confirmation cancelled.'), TRUE);
+ }
+
+ function remove_address() {
+
+ $user = common_current_user();
+ $email = $this->arg('email');
+
+ # Maybe an old tab open...?
+
+ if ($user->email != $email) {
+ $this->show_form(_('That is not your email address.'));
+ return;
+ }
+
+ $user->query('BEGIN');
+ $original = clone($user);
+ $user->email = NULL;
+ $result = $user->updateKeys($original);
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+ $user->query('COMMIT');
+
+ $this->show_form(_('The address was removed.'), TRUE);
+ }
+
+ function remove_incoming() {
+ $user = common_current_user();
+
+ if (!$user->incomingemail) {
+ $this->show_form(_('No incoming email address.'));
+ return;
+ }
+
+ $orig = clone($user);
+ $user->incomingemail = NULL;
+
+ if (!$user->updateKeys($orig)) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ $this->server_error(_("Couldn't update user record."));
+ }
+
+ $this->show_form(_('Incoming email address removed.'), TRUE);
+ }
+
+ function new_incoming() {
+ $user = common_current_user();
+
+ $orig = clone($user);
+ $user->incomingemail = mail_new_incoming_address();
+
+ if (!$user->updateKeys($orig)) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ $this->server_error(_("Couldn't update user record."));
+ }
+
+ $this->show_form(_('New incoming email address added.'), TRUE);
+ }
+
+ function email_exists($email) {
+ $user = common_current_user();
+ $other = User::staticGet('email', $email);
+ if (!$other) {
+ return false;
+ } else {
+ return $other->id != $user->id;
+ }
+ }
+}
diff --git a/actions/facebookhome.php b/actions/facebookhome.php
new file mode 100644
index 000000000..8ee2d4cd3
--- /dev/null
+++ b/actions/facebookhome.php
@@ -0,0 +1,132 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/facebookaction.php');
+
+class FacebookhomeAction extends FacebookAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $this->login();
+ }
+
+ function login() {
+
+ $user = null;
+
+ $facebook = $this->get_facebook();
+ $fbuid = $facebook->require_login();
+
+ # check to see whether there's already a Facebook link for this user
+ $flink = Foreign_link::getByForeignID($fbuid, 2); // 2 == Facebook
+
+ if ($flink) {
+
+ $user = $flink->getUser();
+ $this->show_home($facebook, $fbuid, $user);
+
+ } else {
+
+ # Make the user put in her Laconica creds
+ $nickname = common_canonical_nickname($this->trimmed('nickname'));
+ $password = $this->arg('password');
+
+ if ($nickname) {
+
+ if (common_check_user($nickname, $password)) {
+
+
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ echo '<fb:error message="Coudln\'t get user!" />';
+ $this->show_login_form();
+ }
+
+ $flink = DB_DataObject::factory('foreign_link');
+ $flink->user_id = $user->id;
+ $flink->foreign_id = $fbuid;
+ $flink->service = 2; # Facebook
+ $flink->created = common_sql_now();
+
+ # $this->set_flags($flink, $noticesync, $replysync, $friendsync);
+
+ $flink_id = $flink->insert();
+
+ if ($flink_id) {
+ echo '<fb:success message="You can now use the Identi.ca from Facebook!" />';
+ }
+
+ $this->show_home($facebook, $fbuid, $user);
+
+ return;
+ } else {
+ echo '<fb:error message="Incorrect username or password." />';
+ }
+ }
+
+ $this->show_login_form();
+ }
+
+ }
+
+ function show_home($facebook, $fbuid, $user) {
+
+ $this->show_header('Home');
+
+ echo $this->show_notices($user);
+ $this->update_profile_box($facebook, $fbuid, $user);
+
+ $this->show_footer();
+ }
+
+ function show_notices($user) {
+
+ $page = $this->trimmed('page');
+ if (!$page) {
+ $page = 1;
+ }
+
+ $notice = $user->noticesWithFriends(($page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
+ echo '<ul id="notices">';
+
+ $cnt = 0;
+
+ while ($notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
+ $cnt++;
+
+ if ($cnt > NOTICES_PER_PAGE) {
+ break;
+ }
+
+ echo $this->render_notice($notice);
+ }
+
+ echo '<ul>';
+
+ $this->pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'index.php', array('nickname' => $user->nickname));
+
+ }
+
+}
diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php
new file mode 100644
index 000000000..68b351fb9
--- /dev/null
+++ b/actions/facebookinvite.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/facebookaction.php');
+
+class FacebookinviteAction extends FacebookAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $this->display();
+ }
+
+ function display() {
+
+ $facebook = $this->get_facebook();
+
+ $fbuid = $facebook->require_login();
+
+ $this->show_header('Invite');
+
+ echo '<h2>Coming soon...</h2>';
+
+ $this->show_footer();
+
+ }
+
+}
diff --git a/actions/facebookremove.php b/actions/facebookremove.php
new file mode 100644
index 000000000..2a7bdd03e
--- /dev/null
+++ b/actions/facebookremove.php
@@ -0,0 +1,65 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/facebookaction.php');
+
+class FacebookremoveAction extends FacebookAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $secret = common_config('facebook', 'secret');
+
+ $sig = '';
+
+ ksort($_POST);
+
+ foreach ($_POST as $key => $val) {
+ if (substr($key, 0, 7) == 'fb_sig_') {
+ $sig .= substr($key, 7) . '=' . $val;
+ }
+ }
+
+ $sig .= $secret;
+ $verify = md5($sig);
+
+ if ($verify == $this->arg('fb_sig')) {
+
+ $flink = Foreign_link::getByForeignID($this->arg('fb_sig_user'), 2);
+
+ common_debug("Removing foreign link to Facebook - local user ID: $flink->user_id, Facebook ID: $flink->foreign_id");
+
+ $result = $flink->delete();
+
+ if (!$result) {
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ common_server_error(_('Couldn\'t remove Facebook user.'));
+ return;
+ }
+
+ } else {
+ # Someone bad tried to remove facebook link?
+ common_log(LOG_ERR, "Someone from $_SERVER[REMOTE_ADDR] " .
+ 'unsuccessfully tried to remove a foreign link to Facebook!');
+ }
+ }
+
+}
diff --git a/actions/facebooksettings.php b/actions/facebooksettings.php
new file mode 100644
index 000000000..4d7000d60
--- /dev/null
+++ b/actions/facebooksettings.php
@@ -0,0 +1,52 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/facebookaction.php');
+
+class FacebooksettingsAction extends FacebookAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $this->display();
+ }
+
+ function display() {
+
+ $facebook = $this->get_facebook();
+
+ $fbuid = $facebook->require_login();
+
+ $fbml = '<fb:if-section-not-added section="profile">'
+ .'<h2>Add an Identi.ca box to your profile!</h2>'
+ .'<fb:add-section-button section="profile"/>'
+ .'</fb:if-section-not-added>';
+
+
+ $this->show_header('Settings');
+
+ echo $fbml;
+
+ $this->show_footer();
+
+ }
+
+}
diff --git a/actions/favor.php b/actions/favor.php
new file mode 100644
index 000000000..aede32902
--- /dev/null
+++ b/actions/favor.php
@@ -0,0 +1,94 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/mail.php');
+
+class FavorAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ common_redirect(common_local_url('showfavorites', array('nickname' => $user->nickname)));
+ return;
+ }
+
+ $id = $this->trimmed('notice');
+
+ $notice = Notice::staticGet($id);
+
+ # CSRF protection
+
+ $token = $this->trimmed('token-'.$notice->id);
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_("There was a problem with your session token. Try again, please."));
+ return;
+ }
+
+ if ($user->hasFave($notice)) {
+ $this->client_error(_('This notice is already a favorite!'));
+ return;
+ }
+
+ $fave = Fave::addNew($user, $notice);
+
+ if (!$fave) {
+ $this->server_error(_('Could not create favorite.'));
+ return;
+ }
+
+ $this->notify($fave, $notice, $user);
+ $user->blowFavesCache();
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Disfavor favorite'));
+ common_element_end('head');
+ common_element_start('body');
+ common_disfavor_form($notice);
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ common_redirect(common_local_url('showfavorites',
+ array('nickname' => $user->nickname)));
+ }
+ }
+
+ function notify($fave, $notice, $user) {
+ $other = User::staticGet('id', $notice->profile_id);
+ if ($other && $other->id != $user->id) {
+ if ($other->email && $other->emailnotifyfav) {
+ mail_notify_fave($other, $user, $notice);
+ }
+ # XXX: notify by IM
+ # XXX: notify by SMS
+ }
+ }
+
+}
diff --git a/actions/favorited.php b/actions/favorited.php
new file mode 100644
index 000000000..dc8070d06
--- /dev/null
+++ b/actions/favorited.php
@@ -0,0 +1,99 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/stream.php');
+
+class FavoritedAction extends StreamAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ common_show_header(_('Popular notices'),
+ array($this, 'show_header'), NULL,
+ array($this, 'show_top'));
+
+ $this->show_notices($page);
+
+ common_show_footer();
+ }
+
+ function show_top() {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ $this->public_views_menu();
+ }
+
+ function show_header() {
+ return;
+ }
+
+ function get_instructions() {
+ return _('Showing recently popular notices');
+ }
+
+ function show_notices($page) {
+
+ $qry = 'SELECT notice.*, sum(exp(-(now() - fave.modified) / %s)) as weight ' .
+ 'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
+ 'GROUP BY fave.notice_id ' .
+ 'ORDER BY weight DESC';
+
+ $offset = ($page - 1) * NOTICES_PER_PAGE;
+ $limit = NOTICES_PER_PAGE + 1;
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ # Figure out how to cache this query
+
+ $notice = new Notice;
+ $notice->query(sprintf($qry, common_config('popular', 'dropoff')));
+
+ common_element_start('ul', array('id' => 'notices'));
+
+ $cnt = 0;
+
+ while ($notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
+ $cnt++;
+
+ if ($cnt > NOTICES_PER_PAGE) {
+ break;
+ }
+
+ $item = new NoticeListItem($notice);
+ $item->show();
+ }
+
+ common_element_end('ul');
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'favorited');
+ }
+
+}
diff --git a/actions/favoritesrss.php b/actions/favoritesrss.php
new file mode 100644
index 000000000..25dd3861f
--- /dev/null
+++ b/actions/favoritesrss.php
@@ -0,0 +1,73 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class FavoritesrssAction extends Rss10Action {
+
+ var $user = NULL;
+
+ function init() {
+ $nickname = $this->trimmed('nickname');
+ $this->user = User::staticGet('nickname', $nickname);
+
+ if (!$this->user) {
+ common_user_error(_('No such user.'));
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function get_notices($limit=0) {
+
+ $user = $this->user;
+
+ $notice = $user->favoriteNotices(0, $limit);
+
+ $notices = array();
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ $user = $this->user;
+ $c = array('url' => common_local_url('favoritesrss',
+ array('nickname' =>
+ $user->nickname)),
+ 'title' => sprintf(_("%s favorite notices"), $user->nickname),
+ 'link' => common_local_url('showfavorites',
+ array('nickname' =>
+ $user->nickname)),
+ 'description' => sprintf(_('Feed of favorite notices of %s'), $user->nickname));
+ return $c;
+ }
+
+ function get_image() {
+ return NULL;
+ }
+} \ No newline at end of file
diff --git a/actions/featured.php b/actions/featured.php
new file mode 100644
index 000000000..96fbd89ab
--- /dev/null
+++ b/actions/featured.php
@@ -0,0 +1,102 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/stream.php');
+require_once(INSTALLDIR.'/lib/profilelist.php');
+
+class FeaturedAction extends StreamAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ common_show_header(_('Featured users'),
+ array($this, 'show_header'), NULL,
+ array($this, 'show_top'));
+
+ $this->show_notices($page);
+
+ common_show_footer();
+ }
+
+ function show_top() {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ $this->public_views_menu();
+ }
+
+ function show_header() {
+ }
+
+ function get_instructions() {
+ return _('Featured users');
+ }
+
+ function show_notices($page) {
+
+ // XXX: Note I'm doing it this two-stage way because a raw query
+ // with a JOIN was *not* working. --Zach
+
+ $featured_nicks = common_config('nickname', 'featured');
+
+ if (count($featured_nicks) > 0) {
+
+ $quoted = array();
+
+ foreach ($featured_nicks as $nick) {
+ $quoted[] = "'$nick'";
+ }
+
+ $user = new User;
+ $user->whereAdd(sprintf('nickname IN (%s)', implode(',', $quoted)));
+ $user->limit(($page - 1) * PROFILES_PER_PAGE, PROFILES_PER_PAGE + 1);
+ $user->orderBy('user.nickname ASC');
+
+ $user->find();
+
+ $profile_ids = array();
+
+ while ($user->fetch()) {
+ $profile_ids[] = $user->id;
+ }
+
+ $profile = new Profile;
+ $profile->whereAdd(sprintf('profile.id IN (%s)', implode(',', $profile_ids)));
+ $profile->orderBy('nickname ASC');
+
+ $cnt = $profile->find();
+
+ if ($cnt > 0) {
+ $featured = new ProfileList($profile);
+ $featured->show_list();
+ }
+
+ $profile->free();
+
+ common_pagination($page > 1, $cnt > PROFILES_PER_PAGE, $page, 'featured');
+ }
+ }
+
+} \ No newline at end of file
diff --git a/actions/finishaddopenid.php b/actions/finishaddopenid.php
new file mode 100644
index 000000000..54d81b0b4
--- /dev/null
+++ b/actions/finishaddopenid.php
@@ -0,0 +1,103 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class FinishaddopenidAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ } else {
+ $this->try_login();
+ }
+ }
+
+ function try_login() {
+
+ $consumer =& oid_consumer();
+
+ $response = $consumer->complete(common_local_url('finishaddopenid'));
+
+ if ($response->status == Auth_OpenID_CANCEL) {
+ $this->message(_('OpenID authentication cancelled.'));
+ return;
+ } else if ($response->status == Auth_OpenID_FAILURE) {
+ // Authentication failed; display the error message.
+ $this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
+ } else if ($response->status == Auth_OpenID_SUCCESS) {
+
+ $display = $response->getDisplayIdentifier();
+ $canonical = ($response->endpoint && $response->endpoint->canonicalID) ?
+ $response->endpoint->canonicalID : $display;
+
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+ if ($sreg_resp) {
+ $sreg = $sreg_resp->contents();
+ }
+
+ $cur =& common_current_user();
+ $other = oid_get_user($canonical);
+
+ if ($other) {
+ if ($other->id == $cur->id) {
+ $this->message(_('You already have this OpenID!'));
+ } else {
+ $this->message(_('Someone else already has this OpenID.'));
+ }
+ return;
+ }
+
+ # start a transaction
+
+ $cur->query('BEGIN');
+
+ $result = oid_link_user($cur->id, $canonical, $display);
+
+ if (!$result) {
+ $this->message(_('Error connecting user.'));
+ return;
+ }
+ if ($sreg) {
+ if (!oid_update_user($cur, $sreg)) {
+ $this->message(_('Error updating profile'));
+ return;
+ }
+ }
+
+ # success!
+
+ $cur->query('COMMIT');
+
+ oid_set_last($display);
+
+ common_redirect(common_local_url('openidsettings'));
+ }
+ }
+
+ function message($msg) {
+ common_show_header(_('OpenID Login'));
+ common_element('p', NULL, $msg);
+ common_show_footer();
+ }
+}
diff --git a/actions/finishimmediate.php b/actions/finishimmediate.php
new file mode 100644
index 000000000..6dbaa3d1c
--- /dev/null
+++ b/actions/finishimmediate.php
@@ -0,0 +1,65 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class FinishimmediateAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $consumer = oid_consumer();
+
+ $response = $consumer->complete(common_local_url('finishimmediate'));
+
+ if ($response->status == Auth_OpenID_SUCCESS) {
+ $display = $response->getDisplayIdentifier();
+ $canonical = ($response->endpoint->canonicalID) ?
+ $response->endpoint->canonicalID : $response->getDisplayIdentifier();
+
+ $user = oid_get_user($canonical);
+
+ if ($user) {
+ oid_update_user($user, $sreg);
+ oid_set_last($display); # refresh for another year
+ common_set_user($user->nickname);
+ $this->go_backto();
+ return;
+ }
+ }
+
+ # Failure! Clear openid so we don't try it again
+
+ oid_clear_last();
+ $this->go_backto();
+ return;
+ }
+
+ function go_backto() {
+ common_ensure_session();
+ $backto = $_SESSION['openid_immediate_backto'];
+ if (!$backto) {
+ # gar. Well, push them to the public page
+ $backto = common_local_url('public');
+ }
+ common_redirect($backto);
+ }
+}
diff --git a/actions/finishopenidlogin.php b/actions/finishopenidlogin.php
new file mode 100644
index 000000000..766a08b20
--- /dev/null
+++ b/actions/finishopenidlogin.php
@@ -0,0 +1,436 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class FinishopenidloginAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (common_logged_in()) {
+ common_user_error(_('Already logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+ if ($this->arg('create')) {
+ if (!$this->boolean('license')) {
+ $this->show_form(_('You can\'t register if you don\'t agree to the license.'),
+ $this->trimmed('newname'));
+ return;
+ }
+ $this->create_new_user();
+ } else if ($this->arg('connect')) {
+ $this->connect_user();
+ } else {
+ common_debug(print_r($this->args, true), __FILE__);
+ $this->show_form(_('Something weird happened.'),
+ $this->trimmed('newname'));
+ }
+ } else {
+ $this->try_login();
+ }
+ }
+
+ function show_top($error=NULL) {
+ if ($error) {
+ common_element('div', array('class' => 'error'), $error);
+ } else {
+ global $config;
+ common_element('div', 'instructions',
+ sprintf(_('This is the first time you\'ve logged into %s so we must connect your OpenID to a local account. You can either create a new account, or connect with your existing account, if you have one.'), $config['site']['name']));
+ }
+ }
+
+ function show_form($error=NULL, $username=NULL) {
+ common_show_header(_('OpenID Account Setup'), NULL, $error,
+ array($this, 'show_top'));
+
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'account_connect',
+ 'action' => common_local_url('finishopenidlogin')));
+ common_hidden('token', common_session_token());
+ common_element('h2', NULL,
+ _('Create new account'));
+ common_element('p', NULL,
+ _('Create a new user with this nickname.'));
+ common_input('newname', _('New nickname'),
+ ($username) ? $username : '',
+ _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+ common_element_start('p');
+ common_element('input', array('type' => 'checkbox',
+ 'id' => 'license',
+ 'name' => 'license',
+ 'value' => 'true'));
+ common_text(_('My text and files are available under '));
+ common_element('a', array(href => common_config('license', 'url')),
+ common_config('license', 'title'));
+ common_text(_(' except this private data: password, email address, IM address, phone number.'));
+ common_element_end('p');
+ common_submit('create', _('Create'));
+ common_element('h2', NULL,
+ _('Connect existing account'));
+ common_element('p', NULL,
+ _('If you already have an account, login with your username and password to connect it to your OpenID.'));
+ common_input('nickname', _('Existing nickname'));
+ common_password('password', _('Password'));
+ common_submit('connect', _('Connect'));
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function try_login() {
+
+ $consumer = oid_consumer();
+
+ $response = $consumer->complete(common_local_url('finishopenidlogin'));
+
+ if ($response->status == Auth_OpenID_CANCEL) {
+ $this->message(_('OpenID authentication cancelled.'));
+ return;
+ } else if ($response->status == Auth_OpenID_FAILURE) {
+ // Authentication failed; display the error message.
+ $this->message(sprintf(_('OpenID authentication failed: %s'), $response->message));
+ } else if ($response->status == Auth_OpenID_SUCCESS) {
+ // This means the authentication succeeded; extract the
+ // identity URL and Simple Registration data (if it was
+ // returned).
+ $display = $response->getDisplayIdentifier();
+ $canonical = ($response->endpoint->canonicalID) ?
+ $response->endpoint->canonicalID : $response->getDisplayIdentifier();
+
+ $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
+
+ if ($sreg_resp) {
+ $sreg = $sreg_resp->contents();
+ }
+
+ $user = oid_get_user($canonical);
+
+ if ($user) {
+ oid_set_last($display);
+ # XXX: commented out at @edd's request until better
+ # control over how data flows from OpenID provider.
+ # oid_update_user($user, $sreg);
+ common_set_user($user);
+ common_real_login(true);
+ if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+ common_rememberme($user);
+ }
+ unset($_SESSION['openid_rememberme']);
+ $this->go_home($user->nickname);
+ } else {
+ $this->save_values($display, $canonical, $sreg);
+ $this->show_form(NULL, $this->best_new_nickname($display, $sreg));
+ }
+ }
+ }
+
+ function message($msg) {
+ common_show_header(_('OpenID Login'));
+ common_element('p', NULL, $msg);
+ common_show_footer();
+ }
+
+ function save_values($display, $canonical, $sreg) {
+ common_ensure_session();
+ $_SESSION['openid_display'] = $display;
+ $_SESSION['openid_canonical'] = $canonical;
+ $_SESSION['openid_sreg'] = $sreg;
+ }
+
+ function get_saved_values() {
+ return array($_SESSION['openid_display'],
+ $_SESSION['openid_canonical'],
+ $_SESSION['openid_sreg']);
+ }
+
+ function create_new_user() {
+
+ # FIXME: save invite code before redirect, and check here
+
+ if (common_config('site', 'closed') || common_config('site', 'inviteonly')) {
+ common_user_error(_('Registration not allowed.'));
+ return;
+ }
+
+ $nickname = $this->trimmed('newname');
+
+ if (!Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ $this->show_form(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+ return;
+ }
+
+ if (!User::allowed_nickname($nickname)) {
+ $this->show_form(_('Nickname not allowed.'));
+ return;
+ }
+
+ if (User::staticGet('nickname', $nickname)) {
+ $this->show_form(_('Nickname already in use. Try another one.'));
+ return;
+ }
+
+ list($display, $canonical, $sreg) = $this->get_saved_values();
+
+ if (!$display || !$canonical) {
+ common_server_error(_('Stored OpenID not found.'));
+ return;
+ }
+
+ # Possible race condition... let's be paranoid
+
+ $other = oid_get_user($canonical);
+
+ if ($other) {
+ common_server_error(_('Creating new account for OpenID that already has a user.'));
+ return;
+ }
+
+ if ($sreg['country']) {
+ if ($sreg['postcode']) {
+ # XXX: use postcode to get city and region
+ # XXX: also, store postcode somewhere -- it's valuable!
+ $location = $sreg['postcode'] . ', ' . $sreg['country'];
+ } else {
+ $location = $sreg['country'];
+ }
+ }
+
+ if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
+ $fullname = $sreg['fullname'];
+ }
+
+ if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+ $email = $sreg['email'];
+ }
+
+ # XXX: add language
+ # XXX: add timezone
+
+ $user = User::register(array('nickname' => $nickname,
+ 'email' => $email,
+ 'fullname' => $fullname,
+ 'location' => $location));
+
+ $result = oid_link_user($user->id, $canonical, $display);
+
+ oid_set_last($display);
+ common_set_user($user);
+ common_real_login(true);
+ if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+ common_rememberme($user);
+ }
+ unset($_SESSION['openid_rememberme']);
+ common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)));
+ }
+
+ function connect_user() {
+
+ $nickname = $this->trimmed('nickname');
+ $password = $this->trimmed('password');
+
+ if (!common_check_user($nickname, $password)) {
+ $this->show_form(_('Invalid username or password.'));
+ return;
+ }
+
+ # They're legit!
+
+ $user = User::staticGet('nickname', $nickname);
+
+ list($display, $canonical, $sreg) = $this->get_saved_values();
+
+ if (!$display || !$canonical) {
+ common_server_error(_('Stored OpenID not found.'));
+ return;
+ }
+
+ $result = oid_link_user($user->id, $canonical, $display);
+
+ if (!$result) {
+ common_server_error(_('Error connecting user to OpenID.'));
+ return;
+ }
+
+ oid_update_user($user, $sreg);
+ oid_set_last($display);
+ common_set_user($user);
+ common_real_login(true);
+ if (isset($_SESSION['openid_rememberme']) && $_SESSION['openid_rememberme']) {
+ common_rememberme($user);
+ }
+ unset($_SESSION['openid_rememberme']);
+ $this->go_home($user->nickname);
+ }
+
+ function go_home($nickname) {
+ $url = common_get_returnto();
+ if ($url) {
+ # We don't have to return to it again
+ common_set_returnto(NULL);
+ } else {
+ $url = common_local_url('all',
+ array('nickname' =>
+ $nickname));
+ }
+ common_redirect($url);
+ }
+
+ function best_new_nickname($display, $sreg) {
+
+ # Try the passed-in nickname
+
+ if ($sreg['nickname']) {
+ $nickname = $this->nicknamize($sreg['nickname']);
+ if ($this->is_new_nickname($nickname)) {
+ return $nickname;
+ }
+ }
+
+ # Try the full name
+
+ if ($sreg['fullname']) {
+ $fullname = $this->nicknamize($sreg['fullname']);
+ if ($this->is_new_nickname($fullname)) {
+ return $fullname;
+ }
+ }
+
+ # Try the URL
+
+ $from_url = $this->openid_to_nickname($display);
+
+ if ($from_url && $this->is_new_nickname($from_url)) {
+ return $from_url;
+ }
+
+ # XXX: others?
+
+ return NULL;
+ }
+
+ function is_new_nickname($str) {
+ if (!Validate::string($str, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ return false;
+ }
+ if (!User::allowed_nickname($str)) {
+ return false;
+ }
+ if (User::staticGet('nickname', $str)) {
+ return false;
+ }
+ return true;
+ }
+
+ function openid_to_nickname($openid) {
+ if (Auth_Yadis_identifierScheme($openid) == 'XRI') {
+ return $this->xri_to_nickname($openid);
+ } else {
+ return $this->url_to_nickname($openid);
+ }
+ }
+
+ # We try to use an OpenID URL as a legal Laconica user name in this order
+ # 1. Plain hostname, like http://evanp.myopenid.com/
+ # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
+ # or http://getopenid.com/evanprodromou
+
+ function url_to_nickname($openid) {
+ static $bad = array('query', 'user', 'password', 'port', 'fragment');
+
+ $parts = parse_url($openid);
+
+ # If any of these parts exist, this won't work
+
+ foreach ($bad as $badpart) {
+ if (array_key_exists($badpart, $parts)) {
+ return NULL;
+ }
+ }
+
+ # We just have host and/or path
+
+ # If it's just a host...
+ if (array_key_exists('host', $parts) &&
+ (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
+ {
+ $hostparts = explode('.', $parts['host']);
+
+ # Try to catch common idiom of nickname.service.tld
+
+ if ((count($hostparts) > 2) &&
+ (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
+ (strcmp($hostparts[0], 'www') != 0))
+ {
+ return $this->nicknamize($hostparts[0]);
+ } else {
+ # Do the whole hostname
+ return $this->nicknamize($parts['host']);
+ }
+ } else {
+ if (array_key_exists('path', $parts)) {
+ # Strip starting, ending slashes
+ $path = preg_replace('@/$@', '', $parts['path']);
+ $path = preg_replace('@^/@', '', $path);
+ if (strpos($path, '/') === false) {
+ return $this->nicknamize($path);
+ }
+ }
+ }
+
+ return NULL;
+ }
+
+ function xri_to_nickname($xri) {
+ $base = $this->xri_base($xri);
+
+ if (!$base) {
+ return NULL;
+ } else {
+ # =evan.prodromou
+ # or @gratis*evan.prodromou
+ $parts = explode('*', substr($base, 1));
+ return $this->nicknamize(array_pop($parts));
+ }
+ }
+
+ function xri_base($xri) {
+ if (substr($xri, 0, 6) == 'xri://') {
+ return substr($xri, 6);
+ } else {
+ return $xri;
+ }
+ }
+
+ # Given a string, try to make it work as a nickname
+
+ function nicknamize($str) {
+ $str = preg_replace('/\W/', '', $str);
+ return strtolower($str);
+ }
+}
diff --git a/actions/finishremotesubscribe.php b/actions/finishremotesubscribe.php
new file mode 100644
index 000000000..58040683f
--- /dev/null
+++ b/actions/finishremotesubscribe.php
@@ -0,0 +1,288 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class FinishremotesubscribeAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ if (common_logged_in()) {
+ common_user_error(_('You can use the local subscription!'));
+ return;
+ }
+
+ $omb = $_SESSION['oauth_authorization_request'];
+
+ if (!$omb) {
+ common_user_error(_('Not expecting this response!'));
+ return;
+ }
+
+ common_debug('stored request: '.print_r($omb,true), __FILE__);
+
+ common_remove_magic_from_request();
+ $req = OAuthRequest::from_request();
+
+ $token = $req->get_parameter('oauth_token');
+
+ # I think this is the success metric
+
+ if ($token != $omb['token']) {
+ common_user_error(_('Not authorized.'));
+ return;
+ }
+
+ $version = $req->get_parameter('omb_version');
+
+ if ($version != OMB_VERSION_01) {
+ common_user_error(_('Unknown version of OMB protocol.'));
+ return;
+ }
+
+ $nickname = $req->get_parameter('omb_listener_nickname');
+
+ if (!$nickname) {
+ common_user_error(_('No nickname provided by remote server.'));
+ return;
+ }
+
+ $profile_url = $req->get_parameter('omb_listener_profile');
+
+ if (!$profile_url) {
+ common_user_error(_('No profile URL returned by server.'));
+ return;
+ }
+
+ if (!Validate::uri($profile_url, array('allowed_schemes' => array('http', 'https')))) {
+ common_user_error(_('Invalid profile URL returned by server.'));
+ return;
+ }
+
+ if ($profile_url == common_local_url('showstream', array('nickname' => $nickname))) {
+ common_user_error(_('You can use the local subscription!'));
+ return;
+ }
+
+ common_debug('listenee: "'.$omb['listenee'].'"', __FILE__);
+
+ $user = User::staticGet('nickname', $omb['listenee']);
+
+ if (!$user) {
+ common_user_error(_('User being listened to doesn\'t exist.'));
+ return;
+ }
+
+ $other = User::staticGet('uri', $omb['listener']);
+
+ if ($other) {
+ common_user_error(_('You can use the local subscription!'));
+ return;
+ }
+
+ $fullname = $req->get_parameter('omb_listener_fullname');
+ $homepage = $req->get_parameter('omb_listener_homepage');
+ $bio = $req->get_parameter('omb_listener_bio');
+ $location = $req->get_parameter('omb_listener_location');
+ $avatar_url = $req->get_parameter('omb_listener_avatar');
+
+ list($newtok, $newsecret) = $this->access_token($omb);
+
+ if (!$newtok || !$newsecret) {
+ common_user_error(_('Couldn\'t convert request tokens to access tokens.'));
+ return;
+ }
+
+ # XXX: possible attack point; subscribe and return someone else's profile URI
+
+ $remote = Remote_profile::staticGet('uri', $omb['listener']);
+
+ if ($remote) {
+ $exists = true;
+ $profile = Profile::staticGet($remote->id);
+ $orig_remote = clone($remote);
+ $orig_profile = clone($profile);
+ # XXX: compare current postNotice and updateProfile URLs to the ones
+ # stored in the DB to avoid (possibly...) above attack
+ } else {
+ $exists = false;
+ $remote = new Remote_profile();
+ $remote->uri = $omb['listener'];
+ $profile = new Profile();
+ }
+
+ $profile->nickname = $nickname;
+ $profile->profileurl = $profile_url;
+
+ if ($fullname) {
+ $profile->fullname = $fullname;
+ }
+ if ($homepage) {
+ $profile->homepage = $homepage;
+ }
+ if ($bio) {
+ $profile->bio = $bio;
+ }
+ if ($location) {
+ $profile->location = $location;
+ }
+
+ if ($exists) {
+ $profile->update($orig_profile);
+ } else {
+ $profile->created = DB_DataObject_Cast::dateTime(); # current time
+ $id = $profile->insert();
+ if (!$id) {
+ common_server_error(_('Error inserting new profile'));
+ return;
+ }
+ $remote->id = $id;
+ }
+
+ if ($avatar_url) {
+ if (!$this->add_avatar($profile, $avatar_url)) {
+ common_server_error(_('Error inserting avatar'));
+ return;
+ }
+ }
+
+ $remote->postnoticeurl = $omb['post_notice_url'];
+ $remote->updateprofileurl = $omb['update_profile_url'];
+
+ if ($exists) {
+ if (!$remote->update($orig_remote)) {
+ common_server_error(_('Error updating remote profile'));
+ return;
+ }
+ } else {
+ $remote->created = DB_DataObject_Cast::dateTime(); # current time
+ if (!$remote->insert()) {
+ common_server_error(_('Error inserting remote profile'));
+ return;
+ }
+ }
+
+ if ($user->hasBlocked($profile)) {
+ $this->client_error(_('That user has blocked you from subscribing.'));
+ return;
+ }
+
+ $sub = new Subscription();
+
+ $sub->subscriber = $remote->id;
+ $sub->subscribed = $user->id;
+
+ $sub_exists = false;
+
+ if ($sub->find(true)) {
+ $sub_exists = true;
+ $orig_sub = clone($sub);
+ } else {
+ $sub_exists = false;
+ $sub->created = DB_DataObject_Cast::dateTime(); # current time
+ }
+
+ $sub->token = $newtok;
+ $sub->secret = $newsecret;
+
+ if ($sub_exists) {
+ $result = $sub->update($orig_sub);
+ } else {
+ $result = $sub->insert();
+ }
+
+ if (!$result) {
+ common_log_db_error($sub, ($sub_exists) ? 'UPDATE' : 'INSERT', __FILE__);
+ common_user_error(_('Couldn\'t insert new subscription.'));
+ return;
+ }
+
+ # Notify user, if necessary
+
+ mail_subscribe_notify_profile($user, $profile);
+
+ # Clear the data
+ unset($_SESSION['oauth_authorization_request']);
+
+ # If we show subscriptions in reverse chron order, this should
+ # show up close to the top of the page
+
+ common_redirect(common_local_url('subscribers', array('nickname' =>
+ $user->nickname)));
+ }
+
+ function add_avatar($profile, $url) {
+ $temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
+ copy($url, $temp_filename);
+ return $profile->setOriginal($temp_filename);
+ }
+
+ function access_token($omb) {
+
+ common_debug('starting request for access token', __FILE__);
+
+ $con = omb_oauth_consumer();
+ $tok = new OAuthToken($omb['token'], $omb['secret']);
+
+ common_debug('using request token "'.$tok.'"', __FILE__);
+
+ $url = $omb['access_token_url'];
+
+ common_debug('using access token url "'.$url.'"', __FILE__);
+
+ # XXX: Is this the right thing to do? Strip off GET params and make them
+ # POST params? Seems wrong to me.
+
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
+
+ $req = OAuthRequest::from_consumer_and_token($con, $tok, "POST", $url, $params);
+
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+
+ # XXX: test to see if endpoint accepts this signature method
+
+ $req->sign_request(omb_hmac_sha1(), $con, $tok);
+
+ # We re-use this tool's fetcher, since it's pretty good
+
+ common_debug('posting to access token url "'.$req->get_normalized_http_url().'"', __FILE__);
+ common_debug('posting request data "'.$req->to_postdata().'"', __FILE__);
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $result = $fetcher->post($req->get_normalized_http_url(),
+ $req->to_postdata(),
+ array('User-Agent' => 'Laconica/' . LACONICA_VERSION));
+
+ common_debug('got result: "'.print_r($result,TRUE).'"', __FILE__);
+
+ if ($result->status != 200) {
+ return NULL;
+ }
+
+ parse_str($result->body, $return);
+
+ return array($return['oauth_token'], $return['oauth_token_secret']);
+ }
+}
diff --git a/actions/foaf.php b/actions/foaf.php
new file mode 100644
index 000000000..6811fc05a
--- /dev/null
+++ b/actions/foaf.php
@@ -0,0 +1,202 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+define('LISTENER', 1);
+define('LISTENEE', -1);
+define('BOTH', 0);
+
+class FoafAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+
+ $nickname = $this->trimmed('nickname');
+
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ common_user_error(_('No such user.'), 404);
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'), 500);
+ return;
+ }
+
+ header('Content-Type: application/rdf+xml');
+
+ common_start_xml();
+ common_element_start('rdf:RDF', array('xmlns:rdf' =>
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'xmlns:rdfs' =>
+ 'http://www.w3.org/2000/01/rdf-schema#',
+ 'xmlns:geo' =>
+ 'http://www.w3.org/2003/01/geo/wgs84_pos#',
+ 'xmlns' => 'http://xmlns.com/foaf/0.1/'));
+
+ # This is the document about the user
+
+ $this->show_ppd('', $user->uri);
+
+ # XXX: might not be a person
+ common_element_start('Person', array('rdf:about' =>
+ $user->uri));
+ common_element('mbox_sha1sum', NULL, sha1('mailto:' . $user->email));
+ if ($profile->fullname) {
+ common_element('name', NULL, $profile->fullname);
+ }
+ if ($profile->homepage) {
+ common_element('homepage', array('rdf:resource' => $profile->homepage));
+ }
+ if ($profile->bio) {
+ common_element('rdfs:comment', NULL, $profile->bio);
+ }
+ # XXX: more structured location data
+ if ($profile->location) {
+ common_element_start('based_near');
+ common_element_start('geo:SpatialThing');
+ common_element('name', NULL, $profile->location);
+ common_element_end('geo:SpatialThing');
+ common_element_end('based_near');
+ }
+
+ $this->show_microblogging_account($profile, common_root_url());
+
+ $avatar = $profile->getOriginalAvatar();
+
+ if ($avatar) {
+ common_element_start('img');
+ common_element_start('Image', array('rdf:about' => $avatar->url));
+ foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
+ $scaled = $profile->getAvatar($size);
+ if (!$scaled->original) { # sometimes the original has one of our scaled sizes
+ common_element_start('thumbnail');
+ common_element('Image', array('rdf:about' => $scaled->url));
+ common_element_end('thumbnail');
+ }
+ }
+ common_element_end('Image');
+ common_element_end('img');
+ }
+
+ # Get people user is subscribed to
+
+ $person = array();
+
+ $sub = new Subscription();
+ $sub->subscriber = $profile->id;
+ $sub->whereAdd('subscriber != subscribed');
+
+ if ($sub->find()) {
+ while ($sub->fetch()) {
+ if ($sub->token) {
+ $other = Remote_profile::staticGet('id', $sub->subscribed);
+ } else {
+ $other = User::staticGet('id', $sub->subscribed);
+ }
+ if (!$other) {
+ common_debug('Got a bad subscription: '.print_r($sub,TRUE));
+ continue;
+ }
+ common_element('knows', array('rdf:resource' => $other->uri));
+ $person[$other->uri] = array(LISTENEE, $other);
+ }
+ }
+
+ # Get people who subscribe to user
+
+ $sub = new Subscription();
+ $sub->subscribed = $profile->id;
+ $sub->whereAdd('subscriber != subscribed');
+
+ if ($sub->find()) {
+ while ($sub->fetch()) {
+ if ($sub->token) {
+ $other = Remote_profile::staticGet('id', $sub->subscriber);
+ } else {
+ $other = User::staticGet('id', $sub->subscriber);
+ }
+ if (!$other) {
+ common_debug('Got a bad subscription: '.print_r($sub,TRUE));
+ continue;
+ }
+ if (array_key_exists($other->uri, $person)) {
+ $person[$other->uri][0] = BOTH;
+ } else {
+ $person[$other->uri] = array(LISTENER, $other);
+ }
+ }
+ }
+
+ common_element_end('Person');
+
+ foreach ($person as $uri => $p) {
+ $foaf_url = NULL;
+ if ($p[1] instanceof User) {
+ $foaf_url = common_local_url('foaf', array('nickname' => $p[1]->nickname));
+ }
+ $profile = Profile::staticGet($p[1]->id);
+ common_element_start('Person', array('rdf:about' => $uri));
+ if ($p[0] == LISTENER || $p[0] == BOTH) {
+ common_element('knows', array('rdf:resource' => $user->uri));
+ }
+ $this->show_microblogging_account($profile, ($p[1] instanceof User) ?
+ common_root_url() : NULL);
+ if ($foaf_url) {
+ common_element('rdfs:seeAlso', array('rdf:resource' => $foaf_url));
+ }
+ common_element_end('Person');
+ if ($foaf_url) {
+ $this->show_ppd($foaf_url, $uri);
+ }
+ }
+
+ common_element_end('rdf:RDF');
+ }
+
+ function show_ppd($foaf_url, $person_uri) {
+ common_element_start('PersonalProfileDocument', array('rdf:about' => $foaf_url));
+ common_element('maker', array('rdf:resource' => $person_uri));
+ common_element('primaryTopic', array('rdf:resource' => $person_uri));
+ common_element_end('PersonalProfileDocument');
+ }
+
+ function show_microblogging_account($profile, $service=NULL) {
+ # Their account
+ common_element_start('holdsAccount');
+ common_element_start('OnlineAccount');
+ if ($service) {
+ common_element('accountServiceHomepage', array('rdf:resource' =>
+ $service));
+ }
+ common_element('accountName', NULL, $profile->nickname);
+ common_element('homepage', array('rdf:resource' => $profile->profileurl));
+ common_element_end('OnlineAccount');
+ common_element_end('holdsAccount');
+ }
+}
diff --git a/actions/imsettings.php b/actions/imsettings.php
new file mode 100644
index 000000000..0aa7631dc
--- /dev/null
+++ b/actions/imsettings.php
@@ -0,0 +1,270 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+require_once(INSTALLDIR.'/lib/jabber.php');
+
+class ImsettingsAction extends SettingsAction {
+
+ function get_instructions() {
+ return _('You can send and receive notices through Jabber/GTalk [instant messages](%%doc.im%%). Configure your address and settings below.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $user = common_current_user();
+ $this->form_header(_('IM Settings'), $msg, $success);
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'imsettings',
+ 'action' =>
+ common_local_url('imsettings')));
+ common_hidden('token', common_session_token());
+
+ common_element('h2', NULL, _('Address'));
+
+ if ($user->jabber) {
+ common_element_start('p');
+ common_element('span', 'address confirmed', $user->jabber);
+ common_element('span', 'input_instructions',
+ _('Current confirmed Jabber/GTalk address.'));
+ common_hidden('jabber', $user->jabber);
+ common_element_end('p');
+ common_submit('remove', _('Remove'));
+ } else {
+ $confirm = $this->get_confirmation();
+ if ($confirm) {
+ common_element_start('p');
+ common_element('span', 'address unconfirmed', $confirm->address);
+ common_element('span', 'input_instructions',
+ sprintf(_('Awaiting confirmation on this address. Check your Jabber/GTalk account for a message with further instructions. (Did you add %s to your buddy list?)'), jabber_daemon_address()));
+ common_hidden('jabber', $confirm->address);
+ common_element_end('p');
+ common_submit('cancel', _('Cancel'));
+ } else {
+ common_input('jabber', _('IM Address'),
+ ($this->arg('jabber')) ? $this->arg('jabber') : NULL,
+ sprintf(_('Jabber or GTalk address, like "UserName@example.org". First, make sure to add %s to your buddy list in your IM client or on GTalk.'), jabber_daemon_address()));
+ common_submit('add', _('Add'));
+ }
+ }
+
+ common_element('h2', NULL, _('Preferences'));
+
+ common_checkbox('jabbernotify',
+ _('Send me notices through Jabber/GTalk.'),
+ $user->jabbernotify);
+ common_checkbox('updatefrompresence',
+ _('Post a notice when my Jabber/GTalk status changes.'),
+ $user->updatefrompresence);
+ common_checkbox('jabberreplies',
+ _('Send me replies through Jabber/GTalk from people I\'m not subscribed to.'),
+ $user->jabberreplies);
+ common_checkbox('jabbermicroid',
+ _('Publish a MicroID for my Jabber/GTalk address.'),
+ $user->jabbermicroid);
+ common_submit('save', _('Save'));
+
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function get_confirmation() {
+ $user = common_current_user();
+ $confirm = new Confirm_address();
+ $confirm->user_id = $user->id;
+ $confirm->address_type = 'jabber';
+ if ($confirm->find(TRUE)) {
+ return $confirm;
+ } else {
+ return NULL;
+ }
+ }
+
+ function handle_post() {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->save_preferences();
+ } else if ($this->arg('add')) {
+ $this->add_address();
+ } else if ($this->arg('cancel')) {
+ $this->cancel_confirmation();
+ } else if ($this->arg('remove')) {
+ $this->remove_address();
+ } else {
+ $this->show_form(_('Unexpected form submission.'));
+ }
+ }
+
+ function save_preferences() {
+
+ $jabbernotify = $this->boolean('jabbernotify');
+ $updatefrompresence = $this->boolean('updatefrompresence');
+ $jabberreplies = $this->boolean('jabberreplies');
+ $jabbermicroid = $this->boolean('jabbermicroid');
+
+ $user = common_current_user();
+
+ assert(!is_null($user)); # should already be checked
+
+ $user->query('BEGIN');
+
+ $original = clone($user);
+
+ $user->jabbernotify = $jabbernotify;
+ $user->updatefrompresence = $updatefrompresence;
+ $user->jabberreplies = $jabberreplies;
+ $user->jabbermicroid = $jabbermicroid;
+
+ $result = $user->update($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+
+ $user->query('COMMIT');
+
+ $this->show_form(_('Preferences saved.'), true);
+ }
+
+ function add_address() {
+
+ $user = common_current_user();
+
+ $jabber = $this->trimmed('jabber');
+
+ # Some validation
+
+ if (!$jabber) {
+ $this->show_form(_('No Jabber ID.'));
+ return;
+ }
+
+ $jabber = jabber_normalize_jid($jabber);
+
+ if (!$jabber) {
+ $this->show_form(_('Cannot normalize that Jabber ID'));
+ return;
+ }
+ if (!jabber_valid_base_jid($jabber)) {
+ $this->show_form(_('Not a valid Jabber ID'));
+ return;
+ } else if ($user->jabber == $jabber) {
+ $this->show_form(_('That is already your Jabber ID.'));
+ return;
+ } else if ($this->jabber_exists($jabber)) {
+ $this->show_form(_('Jabber ID already belongs to another user.'));
+ return;
+ }
+
+ $confirm = new Confirm_address();
+ $confirm->address = $jabber;
+ $confirm->address_type = 'jabber';
+ $confirm->user_id = $user->id;
+ $confirm->code = common_confirmation_code(64);
+
+ $result = $confirm->insert();
+
+ if ($result === FALSE) {
+ common_log_db_error($confirm, 'INSERT', __FILE__);
+ common_server_error(_('Couldn\'t insert confirmation code.'));
+ return;
+ }
+
+ if (!common_config('queue', 'enabled')) {
+ jabber_confirm_address($confirm->code,
+ $user->nickname,
+ $jabber);
+ }
+
+ $msg = sprintf(_('A confirmation code was sent to the IM address you added. You must approve %s for sending messages to you.'), jabber_daemon_address());
+
+ $this->show_form($msg, TRUE);
+ }
+
+ function cancel_confirmation() {
+ $jabber = $this->arg('jabber');
+ $confirm = $this->get_confirmation();
+ if (!$confirm) {
+ $this->show_form(_('No pending confirmation to cancel.'));
+ return;
+ }
+ if ($confirm->address != $jabber) {
+ $this->show_form(_('That is the wrong IM address.'));
+ return;
+ }
+
+ $result = $confirm->delete();
+
+ if (!$result) {
+ common_log_db_error($confirm, 'DELETE', __FILE__);
+ $this->server_error(_('Couldn\'t delete email confirmation.'));
+ return;
+ }
+
+ $this->show_form(_('Confirmation cancelled.'), TRUE);
+ }
+
+ function remove_address() {
+
+ $user = common_current_user();
+ $jabber = $this->arg('jabber');
+
+ # Maybe an old tab open...?
+
+ if ($user->jabber != $jabber) {
+ $this->show_form(_('That is not your Jabber ID.'));
+ return;
+ }
+
+ $user->query('BEGIN');
+ $original = clone($user);
+ $user->jabber = NULL;
+ $result = $user->updateKeys($original);
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+ $user->query('COMMIT');
+
+ # XXX: unsubscribe to the old address
+
+ $this->show_form(_('The address was removed.'), TRUE);
+ }
+
+ function jabber_exists($jabber) {
+ $user = common_current_user();
+ $other = User::staticGet('jabber', $jabber);
+ if (!$other) {
+ return false;
+ } else {
+ return $other->id != $user->id;
+ }
+ }
+}
diff --git a/actions/inbox.php b/actions/inbox.php
new file mode 100644
index 000000000..c752e404e
--- /dev/null
+++ b/actions/inbox.php
@@ -0,0 +1,55 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/mailbox.php');
+
+class InboxAction extends MailboxAction {
+
+ function get_title($user, $page) {
+ if ($page > 1) {
+ $title = sprintf(_("Inbox for %s - page %d"), $user->nickname, $page);
+ } else {
+ $title = sprintf(_("Inbox for %s"), $user->nickname);
+ }
+ return $title;
+ }
+
+ function get_messages($user, $page) {
+ $message = new Message();
+ $message->to_profile = $user->id;
+ $message->orderBy('created DESC, id DESC');
+ $message->limit((($page-1)*MESSAGES_PER_PAGE), MESSAGES_PER_PAGE + 1);
+
+ if ($message->find()) {
+ return $message;
+ } else {
+ return NULL;
+ }
+ }
+
+ function get_message_profile($message) {
+ return $message->getFrom();
+ }
+
+ function get_instructions() {
+ return _('This is your inbox, which lists your incoming private messages.');
+ }
+}
diff --git a/actions/invite.php b/actions/invite.php
new file mode 100644
index 000000000..c7d92085c
--- /dev/null
+++ b/actions/invite.php
@@ -0,0 +1,199 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class InviteAction extends Action {
+
+ function is_readonly() {
+ return false;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ $this->client_error(sprintf(_('You must be logged in to invite other users to use %s'),
+ common_config('site', 'name')));
+ return;
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->send_invitations();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ function send_invitations() {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $user = common_current_user();
+ $profile = $user->getProfile();
+
+ $bestname = $profile->getBestName();
+ $sitename = common_config('site', 'name');
+ $personal = $this->trimmed('personal');
+
+ $addresses = explode("\n", $this->trimmed('addresses'));
+
+ foreach ($addresses as $email) {
+ $email = trim($email);
+ if (!Validate::email($email, true)) {
+ $this->show_form(sprintf(_('Invalid email address: %s'), $email));
+ return;
+ }
+ }
+
+ $already = array();
+ $subbed = array();
+
+ foreach ($addresses as $email) {
+ $email = common_canonical_email($email);
+ $other = User::staticGet('email', $email);
+ if ($other) {
+ if ($user->isSubscribed($other)) {
+ $already[] = $other;
+ } else {
+ subs_subscribe_to($user, $other);
+ $subbed[] = $other;
+ }
+ } else {
+ $sent[] = $email;
+ $this->send_invitation($email, $user, $personal);
+ }
+ }
+
+ common_show_header(_('Invitation(s) sent'));
+ if ($already) {
+ common_element('p', NULL, _('You are already subscribed to these users:'));
+ common_element_start('ul');
+ foreach ($already as $other) {
+ common_element('li', NULL, sprintf(_('%s (%s)'), $other->nickname, $other->email));
+ }
+ common_element_end('ul');
+ }
+ if ($subbed) {
+ common_element('p', NULL, _('These people are already users and you were automatically subscribed to them:'));
+ common_element_start('ul');
+ foreach ($subbed as $other) {
+ common_element('li', NULL, sprintf(_('%s (%s)'), $other->nickname, $other->email));
+ }
+ common_element_end('ul');
+ }
+ if ($sent) {
+ common_element('p', NULL, _('Invitation(s) sent to the following people:'));
+ common_element_start('ul');
+ foreach ($sent as $other) {
+ common_element('li', NULL, $other);
+ }
+ common_element_end('ul');
+ common_element('p', NULL, _('You will be notified when your invitees accept the invitation and register on the site. Thanks for growing the community!'));
+ }
+ common_show_footer();
+ }
+
+ function show_top($error=NULL) {
+ if ($error) {
+ common_element('p', 'error', $error);
+ } else {
+ common_element_start('div', 'instructions');
+ common_element('p', NULL,
+ _('Use this form to invite your friends and colleagues to use this service.'));
+ common_element_end('div');
+ }
+ }
+
+ function show_form($error=NULL) {
+
+ global $config;
+
+ common_show_header(_('Invite new users'), NULL, $error, array($this, 'show_top'));
+
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'invite',
+ 'action' => common_local_url('invite')));
+ common_hidden('token', common_session_token());
+
+ common_textarea('addresses', _('Email addresses'),
+ $this->trimmed('addresses'),
+ _('Addresses of friends to invite (one per line)'));
+
+ common_textarea('personal', _('Personal message'),
+ $this->trimmed('personal'),
+ _('Optionally add a personal message to the invitation.'));
+
+ common_submit('send', _('Send'));
+
+ common_element_end('form');
+
+ common_show_footer();
+ }
+
+ function send_invitation($email, $user, $personal) {
+
+ $profile = $user->getProfile();
+ $bestname = $profile->getBestName();
+
+ $sitename = common_config('site', 'name');
+
+ $invite = new Invitation();
+
+ $invite->address = $email;
+ $invite->address_type = 'email';
+ $invite->code = common_confirmation_code(128);
+ $invite->user_id = $user->id;
+ $invite->created = common_sql_now();
+
+ if (!$invite->insert()) {
+ common_log_db_error($invite, 'INSERT', __FILE__);
+ return false;
+ }
+
+ $recipients = array($email);
+
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $email;
+ $headers['Subject'] = sprintf(_('%1$s has invited you to join them on %2$s'), $bestname, $sitename);
+
+ $body = sprintf(_("%1\$s has invited you to join them on %2\$s (%3\$s).\n\n".
+ "%2\$s is a micro-blogging service that lets you keep up-to-date with people you know and people who interest you.\n\n".
+ "You can also share news about yourself, your thoughts, or your life online with people who know about you. ".
+ "It's also great for meeting new people who share your interests.\n\n".
+ "%1\$s said:\n\n%4\$s\n\n".
+ "You can see %1\$s's profile page on %2\$s here:\n\n".
+ "%5\$s\n\n".
+ "If you'd like to try the service, click on the link below to accept the invitation.\n\n".
+ "%6\$s\n\n".
+ "If not, you can ignore this message. Thanks for your patience and your time.\n\n".
+ "Sincerely, %2\$s\n"),
+ $bestname,
+ $sitename,
+ common_root_url(),
+ $personal,
+ common_local_url('showstream', array('nickname' => $user->nickname)),
+ common_local_url('register', array('code' => $invite->code)));
+
+ mail_send($recipients, $headers, $body);
+ }
+
+}
diff --git a/actions/login.php b/actions/login.php
new file mode 100644
index 000000000..ccec9cf8a
--- /dev/null
+++ b/actions/login.php
@@ -0,0 +1,152 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class LoginAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ if (common_is_real_login()) {
+ common_user_error(_('Already logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->check_login();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ function check_login() {
+ # XXX: login throttle
+
+ # CSRF protection - token set in common_notice_form()
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $nickname = common_canonical_nickname($this->trimmed('nickname'));
+ $password = $this->arg('password');
+ if (common_check_user($nickname, $password)) {
+ # success!
+ if (!common_set_user($nickname)) {
+ common_server_error(_('Error setting user.'));
+ return;
+ }
+ common_real_login(true);
+ if ($this->boolean('rememberme')) {
+ common_debug('Adding rememberme cookie for ' . $nickname);
+ common_rememberme();
+ }
+ # success!
+ $url = common_get_returnto();
+ if ($url) {
+ # We don't have to return to it again
+ common_set_returnto(NULL);
+ } else {
+ $url = common_local_url('all',
+ array('nickname' =>
+ $nickname));
+ }
+ common_redirect($url);
+ } else {
+ $this->show_form(_('Incorrect username or password.'));
+ return;
+ }
+
+ # success!
+ if (!common_set_user($user)) {
+ common_server_error(_('Error setting user.'));
+ return;
+ }
+
+ common_real_login(true);
+
+ if ($this->boolean('rememberme')) {
+ common_debug('Adding rememberme cookie for ' . $nickname);
+ common_rememberme($user);
+ }
+ # success!
+ $url = common_get_returnto();
+ if ($url) {
+ # We don't have to return to it again
+ common_set_returnto(NULL);
+ } else {
+ $url = common_local_url('all',
+ array('nickname' =>
+ $nickname));
+ }
+ common_redirect($url);
+ }
+
+ function show_form($error=NULL) {
+ common_show_header(_('Login'), NULL, $error, array($this, 'show_top'));
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'login',
+ 'action' => common_local_url('login')));
+ common_input('nickname', _('Nickname'));
+ common_password('password', _('Password'));
+ common_checkbox('rememberme', _('Remember me'), false,
+ _('Automatically login in the future; ' .
+ 'not for shared computers!'));
+ common_submit('submit', _('Login'));
+ common_hidden('token', common_session_token());
+ common_element_end('form');
+ common_element_start('p');
+ common_element('a', array('href' => common_local_url('recoverpassword')),
+ _('Lost or forgotten password?'));
+ common_element_end('p');
+ common_show_footer();
+ }
+
+ function get_instructions() {
+ if (common_logged_in() &&
+ !common_is_real_login() &&
+ common_get_returnto())
+ {
+ # rememberme logins have to reauthenticate before
+ # changing any profile settings (cookie-stealing protection)
+ return _('For security reasons, please re-enter your ' .
+ 'user name and password ' .
+ 'before changing your settings.');
+ } else {
+ return _('Login with your username and password. ' .
+ 'Don\'t have a username yet? ' .
+ '[Register](%%action.register%%) a new account, or ' .
+ 'try [OpenID](%%action.openidlogin%%). ');
+ }
+ }
+
+ function show_top($error=NULL) {
+ if ($error) {
+ common_element('p', 'error', $error);
+ } else {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+ }
+}
diff --git a/actions/logout.php b/actions/logout.php
new file mode 100644
index 000000000..f00fa0ba7
--- /dev/null
+++ b/actions/logout.php
@@ -0,0 +1,41 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class LogoutAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ } else {
+ common_set_user(NULL);
+ common_real_login(false); # not logged in
+ common_forgetme(); # don't log back in!
+ common_redirect(common_local_url('public'));
+ }
+ }
+}
diff --git a/actions/microsummary.php b/actions/microsummary.php
new file mode 100644
index 000000000..104467d29
--- /dev/null
+++ b/actions/microsummary.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class MicrosummaryAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->client_error(_('No such user'), 404);
+ return;
+ }
+
+ $notice = $user->getCurrentNotice();
+
+ if (!$notice) {
+ $this->client_error(_('No current status'), 404);
+ }
+
+ header('Content-Type: text/plain');
+
+ print $user->nickname . ': ' . $notice->content;
+ }
+}
diff --git a/actions/newmessage.php b/actions/newmessage.php
new file mode 100644
index 000000000..da48fc7e7
--- /dev/null
+++ b/actions/newmessage.php
@@ -0,0 +1,135 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class NewmessageAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ $this->client_error(_('Not logged in.'), 403);
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->save_new_message();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ function save_new_message() {
+ $user = common_current_user();
+ assert($user); # XXX: maybe an error instead...
+
+ # CSRF protection
+
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $content = $this->trimmed('content');
+ $to = $this->trimmed('to');
+
+ if (!$content) {
+ $this->show_form(_('No content!'));
+ return;
+ } else {
+ $content_shortened = common_shorten_links($content);
+
+ if (mb_strlen($content_shortened) > 140) {
+ common_debug("Content = '$content_shortened'", __FILE__);
+ common_debug("mb_strlen(\$content) = " . mb_strlen($content_shortened), __FILE__);
+ $this->show_form(_('That\'s too long. Max message size is 140 chars.'));
+ return;
+ }
+ }
+
+ $other = User::staticGet('id', $to);
+
+ if (!$other) {
+ $this->show_form(_('No recipient specified.'));
+ return;
+ } else if (!$user->mutuallySubscribed($other)) {
+ $this->client_error(_('You can\'t send a message to this user.'), 404);
+ return;
+ } else if ($user->id == $other->id) {
+ $this->client_error(_('Don\'t send a message to yourself; just say it to yourself quietly instead.'), 403);
+ return;
+ }
+
+ $message = Message::saveNew($user->id, $other->id, $content, 'web');
+
+ if (is_string($message)) {
+ $this->show_form($message);
+ return;
+ }
+
+ $this->notify($user, $other, $message);
+
+ $url = common_local_url('outbox', array('nickname' => $user->nickname));
+
+ common_redirect($url, 303);
+ }
+
+ function show_top($params) {
+
+ list($content, $user, $to) = $params;
+
+ assert(!is_null($user));
+
+ common_message_form($content, $user, $to);
+ }
+
+ function show_form($msg=NULL) {
+
+ $content = $this->trimmed('content');
+ $user = common_current_user();
+
+ $to = $this->trimmed('to');
+
+ $other = User::staticGet('id', $to);
+
+ if (!$other) {
+ $this->client_error(_('No such user'), 404);
+ return;
+ }
+
+ if (!$user->mutuallySubscribed($other)) {
+ $this->client_error(_('You can\'t send a message to this user.'), 404);
+ return;
+ }
+
+ common_show_header(_('New message'), NULL,
+ array($content, $user, $other),
+ array($this, 'show_top'));
+
+ if ($msg) {
+ common_element('p', array('id'=>'error'), $msg);
+ }
+
+ common_show_footer();
+ }
+
+ function notify($from, $to, $message) {
+ mail_notify_message($message, $from, $to);
+ # XXX: Jabber, SMS notifications... probably queued
+ }
+}
diff --git a/actions/newnotice.php b/actions/newnotice.php
new file mode 100644
index 000000000..42b48923f
--- /dev/null
+++ b/actions/newnotice.php
@@ -0,0 +1,154 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR . '/lib/noticelist.php';
+
+class NewnoticeAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+ # CSRF protection - token set in common_notice_form()
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $this->save_new_notice();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ function save_new_notice() {
+
+ $user = common_current_user();
+ assert($user); # XXX: maybe an error instead...
+ $content = $this->trimmed('status_textarea');
+
+ if (!$content) {
+ $this->show_form(_('No content!'));
+ return;
+ } else {
+ $content_shortened = common_shorten_links($content);
+
+ if (mb_strlen($content_shortened) > 140) {
+ common_debug("Content = '$content_shortened'", __FILE__);
+ common_debug("mb_strlen(\$content) = " . mb_strlen($content_shortened), __FILE__);
+ $this->show_form(_('That\'s too long. Max notice size is 140 chars.'));
+ return;
+ }
+ }
+
+ $inter = new CommandInterpreter();
+
+ $cmd = $inter->handle_command($user, $content_shortened);
+
+ if ($cmd) {
+ if ($this->boolean('ajax')) {
+ $cmd->execute(new AjaxWebChannel());
+ } else {
+ $cmd->execute(new WebChannel());
+ }
+ return;
+ }
+
+ $replyto = $this->trimmed('inreplyto');
+
+ $notice = Notice::saveNew($user->id, $content, 'web', 1, ($replyto == 'false') ? NULL : $replyto);
+
+ if (is_string($notice)) {
+ $this->show_form($notice);
+ return;
+ }
+
+ common_broadcast_notice($notice);
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Notice posted'));
+ common_element_end('head');
+ common_element_start('body');
+ $this->show_notice($notice);
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ $returnto = $this->trimmed('returnto');
+
+ if ($returnto) {
+ $url = common_local_url($returnto,
+ array('nickname' => $user->nickname));
+ } else {
+ $url = common_local_url('shownotice',
+ array('notice' => $notice->id));
+ }
+ common_redirect($url, 303);
+ }
+ }
+
+ function ajax_error_msg($msg) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Ajax Error'));
+ common_element_end('head');
+ common_element_start('body');
+ common_element('p', array('id' => 'error'), $msg);
+ common_element_end('body');
+ common_element_end('html');
+ }
+
+ function show_top($content=NULL) {
+ common_notice_form(NULL, $content);
+ }
+
+ function show_form($msg=NULL) {
+ if ($msg && $this->boolean('ajax')) {
+ $this->ajax_error_msg($msg);
+ return;
+ }
+ $content = $this->trimmed('status_textarea');
+ if (!$content) {
+ $replyto = $this->trimmed('replyto');
+ $profile = Profile::staticGet('nickname', $replyto);
+ if ($profile) {
+ $content = '@' . $profile->nickname . ' ';
+ }
+ }
+ common_show_header(_('New notice'), NULL, $content,
+ array($this, 'show_top'));
+ if ($msg) {
+ common_element('p', array('id' => 'error'), $msg);
+ }
+ common_show_footer();
+ }
+
+ function show_notice($notice) {
+ $nli = new NoticeListItem($notice);
+ $nli->show();
+ }
+
+}
diff --git a/actions/noticesearch.php b/actions/noticesearch.php
new file mode 100644
index 000000000..96e4d777f
--- /dev/null
+++ b/actions/noticesearch.php
@@ -0,0 +1,164 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/searchaction.php');
+
+# XXX common parent for people and content search?
+
+class NoticesearchAction extends SearchAction {
+
+ function get_instructions() {
+ return _('Search for notices on %%site.name%% by their contents. Separate search terms by spaces; they must be 3 characters or more.');
+ }
+
+ function get_title() {
+ return _('Text search');
+ }
+
+ function show_results($q, $page) {
+
+ $notice = new Notice();
+
+ # lcase it for comparison
+ $q = strtolower($q);
+
+ $search_engine = $notice->getSearchEngine('identica_notices');
+
+ $search_engine->set_sort_mode('chron');
+ # Ask for an extra to see if there's more.
+ $search_engine->limit((($page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
+
+ if (false === $search_engine->query($q)) {
+ $cnt = 0;
+ }
+ else {
+ $cnt = $notice->find();
+ }
+ if ($cnt > 0) {
+ $terms = preg_split('/[\s,]+/', $q);
+ common_element_start('ul', array('id' => 'notices'));
+ for ($i = 0; $i < min($cnt, NOTICES_PER_PAGE); $i++) {
+ if ($notice->fetch()) {
+ $this->show_notice($notice, $terms);
+ } else {
+ // shouldn't happen!
+ break;
+ }
+ }
+ common_element_end('ul');
+ } else {
+ common_element('p', 'error', _('No results'));
+ }
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'noticesearch', array('q' => $q));
+ }
+
+ function show_header($arr) {
+ if ($arr) {
+ $q = $arr[0];
+ }
+ if ($q) {
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('noticesearchrss',
+ array('q' => $q)),
+ 'type' => 'application/rss+xml',
+ 'title' => _('Search Stream Feed')));
+ }
+ }
+
+ # XXX: refactor and combine with StreamAction::show_notice()
+
+ function show_notice($notice, $terms) {
+ $profile = $notice->getProfile();
+ if (!$profile) {
+ common_log_db_error($notice, 'SELECT', __FILE__);
+ $this->server_error(_('Notice without matching profile'));
+ return;
+ }
+ # XXX: RDFa
+ common_element_start('li', array('class' => 'notice_single',
+ 'id' => 'notice-' . $notice->id));
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+ common_element_start('a', array('href' => $profile->profileurl));
+ common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
+ 'class' => 'avatar stream',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($profile->fullname) ? $profile->fullname :
+ $profile->nickname));
+ common_element_end('a');
+ common_element('a', array('href' => $profile->profileurl,
+ 'class' => 'nickname'),
+ $profile->nickname);
+ # FIXME: URL, image, video, audio
+ common_element_start('p', array('class' => 'content'));
+ if ($notice->rendered) {
+ common_raw($this->highlight($notice->rendered, $terms));
+ } else {
+ # XXX: may be some uncooked notices in the DB,
+ # we cook them right now. This should probably disappear in future
+ # versions (>> 0.4.x)
+ common_raw($this->highlight(common_render_content($notice->content, $notice), $terms));
+ }
+ common_element_end('p');
+ $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
+ common_element_start('p', 'time');
+ common_element('a', array('class' => 'permalink',
+ 'href' => $noticeurl,
+ 'title' => common_exact_date($notice->created)),
+ common_date_string($notice->created));
+ if ($notice->reply_to) {
+ $replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
+ common_text(' (');
+ common_element('a', array('class' => 'inreplyto',
+ 'href' => $replyurl),
+ _('in reply to...'));
+ common_text(')');
+ }
+ common_element_start('a',
+ array('href' => common_local_url('newnotice',
+ array('replyto' => $profile->nickname)),
+ 'onclick' => 'doreply("'.$profile->nickname.'"); return false',
+ 'title' => _('reply'),
+ 'class' => 'replybutton'));
+ common_hidden('posttoken', common_session_token());
+
+ common_raw('&rarr;');
+ common_element_end('a');
+ common_element_end('p');
+ common_element_end('li');
+ }
+
+ function highlight($text, $terms) {
+ /* Highligh serach terms */
+ $pattern = '/('.implode('|',array_map('htmlspecialchars', $terms)).')/i';
+ $result = preg_replace($pattern, '<strong>\\1</strong>', $text);
+
+ /* Remove highlighting from inside links, loop incase multiple highlights in links */
+ $pattern = '/(href="[^"]*)<strong>('.implode('|',array_map('htmlspecialchars', $terms)).')<\/strong>([^"]*")/iU';
+ do {
+ $result = preg_replace($pattern, '\\1\\2\\3', $result, -1, $count);
+ } while ($count);
+ return $result;
+ }
+}
diff --git a/actions/noticesearchrss.php b/actions/noticesearchrss.php
new file mode 100644
index 000000000..0f38515a0
--- /dev/null
+++ b/actions/noticesearchrss.php
@@ -0,0 +1,70 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class NoticesearchrssAction extends Rss10Action {
+
+ function init() {
+ return true;
+ }
+
+ function get_notices($limit=0) {
+
+ $q = $this->trimmed('q');
+ $notices = array();
+
+ $notice = new Notice();
+
+ # lcase it for comparison
+ $q = strtolower($q);
+
+ $search_engine = $notice->getSearchEngine('identica_notices');
+ $search_engine->set_sort_mode('chron');
+
+ if (!$limit) $limit = 20;
+ $search_engine->limit(0, $limit, true);
+ $search_engine->query($q);
+ $notice->find();
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ global $config;
+ $q = $this->trimmed('q');
+ $c = array('url' => common_local_url('noticesearchrss', array('q' => $q)),
+ 'title' => $config['site']['name'] . sprintf(_(' Search Stream for "%s"'), $q),
+ 'link' => common_local_url('noticesearch', array('q' => $q)),
+ 'description' => sprintf(_('All updates matching search term "%s"'), $q));
+ return $c;
+ }
+
+ function get_image() {
+ return NULL;
+ }
+}
diff --git a/actions/nudge.php b/actions/nudge.php
new file mode 100644
index 000000000..677f58800
--- /dev/null
+++ b/actions/nudge.php
@@ -0,0 +1,84 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/mail.php');
+
+class NudgeAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ $this->client_error(_('Not logged in.'));
+ return;
+ }
+
+ $user = common_current_user();
+ $other = User::staticGet('nickname', $this->arg('nickname'));
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ common_redirect(common_local_url('showstream', array('nickname' => $other->nickname)));
+ return;
+ }
+
+ # CSRF protection
+
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if (!$other->email || !$other->emailnotifynudge) {
+ $this->client_error(_('This user doesn\'t allow nudges or hasn\'t confirmed or set his email yet.'));
+ return;
+ }
+
+ $this->notify($user, $other);
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Nudge sent'));
+ common_element_end('head');
+ common_element_start('body');
+ common_nudge_response();
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ // display a confirmation to the user
+ common_redirect(common_local_url('showstream',
+ array('nickname' => $other->nickname)));
+ }
+ }
+
+ function notify($user, $other) {
+ if ($other->id != $user->id) {
+ if ($other->email && $other->emailnotifynudge) {
+ mail_notify_nudge($user, $other);
+ }
+ # XXX: notify by IM
+ # XXX: notify by SMS
+ }
+ }
+}
+
diff --git a/actions/openidlogin.php b/actions/openidlogin.php
new file mode 100644
index 000000000..1b289dbea
--- /dev/null
+++ b/actions/openidlogin.php
@@ -0,0 +1,92 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class OpenidloginAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (common_logged_in()) {
+ common_user_error(_('Already logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $openid_url = $this->trimmed('openid_url');
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'), $openid_url);
+ return;
+ }
+
+ $rememberme = $this->boolean('rememberme');
+
+ common_ensure_session();
+
+ $_SESSION['openid_rememberme'] = $rememberme;
+
+ $result = oid_authenticate($openid_url,
+ 'finishopenidlogin');
+
+ if (is_string($result)) { # error message
+ unset($_SESSION['openid_rememberme']);
+ $this->show_form($result, $openid_url);
+ }
+ } else {
+ $openid_url = oid_get_last();
+ $this->show_form(NULL, $openid_url);
+ }
+ }
+
+ function get_instructions() {
+ return _('Login with an [OpenID](%%doc.openid%%) account.');
+ }
+
+ function show_top($error=NULL) {
+ if ($error) {
+ common_element('div', array('class' => 'error'), $error);
+ } else {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+ }
+
+ function show_form($error=NULL, $openid_url) {
+ common_show_header(_('OpenID Login'), NULL, $error, array($this, 'show_top'));
+ $formaction = common_local_url('openidlogin');
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'openidlogin',
+ 'action' => $formaction));
+ common_hidden('token', common_session_token());
+ common_input('openid_url', _('OpenID URL'),
+ $openid_url,
+ _('Your OpenID URL'));
+ common_checkbox('rememberme', _('Remember me'), false,
+ _('Automatically login in the future; ' .
+ 'not for shared computers!'));
+ common_submit('submit', _('Login'));
+ common_element_end('form');
+ common_show_footer();
+ }
+}
diff --git a/actions/openidsettings.php b/actions/openidsettings.php
new file mode 100644
index 000000000..f539d111f
--- /dev/null
+++ b/actions/openidsettings.php
@@ -0,0 +1,156 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+require_once(INSTALLDIR.'/lib/openid.php');
+
+class OpenidsettingsAction extends SettingsAction {
+
+ function get_instructions() {
+ return _('[OpenID](%%doc.openid%%) lets you log into many sites ' .
+ ' with the same user account. '.
+ ' Manage your associated OpenIDs from here.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+
+ $user = common_current_user();
+
+ $this->form_header(_('OpenID settings'), $msg, $success);
+
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'openidadd',
+ 'action' =>
+ common_local_url('openidsettings')));
+ common_hidden('token', common_session_token());
+ common_element('h2', NULL, _('Add OpenID'));
+ common_element('p', NULL,
+ _('If you want to add an OpenID to your account, ' .
+ 'enter it in the box below and click "Add".'));
+ common_element_start('p');
+ common_element('label', array('for' => 'openid_url'),
+ _('OpenID URL'));
+ common_element('input', array('name' => 'openid_url',
+ 'type' => 'text',
+ 'id' => 'openid_url'));
+ common_element('input', array('type' => 'submit',
+ 'id' => 'add',
+ 'name' => 'add',
+ 'class' => 'submit',
+ 'value' => _('Add')));
+ common_element_end('p');
+ common_element_end('form');
+
+ $oid = new User_openid();
+ $oid->user_id = $user->id;
+
+ $cnt = $oid->find();
+
+ if ($cnt > 0) {
+
+ common_element('h2', NULL, _('Remove OpenID'));
+
+ if ($cnt == 1 && !$user->password) {
+
+ common_element('p', NULL,
+ _('Removing your only OpenID would make it impossible to log in! ' .
+ 'If you need to remove it, add another OpenID first.'));
+
+ if ($oid->fetch()) {
+ common_element_start('p');
+ common_element('a', array('href' => $oid->canonical),
+ $oid->display);
+ common_element_end('p');
+ }
+
+ } else {
+
+ common_element('p', NULL,
+ _('You can remove an OpenID from your account '.
+ 'by clicking the button marked "Remove".'));
+ $idx = 0;
+
+ while ($oid->fetch()) {
+ common_element_start('form', array('method' => 'POST',
+ 'id' => 'openiddelete' . $idx,
+ 'action' =>
+ common_local_url('openidsettings')));
+ common_element_start('p');
+ common_hidden('token', common_session_token());
+ common_element('a', array('href' => $oid->canonical),
+ $oid->display);
+ common_element('input', array('type' => 'hidden',
+ 'id' => 'openid_url'.$idx,
+ 'name' => 'openid_url',
+ 'value' => $oid->canonical));
+ common_element('input', array('type' => 'submit',
+ 'id' => 'remove'.$idx,
+ 'name' => 'remove',
+ 'class' => 'submit',
+ 'value' => _('Remove')));
+ common_element_end('p');
+ common_element_end('form');
+ $idx++;
+ }
+ }
+ }
+
+ common_show_footer();
+ }
+
+ function handle_post() {
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('add')) {
+ $result = oid_authenticate($this->trimmed('openid_url'), 'finishaddopenid');
+ if (is_string($result)) { # error message
+ $this->show_form($result);
+ }
+ } else if ($this->arg('remove')) {
+ $this->remove_openid();
+ } else {
+ $this->show_form(_('Something weird happened.'));
+ }
+ }
+
+ function remove_openid() {
+
+ $openid_url = $this->trimmed('openid_url');
+ $oid = User_openid::staticGet('canonical', $openid_url);
+ if (!$oid) {
+ $this->show_form(_('No such OpenID.'));
+ return;
+ }
+ $cur = common_current_user();
+ if (!$cur || $oid->user_id != $cur->id) {
+ $this->show_form(_('That OpenID does not belong to you.'));
+ return;
+ }
+ $oid->delete();
+ $this->show_form(_('OpenID removed.'), true);
+ return;
+ }
+}
diff --git a/actions/opensearch.php b/actions/opensearch.php
new file mode 100644
index 000000000..0f366be4c
--- /dev/null
+++ b/actions/opensearch.php
@@ -0,0 +1,59 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class OpensearchAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $type = $this->trimmed('type');
+
+ $short_name = '';
+ if ($type == 'people') {
+ $type = 'peoplesearch';
+ $short_name = _('People Search');
+ } else {
+ $short_name = _('Notice Search');
+ $type = 'noticesearch';
+ }
+
+ header('Content-Type: text/html');
+
+ common_start_xml();
+ common_element_start('OpenSearchDescription', array('xmlns' => 'http://a9.com/-/spec/opensearch/1.1/'));
+
+ $short_name = common_config('site', 'name').' '.$short_name;
+ common_element('ShortName', NULL, $short_name);
+ common_element('Contact', NULL, common_config('site', 'email'));
+ common_element('Url', array('type' => 'text/html', 'method' => 'get',
+ 'template' => str_replace('---', '{searchTerms}', common_local_url($type, array('q' => '---')))));
+ common_element('Image', array('height' => 16, 'width' => 16, 'type' => 'image/vnd.microsoft.icon'), common_path('favicon.ico'));
+ common_element('Image', array('height' => 50, 'width' => 50, 'type' => 'image/png'), theme_path('logo.png'));
+ common_element('AdultContent', NULL, 'false');
+ common_element('Language', NULL, common_language());
+ common_element('OutputEncoding', NULL, 'UTF-8');
+ common_element('InputEncoding', NULL, 'UTF-8');
+
+ common_element_end('OpenSearchDescription');
+ common_end_xml();
+ }
+}
diff --git a/actions/othersettings.php b/actions/othersettings.php
new file mode 100644
index 000000000..eccf90e91
--- /dev/null
+++ b/actions/othersettings.php
@@ -0,0 +1,181 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+
+class OthersettingsAction extends SettingsAction {
+
+ function get_instructions() {
+ return _('Manage various other options.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $user = common_current_user();
+
+ $this->form_header(_('Other Settings'), $msg, $success);
+
+ common_element('h2', NULL, _('URL Auto-shortening'));
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'othersettings',
+ 'action' =>
+ common_local_url('othersettings')));
+ common_hidden('token', common_session_token());
+
+ $services = array(
+ '' => 'None',
+ 'ur1.ca' => 'ur1.ca (free service)',
+ '2tu.us' => '2tu.us (free service)',
+ 'ptiturl.com' => 'ptiturl.com',
+ 'bit.ly' => 'bit.ly',
+ 'tinyurl.com' => 'tinyurl.com',
+ 'is.gd' => 'is.gd',
+ 'snipr.com' => 'snipr.com',
+ 'metamark.net' => 'metamark.net'
+ );
+
+ common_dropdown('urlshorteningservice', _('Service'), $services, _('Automatic shortening service to use.'), FALSE, $user->urlshorteningservice);
+
+ common_submit('save', _('Save'));
+
+ common_element_end('form');
+
+// common_element('h2', NULL, _('Delete my account'));
+// $this->show_delete_form();
+
+ common_show_footer();
+ }
+
+ function show_feeds_list($feeds) {
+ common_element_start('div', array('class' => 'feedsdel'));
+ common_element('p', null, 'Feeds:');
+ common_element_start('ul', array('class' => 'xoxo'));
+
+ foreach ($feeds as $key => $value) {
+ $this->common_feed_item($feeds[$key]);
+ }
+ common_element_end('ul');
+ common_element_end('div');
+ }
+
+ //TODO move to common.php (and retrace its origin)
+ function common_feed_item($feed) {
+ $user = common_current_user();
+ $nickname = $user->nickname;
+
+ switch($feed['item']) {
+ case 'notices': default:
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's ".$feed['version']." notice feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'foaf':
+ $feed_classname = "foaf";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's FOAF file";
+ $feed['textContent'] = "FOAF";
+ break;
+ }
+ common_element_start('li');
+ common_element('a', array('href' => $feed['href'],
+ 'class' => $feed_classname,
+ 'type' => $feed_mimetype,
+ 'title' => $feed_title),
+ $feed['textContent']);
+ common_element_end('li');
+ }
+
+// function show_delete_form() {
+// $user = common_current_user();
+// $notices = DB_DataObject::factory('notice');
+// $notices->profile_id = $user->id;
+// $notice_count = (int) $notices->count();
+//
+// common_element_start('form', array('method' => 'POST',
+// 'id' => 'delete',
+// 'action' =>
+// common_local_url('deleteprofile')));
+//
+// common_hidden('token', common_session_token());
+// common_element('p', null, "You can copy your notices and contacts by saving the two links below before deleting your account. Be careful, this operation cannot be undone.");
+//
+// $this->show_feeds_list(array(0=>array('href'=>common_local_url('userrss', array('limit' => $notice_count, 'nickname' => $user->nickname)),
+// 'type' => 'rss',
+// 'version' => 'RSS 1.0',
+// 'item' => 'notices'),
+// 1=>array('href'=>common_local_url('foaf',array('nickname' => $user->nickname)),
+// 'type' => 'rdf',
+// 'version' => 'FOAF',
+// 'item' => 'foaf')));
+//
+// common_submit('deleteaccount', _('Delete my account'));
+// common_element_end('form');
+// }
+
+ function handle_post() {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->save_preferences();
+ }else {
+ $this->show_form(_('Unexpected form submission.'));
+ }
+ }
+
+ function save_preferences() {
+
+ $urlshorteningservice = $this->trimmed('urlshorteningservice');
+
+ if (!is_null($urlshorteningservice) && strlen($urlshorteningservice) > 50) {
+ $this->show_form(_('URL shortening service is too long (max 50 chars).'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ assert(!is_null($user)); # should already be checked
+
+ $user->query('BEGIN');
+
+ $original = clone($user);
+
+ $user->urlshorteningservice = $urlshorteningservice;
+
+ $result = $user->update($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+
+ $user->query('COMMIT');
+
+ $this->show_form(_('Preferences saved.'), true);
+ }
+}
diff --git a/actions/outbox.php b/actions/outbox.php
new file mode 100644
index 000000000..c48d9c206
--- /dev/null
+++ b/actions/outbox.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/mailbox.php');
+
+class OutboxAction extends MailboxAction {
+
+ function get_title($user, $page) {
+ if ($page > 1) {
+ $title = sprintf(_("Outbox for %s - page %d"), $user->nickname, $page);
+ } else {
+ $title = sprintf(_("Outbox for %s"), $user->nickname);
+ }
+ return $title;
+ }
+
+ function get_messages($user, $page) {
+ $message = new Message();
+ $message->from_profile = $user->id;
+ $message->orderBy('created DESC, id DESC');
+ $message->limit((($page-1)*MESSAGES_PER_PAGE), MESSAGES_PER_PAGE + 1);
+
+ if ($message->find()) {
+ return $message;
+ } else {
+ return NULL;
+ }
+ }
+
+ function get_message_profile($message) {
+ return $message->getTo();
+ }
+
+ function get_instructions() {
+ return _('This is your outbox, which lists private messages you have sent.');
+ }
+
+}
diff --git a/actions/peoplesearch.php b/actions/peoplesearch.php
new file mode 100644
index 000000000..2e54233ec
--- /dev/null
+++ b/actions/peoplesearch.php
@@ -0,0 +1,84 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/searchaction.php');
+require_once(INSTALLDIR.'/lib/profilelist.php');
+
+class PeoplesearchAction extends SearchAction {
+
+ function get_instructions() {
+ return _('Search for people on %%site.name%% by their name, location, or interests. ' .
+ 'Separate the terms by spaces; they must be 3 characters or more.');
+ }
+
+ function get_title() {
+ return _('People search');
+ }
+
+ function show_results($q, $page) {
+
+ $profile = new Profile();
+
+ # lcase it for comparison
+ $q = strtolower($q);
+
+ $search_engine = $profile->getSearchEngine('identica_people');
+
+ $search_engine->set_sort_mode('chron');
+ # Ask for an extra to see if there's more.
+ $search_engine->limit((($page-1)*PROFILES_PER_PAGE), PROFILES_PER_PAGE + 1);
+ if (false === $search_engine->query($q)) {
+ $cnt = 0;
+ }
+ else {
+ $cnt = $profile->find();
+ }
+ if ($cnt > 0) {
+ $terms = preg_split('/[\s,]+/', $q);
+ $results = new PeopleSearchResults($profile, $terms);
+ $results->show_list();
+ } else {
+ common_element('p', 'error', _('No results'));
+ }
+
+ $profile->free();
+
+ common_pagination($page > 1, $cnt > PROFILES_PER_PAGE,
+ $page, 'peoplesearch', array('q' => $q));
+ }
+}
+
+class PeopleSearchResults extends ProfileList {
+
+ var $terms = NULL;
+ var $pattern = NULL;
+
+ function __construct($profile, $terms) {
+ parent::__construct($profile);
+ $this->terms = array_map('preg_quote',
+ array_map('htmlspecialchars', $terms));
+ $this->pattern = '/('.implode('|',$terms).')/i';
+ }
+
+ function highlight($text) {
+ return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
+ }
+}
diff --git a/actions/peopletag.php b/actions/peopletag.php
new file mode 100644
index 000000000..c508e0594
--- /dev/null
+++ b/actions/peopletag.php
@@ -0,0 +1,103 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/profilelist.php');
+
+class PeopletagAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $tag = $this->trimmed('tag');
+
+ if (!common_valid_profile_tag($tag)) {
+ $this->client_error(sprintf(_('Not a valid people tag: %s'), $tag));
+ return;
+ }
+
+ $page = $this->trimmed('page');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ # Looks like we're good; show the header
+
+ common_show_header(sprintf(_('Users self-tagged with %s - page %d'), $tag, $page),
+ NULL, $tag, array($this, 'show_top'));
+
+ $this->show_people($tag, $page);
+
+ common_show_footer();
+ }
+
+ function show_people($tag, $page) {
+
+ $profile = new Profile();
+
+ $offset = ($page-1)*PROFILES_PER_PAGE;
+ $limit = PROFILES_PER_PAGE + 1;
+
+ if (common_config('db','type') == 'pgsql') {
+ $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $lim = ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ # XXX: memcached this
+
+ $profile->query(sprintf('SELECT profile.* ' .
+ 'FROM profile JOIN profile_tag ' .
+ 'ON profile.id = profile_tag.tagger ' .
+ 'WHERE profile_tag.tagger = profile_tag.tagged ' .
+ 'AND tag = "%s" ' .
+ 'ORDER BY profile_tag.modified DESC ' .
+ $lim, $tag));
+
+ $pl = new ProfileList($profile);
+ $cnt = $pl->show_list();
+
+ common_pagination($page > 1,
+ $cnt > PROFILES_PER_PAGE,
+ $page,
+ $this->trimmed('action'),
+ array('tag' => $tag));
+ }
+
+ function show_top($tag) {
+ $instr = sprintf(_('These are users who have tagged themselves "%s" ' .
+ 'to show a common interest, characteristic, hobby or job.'), $tag);
+ common_element_start('div', 'instructions');
+ common_element_start('p');
+ common_text($instr);
+ common_element_end('p');
+ common_element_end('div');
+ }
+
+ function get_title() {
+ return NULL;
+ }
+
+ function show_header($arr) {
+ return;
+ }
+}
diff --git a/actions/postnotice.php b/actions/postnotice.php
new file mode 100644
index 000000000..243081f12
--- /dev/null
+++ b/actions/postnotice.php
@@ -0,0 +1,88 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class PostnoticeAction extends Action {
+ function handle($args) {
+ parent::handle($args);
+ try {
+ common_remove_magic_from_request();
+ $req = OAuthRequest::from_request();
+ # Note: server-to-server function!
+ $server = omb_oauth_server();
+ list($consumer, $token) = $server->verify_request($req);
+ if ($this->save_notice($req, $consumer, $token)) {
+ print "omb_version=".OMB_VERSION_01;
+ }
+ } catch (OAuthException $e) {
+ common_server_error($e->getMessage());
+ return;
+ }
+ }
+
+ function save_notice(&$req, &$consumer, &$token) {
+ $version = $req->get_parameter('omb_version');
+ if ($version != OMB_VERSION_01) {
+ common_user_error(_('Unsupported OMB version'), 400);
+ return false;
+ }
+ # First, check to see
+ $listenee = $req->get_parameter('omb_listenee');
+ $remote_profile = Remote_profile::staticGet('uri', $listenee);
+ if (!$remote_profile) {
+ common_user_error(_('Profile unknown'), 403);
+ return false;
+ }
+ $sub = Subscription::staticGet('token', $token->key);
+ if (!$sub) {
+ common_user_error(_('No such subscription'), 403);
+ return false;
+ }
+ $content = $req->get_parameter('omb_notice_content');
+ $content_shortened = common_shorten_links($content);
+ if (mb_strlen($content_shortened) > 140) {
+ common_user_error(_('Invalid notice content'), 400);
+ return false;
+ }
+ $notice_uri = $req->get_parameter('omb_notice');
+ if (!Validate::uri($notice_uri) &&
+ !common_valid_tag($notice_uri)) {
+ common_user_error(_('Invalid notice uri'), 400);
+ return false;
+ }
+ $notice_url = $req->get_parameter('omb_notice_url');
+ if ($notice_url && !common_valid_http_url($notice_url)) {
+ common_user_error(_('Invalid notice url'), 400);
+ return false;
+ }
+ $notice = Notice::staticGet('uri', $notice_uri);
+ if (!$notice) {
+ $notice = Notice::saveNew($remote_profile->id, $content, 'omb', false, 0, $notice_uri);
+ if (is_string($notice)) {
+ common_server_serror($notice, 500);
+ return false;
+ }
+ common_broadcast_notice($notice, true);
+ }
+ return true;
+ }
+}
diff --git a/actions/profilesettings.php b/actions/profilesettings.php
new file mode 100644
index 000000000..ed2623c9b
--- /dev/null
+++ b/actions/profilesettings.php
@@ -0,0 +1,439 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+
+class ProfilesettingsAction extends SettingsAction {
+
+ function get_instructions() {
+ return _('You can update your personal profile info here '.
+ 'so people know more about you.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $this->form_header(_('Profile settings'), $msg, $success);
+ $this->show_settings_form();
+ common_element('h2', NULL, _('Avatar'));
+ $this->show_avatar_form();
+ common_element('h2', NULL, _('Change password'));
+ $this->show_password_form();
+// common_element('h2', NULL, _('Delete my account'));
+// $this->show_delete_form();
+ common_show_footer();
+ }
+
+ function handle_post() {
+
+ # CSRF protection
+
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->save_profile();
+ } else if ($this->arg('upload')) {
+ $this->upload_avatar();
+ } else if ($this->arg('changepass')) {
+ $this->change_password();
+ }
+
+ }
+
+ function show_settings_form() {
+
+ $user = common_current_user();
+ $profile = $user->getProfile();
+
+ common_element_start('form', array('method' => 'POST',
+ 'id' => 'profilesettings',
+ 'action' =>
+ common_local_url('profilesettings')));
+ common_hidden('token', common_session_token());
+
+ # too much common patterns here... abstractable?
+
+ common_input('nickname', _('Nickname'),
+ ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname,
+ _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+ common_input('fullname', _('Full name'),
+ ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname);
+ common_input('homepage', _('Homepage'),
+ ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage,
+ _('URL of your homepage, blog, or profile on another site'));
+ common_textarea('bio', _('Bio'),
+ ($this->arg('bio')) ? $this->arg('bio') : $profile->bio,
+ _('Describe yourself and your interests in 140 chars'));
+ common_input('location', _('Location'),
+ ($this->arg('location')) ? $this->arg('location') : $profile->location,
+ _('Where you are, like "City, State (or Region), Country"'));
+ common_input('tags', _('Tags'),
+ ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()),
+ _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated'));
+
+ $language = common_language();
+ common_dropdown('language', _('Language'), get_nice_language_list(), _('Preferred language'), TRUE, $language);
+ $timezone = common_timezone();
+ $timezones = array();
+ foreach(DateTimeZone::listIdentifiers() as $k => $v) {
+ $timezones[$v] = $v;
+ }
+ common_dropdown('timezone', _('Timezone'), $timezones, _('What timezone are you normally in?'), TRUE, $timezone);
+
+ common_checkbox('autosubscribe', _('Automatically subscribe to whoever subscribes to me (best for non-humans)'),
+ ($this->arg('autosubscribe')) ? $this->boolean('autosubscribe') : $user->autosubscribe);
+
+ common_submit('save', _('Save'));
+
+ common_element_end('form');
+
+
+ }
+
+ function show_avatar_form() {
+
+ $user = common_current_user();
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_log_db_error($user, 'SELECT', __FILE__);
+ $this->server_error(_('User without matching profile'));
+ return;
+ }
+
+ $original = $profile->getOriginalAvatar();
+
+
+ common_element_start('form', array('enctype' => 'multipart/form-data',
+ 'method' => 'POST',
+ 'id' => 'avatar',
+ 'action' =>
+ common_local_url('profilesettings')));
+ common_hidden('token', common_session_token());
+
+ if ($original) {
+ common_element('img', array('src' => $original->url,
+ 'class' => 'avatar original',
+ 'width' => $original->width,
+ 'height' => $original->height,
+ 'alt' => $user->nickname));
+ }
+
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+ if ($avatar) {
+ common_element('img', array('src' => $avatar->url,
+ 'class' => 'avatar profile',
+ 'width' => AVATAR_PROFILE_SIZE,
+ 'height' => AVATAR_PROFILE_SIZE,
+ 'alt' => $user->nickname));
+ }
+
+
+ common_element('input', array('name' => 'MAX_FILE_SIZE',
+ 'type' => 'hidden',
+ 'id' => 'MAX_FILE_SIZE',
+ 'value' => MAX_AVATAR_SIZE));
+
+ common_element_start('p');
+
+
+ common_element('input', array('name' => 'avatarfile',
+ 'type' => 'file',
+ 'id' => 'avatarfile'));
+ common_element_end('p');
+
+ common_submit('upload', _('Upload'));
+ common_element_end('form');
+
+ }
+
+ function show_password_form() {
+
+ $user = common_current_user();
+ common_element_start('form', array('method' => 'POST',
+ 'id' => 'password',
+ 'action' =>
+ common_local_url('profilesettings')));
+
+ common_hidden('token', common_session_token());
+
+ # Users who logged in with OpenID won't have a pwd
+ if ($user->password) {
+ common_password('oldpassword', _('Old password'));
+ }
+ common_password('newpassword', _('New password'),
+ _('6 or more characters'));
+ common_password('confirm', _('Confirm'),
+ _('same as password above'));
+ common_submit('changepass', _('Change'));
+ common_element_end('form');
+ }
+
+ function save_profile() {
+ $nickname = $this->trimmed('nickname');
+ $fullname = $this->trimmed('fullname');
+ $homepage = $this->trimmed('homepage');
+ $bio = $this->trimmed('bio');
+ $location = $this->trimmed('location');
+ $autosubscribe = $this->boolean('autosubscribe');
+ $language = $this->trimmed('language');
+ $timezone = $this->trimmed('timezone');
+ $tagstring = $this->trimmed('tags');
+
+ # Some validation
+
+ if (!Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ $this->show_form(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+ return;
+ } else if (!User::allowed_nickname($nickname)) {
+ $this->show_form(_('Not a valid nickname.'));
+ return;
+ } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
+ !Validate::uri($homepage, array('allowed_schemes' => array('http', 'https')))) {
+ $this->show_form(_('Homepage is not a valid URL.'));
+ return;
+ } else if (!is_null($fullname) && strlen($fullname) > 255) {
+ $this->show_form(_('Full name is too long (max 255 chars).'));
+ return;
+ } else if (!is_null($bio) && strlen($bio) > 140) {
+ $this->show_form(_('Bio is too long (max 140 chars).'));
+ return;
+ } else if (!is_null($location) && strlen($location) > 255) {
+ $this->show_form(_('Location is too long (max 255 chars).'));
+ return;
+ } else if (is_null($timezone) || !in_array($timezone, DateTimeZone::listIdentifiers())) {
+ $this->show_form(_('Timezone not selected.'));
+ return;
+ } else if ($this->nickname_exists($nickname)) {
+ $this->show_form(_('Nickname already in use. Try another one.'));
+ return;
+ } else if (!is_null($language) && strlen($language) > 50) {
+ $this->show_form(_('Language is too long (max 50 chars).'));
+ return;
+ }
+
+ if ($tagstring) {
+ $tags = array_map('common_canonical_tag', preg_split('/[\s,]+/', $tagstring));
+ } else {
+ $tags = array();
+ }
+
+ foreach ($tags as $tag) {
+ if (!common_valid_profile_tag($tag)) {
+ $this->show_form(sprintf(_('Invalid tag: "%s"'), $tag));
+ return;
+ }
+ }
+
+ $user = common_current_user();
+
+ $user->query('BEGIN');
+
+ if ($user->nickname != $nickname ||
+ $user->language != $language ||
+ $user->timezone != $timezone) {
+
+ common_debug('Updating user nickname from ' . $user->nickname . ' to ' . $nickname,
+ __FILE__);
+ common_debug('Updating user language from ' . $user->language . ' to ' . $language,
+ __FILE__);
+ common_debug('Updating user timezone from ' . $user->timezone . ' to ' . $timezone,
+ __FILE__);
+
+ $original = clone($user);
+
+ $user->nickname = $nickname;
+ $user->language = $language;
+ $user->timezone = $timezone;
+
+ $result = $user->updateKeys($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ } else {
+ # Re-initialize language environment if it changed
+ common_init_language();
+ }
+ }
+
+ # XXX: XOR
+
+ if ($user->autosubscribe ^ $autosubscribe) {
+
+ $original = clone($user);
+
+ $user->autosubscribe = $autosubscribe;
+
+ $result = $user->update($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user for autosubscribe.'));
+ return;
+ }
+ }
+
+ $profile = $user->getProfile();
+
+ $orig_profile = clone($profile);
+
+ $profile->nickname = $user->nickname;
+ $profile->fullname = $fullname;
+ $profile->homepage = $homepage;
+ $profile->bio = $bio;
+ $profile->location = $location;
+ $profile->profileurl = common_profile_url($nickname);
+
+ common_debug('Old profile: ' . common_log_objstring($orig_profile), __FILE__);
+ common_debug('New profile: ' . common_log_objstring($profile), __FILE__);
+
+ $result = $profile->update($orig_profile);
+
+ if (!$result) {
+ common_log_db_error($profile, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t save profile.'));
+ return;
+ }
+
+ # Set the user tags
+
+ $result = $user->setSelfTags($tags);
+
+ if (!$result) {
+ common_server_error(_('Couldn\'t save tags.'));
+ return;
+ }
+
+ $user->query('COMMIT');
+
+ common_broadcast_profile($profile);
+
+ $this->show_form(_('Settings saved.'), TRUE);
+ }
+
+
+ function upload_avatar() {
+ switch ($_FILES['avatarfile']['error']) {
+ case UPLOAD_ERR_OK: # success, jump out
+ break;
+ case UPLOAD_ERR_INI_SIZE:
+ case UPLOAD_ERR_FORM_SIZE:
+ $this->show_form(_('That file is too big.'));
+ return;
+ case UPLOAD_ERR_PARTIAL:
+ @unlink($_FILES['avatarfile']['tmp_name']);
+ $this->show_form(_('Partial upload.'));
+ return;
+ default:
+ $this->show_form(_('System error uploading file.'));
+ return;
+ }
+
+ $info = @getimagesize($_FILES['avatarfile']['tmp_name']);
+
+ if (!$info) {
+ @unlink($_FILES['avatarfile']['tmp_name']);
+ $this->show_form(_('Not an image or corrupt file.'));
+ return;
+ }
+
+ switch ($info[2]) {
+ case IMAGETYPE_GIF:
+ case IMAGETYPE_JPEG:
+ case IMAGETYPE_PNG:
+ break;
+ default:
+ $this->show_form(_('Unsupported image file format.'));
+ return;
+ }
+
+ $user = common_current_user();
+ $profile = $user->getProfile();
+
+ if ($profile->setOriginal($_FILES['avatarfile']['tmp_name'])) {
+ $this->show_form(_('Avatar updated.'), true);
+ } else {
+ $this->show_form(_('Failed updating avatar.'));
+ }
+
+ @unlink($_FILES['avatarfile']['tmp_name']);
+ }
+
+ function nickname_exists($nickname) {
+ $user = common_current_user();
+ $other = User::staticGet('nickname', $nickname);
+ if (!$other) {
+ return false;
+ } else {
+ return $other->id != $user->id;
+ }
+ }
+
+ function change_password() {
+
+ $user = common_current_user();
+ assert(!is_null($user)); # should already be checked
+
+ # FIXME: scrub input
+
+ $newpassword = $this->arg('newpassword');
+ $confirm = $this->arg('confirm');
+ $token = $this->arg('token');
+
+ if (0 != strcmp($newpassword, $confirm)) {
+ $this->show_form(_('Passwords don\'t match.'));
+ return;
+ }
+
+ if ($user->password) {
+ $oldpassword = $this->arg('oldpassword');
+
+ if (!common_check_user($user->nickname, $oldpassword)) {
+ $this->show_form(_('Incorrect old password'));
+ return;
+ }
+ }
+
+ $original = clone($user);
+
+ $user->password = common_munge_password($newpassword, $user->id);
+
+ $val = $user->validate();
+ if ($val !== TRUE) {
+ $this->show_form(_('Error saving user; invalid.'));
+ return;
+ }
+
+ if (!$user->update($original)) {
+ common_server_error(_('Can\'t save new password.'));
+ return;
+ }
+
+ $this->show_form(_('Password saved.'), true);
+ }
+}
diff --git a/actions/public.php b/actions/public.php
new file mode 100644
index 000000000..218f80194
--- /dev/null
+++ b/actions/public.php
@@ -0,0 +1,99 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/stream.php');
+
+class PublicAction extends StreamAction {
+
+ function handle($args) {
+ parent::handle($args);
+
+ $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ header('X-XRDS-Location: '. common_local_url('publicxrds'));
+
+ common_show_header(_('Public timeline'),
+ array($this, 'show_header'), NULL,
+ array($this, 'show_top'));
+
+ # XXX: Public sidebar here?
+
+ $this->show_notices($page);
+
+ common_show_footer();
+ }
+
+ function show_top() {
+ if (common_logged_in()) {
+ common_notice_form('public');
+ } else {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+
+ $this->public_views_menu();
+
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('publicrss'),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'publicrss'),
+ 1=>array('href'=>common_local_url('publicatom'),
+ 'type' => 'atom',
+ 'version' => 'Atom 1.0',
+ 'item' => 'publicatom')));
+ }
+
+ function get_instructions() {
+ return _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
+ 'based on the Free Software [Laconica](http://laconi.ca/) tool. ' .
+ '[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ([Read more](%%doc.help%%))');
+ }
+
+ function show_header() {
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('publicrss'),
+ 'type' => 'application/rss+xml',
+ 'title' => _('Public Stream Feed')));
+ # for client side of OpenID authentication
+ common_element('meta', array('http-equiv' => 'X-XRDS-Location',
+ 'content' => common_local_url('publicxrds')));
+ }
+
+ function show_notices($page) {
+
+ $cnt = 0;
+ $notice = Notice::publicStream(($page-1)*NOTICES_PER_PAGE,
+ NOTICES_PER_PAGE + 1);
+
+ if (!$notice) {
+ $this->server_error(_('Could not retrieve public stream.'));
+ return;
+ }
+
+ $cnt = $this->show_notice_list($notice);
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'public');
+ }
+}
diff --git a/actions/publicrss.php b/actions/publicrss.php
new file mode 100644
index 000000000..1ab6a8be0
--- /dev/null
+++ b/actions/publicrss.php
@@ -0,0 +1,57 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class PublicrssAction extends Rss10Action {
+
+ function init() {
+ return true;
+ }
+
+ function get_notices($limit=0) {
+
+ $notices = array();
+
+ $notice = Notice::publicStream(0, ($limit == 0) ? 48 : $limit);
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ global $config;
+ $c = array('url' => common_local_url('publicrss'),
+ 'title' => sprintf(_('%s Public Stream'), $config['site']['name']),
+ 'link' => common_local_url('public'),
+ 'description' => sprintf(_('All updates for %s'), $config['site']['name']));
+ return $c;
+ }
+
+ function get_image() {
+ return NULL;
+ }
+} \ No newline at end of file
diff --git a/actions/publicxrds.php b/actions/publicxrds.php
new file mode 100644
index 000000000..951434c87
--- /dev/null
+++ b/actions/publicxrds.php
@@ -0,0 +1,79 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/openid.php');
+
+# XXX: factor out similarities with XrdsAction
+
+class PublicxrdsAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ header('Content-Type: application/xrds+xml');
+
+ common_start_xml();
+ common_element_start('XRDS', array('xmlns' => 'xri://$xrds'));
+
+ common_element_start('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+
+ common_element('Type', NULL, 'xri://$xrds*simple');
+
+ foreach (array('finishopenidlogin', 'finishaddopenid', 'finishimmediate') as $finish) {
+ $this->show_service(Auth_OpenID_RP_RETURN_TO_URL_TYPE,
+ common_local_url($finish));
+ }
+
+ common_element_end('XRD');
+
+ common_element_end('XRDS');
+ common_end_xml();
+ }
+
+ function show_service($type, $uri, $params=NULL, $sigs=NULL, $localId=NULL) {
+ common_element_start('Service');
+ if ($uri) {
+ common_element('URI', NULL, $uri);
+ }
+ common_element('Type', NULL, $type);
+ if ($params) {
+ foreach ($params as $param) {
+ common_element('Type', NULL, $param);
+ }
+ }
+ if ($sigs) {
+ foreach ($sigs as $sig) {
+ common_element('Type', NULL, $sig);
+ }
+ }
+ if ($localId) {
+ common_element('LocalID', NULL, $localId);
+ }
+ common_element_end('Service');
+ }
+} \ No newline at end of file
diff --git a/actions/recoverpassword.php b/actions/recoverpassword.php
new file mode 100644
index 000000000..38c42f41d
--- /dev/null
+++ b/actions/recoverpassword.php
@@ -0,0 +1,331 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+# You have 24 hours to claim your password
+
+define(MAX_RECOVERY_TIME, 24 * 60 * 60);
+
+class RecoverpasswordAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (common_logged_in()) {
+ $this->client_error(_('You are already logged in!'));
+ return;
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ if ($this->arg('recover')) {
+ $this->recover_password();
+ } else if ($this->arg('reset')) {
+ $this->reset_password();
+ } else {
+ $this->client_error(_('Unexpected form submission.'));
+ }
+ } else {
+ if ($this->trimmed('code')) {
+ $this->check_code();
+ } else {
+ $this->show_form();
+ }
+ }
+ }
+
+ function check_code() {
+
+ $code = $this->trimmed('code');
+ $confirm = Confirm_address::staticGet('code', $code);
+
+ if (!$confirm) {
+ $this->client_error(_('No such recovery code.'));
+ return;
+ }
+ if ($confirm->address_type != 'recover') {
+ $this->client_error(_('Not a recovery code.'));
+ return;
+ }
+
+ $user = User::staticGet($confirm->user_id);
+
+ if (!$user) {
+ $this->server_error(_('Recovery code for unknown user.'));
+ return;
+ }
+
+ $touched = strtotime($confirm->modified);
+ $email = $confirm->address;
+
+ # Burn this code
+
+ $result = $confirm->delete();
+
+ if (!$result) {
+ common_log_db_error($confirm, 'DELETE', __FILE__);
+ common_server_error(_('Error with confirmation code.'));
+ return;
+ }
+
+ # These should be reaped, but for now we just check mod time
+ # Note: it's still deleted; let's avoid a second attempt!
+
+ if ((time() - $touched) > MAX_RECOVERY_TIME) {
+ common_log(LOG_WARNING,
+ 'Attempted redemption on recovery code ' .
+ 'that is ' . $touched . ' seconds old. ');
+ $this->client_error(_('This confirmation code is too old. ' .
+ 'Please start again.'));
+ return;
+ }
+
+ # If we used an outstanding confirmation to send the email,
+ # it's been confirmed at this point.
+
+ if (!$user->email) {
+ $orig = clone($user);
+ $user->email = $email;
+ $result = $user->updateKeys($orig);
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ $this->server_error(_('Could not update user with confirmed email address.'));
+ return;
+ }
+ }
+
+ # Success!
+
+ $this->set_temp_user($user);
+ $this->show_password_form();
+ }
+
+ function set_temp_user(&$user) {
+ common_ensure_session();
+ $_SESSION['tempuser'] = $user->id;
+ }
+
+ function get_temp_user() {
+ common_ensure_session();
+ $user_id = $_SESSION['tempuser'];
+ if ($user_id) {
+ $user = User::staticGet($user_id);
+ }
+ return $user;
+ }
+
+ function clear_temp_user() {
+ common_ensure_session();
+ unset($_SESSION['tempuser']);
+ }
+
+ function show_top($msg=NULL) {
+ if ($msg) {
+ common_element('div', 'error', $msg);
+ } else {
+ common_element_start('div', 'instructions');
+ common_element('p', NULL,
+ _('If you\'ve forgotten or lost your' .
+ ' password, you can get a new one sent to' .
+ ' the email address you have stored ' .
+ ' in your account.'));
+ common_element_end('div');
+ }
+ }
+
+ function show_password_top($msg=NULL) {
+ if ($msg) {
+ common_element('div', 'error', $msg);
+ } else {
+ common_element('div', 'instructions',
+ _('You\'ve been identified. Enter a ' .
+ ' new password below. '));
+ }
+ }
+
+ function show_form($msg=NULL) {
+
+ common_show_header(_('Recover password'), NULL,
+ $msg, array($this, 'show_top'));
+
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'recoverpassword',
+ 'action' => common_local_url('recoverpassword')));
+ common_input('nicknameoremail', _('Nickname or email'),
+ $this->trimmed('nicknameoremail'),
+ _('Your nickname on this server, ' .
+ 'or your registered email address.'));
+ common_submit('recover', _('Recover'));
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function show_password_form($msg=NULL) {
+
+ common_show_header(_('Reset password'), NULL,
+ $msg, array($this, 'show_password_top'));
+
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'recoverpassword',
+ 'action' => common_local_url('recoverpassword')));
+ common_hidden('token', common_session_token());
+ common_password('newpassword', _('New password'),
+ _('6 or more characters, and don\'t forget it!'));
+ common_password('confirm', _('Confirm'),
+ _('Same as password above'));
+ common_submit('reset', _('Reset'));
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function recover_password() {
+ $nore = $this->trimmed('nicknameoremail');
+ if (!$nore) {
+ $this->show_form(_('Enter a nickname or email address.'));
+ return;
+ }
+
+ $user = User::staticGet('email', common_canonical_email($nore));
+
+ if (!$user) {
+ $user = User::staticGet('nickname', common_canonical_nickname($nore));
+ }
+
+ # See if it's an unconfirmed email address
+
+ if (!$user) {
+ $confirm_email = Confirm_address::staticGet('address', common_canonical_email($nore));
+ if ($confirm_email && $confirm_email->address_type == 'email') {
+ $user = User::staticGet($confirm_email->user_id);
+ }
+ }
+
+ if (!$user) {
+ $this->show_form(_('No user with that email address or username.'));
+ return;
+ }
+
+ # Try to get an unconfirmed email address if they used a user name
+
+ if (!$user->email && !$confirm_email) {
+ $confirm_email = Confirm_address::staticGet('user_id', $user->id);
+ if ($confirm_email && $confirm_email->address_type != 'email') {
+ # Skip non-email confirmations
+ $confirm_email = NULL;
+ }
+ }
+
+ if (!$user->email && !$confirm_email) {
+ $this->client_error(_('No registered email address for that user.'));
+ return;
+ }
+
+ # Success! We have a valid user and a confirmed or unconfirmed email address
+
+ $confirm = new Confirm_address();
+ $confirm->code = common_confirmation_code(128);
+ $confirm->address_type = 'recover';
+ $confirm->user_id = $user->id;
+ $confirm->address = (isset($user->email)) ? $user->email : $confirm_email->address;
+
+ if (!$confirm->insert()) {
+ common_log_db_error($confirm, 'INSERT', __FILE__);
+ $this->server_error(_('Error saving address confirmation.'));
+ return;
+ }
+
+ $body = "Hey, $user->nickname.";
+ $body .= "\n\n";
+ $body .= 'Someone just asked for a new password ' .
+ 'for this account on ' . common_config('site', 'name') . '.';
+ $body .= "\n\n";
+ $body .= 'If it was you, and you want to confirm, use the URL below:';
+ $body .= "\n\n";
+ $body .= "\t".common_local_url('recoverpassword',
+ array('code' => $confirm->code));
+ $body .= "\n\n";
+ $body .= 'If not, just ignore this message.';
+ $body .= "\n\n";
+ $body .= 'Thanks for your time, ';
+ $body .= "\n";
+ $body .= common_config('site', 'name');
+ $body .= "\n";
+
+ mail_to_user($user, _('Password recovery requested'), $body, $confirm->address);
+
+ common_show_header(_('Password recovery requested'));
+ common_element('p', NULL,
+ _('Instructions for recovering your password ' .
+ 'have been sent to the email address registered to your ' .
+ 'account.'));
+ common_show_footer();
+ }
+
+ function reset_password() {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $user = $this->get_temp_user();
+
+ if (!$user) {
+ $this->client_error(_('Unexpected password reset.'));
+ return;
+ }
+
+ $newpassword = $this->trimmed('newpassword');
+ $confirm = $this->trimmed('confirm');
+
+ if (!$newpassword || strlen($newpassword) < 6) {
+ $this->show_password_form(_('Password must be 6 chars or more.'));
+ return;
+ }
+ if ($newpassword != $confirm) {
+ $this->show_password_form(_('Password and confirmation do not match.'));
+ return;
+ }
+
+ # OK, we're ready to go
+
+ $original = clone($user);
+
+ $user->password = common_munge_password($newpassword, $user->id);
+
+ if (!$user->update($original)) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Can\'t save new password.'));
+ return;
+ }
+
+ $this->clear_temp_user();
+
+ if (!common_set_user($user->nickname)) {
+ common_server_error(_('Error setting user.'));
+ return;
+ }
+
+ common_real_login(true);
+
+ common_show_header(_('Password saved.'));
+ common_element('p', NULL, _('New password successfully saved. ' .
+ 'You are now logged in.'));
+ common_show_footer();
+ }
+}
diff --git a/actions/register.php b/actions/register.php
new file mode 100644
index 000000000..a22ffca28
--- /dev/null
+++ b/actions/register.php
@@ -0,0 +1,262 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class RegisterAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if (common_config('site', 'closed')) {
+ common_user_error(_('Registration not allowed.'));
+ } else if (common_logged_in()) {
+ common_user_error(_('Already logged in.'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->try_register();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ function try_register() {
+
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $nickname = $this->trimmed('nickname');
+ $email = $this->trimmed('email');
+ $fullname = $this->trimmed('fullname');
+ $homepage = $this->trimmed('homepage');
+ $bio = $this->trimmed('bio');
+ $location = $this->trimmed('location');
+
+ # We don't trim these... whitespace is OK in a password!
+
+ $password = $this->arg('password');
+ $confirm = $this->arg('confirm');
+
+ # invitation code, if any
+
+ $code = $this->trimmed('code');
+
+ if ($code) {
+ $invite = Invitation::staticGet($code);
+ }
+
+ if (common_config('site', 'inviteonly') && !($code && $invite)) {
+ $this->client_error(_('Sorry, only invited people can register.'));
+ return;
+ }
+
+ # Input scrubbing
+
+ $nickname = common_canonical_nickname($nickname);
+ $email = common_canonical_email($email);
+
+ if (!$this->boolean('license')) {
+ $this->show_form(_('You can\'t register if you don\'t agree to the license.'));
+ } else if ($email && !Validate::email($email, true)) {
+ $this->show_form(_('Not a valid email address.'));
+ } else if (!Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ $this->show_form(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+ } else if ($this->nickname_exists($nickname)) {
+ $this->show_form(_('Nickname already in use. Try another one.'));
+ } else if (!User::allowed_nickname($nickname)) {
+ $this->show_form(_('Not a valid nickname.'));
+ } else if ($this->email_exists($email)) {
+ $this->show_form(_('Email address already exists.'));
+ } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
+ !Validate::uri($homepage, array('allowed_schemes' => array('http', 'https')))) {
+ $this->show_form(_('Homepage is not a valid URL.'));
+ return;
+ } else if (!is_null($fullname) && strlen($fullname) > 255) {
+ $this->show_form(_('Full name is too long (max 255 chars).'));
+ return;
+ } else if (!is_null($bio) && strlen($bio) > 140) {
+ $this->show_form(_('Bio is too long (max 140 chars).'));
+ return;
+ } else if (!is_null($location) && strlen($location) > 255) {
+ $this->show_form(_('Location is too long (max 255 chars).'));
+ return;
+ } else if (strlen($password) < 6) {
+ $this->show_form(_('Password must be 6 or more characters.'));
+ return;
+ } else if ($password != $confirm) {
+ $this->show_form(_('Passwords don\'t match.'));
+ } else if ($user = User::register(array('nickname' => $nickname, 'password' => $password, 'email' => $email,
+ 'fullname' => $fullname, 'homepage' => $homepage, 'bio' => $bio,
+ 'location' => $location, 'code' => $code))) {
+ if (!$user) {
+ $this->show_form(_('Invalid username or password.'));
+ return;
+ }
+ # success!
+ if (!common_set_user($user)) {
+ common_server_error(_('Error setting user.'));
+ return;
+ }
+ # this is a real login
+ common_real_login(true);
+ if ($this->boolean('rememberme')) {
+ common_debug('Adding rememberme cookie for ' . $nickname);
+ common_rememberme($user);
+ }
+ # Re-init language env in case it changed (not yet, but soon)
+ common_init_language();
+ $this->show_success();
+ } else {
+ $this->show_form(_('Invalid username or password.'));
+ }
+ }
+
+ # checks if *CANONICAL* nickname exists
+
+ function nickname_exists($nickname) {
+ $user = User::staticGet('nickname', $nickname);
+ return ($user !== false);
+ }
+
+ # checks if *CANONICAL* email exists
+
+ function email_exists($email) {
+ $email = common_canonical_email($email);
+ if (!$email || strlen($email) == 0) {
+ return false;
+ }
+ $user = User::staticGet('email', $email);
+ return ($user !== false);
+ }
+
+ function show_top($error=NULL) {
+ if ($error) {
+ common_element('p', 'error', $error);
+ } else {
+ $instr = common_markup_to_html(_('With this form you can create a new account. ' .
+ 'You can then post notices and link up to friends and colleagues. '.
+ '(Have an [OpenID](http://openid.net/)? ' .
+ 'Try our [OpenID registration](%%action.openidlogin%%)!)'));
+
+ common_element_start('div', 'instructions');
+ common_raw($instr);
+ common_element_end('div');
+ }
+ }
+
+ function show_form($error=NULL) {
+ global $config;
+
+ $code = $this->trimmed('code');
+
+ if ($code) {
+ $invite = Invitation::staticGet($code);
+ }
+
+ if (common_config('site', 'inviteonly') && !($code && $invite)) {
+ $this->client_error(_('Sorry, only invited people can register.'));
+ return;
+ }
+
+ common_show_header(_('Register'), NULL, $error, array($this, 'show_top'));
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'login',
+ 'action' => common_local_url('register')));
+
+ common_hidden('token', common_session_token());
+
+ if ($code) {
+ common_hidden('code', $code);
+ }
+
+ common_input('nickname', _('Nickname'), $this->trimmed('nickname'),
+ _('1-64 lowercase letters or numbers, no punctuation or spaces. Required.'));
+ common_password('password', _('Password'),
+ _('6 or more characters. Required.'));
+ common_password('confirm', _('Confirm'),
+ _('Same as password above. Required.'));
+ if ($invite && $invite->address_type == 'email') {
+ common_input('email', _('Email'), $invite->address,
+ _('Used only for updates, announcements, and password recovery'));
+ } else {
+ common_input('email', _('Email'), $this->trimmed('email'),
+ _('Used only for updates, announcements, and password recovery'));
+ }
+ common_input('fullname', _('Full name'),
+ $this->trimmed('fullname'),
+ _('Longer name, preferably your "real" name'));
+ common_input('homepage', _('Homepage'),
+ $this->trimmed('homepage'),
+ _('URL of your homepage, blog, or profile on another site'));
+ common_textarea('bio', _('Bio'),
+ $this->trimmed('bio'),
+ _('Describe yourself and your interests in 140 chars'));
+ common_input('location', _('Location'),
+ $this->trimmed('location'),
+ _('Where you are, like "City, State (or Region), Country"'));
+ common_checkbox('rememberme', _('Remember me'),
+ $this->boolean('rememberme'),
+ _('Automatically login in the future; not for shared computers!'));
+ common_element_start('p');
+ $attrs = array('type' => 'checkbox',
+ 'id' => 'license',
+ 'name' => 'license',
+ 'value' => 'true');
+ if ($this->boolean('license')) {
+ $attrs['checked'] = 'checked';
+ }
+ common_element('input', $attrs);
+ common_text(_('My text and files are available under '));
+ common_element('a', array('href' => $config['license']['url']),
+ $config['license']['title']);
+ common_text(_(' except this private data: password, email address, IM address, phone number.'));
+ common_element_end('p');
+ common_submit('submit', _('Register'));
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function show_success() {
+ $nickname = $this->arg('nickname');
+ common_show_header(_('Registration successful'));
+ common_element_start('div', 'success');
+ $instr = sprintf(_('Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may want to...'. "\n\n" .
+ '* Go to [your profile](%s) and post your first message.' . "\n" .
+ '* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send notices through instant messages.' . "\n" .
+ '* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that share your interests. ' . "\n" .
+ '* Update your [profile settings](%%%%action.profilesettings%%%%) to tell others more about you. ' . "\n" .
+ '* Read over the [online docs](%%%%doc.help%%%%) for features you may have missed. ' . "\n\n" .
+ 'Thanks for signing up and we hope you enjoy using this service.'),
+ $nickname, common_local_url('showstream', array('nickname' => $nickname)));
+ common_raw(common_markup_to_html($instr));
+ $have_email = $this->trimmed('email');
+ if ($have_email) {
+ $emailinstr = _('(You should receive a message by email momentarily, with ' .
+ 'instructions on how to confirm your email address.)');
+ common_raw(common_markup_to_html($emailinstr));
+ }
+ common_element_end('div');
+ common_show_footer();
+ }
+
+}
diff --git a/actions/remotesubscribe.php b/actions/remotesubscribe.php
new file mode 100644
index 000000000..c3a09bcfc
--- /dev/null
+++ b/actions/remotesubscribe.php
@@ -0,0 +1,386 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class RemotesubscribeAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ if (common_logged_in()) {
+ common_user_error(_('You can use the local subscription!'));
+ return;
+ }
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $this->remote_subscription();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ function get_instructions() {
+ return _('To subscribe, you can [login](%%action.login%%),' .
+ ' or [register](%%action.register%%) a new ' .
+ ' account. If you already have an account ' .
+ ' on a [compatible microblogging site](%%doc.openmublog%%), ' .
+ ' enter your profile URL below.');
+ }
+
+ function show_top($err=NULL) {
+ if ($err) {
+ common_element('div', 'error', $err);
+ } else {
+ $instructions = $this->get_instructions();
+ $output = common_markup_to_html($instructions);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('p');
+ }
+ }
+
+ function show_form($err=NULL) {
+ $nickname = $this->trimmed('nickname');
+ $profile = $this->trimmed('profile_url');
+ common_show_header(_('Remote subscribe'), NULL, $err,
+ array($this, 'show_top'));
+ # id = remotesubscribe conflicts with the
+ # button on profile page
+ common_element_start('form', array('id' => 'remsub', 'method' => 'post',
+ 'action' => common_local_url('remotesubscribe')));
+ common_hidden('token', common_session_token());
+ common_input('nickname', _('User nickname'), $nickname,
+ _('Nickname of the user you want to follow'));
+ common_input('profile_url', _('Profile URL'), $profile,
+ _('URL of your profile on another compatible microblogging service'));
+ common_submit('submit', _('Subscribe'));
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function remote_subscription() {
+ $user = $this->get_user();
+
+ if (!$user) {
+ $this->show_form(_('No such user.'));
+ return;
+ }
+
+ $profile = $this->trimmed('profile_url');
+
+ if (!$profile) {
+ $this->show_form(_('No such user.'));
+ return;
+ }
+
+ if (!Validate::uri($profile, array('allowed_schemes' => array('http', 'https')))) {
+ $this->show_form(_('Invalid profile URL (bad format)'));
+ return;
+ }
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ $yadis = Auth_Yadis_Yadis::discover($profile, $fetcher);
+
+ if (!$yadis || $yadis->failed) {
+ $this->show_form(_('Not a valid profile URL (no YADIS document).'));
+ return;
+ }
+
+ # XXX: a little liberal for sites that accidentally put whitespace before the xml declaration
+
+ $xrds =& Auth_Yadis_XRDS::parseXRDS(trim($yadis->response_text));
+
+ if (!$xrds) {
+ $this->show_form(_('Not a valid profile URL (no XRDS defined).'));
+ return;
+ }
+
+ $omb = $this->getOmb($xrds);
+
+ if (!$omb) {
+ $this->show_form(_('Not a valid profile URL (incorrect services).'));
+ return;
+ }
+
+ if (omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]) ==
+ common_local_url('requesttoken'))
+ {
+ $this->show_form(_('That\'s a local profile! Login to subscribe.'));
+ return;
+ }
+
+ if (User::staticGet('uri', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]))) {
+ $this->show_form(_('That\'s a local profile! Login to subscribe.'));
+ return;
+ }
+
+ list($token, $secret) = $this->request_token($omb);
+
+ if (!$token || !$secret) {
+ $this->show_form(_('Couldn\'t get a request token.'));
+ return;
+ }
+
+ $this->request_authorization($user, $omb, $token, $secret);
+ }
+
+ function get_user() {
+ $user = NULL;
+ $nickname = $this->trimmed('nickname');
+ if ($nickname) {
+ $user = User::staticGet('nickname', $nickname);
+ }
+ return $user;
+ }
+
+ function getOmb($xrds) {
+
+ static $omb_endpoints = array(OMB_ENDPOINT_UPDATEPROFILE, OMB_ENDPOINT_POSTNOTICE);
+ static $oauth_endpoints = array(OAUTH_ENDPOINT_REQUEST, OAUTH_ENDPOINT_AUTHORIZE,
+ OAUTH_ENDPOINT_ACCESS);
+ $omb = array();
+
+ # XXX: the following code could probably be refactored to eliminate dupes
+
+ $oauth_services = omb_get_services($xrds, OAUTH_DISCOVERY);
+
+ if (!$oauth_services) {
+ return NULL;
+ }
+
+ $oauth_service = $oauth_services[0];
+
+ $oauth_xrd = $this->getXRD($oauth_service, $xrds);
+
+ if (!$oauth_xrd) {
+ return NULL;
+ }
+
+ if (!$this->addServices($oauth_xrd, $oauth_endpoints, $omb)) {
+ return NULL;
+ }
+
+ $omb_services = omb_get_services($xrds, OMB_NAMESPACE);
+
+ if (!$omb_services) {
+ return NULL;
+ }
+
+ $omb_service = $omb_services[0];
+
+ $omb_xrd = $this->getXRD($omb_service, $xrds);
+
+ if (!$omb_xrd) {
+ return NULL;
+ }
+
+ if (!$this->addServices($omb_xrd, $omb_endpoints, $omb)) {
+ return NULL;
+ }
+
+ # XXX: check that we got all the services we needed
+
+ foreach (array_merge($omb_endpoints, $oauth_endpoints) as $type) {
+ if (!array_key_exists($type, $omb) || !$omb[$type]) {
+ return NULL;
+ }
+ }
+
+ if (!omb_local_id($omb[OAUTH_ENDPOINT_REQUEST])) {
+ return NULL;
+ }
+
+ return $omb;
+ }
+
+ function getXRD($main_service, $main_xrds) {
+ $uri = omb_service_uri($main_service);
+ if (strpos($uri, "#") !== 0) {
+ # FIXME: more rigorous handling of external service definitions
+ return NULL;
+ }
+ $id = substr($uri, 1);
+ $nodes = $main_xrds->allXrdNodes;
+ $parser = $main_xrds->parser;
+ foreach ($nodes as $node) {
+ $attrs = $parser->attributes($node);
+ if (array_key_exists('xml:id', $attrs) &&
+ $attrs['xml:id'] == $id) {
+ # XXX: trick the constructor into thinking this is the only node
+ $bogus_nodes = array($node);
+ return new Auth_Yadis_XRDS($parser, $bogus_nodes);
+ }
+ }
+ return NULL;
+ }
+
+ function addServices($xrd, $types, &$omb) {
+ foreach ($types as $type) {
+ $matches = omb_get_services($xrd, $type);
+ if ($matches) {
+ $omb[$type] = $matches[0];
+ } else {
+ # no match for type
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function request_token($omb) {
+ $con = omb_oauth_consumer();
+
+ $url = omb_service_uri($omb[OAUTH_ENDPOINT_REQUEST]);
+
+ # XXX: Is this the right thing to do? Strip off GET params and make them
+ # POST params? Seems wrong to me.
+
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
+
+ $req = OAuthRequest::from_consumer_and_token($con, NULL, "POST", $url, $params);
+
+ $listener = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
+
+ if (!$listener) {
+ return NULL;
+ }
+
+ $req->set_parameter('omb_listener', $listener);
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+
+ # XXX: test to see if endpoint accepts this signature method
+
+ $req->sign_request(omb_hmac_sha1(), $con, NULL);
+
+ # We re-use this tool's fetcher, since it's pretty good
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ $result = $fetcher->post($req->get_normalized_http_url(),
+ $req->to_postdata(),
+ array('User-Agent' => 'Laconica/' . LACONICA_VERSION));
+
+ if ($result->status != 200) {
+ return NULL;
+ }
+
+ parse_str($result->body, $return);
+
+ return array($return['oauth_token'], $return['oauth_token_secret']);
+ }
+
+ function request_authorization($user, $omb, $token, $secret) {
+ global $config; # for license URL
+
+ $con = omb_oauth_consumer();
+ $tok = new OAuthToken($token, $secret);
+
+ $url = omb_service_uri($omb[OAUTH_ENDPOINT_AUTHORIZE]);
+
+ # XXX: Is this the right thing to do? Strip off GET params and make them
+ # POST params? Seems wrong to me.
+
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
+
+ $req = OAuthRequest::from_consumer_and_token($con, $tok, 'GET', $url, $params);
+
+ # We send over a ton of information. This lets the other
+ # server store info about our user, and it lets the current
+ # user decide if they really want to authorize the subscription.
+
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+ $req->set_parameter('omb_listener', omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]));
+ $req->set_parameter('omb_listenee', $user->uri);
+ $req->set_parameter('omb_listenee_profile', common_profile_url($user->nickname));
+ $req->set_parameter('omb_listenee_nickname', $user->nickname);
+ $req->set_parameter('omb_listenee_license', $config['license']['url']);
+
+ $profile = $user->getProfile();
+ if (!$profile) {
+ common_log_db_error($user, 'SELECT', __FILE__);
+ $this->server_error(_('User without matching profile'));
+ return;
+ }
+
+ if ($profile->fullname) {
+ $req->set_parameter('omb_listenee_fullname', $profile->fullname);
+ }
+ if ($profile->homepage) {
+ $req->set_parameter('omb_listenee_homepage', $profile->homepage);
+ }
+ if ($profile->bio) {
+ $req->set_parameter('omb_listenee_bio', $profile->bio);
+ }
+ if ($profile->location) {
+ $req->set_parameter('omb_listenee_location', $profile->location);
+ }
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ if ($avatar) {
+ $req->set_parameter('omb_listenee_avatar', $avatar->url);
+ }
+
+ # XXX: add a nonce to prevent replay attacks
+
+ $req->set_parameter('oauth_callback', common_local_url('finishremotesubscribe'));
+
+ # XXX: test to see if endpoint accepts this signature method
+
+ $req->sign_request(omb_hmac_sha1(), $con, $tok);
+
+ # store all our info here
+
+ $omb['listenee'] = $user->nickname;
+ $omb['listener'] = omb_local_id($omb[OAUTH_ENDPOINT_REQUEST]);
+ $omb['token'] = $token;
+ $omb['secret'] = $secret;
+ # call doesn't work after bounce back so we cache; maybe serialization issue...?
+ $omb['access_token_url'] = omb_service_uri($omb[OAUTH_ENDPOINT_ACCESS]);
+ $omb['post_notice_url'] = omb_service_uri($omb[OMB_ENDPOINT_POSTNOTICE]);
+ $omb['update_profile_url'] = omb_service_uri($omb[OMB_ENDPOINT_UPDATEPROFILE]);
+
+ common_ensure_session();
+
+ $_SESSION['oauth_authorization_request'] = $omb;
+
+ # Redirect to authorization service
+
+ common_redirect($req->to_url());
+ return;
+ }
+
+ function make_nonce() {
+ return common_good_rand(16);
+ }
+}
diff --git a/actions/replies.php b/actions/replies.php
new file mode 100644
index 000000000..835871ffc
--- /dev/null
+++ b/actions/replies.php
@@ -0,0 +1,94 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/actions/showstream.php');
+
+class RepliesAction extends StreamAction {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->no_such_user();
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ # Looks like we're good; show the header
+
+ common_show_header(sprintf(_("Replies to %s"), $profile->nickname),
+ array($this, 'show_header'), $user,
+ array($this, 'show_top'));
+
+ $this->show_replies($user);
+
+ common_show_footer();
+ }
+
+ function no_such_user() {
+ common_user_error(_('No such user.'));
+ }
+
+ function show_header($user) {
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('repliesrss', array('nickname' =>
+ $user->nickname)),
+ 'type' => 'application/rss+xml',
+ 'title' => sprintf(_('Feed for replies to %s'), $user->nickname)));
+ }
+
+ function show_top($user) {
+ $cur = common_current_user();
+
+ if ($cur && $cur->id == $user->id) {
+ common_notice_form('replies');
+ }
+
+ $this->views_menu();
+
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('repliesrss', array('nickname' => $user->nickname)),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'repliesrss')));
+ }
+
+ function show_replies($user) {
+
+ $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ $notice = $user->getReplies(($page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
+ $cnt = $this->show_notice_list($notice);
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'replies', array('nickname' => $user->nickname));
+ }
+}
diff --git a/actions/repliesrss.php b/actions/repliesrss.php
new file mode 100644
index 000000000..7369db5e0
--- /dev/null
+++ b/actions/repliesrss.php
@@ -0,0 +1,79 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class RepliesrssAction extends Rss10Action {
+
+ var $user = NULL;
+
+ function init() {
+ $nickname = $this->trimmed('nickname');
+ $this->user = User::staticGet('nickname', $nickname);
+
+ if (!$this->user) {
+ common_user_error(_('No such user.'));
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function get_notices($limit=0) {
+
+ $user = $this->user;
+
+ $notice = $user->getReplies(0, ($limit == 0) ? 48 : $limit);
+
+ $notices = array();
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ $user = $this->user;
+ $c = array('url' => common_local_url('repliesrss',
+ array('nickname' =>
+ $user->nickname)),
+ 'title' => sprintf(_("Replies to %s"), $user->nickname),
+ 'link' => common_local_url('replies',
+ array('nickname' =>
+ $user->nickname)),
+ 'description' => sprintf(_('Feed for replies to %s'), $user->nickname));
+ return $c;
+ }
+
+ function get_image() {
+ $user = $this->user;
+ $profile = $user->getProfile();
+ if (!$profile) {
+ return NULL;
+ }
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ return ($avatar) ? $avatar->url : NULL;
+ }
+} \ No newline at end of file
diff --git a/actions/requesttoken.php b/actions/requesttoken.php
new file mode 100644
index 000000000..76019a929
--- /dev/null
+++ b/actions/requesttoken.php
@@ -0,0 +1,42 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class RequesttokenAction extends Action {
+
+ function is_readonly() {
+ return false;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ try {
+ common_remove_magic_from_request();
+ $req = OAuthRequest::from_request();
+ $server = omb_oauth_server();
+ $token = $server->fetch_request_token($req);
+ print $token;
+ } catch (OAuthException $e) {
+ common_server_error($e->getMessage());
+ }
+ }
+}
diff --git a/actions/showfavorites.php b/actions/showfavorites.php
new file mode 100644
index 000000000..4de4b1271
--- /dev/null
+++ b/actions/showfavorites.php
@@ -0,0 +1,97 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/actions/showstream.php');
+
+class ShowfavoritesAction extends StreamAction {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->client_error(_('No such user.'));
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ # Looks like we're good; show the header
+
+ common_show_header(sprintf(_("%s favorite notices"), $profile->nickname),
+ array($this, 'show_header'), $user,
+ array($this, 'show_top'));
+
+ $this->show_notices($user);
+
+ common_show_footer();
+ }
+
+ function show_header($user) {
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('favoritesrss', array('nickname' =>
+ $user->nickname)),
+ 'type' => 'application/rss+xml',
+ 'title' => sprintf(_('Feed for favorites of %s'), $user->nickname)));
+ }
+
+ function show_top($user) {
+ $cur = common_current_user();
+
+ if ($cur && $cur->id == $user->id) {
+ common_notice_form('all');
+ }
+
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('favoritesrss', array('nickname' => $user->nickname)),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'Favorites')));
+ $this->views_menu();
+ }
+
+ function show_notices($user) {
+
+ $page = $this->trimmed('page');
+ if (!$page) {
+ $page = 1;
+ }
+
+ $notice = $user->favoriteNotices(($page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
+ if (!$notice) {
+ $this->server_error(_('Could not retrieve favorite notices.'));
+ return;
+ }
+
+ $cnt = $this->show_notice_list($notice);
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'showfavorites', array('nickname' => $user->nickname));
+ }
+}
diff --git a/actions/showmessage.php b/actions/showmessage.php
new file mode 100644
index 000000000..c171ffe0b
--- /dev/null
+++ b/actions/showmessage.php
@@ -0,0 +1,100 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/mailbox.php');
+
+class ShowmessageAction extends MailboxAction {
+
+ function handle($args) {
+
+ Action::handle($args);
+
+ $message = $this->get_message();
+
+ if (!$message) {
+ $this->client_error(_('No such message.'), 404);
+ return;
+ }
+
+ $cur = common_current_user();
+
+ if ($cur && ($cur->id == $message->from_profile || $cur->id == $message->to_profile)) {
+ $this->show_page($cur, 1);
+ } else {
+ $this->client_error(_('Only the sender and recipient may read this message.'), 403);
+ return;
+ }
+ }
+
+ function get_message() {
+ $id = $this->trimmed('message');
+ $message = Message::staticGet('id', $id);
+ return $message;
+ }
+
+ function get_title($user, $page) {
+ $message = $this->get_message();
+ if (!$message) {
+ return NULL;
+ }
+
+ if ($user->id == $message->from_profile) {
+ $to = $message->getTo();
+ $title = sprintf(_("Message to %1\$s on %2\$s"),
+ $to->nickname,
+ common_exact_date($message->created));
+ } else if ($user->id == $message->to_profile) {
+ $from = $message->getFrom();
+ $title = sprintf(_("Message from %1\$s on %2\$s"),
+ $from->nickname,
+ common_exact_date($message->created));
+ }
+ return $title;
+ }
+
+ function get_messages($user, $page) {
+ $message = new Message();
+ $message->id = $this->trimmed('message');
+ $message->find();
+ return $message;
+ }
+
+ function get_message_profile($message) {
+ $user = common_current_user();
+ if ($user->id == $message->from_profile) {
+ return $message->getTo();
+ } else if ($user->id == $message->to_profile) {
+ return $message->getFrom();
+ } else {
+ # This shouldn't happen
+ return NULL;
+ }
+ }
+
+ function get_instructions() {
+ return '';
+ }
+
+ function views_menu() {
+ return;
+ }
+}
+ \ No newline at end of file
diff --git a/actions/shownotice.php b/actions/shownotice.php
new file mode 100644
index 000000000..6dea6d7bb
--- /dev/null
+++ b/actions/shownotice.php
@@ -0,0 +1,116 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/stream.php');
+
+class ShownoticeAction extends StreamAction {
+
+ var $notice = NULL;
+ var $profile = NULL;
+ var $avatar = NULL;
+
+ function prepare($args) {
+
+ parent::prepare($args);
+
+ $id = $this->arg('notice');
+ $this->notice = Notice::staticGet($id);
+
+ if (!$this->notice) {
+ $this->client_error(_('No such notice.'), 404);
+ return false;
+ }
+
+ $this->profile = $this->notice->getProfile();
+
+ if (!$this->profile) {
+ $this->server_error(_('Notice has no profile'), 500);
+ return false;
+ }
+
+ $this->avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
+
+ return true;
+ }
+
+ function last_modified() {
+ return max(strtotime($this->notice->created),
+ strtotime($this->profile->modified),
+ ($this->avatar) ? strtotime($this->avatar->modified) : 0);
+ }
+
+ function etag() {
+ return 'W/"' . implode(':', array($this->arg('action'),
+ common_language(),
+ $this->notice->id,
+ strtotime($this->notice->created),
+ strtotime($this->profile->modified),
+ ($this->avatar) ? strtotime($this->avatar->modified) : 0)) . '"';
+ }
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ common_show_header(sprintf(_('%1$s\'s status on %2$s'),
+ $this->profile->nickname,
+ common_exact_date($this->notice->created)),
+ array($this, 'show_header'), NULL,
+ array($this, 'show_top'));
+
+ common_element_start('ul', array('id' => 'notices'));
+ $nli = new NoticeListItem($this->notice);
+ $nli->show();
+ common_element_end('ul');
+
+ common_show_footer();
+ }
+
+ function show_header() {
+
+ $user = User::staticGet($this->profile->id);
+
+ if (!$user) {
+ return;
+ }
+
+ if ($user->emailmicroid && $user->email && $this->notice->uri) {
+ common_element('meta', array('name' => 'microid',
+ 'content' => "mailto+http:sha1:" . sha1(sha1('mailto:' . $user->email) . sha1($this->notice->uri))));
+ }
+
+ if ($user->jabbermicroid && $user->jabber && $this->notice->uri) {
+ common_element('meta', array('name' => 'microid',
+ 'content' => "xmpp+http:sha1:" . sha1(sha1('xmpp:' . $user->jabber) . sha1($this->notice->uri))));
+ }
+ }
+
+ function show_top() {
+ $cur = common_current_user();
+ if ($cur && $cur->id == $this->profile->id) {
+ common_notice_form();
+ }
+ }
+
+ function no_such_notice() {
+ common_user_error(_('No such notice.'));
+ }
+}
diff --git a/actions/showstream.php b/actions/showstream.php
new file mode 100644
index 000000000..6d6225661
--- /dev/null
+++ b/actions/showstream.php
@@ -0,0 +1,450 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/stream.php');
+
+define('SUBSCRIPTIONS_PER_ROW', 4);
+define('SUBSCRIPTIONS', 80);
+
+class ShowstreamAction extends StreamAction {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $nickname_arg = $this->arg('nickname');
+ $nickname = common_canonical_nickname($nickname_arg);
+
+ # Permanent redirect on non-canonical nickname
+
+ if ($nickname_arg != $nickname) {
+ $args = array('nickname' => $nickname);
+ if ($this->arg('page') && $this->arg('page') != 1) {
+ $args['page'] = $this->arg['page'];
+ }
+ common_redirect(common_local_url('showstream', $args), 301);
+ return;
+ }
+
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->no_such_user();
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ # Looks like we're good; start output
+
+ # For YADIS discovery, we also have a <meta> tag
+
+ header('X-XRDS-Location: '. common_local_url('xrds', array('nickname' =>
+ $user->nickname)));
+
+ common_show_header($profile->nickname,
+ array($this, 'show_header'), $user,
+ array($this, 'show_top'));
+
+ $this->show_profile($profile);
+
+ $this->show_notices($user);
+
+ common_show_footer();
+ }
+
+ function show_top($user) {
+ $cur = common_current_user();
+
+ if ($cur && $cur->id == $user->id) {
+ common_notice_form('showstream');
+ }
+
+ $this->views_menu();
+
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('userrss', array('nickname' => $user->nickname)),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'notices'),
+ 1=>array('href'=>common_local_url('usertimeline', array('nickname' => $user->nickname)),
+ 'type' => 'atom',
+ 'version' => 'Atom 1.0',
+ 'item' => 'usertimeline'),
+
+ 2=>array('href'=>common_local_url('foaf',array('nickname' => $user->nickname)),
+ 'type' => 'rdf',
+ 'version' => 'FOAF',
+ 'item' => 'foaf')));
+ }
+
+ function show_header($user) {
+ # Feeds
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('api',
+ array('apiaction' => 'statuses',
+ 'method' => 'user_timeline.rss',
+ 'argument' => $user->nickname)),
+ 'type' => 'application/rss+xml',
+ 'title' => sprintf(_('Notice feed for %s'), $user->nickname)));
+ common_element('link', array('rel' => 'alternate feed',
+ 'href' => common_local_url('api',
+ array('apiaction' => 'statuses',
+ 'method' => 'user_timeline.atom',
+ 'argument' => $user->nickname)),
+ 'type' => 'application/atom+xml',
+ 'title' => sprintf(_('Notice feed for %s'), $user->nickname)));
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('userrss', array('nickname' =>
+ $user->nickname)),
+ 'type' => 'application/rdf+xml',
+ 'title' => sprintf(_('Notice feed for %s'), $user->nickname)));
+ # FOAF
+ common_element('link', array('rel' => 'meta',
+ 'href' => common_local_url('foaf', array('nickname' =>
+ $user->nickname)),
+ 'type' => 'application/rdf+xml',
+ 'title' => 'FOAF'));
+ # for remote subscriptions etc.
+ common_element('meta', array('http-equiv' => 'X-XRDS-Location',
+ 'content' => common_local_url('xrds', array('nickname' =>
+ $user->nickname))));
+ $profile = $user->getProfile();
+ if ($profile->bio) {
+ common_element('meta', array('name' => 'description',
+ 'content' => $profile->bio));
+ }
+
+ if ($user->emailmicroid && $user->email && $profile->profileurl) {
+ common_element('meta', array('name' => 'microid',
+ 'content' => "mailto+http:sha1:" . sha1(sha1('mailto:' . $user->email) . sha1($profile->profileurl))));
+ }
+ if ($user->jabbermicroid && $user->jabber && $profile->profileurl) {
+ common_element('meta', array('name' => 'microid',
+ 'content' => "xmpp+http:sha1:" . sha1(sha1('xmpp:' . $user->jabber) . sha1($profile->profileurl))));
+ }
+
+ # See https://wiki.mozilla.org/Microsummaries
+
+ common_element('link', array('rel' => 'microsummary',
+ 'href' => common_local_url('microsummary',
+ array('nickname' => $profile->nickname))));
+ }
+
+ function no_such_user() {
+ $this->client_error(_('No such user.'), 404);
+ }
+
+ function show_profile($profile) {
+
+ common_element_start('div', array('id' => 'profile', 'class' => 'vcard'));
+
+ $this->show_personal($profile);
+
+ $this->show_last_notice($profile);
+
+ $cur = common_current_user();
+
+ $this->show_subscriptions($profile);
+
+ common_element_end('div');
+ }
+
+ function show_personal($profile) {
+
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ common_element_start('div', array('id' => 'profile_avatar'));
+ common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_PROFILE_SIZE),
+ 'class' => 'avatar profile photo',
+ 'width' => AVATAR_PROFILE_SIZE,
+ 'height' => AVATAR_PROFILE_SIZE,
+ 'alt' => $profile->nickname));
+
+ common_element_start('ul', array('id' => 'profile_actions'));
+
+ common_element_start('li', array('id' => 'profile_subscribe'));
+ $cur = common_current_user();
+ if ($cur) {
+ if ($cur->id != $profile->id) {
+ if ($cur->isSubscribed($profile)) {
+ common_unsubscribe_form($profile);
+ } else {
+ common_subscribe_form($profile);
+ }
+ }
+ } else {
+ $this->show_remote_subscribe_link($profile);
+ }
+ common_element_end('li');
+
+ $user = User::staticGet('id', $profile->id);
+ common_profile_new_message_nudge($cur, $user, $profile);
+
+ if ($cur && $cur->id != $profile->id) {
+ $blocked = $cur->hasBlocked($profile);
+ common_element_start('li', array('id' => 'profile_block'));
+ if ($blocked) {
+ common_unblock_form($profile, array('action' => 'showstream',
+ 'nickname' => $profile->nickname));
+ } else {
+ common_block_form($profile, array('action' => 'showstream',
+ 'nickname' => $profile->nickname));
+ }
+ common_element_end('li');
+ }
+
+ common_element_end('ul');
+
+ common_element_end('div');
+
+ common_element_start('div', array('id' => 'profile_information'));
+
+ if ($profile->fullname) {
+ common_element('h1', array('class' => 'fn'), $profile->fullname . ' (' . $profile->nickname . ')');
+ } else {
+ common_element('h1', array('class' => 'fn nickname'), $profile->nickname);
+ }
+
+ if ($profile->location) {
+ common_element('p', 'location', $profile->location);
+ }
+ if ($profile->bio) {
+ common_element('p', 'description note', $profile->bio);
+ }
+ if ($profile->homepage) {
+ common_element_start('p', 'website');
+ common_element('a', array('href' => $profile->homepage,
+ 'rel' => 'me', 'class' => 'url'),
+ $profile->homepage);
+ common_element_end('p');
+ }
+
+ $this->show_statistics($profile);
+
+ common_element_end('div');
+ }
+
+ function show_remote_subscribe_link($profile) {
+ $url = common_local_url('remotesubscribe',
+ array('nickname' => $profile->nickname));
+ common_element('a', array('href' => $url,
+ 'id' => 'remotesubscribe'),
+ _('Subscribe'));
+ }
+
+ function show_unsubscribe_form($profile) {
+ common_element_start('form', array('id' => 'unsubscribe', 'method' => 'post',
+ 'action' => common_local_url('unsubscribe')));
+ common_hidden('token', common_session_token());
+ common_element('input', array('id' => 'unsubscribeto',
+ 'name' => 'unsubscribeto',
+ 'type' => 'hidden',
+ 'value' => $profile->nickname));
+ common_element('input', array('type' => 'submit',
+ 'class' => 'submit',
+ 'value' => _('Unsubscribe')));
+ common_element_end('form');
+ }
+
+ function show_subscriptions($profile) {
+ global $config;
+
+ $subs = DB_DataObject::factory('subscription');
+ $subs->subscriber = $profile->id;
+ $subs->whereAdd('subscribed != ' . $profile->id);
+
+ $subs->orderBy('created DESC');
+
+ # We ask for an extra one to know if we need to do another page
+
+ $subs->limit(0, SUBSCRIPTIONS + 1);
+
+ $subs_count = $subs->find();
+
+ common_element_start('div', array('id' => 'subscriptions'));
+
+ common_element('h2', NULL, _('Subscriptions'));
+
+ if ($subs_count > 0) {
+
+ common_element_start('ul', array('id' => 'subscriptions_avatars'));
+
+ for ($i = 0; $i < min($subs_count, SUBSCRIPTIONS); $i++) {
+
+ if (!$subs->fetch()) {
+ common_debug('Weirdly, broke out of subscriptions loop early', __FILE__);
+ break;
+ }
+
+ $other = Profile::staticGet($subs->subscribed);
+
+ if (!$other) {
+ common_log_db_error($subs, 'SELECT', __FILE__);
+ continue;
+ }
+
+ common_element_start('li', 'vcard');
+ common_element_start('a', array('title' => ($other->fullname) ?
+ $other->fullname :
+ $other->nickname,
+ 'href' => $other->profileurl,
+ 'rel' => 'contact',
+ 'class' => 'subscription fn url'));
+ $avatar = $other->getAvatar(AVATAR_MINI_SIZE);
+ common_element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar mini photo',
+ 'alt' => ($other->fullname) ?
+ $other->fullname :
+ $other->nickname));
+ common_element_end('a');
+ common_element_end('li');
+ }
+
+ common_element_end('ul');
+ }
+
+ if ($subs_count > SUBSCRIPTIONS) {
+ common_element_start('p', array('id' => 'subscriptions_viewall'));
+
+ common_element('a', array('href' => common_local_url('subscriptions',
+ array('nickname' => $profile->nickname)),
+ 'class' => 'moresubscriptions'),
+ _('All subscriptions'));
+ common_element_end('p');
+ }
+
+ common_element_end('div');
+ }
+
+ function show_statistics($profile) {
+
+ // XXX: WORM cache this
+ $subs = DB_DataObject::factory('subscription');
+ $subs->subscriber = $profile->id;
+ $subs_count = (int) $subs->count() - 1;
+
+ $subbed = DB_DataObject::factory('subscription');
+ $subbed->subscribed = $profile->id;
+ $subbed_count = (int) $subbed->count() - 1;
+
+ $notices = DB_DataObject::factory('notice');
+ $notices->profile_id = $profile->id;
+ $notice_count = (int) $notices->count();
+
+ common_element_start('div', 'statistics');
+ common_element('h2', 'statistics', _('Statistics'));
+
+ # Other stats...?
+ common_element_start('dl', 'statistics');
+ common_element('dt', 'membersince', _('Member since'));
+ common_element('dd', 'membersince', date('j M Y',
+ strtotime($profile->created)));
+
+ common_element_start('dt', 'subscriptions');
+ common_element('a', array('href' => common_local_url('subscriptions',
+ array('nickname' => $profile->nickname))),
+ _('Subscriptions'));
+ common_element_end('dt');
+ common_element('dd', 'subscriptions', (is_int($subs_count)) ? $subs_count : '0');
+ common_element_start('dt', 'subscribers');
+ common_element('a', array('href' => common_local_url('subscribers',
+ array('nickname' => $profile->nickname))),
+ _('Subscribers'));
+ common_element_end('dt');
+ common_element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
+ common_element('dt', 'notices', _('Notices'));
+ common_element('dd', 'notices', (is_int($notice_count)) ? $notice_count : '0');
+ # XXX: link these to something
+ common_element('dt', 'tags', _('Tags'));
+ common_element_start('dd', 'tags');
+ $tags = Profile_tag::getTags($profile->id, $profile->id);
+
+ common_element_start('ul', 'tags xoxo');
+ foreach ($tags as $tag) {
+ common_element_start('li');
+ common_element('a', array('rel' => 'bookmark tag',
+ 'href' => common_local_url('peopletag',
+ array('tag' => $tag))),
+ $tag);
+ common_element_end('li');
+ }
+ common_element_end('ul');
+ common_element_end('dd');
+
+ common_element_end('dl');
+
+ common_element_end('div');
+ }
+
+ function show_notices($user) {
+
+ $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ $notice = $user->getNotices(($page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
+ $pnl = new ProfileNoticeList($notice);
+ $cnt = $pnl->show();
+
+ common_pagination($page>1, $cnt>NOTICES_PER_PAGE, $page,
+ 'showstream', array('nickname' => $user->nickname));
+ }
+
+ function show_last_notice($profile) {
+
+ common_element('h2', NULL, _('Currently'));
+
+ $notice = $profile->getCurrentNotice();
+
+ if ($notice) {
+ # FIXME: URL, image, video, audio
+ common_element_start('p', array('class' => 'notice_current'));
+ if ($notice->rendered) {
+ common_raw($notice->rendered);
+ } else {
+ # XXX: may be some uncooked notices in the DB,
+ # we cook them right now. This can probably disappear in future
+ # versions (>> 0.4.x)
+ common_raw(common_render_content($notice->content, $notice));
+ }
+ common_element_end('p');
+ }
+ }
+}
+
+# We don't show the author for a profile, since we already know who it is!
+
+class ProfileNoticeList extends NoticeList {
+ function new_list_item($notice) {
+ return new ProfileNoticeListItem($notice);
+ }
+}
+
+class ProfileNoticeListItem extends NoticeListItem {
+ function show_author() {
+ return;
+ }
+}
diff --git a/actions/smssettings.php b/actions/smssettings.php
new file mode 100644
index 000000000..5db26730a
--- /dev/null
+++ b/actions/smssettings.php
@@ -0,0 +1,331 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+require_once(INSTALLDIR.'/actions/emailsettings.php');
+
+class SmssettingsAction extends EmailsettingsAction {
+
+ function get_instructions() {
+ return _('You can receive SMS messages through email from %%site.name%%.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $user = common_current_user();
+ $this->form_header(_('SMS Settings'), $msg, $success);
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'smssettings',
+ 'action' =>
+ common_local_url('smssettings')));
+ common_hidden('token', common_session_token());
+ common_element('h2', NULL, _('Address'));
+
+ if ($user->sms) {
+ common_element_start('p');
+ $carrier = $user->getCarrier();
+ common_element('span', 'address confirmed', $user->sms . ' (' . $carrier->name . ')');
+ common_element('span', 'input_instructions',
+ _('Current confirmed SMS-enabled phone number.'));
+ common_hidden('sms', $user->sms);
+ common_hidden('carrier', $user->carrier);
+ common_element_end('p');
+ common_submit('remove', _('Remove'));
+ } else {
+ $confirm = $this->get_confirmation();
+ if ($confirm) {
+ $carrier = Sms_carrier::staticGet($confirm->address_extra);
+ common_element_start('p');
+ common_element('span', 'address unconfirmed', $confirm->address . ' (' . $carrier->name . ')');
+ common_element('span', 'input_instructions',
+ _('Awaiting confirmation on this phone number.'));
+ common_hidden('sms', $confirm->address);
+ common_hidden('carrier', $confirm->address_extra);
+ common_element_end('p');
+ common_submit('cancel', _('Cancel'));
+ common_input('code', _('Confirmation code'), NULL,
+ _('Enter the code you received on your phone.'));
+ common_submit('confirm', _('Confirm'));
+ } else {
+ common_input('sms', _('SMS Phone number'),
+ ($this->arg('sms')) ? $this->arg('sms') : NULL,
+ _('Phone number, no punctuation or spaces, with area code'));
+ $this->carrier_select();
+ common_submit('add', _('Add'));
+ }
+ }
+
+ if ($user->sms) {
+ common_element('h2', NULL, _('Incoming email'));
+
+ if ($user->incomingemail) {
+ common_element_start('p');
+ common_element('span', 'address', $user->incomingemail);
+ common_element('span', 'input_instructions',
+ _('Send email to this address to post new notices.'));
+ common_element_end('p');
+ common_submit('removeincoming', _('Remove'));
+ }
+
+ common_element_start('p');
+ common_element('span', 'input_instructions',
+ _('Make a new email address for posting to; cancels the old one.'));
+ common_element_end('p');
+ common_submit('newincoming', _('New'));
+ }
+
+ common_element('h2', NULL, _('Preferences'));
+
+ common_checkbox('smsnotify',
+ _('Send me notices through SMS; I understand I may incur exorbitant charges from my carrier.'),
+ $user->smsnotify);
+
+ common_submit('save', _('Save'));
+
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function get_confirmation() {
+ $user = common_current_user();
+ $confirm = new Confirm_address();
+ $confirm->user_id = $user->id;
+ $confirm->address_type = 'sms';
+ if ($confirm->find(TRUE)) {
+ return $confirm;
+ } else {
+ return NULL;
+ }
+ }
+
+ function handle_post() {
+
+ # CSRF protection
+
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->save_preferences();
+ } else if ($this->arg('add')) {
+ $this->add_address();
+ } else if ($this->arg('cancel')) {
+ $this->cancel_confirmation();
+ } else if ($this->arg('remove')) {
+ $this->remove_address();
+ } else if ($this->arg('removeincoming')) {
+ $this->remove_incoming();
+ } else if ($this->arg('newincoming')) {
+ $this->new_incoming();
+ } else if ($this->arg('confirm')) {
+ $this->confirm_code();
+ } else {
+ $this->show_form(_('Unexpected form submission.'));
+ }
+ }
+
+ function save_preferences() {
+
+ $smsnotify = $this->boolean('smsnotify');
+
+ $user = common_current_user();
+
+ assert(!is_null($user)); # should already be checked
+
+ $user->query('BEGIN');
+
+ $original = clone($user);
+
+ $user->smsnotify = $smsnotify;
+
+ $result = $user->update($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+
+ $user->query('COMMIT');
+
+ $this->show_form(_('Preferences saved.'), true);
+ }
+
+ function add_address() {
+
+ $user = common_current_user();
+
+ $sms = $this->trimmed('sms');
+ $carrier_id = $this->trimmed('carrier');
+
+ # Some validation
+
+ if (!$sms) {
+ $this->show_form(_('No phone number.'));
+ return;
+ }
+
+ if (!$carrier_id) {
+ $this->show_form(_('No carrier selected.'));
+ return;
+ }
+
+ $sms = common_canonical_sms($sms);
+
+ if ($user->sms == $sms) {
+ $this->show_form(_('That is already your phone number.'));
+ return;
+ } else if ($this->sms_exists($sms)) {
+ $this->show_form(_('That phone number already belongs to another user.'));
+ return;
+ }
+
+ $confirm = new Confirm_address();
+ $confirm->address = $sms;
+ $confirm->address_extra = $carrier_id;
+ $confirm->address_type = 'sms';
+ $confirm->user_id = $user->id;
+ $confirm->code = common_confirmation_code(40);
+
+ $result = $confirm->insert();
+
+ if ($result === FALSE) {
+ common_log_db_error($confirm, 'INSERT', __FILE__);
+ common_server_error(_('Couldn\'t insert confirmation code.'));
+ return;
+ }
+
+ $carrier = Sms_carrier::staticGet($carrier_id);
+
+ mail_confirm_sms($confirm->code,
+ $user->nickname,
+ $carrier->toEmailAddress($sms));
+
+ $msg = _('A confirmation code was sent to the phone number you added. Check your inbox (and spam box!) for the code and instructions on how to use it.');
+
+ $this->show_form($msg, TRUE);
+ }
+
+ function cancel_confirmation() {
+
+ $sms = $this->trimmed('sms');
+ $carrier = $this->trimmed('carrier');
+
+ $confirm = $this->get_confirmation();
+
+ if (!$confirm) {
+ $this->show_form(_('No pending confirmation to cancel.'));
+ return;
+ }
+ if ($confirm->address != $sms) {
+ $this->show_form(_('That is the wrong confirmation number.'));
+ return;
+ }
+
+ $result = $confirm->delete();
+
+ if (!$result) {
+ common_log_db_error($confirm, 'DELETE', __FILE__);
+ $this->server_error(_('Couldn\'t delete email confirmation.'));
+ return;
+ }
+
+ $this->show_form(_('Confirmation cancelled.'), TRUE);
+ }
+
+ function remove_address() {
+
+ $user = common_current_user();
+ $sms = $this->arg('sms');
+ $carrier = $this->arg('carrier');
+
+ # Maybe an old tab open...?
+
+ if ($user->sms != $sms) {
+ $this->show_form(_('That is not your phone number.'));
+ return;
+ }
+
+ $user->query('BEGIN');
+ $original = clone($user);
+ $user->sms = NULL;
+ $user->carrier = NULL;
+ $user->smsemail = NULL;
+ $result = $user->updateKeys($original);
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t update user.'));
+ return;
+ }
+ $user->query('COMMIT');
+
+ $this->show_form(_('The address was removed.'), TRUE);
+ }
+
+ function sms_exists($sms) {
+ $user = common_current_user();
+ $other = User::staticGet('sms', $sms);
+ if (!$other) {
+ return false;
+ } else {
+ return $other->id != $user->id;
+ }
+ }
+
+ function carrier_select() {
+ $carrier = new Sms_carrier();
+ $cnt = $carrier->find();
+
+ common_element_start('p');
+ common_element('label', array('for' => 'carrier'));
+ common_element_start('select', array('name' => 'carrier',
+ 'id' => 'carrier'));
+ common_element('option', array('value' => 0),
+ _('Select a carrier'));
+ while ($carrier->fetch()) {
+ common_element('option', array('value' => $carrier->id),
+ $carrier->name);
+ }
+ common_element_end('select');
+ common_element_end('p');
+ common_element('span', 'input_instructions',
+ sprintf(_('Mobile carrier for your phone. '.
+ 'If you know a carrier that accepts ' .
+ 'SMS over email but isn\'t listed here, ' .
+ 'send email to let us know at %s.'),
+ common_config('site', 'email')));
+ }
+
+ function confirm_code() {
+
+ $code = $this->trimmed('code');
+
+ if (!$code) {
+ $this->show_form(_('No code entered'));
+ return;
+ }
+
+ common_redirect(common_local_url('confirmaddress',
+ array('code' => $code)));
+ }
+}
diff --git a/actions/subedit.php b/actions/subedit.php
new file mode 100644
index 000000000..e7505e3fe
--- /dev/null
+++ b/actions/subedit.php
@@ -0,0 +1,89 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class SubeditAction extends Action {
+
+ var $profile = NULL;
+
+ function prepare($args) {
+
+ parent::prepare($args);
+
+ if (!common_logged_in()) {
+ $this->client_error(_('Not logged in.'));
+ return false;
+ }
+
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $id = $this->trimmed('profile');
+
+ if (!$id) {
+ $this->client_error(_('No profile specified.'));
+ return false;
+ }
+
+ $this->profile = Profile::staticGet('id', $id);
+
+ if (!$this->profile) {
+ $this->client_error(_('No profile with that ID.'));
+ return false;
+ }
+
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $cur = common_current_user();
+
+ $sub = Subscription::pkeyGet(array('subscriber' => $cur->id,
+ 'subscribed' => $this->profile->id));
+
+ if (!$sub) {
+ $this->client_error(_('You are not subscribed to that profile.'));
+ return false;
+ }
+
+ $orig = clone($sub);
+
+ $sub->jabber = $this->boolean('jabber');
+ $sub->sms = $this->boolean('sms');
+
+ $result = $sub->update($orig);
+
+ if (!$result) {
+ common_log_db_error($sub, 'UPDATE', __FILE__);
+ $this->server_error(_('Could not save subscription.'));
+ return false;
+ }
+
+ common_redirect(common_local_url('subscriptions',
+ array('nickname' => $cur->nickname)));
+ }
+ }
+}
diff --git a/actions/subscribe.php b/actions/subscribe.php
new file mode 100644
index 000000000..64abda004
--- /dev/null
+++ b/actions/subscribe.php
@@ -0,0 +1,78 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class SubscribeAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
+ return;
+ }
+
+ # CSRF protection
+
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $other_id = $this->arg('subscribeto');
+
+ $other = User::staticGet('id', $other_id);
+
+ if (!$other) {
+ $this->client_error(_('Not a local user.'));
+ return;
+ }
+
+ $result = subs_subscribe_to($user, $other);
+
+ if($result != true) {
+ common_user_error($result);
+ return;
+ }
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Subscribed'));
+ common_element_end('head');
+ common_element_start('body');
+ common_unsubscribe_form($other->getProfile());
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ common_redirect(common_local_url('subscriptions', array('nickname' =>
+ $user->nickname)));
+ }
+ }
+}
diff --git a/actions/subscribers.php b/actions/subscribers.php
new file mode 100644
index 000000000..ae52526e1
--- /dev/null
+++ b/actions/subscribers.php
@@ -0,0 +1,61 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/gallery.php');
+
+class SubscribersAction extends GalleryAction {
+
+ function gallery_type() {
+ return _('Subscribers');
+ }
+
+ function get_instructions(&$profile) {
+ $user =& common_current_user();
+ if ($user && ($user->id == $profile->id)) {
+ return _('These are the people who listen to your notices.');
+ } else {
+ return sprintf(_('These are the people who listen to %s\'s notices.'), $profile->nickname);
+ }
+ }
+
+ function fields() {
+ return array('subscriber', 'subscribed');
+ }
+
+ function div_class() {
+ return 'subscribers';
+ }
+
+ function get_other(&$subs) {
+ return $subs->subscriber;
+ }
+
+ function profile_list_class() {
+ return 'SubscribersList';
+ }
+}
+
+class SubscribersList extends ProfileList {
+ function show_owner_controls($profile) {
+ common_block_form($profile, array('action' => 'subscribers',
+ 'nickname' => $this->owner->nickname));
+ }
+}
diff --git a/actions/subscriptions.php b/actions/subscriptions.php
new file mode 100644
index 000000000..f518a1f92
--- /dev/null
+++ b/actions/subscriptions.php
@@ -0,0 +1,78 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/gallery.php');
+
+class SubscriptionsAction extends GalleryAction {
+
+ function gallery_type() {
+ return _('Subscriptions');
+ }
+
+ function get_instructions(&$profile) {
+ $user =& common_current_user();
+ if ($user && ($user->id == $profile->id)) {
+ return _('These are the people whose notices you listen to.');
+ } else {
+ return sprintf(_('These are the people whose notices %s listens to.'), $profile->nickname);
+ }
+ }
+
+ function fields() {
+ return array('subscribed', 'subscriber');
+ }
+
+ function div_class() {
+ return 'subscriptions';
+ }
+
+ function get_other(&$subs) {
+ return $subs->subscribed;
+ }
+
+ function profile_list_class() {
+ return 'SubscriptionsList';
+ }
+}
+
+class SubscriptionsList extends ProfileList {
+
+ function show_owner_controls($profile) {
+
+ $sub = Subscription::pkeyGet(array('subscriber' => $this->owner->id,
+ 'subscribed' => $profile->id));
+ if (!$sub) {
+ return;
+ }
+
+ common_element_start('form', array('id' => 'subedit-' . $profile->id,
+ 'method' => 'post',
+ 'class' => 'subedit',
+ 'action' => common_local_url('subedit')));
+ common_hidden('token', common_session_token());
+ common_hidden('profile', $profile->id);
+ common_checkbox('jabber', _('Jabber'), $sub->jabber);
+ common_checkbox('sms', _('SMS'), $sub->sms);
+ common_submit('save', _('Save'));
+ common_element_end('form');
+ return;
+ }
+}
diff --git a/actions/sup.php b/actions/sup.php
new file mode 100644
index 000000000..887017b2a
--- /dev/null
+++ b/actions/sup.php
@@ -0,0 +1,81 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class SupAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $seconds = $this->trimmed('seconds');
+
+ if (!$seconds) {
+ $seconds = 15;
+ }
+
+ $updates = $this->get_updates($seconds);
+
+ header('Content-Type: application/json; charset=utf-8');
+
+ print json_encode(array('updated_time' => date('c'),
+ 'since_time' => date('c', time() - $seconds),
+ 'available_periods' => $this->available_periods(),
+ 'period' => $seconds,
+ 'updates' => $updates));
+ }
+
+ function available_periods() {
+ static $periods = array(86400, 43200, 21600, 7200,
+ 3600, 1800, 600, 300, 120,
+ 60, 30, 15);
+ $available = array();
+ foreach ($periods as $period) {
+ $available[$period] = common_local_url('sup',
+ array('seconds' => $period));
+ }
+
+ return $available;
+ }
+
+ function get_updates($seconds) {
+ $notice = new Notice();
+
+ # XXX: cache this. Depends on how big this protocol becomes;
+ # Re-doing this query every 15 seconds isn't the end of the world.
+
+ $notice->query('SELECT profile_id, max(id) AS max_id ' .
+ 'FROM notice ' .
+ 'WHERE created > (now() - ' . $seconds . ') ' .
+ 'GROUP BY profile_id');
+
+ $updates = array();
+
+ while ($notice->fetch()) {
+ $updates[] = array($notice->profile_id, $notice->max_id);
+ }
+
+ return $updates;
+ }
+
+ function is_readonly() {
+ return true;
+ }
+}
diff --git a/actions/tag.php b/actions/tag.php
new file mode 100644
index 000000000..ffb393ae8
--- /dev/null
+++ b/actions/tag.php
@@ -0,0 +1,165 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/actions/showstream.php');
+define('TAGS_PER_PAGE', 100);
+
+class TagAction extends StreamAction {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ # Looks like we're good; show the header
+
+ if (isset($args['tag']) && $args['tag']) {
+ $tag = $args['tag'];
+ common_show_header(sprintf(_("Notices tagged with %s"), $tag),
+ array($this, 'show_header'), $tag,
+ array($this, 'show_top'));
+ $this->show_notices($tag);
+ } else {
+ common_show_header(_("Tags"),
+ array($this, 'show_header'), '',
+ array($this, 'show_top'));
+ $this->show_tags();
+ }
+
+ common_show_footer();
+ }
+
+ function show_header($tag = false) {
+ if ($tag) {
+ common_element('link', array('rel' => 'alternate',
+ 'href' => common_local_url('tagrss', array('tag' => $tag)),
+ 'type' => 'application/rss+xml',
+ 'title' => sprintf(_('Feed for tag %s'), $tag)));
+ }
+ }
+
+ function get_instructions() {
+ return _('Showing most popular tags from the last week');
+ }
+
+ function show_top($tag = false) {
+ if (!$tag) {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ $this->public_views_menu();
+ }
+ else {
+ $this->show_feeds_list(array(0=>array('href'=>common_local_url('tagrss', array('tag' => $tag)),
+ 'type' => 'rss',
+ 'version' => 'RSS 1.0',
+ 'item' => 'tagrss')));
+ }
+ }
+
+ function show_tags()
+ {
+ # This should probably be cached rather than recalculated
+ $tags = DB_DataObject::factory('Notice_tag');
+
+ #Need to clear the selection and then only re-add the field
+ #we are grouping by, otherwise it's not a valid 'group by'
+ #even though MySQL seems to let it slide...
+ $tags->selectAdd();
+ $tags->selectAdd('tag');
+
+ #Add the aggregated columns...
+ $tags->selectAdd('max(notice_id) as last_notice_id');
+ if(common_config('db','type')=='pgsql') {
+ $calc='sum(exp(-extract(epoch from (now()-created))/%s)) as weight';
+ } else {
+ $calc='sum(exp(-(now() - created)/%s)) as weight';
+ }
+ $tags->selectAdd(sprintf($calc, common_config('tag', 'dropoff')));
+ $tags->groupBy('tag');
+ $tags->orderBy('weight DESC');
+
+ # $tags->whereAdd('created > "' . strftime('%Y-%m-%d %H:%M:%S', strtotime('-1 MONTH')) . '"');
+
+ $tags->limit(TAGS_PER_PAGE);
+
+ $cnt = $tags->find();
+
+ if ($cnt > 0) {
+ common_element_start('p', 'tagcloud');
+
+ $tw = array();
+ $sum = 0;
+ while ($tags->fetch()) {
+ $tw[$tags->tag] = $tags->weight;
+ $sum += $tags->weight;
+ }
+
+ ksort($tw);
+
+ foreach ($tw as $tag => $weight) {
+ $this->show_tag($tag, $weight, $weight/$sum);
+ }
+
+ common_element_end('p');
+ }
+ }
+
+ function show_tag($tag, $weight, $relative) {
+
+ # XXX: these should probably tune to the size of the site
+ if ($relative > 0.1) {
+ $cls = 'largest';
+ } else if ($relative > 0.05) {
+ $cls = 'verylarge';
+ } else if ($relative > 0.02) {
+ $cls = 'large';
+ } else if ($relative > 0.01) {
+ $cls = 'medium';
+ } else if ($relative > 0.005) {
+ $cls = 'small';
+ } else if ($relative > 0.002) {
+ $cls = 'verysmall';
+ } else {
+ $cls = 'smallest';
+ }
+
+ common_element('a', array('class' => "$cls weight-$weight relative-$relative",
+ 'href' => common_local_url('tag', array('tag' => $tag))),
+ $tag);
+ common_text(' ');
+ }
+
+ function show_notices($tag) {
+
+ $cnt = 0;
+
+ $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+ $notice = Notice_tag::getStream($tag, (($page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
+
+ $cnt = $this->show_notice_list($notice);
+
+ common_pagination($page > 1, $cnt > NOTICES_PER_PAGE,
+ $page, 'tag', array('tag' => $tag));
+ }
+}
diff --git a/actions/tagother.php b/actions/tagother.php
new file mode 100644
index 000000000..a4449dd69
--- /dev/null
+++ b/actions/tagother.php
@@ -0,0 +1,193 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+
+class TagotherAction extends Action {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ if (!common_logged_in()) {
+ $this->client_error(_('Not logged in'), 403);
+ return;
+ }
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->save_tags();
+ } else {
+ $id = $this->trimmed('id');
+ if (!$id) {
+ $this->client_error(_('No id argument.'));
+ return;
+ }
+ $profile = Profile::staticGet('id', $id);
+ if (!$profile) {
+ $this->client_error(_('No profile with that ID.'));
+ return;
+ }
+ $this->show_form($profile);
+ }
+ }
+
+ function show_form($profile, $error=NULL) {
+
+ $user = common_current_user();
+
+ common_show_header(_('Tag a person'),
+ NULL, array($profile, $error), array($this, 'show_top'));
+
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+
+ common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_PROFILE_SIZE),
+ 'class' => 'avatar stream',
+ 'width' => AVATAR_PROFILE_SIZE,
+ 'height' => AVATAR_PROFILE_SIZE,
+ 'alt' =>
+ ($profile->fullname) ? $profile->fullname :
+ $profile->nickname));
+
+ common_element('a', array('href' => $profile->profileurl,
+ 'class' => 'external profile nickname'),
+ $profile->nickname);
+
+ if ($profile->fullname) {
+ common_element_start('div', 'fullname');
+ if ($profile->homepage) {
+ common_element('a', array('href' => $profile->homepage),
+ $profile->fullname);
+ } else {
+ common_text($profile->fullname);
+ }
+ common_element_end('div');
+ }
+ if ($profile->location) {
+ common_element('div', 'location', $profile->location);
+ }
+ if ($profile->bio) {
+ common_element('div', 'bio', $profile->bio);
+ }
+
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'tag_user',
+ 'name' => 'tagother',
+ 'action' => $this->self_url()));
+ common_hidden('token', common_session_token());
+ common_hidden('id', $profile->id);
+ common_input('tags', _('Tags'),
+ ($this->arg('tags')) ? $this->arg('tags') : implode(' ', Profile_tag::getTags($user->id, $profile->id)),
+ _('Tags for this user (letters, numbers, -, ., and _), comma- or space- separated'));
+
+ common_submit('save', _('Save'));
+ common_element_end('form');
+ common_show_footer();
+
+ }
+
+ function save_tags() {
+
+ $id = $this->trimmed('id');
+ $tagstring = $this->trimmed('tags');
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $profile = Profile::staticGet('id', $id);
+
+ if (!$profile) {
+ $this->client_error(_('No such profile.'));
+ return;
+ }
+
+ if (is_string($tagstring) && strlen($tagstring) > 0) {
+
+ $tags = array_map('common_canonical_tag',
+ preg_split('/[\s,]+/', $tagstring));
+
+ foreach ($tags as $tag) {
+ if (!common_valid_profile_tag($tag)) {
+ $this->show_form($profile, sprintf(_('Invalid tag: "%s"'), $tag));
+ return;
+ }
+ }
+ } else {
+ $tags = array();
+ }
+
+ $user = common_current_user();
+
+ if (!Subscription::pkeyGet(array('subscriber' => $user->id,
+ 'subscribed' => $profile->id)) &&
+ !Subscription::pkeyGet(array('subscriber' => $profile->id,
+ 'subscribed' => $user->id)))
+ {
+ $this->client_error(_('You can only tag people you are subscribed to or who are subscribed to you.'));
+ return;
+ }
+
+ $result = Profile_tag::setTags($user->id, $profile->id, $tags);
+
+ if (!$result) {
+ $this->client_error(_('Could not save tags.'));
+ return;
+ }
+
+ $action = $user->isSubscribed($profile) ? 'subscriptions' : 'subscribers';
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml');
+ common_element_start('head');
+ common_element('title', null, _('Tags'));
+ common_element_end('head');
+ common_element_start('body');
+ common_element_start('p', 'subtags');
+ foreach ($tags as $tag) {
+ common_element('a', array('href' => common_local_url($action,
+ array('nickname' => $user->nickname,
+ 'tag' => $tag))),
+ $tag);
+ }
+ common_element_end('p');
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ common_redirect(common_local_url($action, array('nickname' =>
+ $user->nickname)));
+ }
+ }
+
+ function show_top($arr = NULL) {
+ list($profile, $error) = $arr;
+ if ($error) {
+ common_element('p', 'error', $error);
+ } else {
+ common_element_start('div', 'instructions');
+ common_element('p', NULL,
+ _('Use this form to add tags to your subscribers or subscriptions.'));
+ common_element_end('div');
+ }
+ }
+}
+
diff --git a/actions/tagrss.php b/actions/tagrss.php
new file mode 100644
index 000000000..737ac113d
--- /dev/null
+++ b/actions/tagrss.php
@@ -0,0 +1,61 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class TagrssAction extends Rss10Action {
+
+ function init() {
+ $tag = $this->trimmed('tag');
+
+ if (!isset($tag) || mb_strlen($tag) == 0) {
+ common_user_error(_('No tag.'));
+ return false;
+ }
+
+ $this->tag = $tag;
+ return true;
+ }
+
+ function get_notices($limit=0) {
+ $tag = $this->tag;
+
+ $notice = Notice_tag::getStream($tag, 0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ $tag = $this->tag;
+
+ $c = array('url' => common_local_url('tagrss', array('tag' => $tag)),
+ 'title' => $tag,
+ 'link' => common_local_url('tagrss', array('tag' => $tag)),
+ 'description' => sprintf(_('Microblog tagged with %s'), $tag));
+ return $c;
+ }
+}
diff --git a/actions/twitapiaccount.php b/actions/twitapiaccount.php
new file mode 100644
index 000000000..c1960561e
--- /dev/null
+++ b/actions/twitapiaccount.php
@@ -0,0 +1,99 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapiaccountAction extends TwitterapiAction {
+
+ function verify_credentials($args, $apidata) {
+
+ if ($apidata['content-type'] == 'xml') {
+ header('Content-Type: application/xml; charset=utf-8');
+ print '<authorized>true</authorized>';
+ } elseif ($apidata['content-type'] == 'json') {
+ header('Content-Type: application/json; charset=utf-8');
+ print '{"authorized":true}';
+ } else {
+ common_user_error(_('API method not found!'), $code=404);
+ }
+
+ }
+
+ function end_session($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+ function update_location($args, $apidata) {
+ parent::handle($args);
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->client_error(_('This method requires a POST.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ $location = trim($this->arg('location'));
+
+ if (!is_null($location) && strlen($location) > 255) {
+
+ // XXX: But Twitter just truncates and runs with it. -- Zach
+ $this->client_error(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']);
+ return;
+ }
+
+ $user = $apidata['user'];
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ $orig_profile = clone($profile);
+ $profile->location = $location;
+
+ $result = $profile->update($orig_profile);
+
+ if (!$result) {
+ common_log_db_error($profile, 'UPDATE', __FILE__);
+ common_server_error(_('Couldn\'t save profile.'));
+ return;
+ }
+
+ common_broadcast_profile($profile);
+ $type = $apidata['content-type'];
+
+ $this->init_document($type);
+ $this->show_profile($profile, $type);
+ $this->end_document($type);
+ }
+
+
+ function update_delivery_device($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+ function rate_limit_status($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+} \ No newline at end of file
diff --git a/actions/twitapiblocks.php b/actions/twitapiblocks.php
new file mode 100644
index 000000000..4852ff938
--- /dev/null
+++ b/actions/twitapiblocks.php
@@ -0,0 +1,69 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapiblocksAction extends TwitterapiAction {
+
+ function create($args, $apidata) {
+
+ parent::handle($args);
+
+ $blockee = $this->get_user($apidata['api_arg'], $apidata);
+
+ if (!$blockee) {
+ $this->client_error('Not Found', 404, $apidata['content-type']);
+ return;
+ }
+
+ $user = $apidata['user'];
+
+ if ($user->hasBlocked($blockee) || $user->block($blockee)) {
+ $type = $apidata['content-type'];
+ $this->init_document($type);
+ $this->show_profile($blockee, $type);
+ $this->end_document($type);
+ } else {
+ common_server_error(_('Block user failed.'));
+ }
+ }
+
+ function destroy($args, $apidata) {
+ parent::handle($args);
+ $blockee = $this->get_user($apidata['api_arg'], $apidata);
+
+ if (!$blockee) {
+ $this->client_error('Not Found', 404, $apidata['content-type']);
+ return;
+ }
+
+ $user = $apidata['user'];
+
+ if (!$user->hasBlocked($blockee) || $user->unblock($blockee)) {
+ $type = $apidata['content-type'];
+ $this->init_document($type);
+ $this->show_profile($blockee, $type);
+ $this->end_document($type);
+ } else {
+ common_server_error(_('Unblock user failed.'));
+ }
+ }
+} \ No newline at end of file
diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php
new file mode 100644
index 000000000..535795ca4
--- /dev/null
+++ b/actions/twitapidirect_messages.php
@@ -0,0 +1,287 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class Twitapidirect_messagesAction extends TwitterapiAction {
+
+ function direct_messages($args, $apidata) {
+ parent::handle($args);
+ return $this->show_messages($args, $apidata, 'received');
+ }
+
+ function sent($args, $apidata) {
+ parent::handle($args);
+ return $this->show_messages($args, $apidata, 'sent');
+ }
+
+ function show_messages($args, $apidata, $type) {
+
+ $user = $apidata['user'];
+
+ $count = $this->arg('count');
+ $since = $this->arg('since');
+ $since_id = $this->arg('since_id');
+ $before_id = $this->arg('before_id');
+
+ $page = $this->arg('page');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ if (!$count) {
+ $count = 20;
+ }
+
+ $message = new Message();
+
+ $title = null;
+ $subtitle = null;
+ $link = null;
+ $server = common_root_url();
+
+ if ($type == 'received') {
+ $message->to_profile = $user->id;
+ $title = sprintf(_("Direct messages to %s"), $user->nickname);
+ $subtitle = sprintf(_("All the direct messages sent to %s"), $user->nickname);
+ $link = $server . $user->nickname . '/inbox';
+ } else {
+ $message->from_profile = $user->id;
+ $title = _('Direct Messages You\'ve Sent');
+ $subtitle = sprintf(_("All the direct messages sent from %s"), $user->nickname);
+ $link = $server . $user->nickname . '/outbox';
+ }
+
+ if ($before_id) {
+ $message->whereAdd("id < $before_id");
+ }
+
+ if ($since_id) {
+ $message->whereAdd("id > $since_id");
+ }
+
+ $since = strtotime($this->arg('since'));
+
+ if ($since) {
+ $d = date('Y-m-d H:i:s', $since);
+ $message->whereAdd("created > '$d'");
+ }
+
+ $message->orderBy('created DESC, id DESC');
+ $message->limit((($page-1)*20), $count);
+ $message->find();
+
+ switch($apidata['content-type']) {
+ case 'xml':
+ $this->show_xml_dmsgs($message);
+ break;
+ case 'rss':
+ $this->show_rss_dmsgs($message, $title, $link, $subtitle);
+ break;
+ case 'atom':
+ $this->show_atom_dmsgs($message, $title, $link, $subtitle);
+ break;
+ case 'json':
+ $this->show_json_dmsgs($message);
+ break;
+ default:
+ common_user_error(_('API method not found!'), $code = 404);
+ }
+
+ }
+
+ // had to change this from "new" to "create" to avoid PHP reserved word
+ function create($args, $apidata) {
+ parent::handle($args);
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->client_error(_('This method requires a POST.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ $user = $apidata['user'];
+ $source = $this->trimmed('source'); // Not supported by Twitter.
+
+ $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
+ if (!$source || in_array($source, $reserved_sources)) {
+ $source = 'api';
+ }
+
+ $content = $this->trimmed('text');
+
+ if (!$content) {
+ $this->client_error(_('No message text!'), $code = 406, $apidata['content-type']);
+ } else {
+ $content_shortened = common_shorten_links($content);
+ if (mb_strlen($content_shortened) > 140) {
+ $this->client_error(_('That\'s too long. Max message size is 140 chars.'),
+ $code = 406, $apidata['content-type']);
+ return;
+ }
+ }
+
+ $other = $this->get_user($this->trimmed('user'));
+
+ if (!$other) {
+ $this->client_error(_('Recipient user not found.'), $code = 403, $apidata['content-type']);
+ return;
+ } else if (!$user->mutuallySubscribed($other)) {
+ $this->client_error(_('Can\'t send direct messages to users who aren\'t your friend.'),
+ $code = 403, $apidata['content-type']);
+ return;
+ } else if ($user->id == $other->id) {
+ // Sending msgs to yourself is allowed by Twitter
+ $this->client_error(_('Don\'t send a message to yourself; just say it to yourself quietly instead.'),
+ $code = 403, $apidata['content-type']);
+ return;
+ }
+
+ $message = Message::saveNew($user->id, $other->id,
+ html_entity_decode($content, ENT_NOQUOTES, 'UTF-8'), $source);
+
+ if (is_string($message)) {
+ $this->server_error($message);
+ return;
+ }
+
+ $this->notify($user, $other, $message);
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->show_single_xml_dmsg($message);
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->show_single_json_dmsg($message);
+ }
+
+ }
+
+ function destroy($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+ function show_xml_dmsgs($message) {
+
+ $this->init_document('xml');
+ common_element_start('direct-messages', array('type' => 'array'));
+
+ if (is_array($messages)) {
+ foreach ($message as $m) {
+ $twitter_dm = $this->twitter_dmsg_array($m);
+ $this->show_twitter_xml_dmsg($twitter_dm);
+ }
+ } else {
+ while ($message->fetch()) {
+ $twitter_dm = $this->twitter_dmsg_array($message);
+ $this->show_twitter_xml_dmsg($twitter_dm);
+ }
+ }
+
+ common_element_end('direct-messages');
+ $this->end_document('xml');
+
+ }
+
+ function show_json_dmsgs($message) {
+
+ $this->init_document('json');
+
+ $dmsgs = array();
+
+ if (is_array($message)) {
+ foreach ($message as $m) {
+ $twitter_dm = $this->twitter_dmsg_array($m);
+ array_push($dmsgs, $twitter_dm);
+ }
+ } else {
+ while ($message->fetch()) {
+ $twitter_dm = $this->twitter_dmsg_array($message);
+ array_push($dmsgs, $twitter_dm);
+ }
+ }
+
+ $this->show_json_objects($dmsgs);
+ $this->end_document('json');
+
+ }
+
+ function show_rss_dmsgs($message, $title, $link, $subtitle) {
+
+ $this->init_document('rss');
+
+ common_element_start('channel');
+ common_element('title', NULL, $title);
+
+ common_element('link', NULL, $link);
+ common_element('description', NULL, $subtitle);
+ common_element('language', NULL, 'en-us');
+ common_element('ttl', NULL, '40');
+
+ if (is_array($message)) {
+ foreach ($message as $m) {
+ $entry = $this->twitter_rss_dmsg_array($m);
+ $this->show_twitter_rss_item($entry);
+ }
+ } else {
+ while ($message->fetch()) {
+ $entry = $this->twitter_rss_dmsg_array($message);
+ $this->show_twitter_rss_item($entry);
+ }
+ }
+
+ common_element_end('channel');
+ $this->end_twitter_rss();
+
+ }
+
+ function show_atom_dmsgs($message, $title, $link, $subtitle) {
+
+ $this->init_document('atom');
+
+ common_element('title', NULL, $title);
+ $siteserver = common_config('site', 'server');
+ common_element('id', NULL, "tag:$siteserver,2008:DirectMessage");
+ common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), NULL);
+ common_element('updated', NULL, common_date_iso8601(strftime('%c')));
+ common_element('subtitle', NULL, $subtitle);
+
+ if (is_array($message)) {
+ foreach ($message as $m) {
+ $entry = $this->twitter_rss_dmsg_array($m);
+ $this->show_twitter_atom_entry($entry);
+ }
+ } else {
+ while ($message->fetch()) {
+ $entry = $this->twitter_rss_dmsg_array($message);
+ $this->show_twitter_atom_entry($entry);
+ }
+ }
+
+ $this->end_document('atom');
+ }
+
+ // swiped from MessageAction. Should it be place in util.php?
+ function notify($from, $to, $message) {
+ mail_notify_message($message, $from, $to);
+ # XXX: Jabber, SMS notifications... probably queued
+ }
+
+}
diff --git a/actions/twitapifavorites.php b/actions/twitapifavorites.php
new file mode 100644
index 000000000..3eaff327a
--- /dev/null
+++ b/actions/twitapifavorites.php
@@ -0,0 +1,175 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapifavoritesAction extends TwitterapiAction {
+
+ function favorites($args, $apidata) {
+ parent::handle($args);
+
+ $this->auth_user = $apidata['user'];
+ $user = $this->get_user($apidata['api_arg'], $apidata);
+
+ if (!$user) {
+ $this->client_error('Not Found', 404, $apidata['content-type']);
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ $page = $this->arg('page');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ if (!$count) {
+ $count = 20;
+ }
+
+ $notice = $user->favoriteNotices((($page-1)*20), $count);
+
+ if (!$notice) {
+ common_server_error(_('Could not retrieve favorite notices.'));
+ return;
+ }
+
+ $sitename = common_config('site', 'name');
+ $siteserver = common_config('site', 'server');
+
+ $title = sprintf(_('%s / Favorites from %s'), $sitename, $user->nickname);
+ $id = "tag:$siteserver:favorites:".$user->id;
+ $link = common_local_url('favorites', array('nickname' => $user->nickname));
+ $subtitle = sprintf(_('%s updates favorited by %s / %s.'), $sitename, $profile->getBestName(), $user->nickname);
+
+ switch($apidata['content-type']) {
+ case 'xml':
+ $this->show_xml_timeline($notice);
+ break;
+ case 'rss':
+ $this->show_rss_timeline($notice, $title, $link, $subtitle);
+ break;
+ case 'atom':
+ $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
+ break;
+ case 'json':
+ $this->show_json_timeline($notice);
+ break;
+ default:
+ common_user_error(_('API method not found!'), $code = 404);
+ }
+
+ }
+
+ function create($args, $apidata) {
+ parent::handle($args);
+
+ // Check for RESTfulness
+ if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
+ // XXX: Twitter just prints the err msg, no XML / JSON.
+ $this->client_error(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ common_user_error(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ $this->auth_user = $apidata['user'];
+ $user = $this->auth_user;
+ $notice_id = $apidata['api_arg'];
+ $notice = Notice::staticGet($notice_id);
+
+ if (!$notice) {
+ $this->client_error(_('No status found with that ID.'), 404, $apidata['content-type']);
+ return;
+ }
+
+ // XXX: Twitter lets you fave things repeatedly via api.
+ if ($user->hasFave($notice)) {
+ $this->client_error(_('This notice is already a favorite!'), 403, $apidata['content-type']);
+ return;
+ }
+
+ $fave = Fave::addNew($user, $notice);
+
+ if (!$fave) {
+ common_server_error(_('Could not create favorite.'));
+ return;
+ }
+
+ $this->notify($fave, $notice, $user);
+ $user->blowFavesCache();
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->show_single_xml_status($notice);
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->show_single_json_status($notice);
+ }
+
+ }
+
+ function destroy($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+ // XXX: these two funcs swiped from faves. Maybe put in util.php, or some common base class?
+
+ function notify($fave, $notice, $user) {
+ $other = User::staticGet('id', $notice->profile_id);
+ if ($other && $other->id != $user->id) {
+ if ($other->email && $other->emailnotifyfav) {
+ $this->notify_mail($other, $user, $notice);
+ }
+ # XXX: notify by IM
+ # XXX: notify by SMS
+ }
+ }
+
+ function notify_mail($other, $user, $notice) {
+ $profile = $user->getProfile();
+ $bestname = $profile->getBestName();
+ $subject = sprintf(_('%s added your notice as a favorite'), $bestname);
+ $body = sprintf(_("%1\$s just added your notice from %2\$s as one of their favorites.\n\n" .
+ "In case you forgot, you can see the text of your notice here:\n\n" .
+ "%3\$s\n\n" .
+ "You can see the list of %1\$s's favorites here:\n\n" .
+ "%4\$s\n\n" .
+ "Faithfully yours,\n" .
+ "%5\$s\n"),
+ $bestname,
+ common_exact_date($notice->created),
+ common_local_url('shownotice', array('notice' => $notice->id)),
+ common_local_url('showfavorites', array('nickname' => $user->nickname)),
+ common_config('site', 'name'));
+
+ mail_to_user($other, $subject, $body);
+ }
+
+} \ No newline at end of file
diff --git a/actions/twitapifriendships.php b/actions/twitapifriendships.php
new file mode 100644
index 000000000..e4b49cbe4
--- /dev/null
+++ b/actions/twitapifriendships.php
@@ -0,0 +1,155 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapifriendshipsAction extends TwitterapiAction {
+
+ function create($args, $apidata) {
+ parent::handle($args);
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->client_error(_('This method requires a POST.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ $id = $apidata['api_arg'];
+
+ $other = $this->get_user($id);
+
+ if (!$other) {
+ $this->client_error(_('Could not follow user: User not found.'), 403, $apidata['content-type']);
+ return;
+ }
+
+ $user = $apidata['user'];
+
+ if ($user->isSubscribed($other)) {
+ $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), $other->nickname);
+ $this->client_error($errmsg, 403, $apidata['content-type']);
+ return;
+ }
+
+ $sub = new Subscription();
+
+ $sub->query('BEGIN');
+
+ $sub->subscriber = $user->id;
+ $sub->subscribed = $other->id;
+ $sub->created = DB_DataObject_Cast::dateTime(); # current time
+
+ $result = $sub->insert();
+
+ if (!$result) {
+ $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), $other->nickname);
+ $this->client_error($errmsg, 400, $apidata['content-type']);
+ return;
+ }
+
+ $sub->query('COMMIT');
+
+ mail_subscribe_notify($other, $user);
+
+ $type = $apidata['content-type'];
+ $this->init_document($type);
+ $this->show_profile($other, $type);
+ $this->end_document($type);
+
+ }
+
+ function destroy($args, $apidata) {
+ parent::handle($args);
+
+ if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
+ $this->client_error(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ $id = $apidata['api_arg'];
+
+ # We can't subscribe to a remote person, but we can unsub
+
+ $other = $this->get_profile($id);
+ $user = $apidata['user'];
+
+ $sub = new Subscription();
+ $sub->subscriber = $user->id;
+ $sub->subscribed = $other->id;
+
+ if ($sub->find(TRUE)) {
+ $sub->query('BEGIN');
+ $sub->delete();
+ $sub->query('COMMIT');
+ } else {
+ $this->client_error(_('You are not friends with the specified user.'), 403, $apidata['content-type']);
+ return;
+ }
+
+ $type = $apidata['content-type'];
+ $this->init_document($type);
+ $this->show_profile($other, $type);
+ $this->end_document($type);
+
+ }
+
+ function exists($args, $apidata) {
+ parent::handle($args);
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ common_user_error(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ $user_a_id = $this->trimmed('user_a');
+ $user_b_id = $this->trimmed('user_b');
+
+ $user_a = $this->get_user($user_a_id);
+ $user_b = $this->get_user($user_b_id);
+
+ if (!$user_a || !$user_b) {
+ $this->client_error(_('Two user ids or screen_names must be supplied.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ if ($user_a->isSubscribed($user_b)) {
+ $result = 'true';
+ } else {
+ $result = 'false';
+ }
+
+ switch ($apidata['content-type']) {
+ case 'xml':
+ $this->init_document('xml');
+ common_element('friends', NULL, $result);
+ $this->end_document('xml');
+ break;
+ case 'json':
+ $this->init_document('json');
+ print json_encode($result);
+ $this->end_document('json');
+ break;
+ default:
+ break;
+ }
+
+ }
+
+} \ No newline at end of file
diff --git a/actions/twitapihelp.php b/actions/twitapihelp.php
new file mode 100644
index 000000000..c5d503e11
--- /dev/null
+++ b/actions/twitapihelp.php
@@ -0,0 +1,52 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapihelpAction extends TwitterapiAction {
+
+ /* Returns the string "ok" in the requested format with a 200 OK HTTP status code.
+ * URL:http://identi.ca/api/help/test.format
+ * Formats: xml, json
+ */
+ function test($args, $apidata) {
+ parent::handle($args);
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->init_document('xml');
+ common_element('ok', NULL, 'true');
+ $this->end_document('xml');
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->init_document('json');
+ print '"ok"';
+ $this->end_document('json');
+ } else {
+ common_user_error(_('API method not found!'), $code=404);
+ }
+
+ }
+
+ function downtime_schedule($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+} \ No newline at end of file
diff --git a/actions/twitapinotifications.php b/actions/twitapinotifications.php
new file mode 100644
index 000000000..8d93309a2
--- /dev/null
+++ b/actions/twitapinotifications.php
@@ -0,0 +1,37 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+# This naming convention looks real sick
+class TwitapinotificationsAction extends TwitterapiAction {
+
+ function follow($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+ function leave($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+} \ No newline at end of file
diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php
new file mode 100644
index 000000000..7b6598b10
--- /dev/null
+++ b/actions/twitapistatuses.php
@@ -0,0 +1,563 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapistatusesAction extends TwitterapiAction {
+
+ function public_timeline($args, $apidata) {
+ parent::handle($args);
+
+ $sitename = common_config('site', 'name');
+ $siteserver = common_config('site', 'server');
+ $title = sprintf(_("%s public timeline"), $sitename);
+ $id = "tag:$siteserver:Statuses";
+ $link = common_root_url();
+ $subtitle = sprintf(_("%s updates from everyone!"), $sitename);
+
+ // Number of public statuses to return by default -- Twitter sends 20
+ $MAX_PUBSTATUSES = 20;
+
+ // FIXME: To really live up to the spec we need to build a list
+ // of notices by users who have custom avatars, so fix this SQL -- Zach
+
+ $page = $this->arg('page');
+ $since_id = $this->arg('since_id');
+ $before_id = $this->arg('before_id');
+
+ // NOTE: page, since_id, and before_id are extensions to Twitter API -- TB
+ if (!$page) {
+ $page = 1;
+ }
+ if (!$since_id) {
+ $since_id = 0;
+ }
+ if (!$before_id) {
+ $before_id = 0;
+ }
+
+ $since = strtotime($this->arg('since'));
+
+ $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $before_id, $since);
+
+ if ($notice) {
+
+ switch($apidata['content-type']) {
+ case 'xml':
+ $this->show_xml_timeline($notice);
+ break;
+ case 'rss':
+ $this->show_rss_timeline($notice, $title, $link, $subtitle);
+ break;
+ case 'atom':
+ $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
+ break;
+ case 'json':
+ $this->show_json_timeline($notice);
+ break;
+ default:
+ common_user_error(_('API method not found!'), $code = 404);
+ break;
+ }
+
+ } else {
+ common_server_error(_('Couldn\'t find any statuses.'), $code = 503);
+ }
+
+ }
+
+ function friends_timeline($args, $apidata) {
+ parent::handle($args);
+
+ $since = $this->arg('since');
+ $since_id = $this->arg('since_id');
+ $count = $this->arg('count');
+ $page = $this->arg('page');
+ $before_id = $this->arg('before_id');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ if (!$count) {
+ $count = 20;
+ }
+
+ if (!$since_id) {
+ $since_id = 0;
+ }
+
+ // NOTE: before_id is an extension to Twitter API -- TB
+ if (!$before_id) {
+ $before_id = 0;
+ }
+
+ $since = strtotime($this->arg('since'));
+
+ $user = $this->get_user(NULL, $apidata);
+ $this->auth_user = $user;
+
+ $profile = $user->getProfile();
+
+ $sitename = common_config('site', 'name');
+ $siteserver = common_config('site', 'server');
+
+ $title = sprintf(_("%s and friends"), $user->nickname);
+ $id = "tag:$siteserver:friends:" . $user->id;
+ $link = common_local_url('all', array('nickname' => $user->nickname));
+ $subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), $user->nickname, $sitename);
+
+ $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $before_id, $since);
+
+ switch($apidata['content-type']) {
+ case 'xml':
+ $this->show_xml_timeline($notice);
+ break;
+ case 'rss':
+ $this->show_rss_timeline($notice, $title, $link, $subtitle);
+ break;
+ case 'atom':
+ $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
+ break;
+ case 'json':
+ $this->show_json_timeline($notice);
+ break;
+ default:
+ common_user_error(_('API method not found!'), $code = 404);
+ }
+
+ }
+
+ function user_timeline($args, $apidata) {
+ parent::handle($args);
+
+ $this->auth_user = $apidata['user'];
+ $user = $this->get_user($apidata['api_arg'], $apidata);
+
+ if (!$user) {
+ $this->client_error('Not Found', 404, $apidata['content-type']);
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ $count = $this->arg('count');
+ $since = $this->arg('since');
+ $since_id = $this->arg('since_id');
+ $page = $this->arg('page');
+ $before_id = $this->arg('before_id');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ if (!$count) {
+ $count = 20;
+ }
+
+ if (!$since_id) {
+ $since_id = 0;
+ }
+
+ // NOTE: before_id is an extensions to Twitter API -- TB
+ if (!$before_id) {
+ $before_id = 0;
+ }
+
+ $since = strtotime($this->arg('since'));
+
+ $sitename = common_config('site', 'name');
+ $siteserver = common_config('site', 'server');
+
+ $title = sprintf(_("%s timeline"), $user->nickname);
+ $id = "tag:$siteserver:user:".$user->id;
+ $link = common_local_url('showstream', array('nickname' => $user->nickname));
+ $subtitle = sprintf(_('Updates from %1$s on %2$s!'), $user->nickname, $sitename);
+
+ # FriendFeed's SUP protocol
+ # Also added RSS and Atom feeds
+
+ $suplink = common_local_url('sup', NULL, $user->id);
+ header('X-SUP-ID: '.$suplink);
+
+ # XXX: since
+
+ $notice = $user->getNotices((($page-1)*20), $count, $since_id, $before_id, $since);
+
+ switch($apidata['content-type']) {
+ case 'xml':
+ $this->show_xml_timeline($notice);
+ break;
+ case 'rss':
+ $this->show_rss_timeline($notice, $title, $link, $subtitle, $suplink);
+ break;
+ case 'atom':
+ $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, $suplink);
+ break;
+ case 'json':
+ $this->show_json_timeline($notice);
+ break;
+ default:
+ common_user_error(_('API method not found!'), $code = 404);
+ }
+
+ }
+
+ function update($args, $apidata) {
+
+ parent::handle($args);
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ common_user_error(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ $this->client_error(_('This method requires a POST.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ $this->auth_user = $apidata['user'];
+ $user = $this->auth_user;
+ $status = $this->trimmed('status');
+ $source = $this->trimmed('source');
+ $in_reply_to_status_id = intval($this->trimmed('in_reply_to_status_id'));
+ $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
+ if (!$source || in_array($source, $reserved_sources)) {
+ $source = 'api';
+ }
+
+ if (!$status) {
+
+ // XXX: Note: In this case, Twitter simply returns '200 OK'
+ // No error is given, but the status is not posted to the
+ // user's timeline. Seems bad. Shouldn't we throw an
+ // errror? -- Zach
+ return;
+
+ } else {
+
+ $status_shortened = common_shorten_links($status);
+
+ if (mb_strlen($status_shortened) > 140) {
+
+ // XXX: Twitter truncates anything over 140, flags the status
+ // as "truncated." Sending this error may screw up some clients
+ // that assume Twitter will truncate for them. Should we just
+ // truncate too? -- Zach
+ $this->client_error(_('That\'s too long. Max notice size is 140 chars.'), $code = 406, $apidata['content-type']);
+ return;
+
+ }
+ }
+
+ // Check for commands
+ $inter = new CommandInterpreter();
+ $cmd = $inter->handle_command($user, $status_shortened);
+
+ if ($cmd) {
+
+ if ($this->supported($cmd)) {
+ $cmd->execute(new Channel());
+ }
+
+ // cmd not supported? Twitter just returns your latest status.
+ // And, it returns your last status whether the cmd was successful
+ // or not!
+ $n = $user->getCurrentNotice();
+ $apidata['api_arg'] = $n->id;
+ } else {
+
+ $reply_to = NULL;
+
+ if ($in_reply_to_status_id) {
+
+ // check whether notice actually exists
+ $reply = Notice::staticGet($in_reply_to_status_id);
+
+ if ($reply) {
+ $reply_to = $in_reply_to_status_id;
+ } else {
+ $this->client_error(_('Not found'), $code = 404, $apidata['content-type']);
+ return;
+ }
+ }
+
+ $notice = Notice::saveNew($user->id, html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'),
+ $source, 1, $reply_to);
+
+ if (is_string($notice)) {
+ $this->server_error($notice);
+ return;
+ }
+
+ common_broadcast_notice($notice);
+ $apidata['api_arg'] = $notice->id;
+ }
+
+ $this->show($args, $apidata);
+ }
+
+ function replies($args, $apidata) {
+
+ parent::handle($args);
+
+ $since = $this->arg('since');
+ $count = $this->arg('count');
+ $page = $this->arg('page');
+ $since_id = $this->arg('since_id');
+ $before_id = $this->arg('before_id');
+
+ $this->auth_user = $apidata['user'];
+ $user = $this->auth_user;
+ $profile = $user->getProfile();
+
+ $sitename = common_config('site', 'name');
+ $siteserver = common_config('site', 'server');
+
+ $title = sprintf(_('%1$s / Updates replying to %2$s'), $sitename, $user->nickname);
+ $id = "tag:$siteserver:replies:".$user->id;
+ $link = common_local_url('replies', array('nickname' => $user->nickname));
+ $subtitle = sprintf(_('%1$s updates that reply to updates from %2$s / %3$s.'), $sitename, $user->nickname, $profile->getBestName());
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ if (!$count) {
+ $count = 20;
+ }
+
+ if (!$since_id) {
+ $since_id = 0;
+ }
+
+ // NOTE: before_id is an extension to Twitter API -- TB
+ if (!$before_id) {
+ $before_id = 0;
+ }
+
+ $since = strtotime($this->arg('since'));
+
+ $notice = $user->getReplies((($page-1)*20), $count, $since_id, $before_id, $since);
+ $notices = array();
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ switch($apidata['content-type']) {
+ case 'xml':
+ $this->show_xml_timeline($notices);
+ break;
+ case 'rss':
+ $this->show_rss_timeline($notices, $title, $link, $subtitle);
+ break;
+ case 'atom':
+ $this->show_atom_timeline($notices, $title, $id, $link, $subtitle);
+ break;
+ case 'json':
+ $this->show_json_timeline($notices);
+ break;
+ default:
+ common_user_error(_('API method not found!'), $code = 404);
+ }
+
+ }
+
+ function show($args, $apidata) {
+ parent::handle($args);
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ common_user_error(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ $this->auth_user = $apidata['user'];
+ $notice_id = $apidata['api_arg'];
+ $notice = Notice::staticGet($notice_id);
+
+ if ($notice) {
+ if ($apidata['content-type'] == 'xml') {
+ $this->show_single_xml_status($notice);
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->show_single_json_status($notice);
+ }
+ } else {
+ // XXX: Twitter just sets a 404 header and doens't bother to return an err msg
+ $this->client_error(_('No status with that ID found.'), 404, $apidata['content-type']);
+ }
+
+ }
+
+ function destroy($args, $apidata) {
+
+ parent::handle($args);
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ common_user_error(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ // Check for RESTfulness
+ if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
+ // XXX: Twitter just prints the err msg, no XML / JSON.
+ $this->client_error(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
+ return;
+ }
+
+ $this->auth_user = $apidata['user'];
+ $user = $this->auth_user;
+ $notice_id = $apidata['api_arg'];
+ $notice = Notice::staticGet($notice_id);
+
+ if (!$notice) {
+ $this->client_error(_('No status found with that ID.'), 404, $apidata['content-type']);
+ return;
+ }
+
+ if ($user->id == $notice->profile_id) {
+ $replies = new Reply;
+ $replies->get('notice_id', $notice_id);
+ common_dequeue_notice($notice);
+ $replies->delete();
+ $notice->delete();
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->show_single_xml_status($notice);
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->show_single_json_status($notice);
+ }
+ } else {
+ $this->client_error(_('You may not delete another user\'s status.'), 403, $apidata['content-type']);
+ }
+
+ }
+
+ function friends($args, $apidata) {
+ parent::handle($args);
+ return $this->subscriptions($apidata, 'subscribed', 'subscriber');
+ }
+
+ function followers($args, $apidata) {
+ parent::handle($args);
+
+ return $this->subscriptions($apidata, 'subscriber', 'subscribed');
+ }
+
+ function subscriptions($apidata, $other_attr, $user_attr) {
+
+ # XXX: lite
+
+ $this->auth_user = $apidate['user'];
+ $user = $this->get_user($apidata['api_arg'], $apidata);
+
+ if (!$user) {
+ $this->client_error('Not Found', 404, $apidata['content-type']);
+ return;
+ }
+
+ $page = $this->trimmed('page');
+
+ if (!$page || !is_numeric($page)) {
+ $page = 1;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ $sub = new Subscription();
+ $sub->$user_attr = $profile->id;
+
+ $since = strtotime($this->trimmed('since'));
+
+ if ($since) {
+ $d = date('Y-m-d H:i:s', $since);
+ $sub->whereAdd("created > '$d'");
+ }
+
+ $sub->orderBy('created DESC');
+ $sub->limit(($page-1)*100, 100);
+
+ $others = array();
+
+ if ($sub->find()) {
+ while ($sub->fetch()) {
+ $others[] = Profile::staticGet($sub->$other_attr);
+ }
+ } else {
+ // user has no followers
+ }
+
+ $type = $apidata['content-type'];
+
+ $this->init_document($type);
+ $this->show_profiles($others, $type);
+ $this->end_document($type);
+ }
+
+ function show_profiles($profiles, $type) {
+ switch ($type) {
+ case 'xml':
+ common_element_start('users', array('type' => 'array'));
+ foreach ($profiles as $profile) {
+ $this->show_profile($profile);
+ }
+ common_element_end('users');
+ break;
+ case 'json':
+ $arrays = array();
+ foreach ($profiles as $profile) {
+ $arrays[] = $this->twitter_user_array($profile, true);
+ }
+ print json_encode($arrays);
+ break;
+ default:
+ $this->client_error(_('unsupported file type'));
+ }
+ }
+
+ function featured($args, $apidata) {
+ parent::handle($args);
+ common_server_error(_('API method under construction.'), $code=501);
+ }
+
+ function supported($cmd) {
+
+ $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', 'FavCommand', 'OnCommand', 'OffCommand');
+
+ if (in_array(get_class($cmd), $cmdlist)) {
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php
new file mode 100644
index 000000000..337ec91d1
--- /dev/null
+++ b/actions/twitapiusers.php
@@ -0,0 +1,118 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/twitterapi.php');
+
+class TwitapiusersAction extends TwitterapiAction {
+
+ function show($args, $apidata) {
+ parent::handle($args);
+
+ if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+ common_user_error(_('API method not found!'), $code = 404);
+ return;
+ }
+
+ $this->auth_user = $apidata['user'];
+ $user = null;
+ $email = $this->arg('email');
+
+ if ($email) {
+ $user = User::staticGet('email', $email);
+ } elseif (isset($apidata['api_arg'])) {
+ $user = $this->get_user($apidata['api_arg']);
+ }
+
+ if (!$user) {
+ // XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
+ $this->client_error(_('Not found.'), 404, $apidata['content-type']);
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ common_server_error(_('User has no profile.'));
+ return;
+ }
+
+ $twitter_user = $this->twitter_user_array($profile, true);
+
+ // Add in extended user fields offered up by this method
+ $twitter_user['created_at'] = $this->date_twitter($profile->created);
+
+ $subbed = DB_DataObject::factory('subscription');
+ $subbed->subscriber = $profile->id;
+ $subbed_count = (int) $subbed->count() - 1;
+
+ $notices = DB_DataObject::factory('notice');
+ $notices->profile_id = $profile->id;
+ $notice_count = (int) $notices->count();
+
+ $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
+ $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
+
+ // Other fields Twitter sends...
+ $twitter_user['profile_background_color'] = '';
+ $twitter_user['profile_text_color'] = '';
+ $twitter_user['profile_link_color'] = '';
+ $twitter_user['profile_sidebar_fill_color'] = '';
+
+ $faves = DB_DataObject::factory('fave');
+ $faves->user_id = $user->id;
+ $faves_count = (int) $faves->count();
+ $twitter_user['favourites_count'] = $faves_count;
+
+ $timezone = 'UTC';
+
+ if ($user->timezone) {
+ $timezone = $user->timezone;
+ }
+
+ $t = new DateTime;
+ $t->setTimezone(new DateTimeZone($timezone));
+ $twitter_user['utc_offset'] = $t->format('Z');
+ $twitter_user['time_zone'] = $timezone;
+
+ if (isset($this->auth_user)) {
+
+ if ($this->auth_user->isSubscribed($profile)) {
+ $twitter_user['following'] = 'true';
+ } else {
+ $twitter_user['following'] = 'false';
+ }
+
+ // Not implemented yet
+ $twitter_user['notifications'] = 'false';
+ }
+
+ if ($apidata['content-type'] == 'xml') {
+ $this->init_document('xml');
+ $this->show_twitter_xml_user($twitter_user);
+ $this->end_document('xml');
+ } elseif ($apidata['content-type'] == 'json') {
+ $this->init_document('json');
+ $this->show_json_objects($twitter_user);
+ $this->end_document('json');
+ }
+
+ }
+}
diff --git a/actions/twittersettings.php b/actions/twittersettings.php
new file mode 100644
index 000000000..ae3aff877
--- /dev/null
+++ b/actions/twittersettings.php
@@ -0,0 +1,378 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/settingsaction.php');
+
+define('SUBSCRIPTIONS', 80);
+
+class TwittersettingsAction extends SettingsAction {
+
+ function get_instructions() {
+ return _('Add your Twitter account to automatically send your notices to Twitter, ' .
+ 'and subscribe to Twitter friends already here.');
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ $user = common_current_user();
+ $profile = $user->getProfile();
+ $fuser = NULL;
+ $flink = Foreign_link::getByUserID($user->id, 1); // 1 == Twitter
+
+ if ($flink) {
+ $fuser = $flink->getForeignUser();
+ }
+
+ $this->form_header(_('Twitter settings'), $msg, $success);
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'twittersettings',
+ 'action' =>
+ common_local_url('twittersettings')));
+ common_hidden('token', common_session_token());
+
+ common_element('h2', NULL, _('Twitter Account'));
+
+ if ($fuser) {
+ common_element_start('p');
+
+ common_element('span', 'twitter_user', $fuser->nickname);
+ common_element('a', array('href' => $fuser->uri), $fuser->uri);
+ common_element('span', 'input_instructions',
+ _('Current verified Twitter account.'));
+ common_hidden('flink_foreign_id', $flink->foreign_id);
+ common_element_end('p');
+ common_submit('remove', _('Remove'));
+ } else {
+ common_input('twitter_username', _('Twitter user name'),
+ ($this->arg('twitter_username')) ? $this->arg('twitter_username') : $profile->nickname,
+ _('No spaces, please.')); // hey, it's what Twitter says
+
+ common_password('twitter_password', _('Twitter password'));
+ }
+
+ common_element('h2', NULL, _('Preferences'));
+
+ common_checkbox('noticesync', _('Automatically send my notices to Twitter.'),
+ ($flink) ? ($flink->noticesync & FOREIGN_NOTICE_SEND) : true);
+
+ common_checkbox('replysync', _('Send local "@" replies to Twitter.'),
+ ($flink) ? ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true);
+
+ common_checkbox('friendsync', _('Subscribe to my Twitter friends here.'),
+ ($flink) ? ($flink->friendsync & FOREIGN_FRIEND_RECV) : false);
+
+ if ($flink) {
+ common_submit('save', _('Save'));
+ } else {
+ common_submit('add', _('Add'));
+ }
+
+ $this->show_twitter_subscriptions();
+
+ common_element_end('form');
+
+ common_show_footer();
+ }
+
+ function subscribed_twitter_users() {
+
+ $current_user = common_current_user();
+
+ $qry = 'SELECT user.* ' .
+ 'FROM subscription ' .
+ 'JOIN user ON subscription.subscribed = user.id ' .
+ 'JOIN foreign_link ON foreign_link.user_id = user.id ' .
+ 'WHERE subscriber = %d ' .
+ 'ORDER BY user.nickname';
+
+ $user = new User();
+
+ $user->query(sprintf($qry, $current_user->id));
+
+ $users = array();
+
+ while ($user->fetch()) {
+
+ // Don't include the user's own self-subscription
+ if ($user->id != $current_user->id) {
+ $users[] = clone($user);
+ }
+ }
+
+ return $users;
+ }
+
+ function show_twitter_subscriptions() {
+
+ $friends = $this->subscribed_twitter_users();
+ $friends_count = count($friends);
+
+ if ($friends_count > 0) {
+
+ common_element('h3', NULL, _('Twitter Friends'));
+ common_element_start('div', array('id' => 'subscriptions'));
+ common_element_start('ul', array('id' => 'subscriptions_avatars'));
+
+ for ($i = 0; $i < min($friends_count, SUBSCRIPTIONS); $i++) {
+
+ $other = Profile::staticGet($friends[$i]->id);
+
+ if (!$other) {
+ common_log_db_error($subs, 'SELECT', __FILE__);
+ continue;
+ }
+
+ common_element_start('li');
+ common_element_start('a', array('title' => ($other->fullname) ?
+ $other->fullname :
+ $other->nickname,
+ 'href' => $other->profileurl,
+ 'rel' => 'contact',
+ 'class' => 'subscription'));
+ $avatar = $other->getAvatar(AVATAR_MINI_SIZE);
+ common_element('img', array('src' => (($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_MINI_SIZE)),
+ 'width' => AVATAR_MINI_SIZE,
+ 'height' => AVATAR_MINI_SIZE,
+ 'class' => 'avatar mini',
+ 'alt' => ($other->fullname) ?
+ $other->fullname :
+ $other->nickname));
+ common_element_end('a');
+ common_element_end('li');
+
+ }
+
+ common_element_end('ul');
+ common_element_end('div');
+
+ }
+
+ // XXX Figure out a way to show all Twitter friends... ?
+
+ /*
+ if ($subs_count > SUBSCRIPTIONS) {
+ common_element_start('p', array('id' => 'subscriptions_viewall'));
+
+ common_element('a', array('href' => common_local_url('subscriptions',
+ array('nickname' => $profile->nickname)),
+ 'class' => 'moresubscriptions'),
+ _('All subscriptions'));
+ common_element_end('p');
+ }
+ */
+
+ }
+
+ function handle_post() {
+
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $this->show_form(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ if ($this->arg('save')) {
+ $this->save_preferences();
+ } else if ($this->arg('add')) {
+ $this->add_twitter_acct();
+ } else if ($this->arg('remove')) {
+ $this->remove_twitter_acct();
+ } else {
+ $this->show_form(_('Unexpected form submission.'));
+ }
+ }
+
+ function add_twitter_acct() {
+
+ $screen_name = $this->trimmed('twitter_username');
+ $password = $this->trimmed('twitter_password');
+ $noticesync = $this->boolean('noticesync');
+ $replysync = $this->boolean('replysync');
+ $friendsync = $this->boolean('friendsync');
+
+ if (!Validate::string($screen_name,
+ array( 'min_length' => 1,
+ 'max_length' => 15,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA . '_'))) {
+ $this->show_form(
+ _('Username must have only numbers, upper- and lowercase letters, and underscore (_). 15 chars max.'));
+ return;
+ }
+
+ if (!$this->verify_credentials($screen_name, $password)) {
+ $this->show_form(_('Could not verify your Twitter credentials!'));
+ return;
+ }
+
+ $twit_user = twitter_user_info($screen_name, $password);
+
+ if (!$twit_user) {
+ $this->show_form(sprintf(_('Unable to retrieve account information for "%s" from Twitter.'),
+ $screen_name));
+ return;
+ }
+
+ if (!save_twitter_user($twit_user->id, $screen_name)) {
+ $this->show_form(_('Unable to save your Twitter settings!'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ $flink = DB_DataObject::factory('foreign_link');
+ $flink->user_id = $user->id;
+ $flink->foreign_id = $twit_user->id;
+ $flink->service = 1; // Twitter
+ $flink->credentials = $password;
+ $flink->created = common_sql_now();
+
+ $this->set_flags($flink, $noticesync, $replysync, $friendsync);
+
+ $flink_id = $flink->insert();
+
+ if (!$flink_id) {
+ common_log_db_error($flink, 'INSERT', __FILE__);
+ $this->show_form(_('Unable to save your Twitter settings!'));
+ return;
+ }
+
+ if ($friendsync) {
+ save_twitter_friends($user, $twit_user->id, $screen_name, $password);
+ }
+
+ $this->show_form(_('Twitter settings saved.'), true);
+ }
+
+ function remove_twitter_acct() {
+
+ $user = common_current_user();
+ $flink = Foreign_link::getByUserID($user->id, 1);
+ $flink_foreign_id = $this->arg('flink_foreign_id');
+
+ # Maybe an old tab open...?
+ if ($flink->foreign_id != $flink_foreign_id) {
+ $this->show_form(_('That is not your Twitter account.'));
+ return;
+ }
+
+ $result = $flink->delete();
+
+ if (!$result) {
+ common_log_db_error($flink, 'DELETE', __FILE__);
+ common_server_error(_('Couldn\'t remove Twitter user.'));
+ return;
+ }
+
+ $this->show_form(_('Twitter account removed.'), TRUE);
+ }
+
+ function save_preferences() {
+
+ $noticesync = $this->boolean('noticesync');
+ $friendsync = $this->boolean('friendsync');
+ $replysync = $this->boolean('replysync');
+
+ $user = common_current_user();
+
+ $flink = Foreign_link::getByUserID($user->id, 1);
+
+ if (!$flink) {
+ common_log_db_error($flink, 'SELECT', __FILE__);
+ $this->show_form(_('Couldn\'t save Twitter preferences.'));
+ return;
+ }
+
+ $twitter_id = $flink->foreign_id;
+ $password = $flink->credentials;
+
+ $fuser = $flink->getForeignUser();
+
+ if (!$fuser) {
+ common_log_db_error($fuser, 'SELECT', __FILE__);
+ $this->show_form(_('Couldn\'t save Twitter preferences.'));
+ return;
+ }
+
+ $screen_name = $fuser->nickname;
+
+ $original = clone($flink);
+ $this->set_flags($flink, $noticesync, $replysync, $friendsync);
+ $result = $flink->update($original);
+
+ if ($result === FALSE) {
+ common_log_db_error($flink, 'UPDATE', __FILE__);
+ $this->show_form(_('Couldn\'t save Twitter preferences.'));
+ return;
+ }
+
+ if ($friendsync) {
+ save_twitter_friends($user, $flink->foreign_id, $screen_name, $password);
+ }
+
+ $this->show_form(_('Twitter preferences saved.'));
+ }
+
+ function verify_credentials($screen_name, $password) {
+ $uri = 'http://twitter.com/account/verify_credentials.json';
+ $data = get_twitter_data($uri, $screen_name, $password);
+
+ if (!$data) {
+ return false;
+ }
+
+ $user = json_decode($data);
+
+ if (!$user) {
+ return false;
+ }
+
+ $twitter_id = $user->status->id;
+
+ if ($twitter_id) {
+ return $twitter_id;
+ }
+
+ return false;
+ }
+
+ function set_flags(&$flink, $noticesync, $replysync, $friendsync) {
+ if ($noticesync) {
+ $flink->noticesync |= FOREIGN_NOTICE_SEND;
+ } else {
+ $flink->noticesync &= ~FOREIGN_NOTICE_SEND;
+ }
+
+ if ($replysync) {
+ $flink->noticesync |= FOREIGN_NOTICE_SEND_REPLY;
+ } else {
+ $flink->noticesync &= ~FOREIGN_NOTICE_SEND_REPLY;
+ }
+
+ if ($friendsync) {
+ $flink->friendsync |= FOREIGN_FRIEND_RECV;
+ } else {
+ $flink->friendsync &= ~FOREIGN_FRIEND_RECV;
+ }
+
+ $flink->profilesync = 0;
+ }
+
+} \ No newline at end of file
diff --git a/actions/unblock.php b/actions/unblock.php
new file mode 100644
index 000000000..d60cc7088
--- /dev/null
+++ b/actions/unblock.php
@@ -0,0 +1,92 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class UnblockAction extends Action {
+
+ var $profile = NULL;
+
+ function prepare($args) {
+
+ parent::prepare($args);
+
+ if (!common_logged_in()) {
+ $this->client_error(_('Not logged in.'));
+ return false;
+ }
+
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $id = $this->trimmed('unblockto');
+
+ if (!$id) {
+ $this->client_error(_('No profile specified.'));
+ return false;
+ }
+
+ $this->profile = Profile::staticGet('id', $id);
+
+ if (!$this->profile) {
+ $this->client_error(_('No profile with that ID.'));
+ return false;
+ }
+
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->unblock_profile();
+ }
+ }
+
+ function unblock_profile() {
+
+ $cur = common_current_user();
+
+ $result = $cur->unblock($this->profile);
+
+ if (!$result) {
+ $this->server_error(_('Error removing the block.'));
+ return;
+ }
+
+ foreach ($this->args as $k => $v) {
+ if ($k == 'returnto-action') {
+ $action = $v;
+ } else if (substr($k, 0, 9) == 'returnto-') {
+ $args[substr($k, 9)] = $v;
+ }
+ }
+
+ if ($action) {
+ common_redirect(common_local_url($action, $args));
+ } else {
+ common_redirect(common_local_url('subscriptions',
+ array('nickname' => $cur->nickname)));
+ }
+ }
+}
diff --git a/actions/unsubscribe.php b/actions/unsubscribe.php
new file mode 100644
index 000000000..98291e897
--- /dev/null
+++ b/actions/unsubscribe.php
@@ -0,0 +1,80 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class UnsubscribeAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ return;
+ }
+
+ $user = common_current_user();
+
+ if ($_SERVER['REQUEST_METHOD'] != 'POST') {
+ common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
+ return;
+ }
+
+ # CSRF protection
+
+ $token = $this->trimmed('token');
+
+ if (!$token || $token != common_session_token()) {
+ $this->client_error(_('There was a problem with your session token. Try again, please.'));
+ return;
+ }
+
+ $other_id = $this->arg('unsubscribeto');
+
+ if (!$other_id) {
+ $this->client_error(_('No profile id in request.'));
+ return;
+ }
+
+ $other = Profile::staticGet('id', $other_id);
+
+ if (!$other_id) {
+ $this->client_error(_('No profile with that id.'));
+ return;
+ }
+
+ $result = subs_unsubscribe_to($user, $other);
+
+ if ($result != true) {
+ common_user_error($result);
+ return;
+ }
+
+ if ($this->boolean('ajax')) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Unsubscribed'));
+ common_element_end('head');
+ common_element_start('body');
+ common_subscribe_form($other);
+ common_element_end('body');
+ common_element_end('html');
+ } else {
+ common_redirect(common_local_url('subscriptions', array('nickname' =>
+ $user->nickname)));
+ }
+ }
+}
diff --git a/actions/updateprofile.php b/actions/updateprofile.php
new file mode 100644
index 000000000..921e88e63
--- /dev/null
+++ b/actions/updateprofile.php
@@ -0,0 +1,174 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class UpdateprofileAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ try {
+ common_remove_magic_from_request();
+ $req = OAuthRequest::from_request();
+ # Note: server-to-server function!
+ $server = omb_oauth_server();
+ list($consumer, $token) = $server->verify_request($req);
+ if ($this->update_profile($req, $consumer, $token)) {
+ print "omb_version=".OMB_VERSION_01;
+ }
+ } catch (OAuthException $e) {
+ $this->server_error($e->getMessage());
+ return;
+ }
+ }
+
+ function update_profile($req, $consumer, $token) {
+ $version = $req->get_parameter('omb_version');
+ if ($version != OMB_VERSION_01) {
+ $this->client_error(_('Unsupported OMB version'), 400);
+ return false;
+ }
+ # First, check to see if listenee exists
+ $listenee = $req->get_parameter('omb_listenee');
+ $remote = Remote_profile::staticGet('uri', $listenee);
+ if (!$remote) {
+ $this->client_error(_('Profile unknown'), 404);
+ return false;
+ }
+ # Second, check to see if they should be able to post updates!
+ # We see if there are any subscriptions to that remote user with
+ # the given token.
+
+ $sub = new Subscription();
+ $sub->subscribed = $remote->id;
+ $sub->token = $token->key;
+ if (!$sub->find(true)) {
+ $this->client_error(_('You did not send us that profile'), 403);
+ return false;
+ }
+
+ $profile = Profile::staticGet('id', $remote->id);
+ if (!$profile) {
+ # This one is our fault
+ $this->server_error(_('Remote profile with no matching profile'), 500);
+ return false;
+ }
+ $nickname = $req->get_parameter('omb_listenee_nickname');
+ if ($nickname && !Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ $this->client_error(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+ return false;
+ }
+ $license = $req->get_parameter('omb_listenee_license');
+ if ($license && !common_valid_http_url($license)) {
+ $this->client_error(sprintf(_("Invalid license URL '%s'"), $license));
+ return false;
+ }
+ $profile_url = $req->get_parameter('omb_listenee_profile');
+ if ($profile_url && !common_valid_http_url($profile_url)) {
+ $this->client_error(sprintf(_("Invalid profile URL '%s'."), $profile_url));
+ return false;
+ }
+ # optional stuff
+ $fullname = $req->get_parameter('omb_listenee_fullname');
+ if ($fullname && strlen($fullname) > 255) {
+ $this->client_error(_("Full name is too long (max 255 chars)."));
+ return false;
+ }
+ $homepage = $req->get_parameter('omb_listenee_homepage');
+ if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
+ $this->client_error(sprintf(_("Invalid homepage '%s'"), $homepage));
+ return false;
+ }
+ $bio = $req->get_parameter('omb_listenee_bio');
+ if ($bio && strlen($bio) > 140) {
+ $this->client_error(_("Bio is too long (max 140 chars)."));
+ return false;
+ }
+ $location = $req->get_parameter('omb_listenee_location');
+ if ($location && strlen($location) > 255) {
+ $this->client_error(_("Location is too long (max 255 chars)."));
+ return false;
+ }
+ $avatar = $req->get_parameter('omb_listenee_avatar');
+ if ($avatar) {
+ if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
+ $this->client_error(sprintf(_("Invalid avatar URL '%s'"), $avatar));
+ return false;
+ }
+ $size = @getimagesize($avatar);
+ if (!$size) {
+ $this->client_error(sprintf(_("Can't read avatar URL '%s'"), $avatar));
+ return false;
+ }
+ if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
+ $this->client_error(sprintf(_("Wrong size image at '%s'"), $avatar));
+ return false;
+ }
+ if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
+ IMAGETYPE_PNG))) {
+ $this->client_error(sprintf(_("Wrong image type for '%s'"), $avatar));
+ return false;
+ }
+ }
+
+ $orig_profile = clone($profile);
+
+ if ($nickname) {
+ $profile->nickname = $nickname;
+ }
+ if ($profile_url) {
+ $profile->profileurl = $profile_url;
+ }
+ if ($fullname) {
+ $profile->fullname = $fullname;
+ }
+ if ($homepage) {
+ $profile->homepage = $homepage;
+ }
+ if ($bio) {
+ $profile->bio = $bio;
+ }
+ if ($location) {
+ $profile->location = $location;
+ }
+
+ if (!$profile->update($orig_profile)) {
+ $this->server_error(_('Could not save new profile info'), 500);
+ return false;
+ } else {
+ if ($avatar) {
+ $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
+ copy($avatar, $temp_filename);
+ if (!$profile->setOriginal($temp_filename)) {
+ $this->server_error(_('Could not save avatar info'), 500);
+ return false;
+ }
+ }
+ header('HTTP/1.1 200 OK');
+ header('Content-type: text/plain');
+ print 'Updated profile';
+ print "\n";
+ return true;
+ }
+ }
+}
diff --git a/actions/userauthorization.php b/actions/userauthorization.php
new file mode 100644
index 000000000..ac0a0728c
--- /dev/null
+++ b/actions/userauthorization.php
@@ -0,0 +1,579 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+define('TIMESTAMP_THRESHOLD', 300);
+
+class UserauthorizationAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ # CSRF protection
+ $token = $this->trimmed('token');
+ if (!$token || $token != common_session_token()) {
+ $req = $this->get_stored_request();
+ $this->show_form(_('There was a problem with your session token. Try again, please.'), $req);
+ return;
+ }
+ # We've shown the form, now post user's choice
+ $this->send_authorization();
+ } else {
+ if (!common_logged_in()) {
+ # Go log in, and then come back
+ common_debug('saving URL for returnto', __FILE__);
+ common_set_returnto($_SERVER['REQUEST_URI']);
+
+ common_debug('redirecting to login', __FILE__);
+ common_redirect(common_local_url('login'));
+ return;
+ }
+ try {
+ # this must be a new request
+ common_debug('getting new request', __FILE__);
+ $req = $this->get_new_request();
+ if (!$req) {
+ $this->client_error(_('No request found!'));
+ }
+ common_debug('validating request', __FILE__);
+ # XXX: only validate new requests, since nonce is one-time use
+ $this->validate_request($req);
+ common_debug('showing form', __FILE__);
+ $this->store_request($req);
+ $this->show_form($req);
+ } catch (OAuthException $e) {
+ $this->clear_request();
+ $this->client_error($e->getMessage());
+ return;
+ }
+
+ }
+ }
+
+ function show_form($req) {
+
+ $nickname = $req->get_parameter('omb_listenee_nickname');
+ $profile = $req->get_parameter('omb_listenee_profile');
+ $license = $req->get_parameter('omb_listenee_license');
+ $fullname = $req->get_parameter('omb_listenee_fullname');
+ $homepage = $req->get_parameter('omb_listenee_homepage');
+ $bio = $req->get_parameter('omb_listenee_bio');
+ $location = $req->get_parameter('omb_listenee_location');
+ $avatar = $req->get_parameter('omb_listenee_avatar');
+
+ common_show_header(_('Authorize subscription'));
+ common_element('p', NULL, _('Please check these details to make sure '.
+ 'that you want to subscribe to this user\'s notices. '.
+ 'If you didn\'t just ask to subscribe to someone\'s notices, '.
+ 'click "Cancel".'));
+ common_element_start('div', 'profile');
+ if ($avatar) {
+ common_element('img', array('src' => $avatar,
+ 'class' => 'avatar profile',
+ 'width' => AVATAR_PROFILE_SIZE,
+ 'height' => AVATAR_PROFILE_SIZE,
+ 'alt' => $nickname));
+ }
+ common_element('a', array('href' => $profile,
+ 'class' => 'external profile nickname'),
+ $nickname);
+ if ($fullname) {
+ common_element_start('div', 'fullname');
+ if ($homepage) {
+ common_element('a', array('href' => $homepage),
+ $fullname);
+ } else {
+ common_text($fullname);
+ }
+ common_element_end('div');
+ }
+ if ($location) {
+ common_element('div', 'location', $location);
+ }
+ if ($bio) {
+ common_element('div', 'bio', $bio);
+ }
+ common_element_start('div', 'license');
+ common_element('a', array('href' => $license,
+ 'class' => 'license'),
+ $license);
+ common_element_end('div');
+ common_element_end('div');
+ common_element_start('form', array('method' => 'post',
+ 'id' => 'userauthorization',
+ 'name' => 'userauthorization',
+ 'action' => common_local_url('userauthorization')));
+ common_hidden('token', common_session_token());
+ common_submit('accept', _('Accept'));
+ common_submit('reject', _('Reject'));
+ common_element_end('form');
+ common_show_footer();
+ }
+
+ function send_authorization() {
+ $req = $this->get_stored_request();
+
+ if (!$req) {
+ common_user_error(_('No authorization request!'));
+ return;
+ }
+
+ $callback = $req->get_parameter('oauth_callback');
+
+ if ($this->arg('accept')) {
+ if (!$this->authorize_token($req)) {
+ $this->client_error(_('Error authorizing token'));
+ }
+ if (!$this->save_remote_profile($req)) {
+ $this->client_error(_('Error saving remote profile'));
+ }
+ if (!$callback) {
+ $this->show_accept_message($req->get_parameter('oauth_token'));
+ } else {
+ $params = array();
+ $params['oauth_token'] = $req->get_parameter('oauth_token');
+ $params['omb_version'] = OMB_VERSION_01;
+ $user = User::staticGet('uri', $req->get_parameter('omb_listener'));
+ $profile = $user->getProfile();
+ if (!$profile) {
+ common_log_db_error($user, 'SELECT', __FILE__);
+ $this->server_error(_('User without matching profile'));
+ return;
+ }
+ $params['omb_listener_nickname'] = $user->nickname;
+ $params['omb_listener_profile'] = common_local_url('showstream',
+ array('nickname' => $user->nickname));
+ if ($profile->fullname) {
+ $params['omb_listener_fullname'] = $profile->fullname;
+ }
+ if ($profile->homepage) {
+ $params['omb_listener_homepage'] = $profile->homepage;
+ }
+ if ($profile->bio) {
+ $params['omb_listener_bio'] = $profile->bio;
+ }
+ if ($profile->location) {
+ $params['omb_listener_location'] = $profile->location;
+ }
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ if ($avatar) {
+ $params['omb_listener_avatar'] = $avatar->url;
+ }
+ $parts = array();
+ foreach ($params as $k => $v) {
+ $parts[] = $k . '=' . OAuthUtil::urlencodeRFC3986($v);
+ }
+ $query_string = implode('&', $parts);
+ $parsed = parse_url($callback);
+ $url = $callback . (($parsed['query']) ? '&' : '?') . $query_string;
+ common_redirect($url, 303);
+ }
+ } else {
+ if (!$callback) {
+ $this->show_reject_message();
+ } else {
+ # XXX: not 100% sure how to signal failure... just redirect without token?
+ common_redirect($callback, 303);
+ }
+ }
+ }
+
+ function authorize_token(&$req) {
+ $consumer_key = $req->get_parameter('oauth_consumer_key');
+ $token_field = $req->get_parameter('oauth_token');
+ common_debug('consumer key = "'.$consumer_key.'"', __FILE__);
+ common_debug('token field = "'.$token_field.'"', __FILE__);
+ $rt = new Token();
+ $rt->consumer_key = $consumer_key;
+ $rt->tok = $token_field;
+ $rt->type = 0;
+ $rt->state = 0;
+ common_debug('request token to look up: "'.print_r($rt,TRUE).'"');
+ if ($rt->find(true)) {
+ common_debug('found request token to authorize', __FILE__);
+ $orig_rt = clone($rt);
+ $rt->state = 1; # Authorized but not used
+ if ($rt->update($orig_rt)) {
+ common_debug('updated request token so it is authorized', __FILE__);
+ return true;
+ }
+ }
+ return FALSE;
+ }
+
+ # XXX: refactor with similar code in finishremotesubscribe.php
+
+ function save_remote_profile(&$req) {
+ # FIXME: we should really do this when the consumer comes
+ # back for an access token. If they never do, we've got stuff in a
+ # weird state.
+
+ $nickname = $req->get_parameter('omb_listenee_nickname');
+ $fullname = $req->get_parameter('omb_listenee_fullname');
+ $profile_url = $req->get_parameter('omb_listenee_profile');
+ $homepage = $req->get_parameter('omb_listenee_homepage');
+ $bio = $req->get_parameter('omb_listenee_bio');
+ $location = $req->get_parameter('omb_listenee_location');
+ $avatar_url = $req->get_parameter('omb_listenee_avatar');
+
+ $listenee = $req->get_parameter('omb_listenee');
+ $remote = Remote_profile::staticGet('uri', $listenee);
+
+ if ($remote) {
+ $exists = true;
+ $profile = Profile::staticGet($remote->id);
+ $orig_remote = clone($remote);
+ $orig_profile = clone($profile);
+ } else {
+ $exists = false;
+ $remote = new Remote_profile();
+ $remote->uri = $listenee;
+ $profile = new Profile();
+ }
+
+ $profile->nickname = $nickname;
+ $profile->profileurl = $profile_url;
+
+ if ($fullname) {
+ $profile->fullname = $fullname;
+ }
+ if ($homepage) {
+ $profile->homepage = $homepage;
+ }
+ if ($bio) {
+ $profile->bio = $bio;
+ }
+ if ($location) {
+ $profile->location = $location;
+ }
+
+ if ($exists) {
+ $profile->update($orig_profile);
+ } else {
+ $profile->created = DB_DataObject_Cast::dateTime(); # current time
+ $id = $profile->insert();
+ if (!$id) {
+ return FALSE;
+ }
+ $remote->id = $id;
+ }
+
+ if ($exists) {
+ if (!$remote->update($orig_remote)) {
+ return FALSE;
+ }
+ } else {
+ $remote->created = DB_DataObject_Cast::dateTime(); # current time
+ if (!$remote->insert()) {
+ return FALSE;
+ }
+ }
+
+ if ($avatar_url) {
+ if (!$this->add_avatar($profile, $avatar_url)) {
+ return FALSE;
+ }
+ }
+
+ $user = common_current_user();
+ $datastore = omb_oauth_datastore();
+ $consumer = $this->get_consumer($datastore, $req);
+ $token = $this->get_token($datastore, $req, $consumer);
+
+ $sub = new Subscription();
+ $sub->subscriber = $user->id;
+ $sub->subscribed = $remote->id;
+ $sub->token = $token->key; # NOTE: request token, not valid for use!
+ $sub->created = DB_DataObject_Cast::dateTime(); # current time
+
+ if (!$sub->insert()) {
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ function add_avatar($profile, $url) {
+ $temp_filename = tempnam(sys_get_temp_dir(), 'listenee_avatar');
+ copy($url, $temp_filename);
+ return $profile->setOriginal($temp_filename);
+ }
+
+ function show_accept_message($tok) {
+ common_show_header(_('Subscription authorized'));
+ common_element('p', NULL,
+ _('The subscription has been authorized, but no '.
+ 'callback URL was passed. Check with the site\'s instructions for '.
+ 'details on how to authorize the subscription. Your subscription token is:'));
+ common_element('blockquote', 'token', $tok);
+ common_show_footer();
+ }
+
+ function show_reject_message($tok) {
+ common_show_header(_('Subscription rejected'));
+ common_element('p', NULL,
+ _('The subscription has been rejected, but no '.
+ 'callback URL was passed. Check with the site\'s instructions for '.
+ 'details on how to fully reject the subscription.'));
+ common_show_footer();
+ }
+
+ function store_request($req) {
+ common_ensure_session();
+ $_SESSION['userauthorizationrequest'] = $req;
+ }
+
+ function clear_request() {
+ common_ensure_session();
+ unset($_SESSION['userauthorizationrequest']);
+ }
+
+ function get_stored_request() {
+ common_ensure_session();
+ $req = $_SESSION['userauthorizationrequest'];
+ return $req;
+ }
+
+ function get_new_request() {
+ common_remove_magic_from_request();
+ $req = OAuthRequest::from_request();
+ return $req;
+ }
+
+ # Throws an OAuthException if anything goes wrong
+
+ function validate_request(&$req) {
+ # OAuth stuff -- have to copy from OAuth.php since they're
+ # all private methods, and there's no user-authentication method
+ common_debug('checking version', __FILE__);
+ $this->check_version($req);
+ common_debug('getting datastore', __FILE__);
+ $datastore = omb_oauth_datastore();
+ common_debug('getting consumer', __FILE__);
+ $consumer = $this->get_consumer($datastore, $req);
+ common_debug('getting token', __FILE__);
+ $token = $this->get_token($datastore, $req, $consumer);
+ common_debug('checking timestamp', __FILE__);
+ $this->check_timestamp($req);
+ common_debug('checking nonce', __FILE__);
+ $this->check_nonce($datastore, $req, $consumer, $token);
+ common_debug('checking signature', __FILE__);
+ $this->check_signature($req, $consumer, $token);
+ common_debug('validating omb stuff', __FILE__);
+ $this->validate_omb($req);
+ common_debug('done validating', __FILE__);
+ return true;
+ }
+
+ function validate_omb(&$req) {
+ foreach (array('omb_version', 'omb_listener', 'omb_listenee',
+ 'omb_listenee_profile', 'omb_listenee_nickname',
+ 'omb_listenee_license') as $param)
+ {
+ if (!$req->get_parameter($param)) {
+ throw new OAuthException("Required parameter '$param' not found");
+ }
+ }
+ # Now, OMB stuff
+ $version = $req->get_parameter('omb_version');
+ if ($version != OMB_VERSION_01) {
+ throw new OAuthException("OpenMicroBlogging version '$version' not supported");
+ }
+ $listener = $req->get_parameter('omb_listener');
+ $user = User::staticGet('uri', $listener);
+ if (!$user) {
+ throw new OAuthException("Listener URI '$listener' not found here");
+ }
+ $cur = common_current_user();
+ if ($cur->id != $user->id) {
+ throw new OAuthException("Can't add for another user!");
+ }
+ $listenee = $req->get_parameter('omb_listenee');
+ if (!Validate::uri($listenee) &&
+ !common_valid_tag($listenee)) {
+ throw new OAuthException("Listenee URI '$listenee' not a recognizable URI");
+ }
+ if (strlen($listenee) > 255) {
+ throw new OAuthException("Listenee URI '$listenee' too long");
+ }
+
+ $other = User::staticGet('uri', $listenee);
+ if ($other) {
+ throw new OAuthException("Listenee URI '$listenee' is local user");
+ }
+
+ $remote = Remote_profile::staticGet('uri', $listenee);
+ if ($remote) {
+ $sub = new Subscription();
+ $sub->subscriber = $user->id;
+ $sub->subscribed = $remote->id;
+ if ($sub->find(TRUE)) {
+ throw new OAuthException("Already subscribed to user!");
+ }
+ }
+ $nickname = $req->get_parameter('omb_listenee_nickname');
+ if (!Validate::string($nickname, array('min_length' => 1,
+ 'max_length' => 64,
+ 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+ throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
+ }
+ $profile = $req->get_parameter('omb_listenee_profile');
+ if (!common_valid_http_url($profile)) {
+ throw new OAuthException("Invalid profile URL '$profile'.");
+ }
+
+ if ($profile == common_local_url('showstream', array('nickname' => $nickname))) {
+ throw new OAuthException("Profile URL '$profile' is for a local user.");
+ }
+
+ $license = $req->get_parameter('omb_listenee_license');
+ if (!common_valid_http_url($license)) {
+ throw new OAuthException("Invalid license URL '$license'.");
+ }
+ $site_license = common_config('license', 'url');
+ if (!common_compatible_license($license, $site_license)) {
+ throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'.");
+ }
+ # optional stuff
+ $fullname = $req->get_parameter('omb_listenee_fullname');
+ if ($fullname && strlen($fullname) > 255) {
+ throw new OAuthException("Full name '$fullname' too long.");
+ }
+ $homepage = $req->get_parameter('omb_listenee_homepage');
+ if ($homepage && (!common_valid_http_url($homepage) || strlen($homepage) > 255)) {
+ throw new OAuthException("Invalid homepage '$homepage'");
+ }
+ $bio = $req->get_parameter('omb_listenee_bio');
+ if ($bio && strlen($bio) > 140) {
+ throw new OAuthException("Bio too long '$bio'");
+ }
+ $location = $req->get_parameter('omb_listenee_location');
+ if ($location && strlen($location) > 255) {
+ throw new OAuthException("Location too long '$location'");
+ }
+ $avatar = $req->get_parameter('omb_listenee_avatar');
+ if ($avatar) {
+ if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
+ throw new OAuthException("Invalid avatar URL '$avatar'");
+ }
+ $size = @getimagesize($avatar);
+ if (!$size) {
+ throw new OAuthException("Can't read avatar URL '$avatar'");
+ }
+ if ($size[0] != AVATAR_PROFILE_SIZE || $size[1] != AVATAR_PROFILE_SIZE) {
+ throw new OAuthException("Wrong size image at '$avatar'");
+ }
+ if (!in_array($size[2], array(IMAGETYPE_GIF, IMAGETYPE_JPEG,
+ IMAGETYPE_PNG))) {
+ throw new OAuthException("Wrong image type for '$avatar'");
+ }
+ }
+ $callback = $req->get_parameter('oauth_callback');
+ if ($callback && !common_valid_http_url($callback)) {
+ throw new OAuthException("Invalid callback URL '$callback'");
+ }
+ if ($callback && $callback == common_local_url('finishremotesubscribe')) {
+ throw new OAuthException("Callback URL '$callback' is for local site.");
+ }
+ }
+
+ # Snagged from OAuthServer
+
+ function check_version(&$req) {
+ $version = $req->get_parameter("oauth_version");
+ if (!$version) {
+ $version = 1.0;
+ }
+ if ($version != 1.0) {
+ throw new OAuthException("OAuth version '$version' not supported");
+ }
+ return $version;
+ }
+
+ # Snagged from OAuthServer
+
+ function get_consumer($datastore, $req) {
+ $consumer_key = @$req->get_parameter("oauth_consumer_key");
+ if (!$consumer_key) {
+ throw new OAuthException("Invalid consumer key");
+ }
+
+ $consumer = $datastore->lookup_consumer($consumer_key);
+ if (!$consumer) {
+ throw new OAuthException("Invalid consumer");
+ }
+ return $consumer;
+ }
+
+ # Mostly cadged from OAuthServer
+
+ function get_token($datastore, &$req, $consumer) {/*{{{*/
+ $token_field = @$req->get_parameter('oauth_token');
+ $token = $datastore->lookup_token($consumer, 'request', $token_field);
+ if (!$token) {
+ throw new OAuthException("Invalid $token_type token: $token_field");
+ }
+ return $token;
+ }
+
+ function check_timestamp(&$req) {
+ $timestamp = @$req->get_parameter('oauth_timestamp');
+ $now = time();
+ if ($now - $timestamp > TIMESTAMP_THRESHOLD) {
+ throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
+ }
+ }
+
+ # NOTE: don't call twice on the same request; will fail!
+ function check_nonce(&$datastore, &$req, $consumer, $token) {
+ $timestamp = @$req->get_parameter('oauth_timestamp');
+ $nonce = @$req->get_parameter('oauth_nonce');
+ $found = $datastore->lookup_nonce($consumer, $token, $nonce, $timestamp);
+ if ($found) {
+ throw new OAuthException("Nonce already used");
+ }
+ return true;
+ }
+
+ function check_signature(&$req, $consumer, $token) {
+ $signature_method = $this->get_signature_method($req);
+ $signature = $req->get_parameter('oauth_signature');
+ $valid_sig = $signature_method->check_signature($req,
+ $consumer,
+ $token,
+ $signature);
+ if (!$valid_sig) {
+ throw new OAuthException("Invalid signature");
+ }
+ }
+
+ function get_signature_method(&$req) {
+ $signature_method = @$req->get_parameter("oauth_signature_method");
+ if (!$signature_method) {
+ $signature_method = "PLAINTEXT";
+ }
+ if ($signature_method != 'HMAC-SHA1') {
+ throw new OAuthException("Signature method '$signature_method' not supported.");
+ }
+ return omb_hmac_sha1();
+ }
+}
diff --git a/actions/userbyid.php b/actions/userbyid.php
new file mode 100644
index 000000000..38bff2ede
--- /dev/null
+++ b/actions/userbyid.php
@@ -0,0 +1,49 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class UserbyidAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ $id = $this->trimmed('id');
+ if (!$id) {
+ $this->client_error(_('No id.'));
+ }
+ $user =& User::staticGet($id);
+ if (!$user) {
+ $this->client_error(_('No such user.'));
+ }
+
+ // support redirecting to FOAF rdf/xml if the agent prefers it
+ $page_prefs = 'application/rdf+xml,text/html,application/xhtml+xml,application/xml;q=0.3,text/xml;q=0.2';
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : NULL;
+ $type = common_negotiate_type(common_accept_to_prefs($httpaccept),
+ common_accept_to_prefs($page_prefs));
+ $page = $type == 'application/rdf+xml' ? 'foaf' : 'showstream';
+
+ $url = common_local_url($page, array('nickname' => $user->nickname));
+ common_redirect($url, 303);
+ }
+}
diff --git a/actions/userrss.php b/actions/userrss.php
new file mode 100644
index 000000000..e57f86105
--- /dev/null
+++ b/actions/userrss.php
@@ -0,0 +1,90 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/rssaction.php');
+
+// Formatting of RSS handled by Rss10Action
+
+class UserrssAction extends Rss10Action {
+
+ var $user = NULL;
+
+ function init() {
+ $nickname = $this->trimmed('nickname');
+ $this->user = User::staticGet('nickname', $nickname);
+
+ if (!$this->user) {
+ common_user_error(_('No such user.'));
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ function get_notices($limit=0) {
+
+ $user = $this->user;
+
+ if (is_null($user)) {
+ return NULL;
+ }
+
+ $notice = $user->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+ function get_channel() {
+ $user = $this->user;
+ $profile = $user->getProfile();
+ $c = array('url' => common_local_url('userrss',
+ array('nickname' =>
+ $user->nickname)),
+ 'title' => $user->nickname,
+ 'link' => $profile->profileurl,
+ 'description' => sprintf(_('Microblog by %s'), $user->nickname));
+ return $c;
+ }
+
+ function get_image() {
+ $user = $this->user;
+ $profile = $user->getProfile();
+ if (!$profile) {
+ common_log_db_error($user, 'SELECT', __FILE__);
+ $this->server_error(_('User without matching profile'));
+ return NULL;
+ }
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ return ($avatar) ? $avatar->url : NULL;
+ }
+
+ # override parent to add X-SUP-ID URL
+
+ function init_rss($limit=0) {
+ $url = common_local_url('sup', NULL, $this->user->id);
+ header('X-SUP-ID: '.$url);
+ parent::init_rss($limit);
+ }
+} \ No newline at end of file
diff --git a/actions/xrds.php b/actions/xrds.php
new file mode 100644
index 000000000..1d516aab7
--- /dev/null
+++ b/actions/xrds.php
@@ -0,0 +1,132 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class XrdsAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ $nickname = $this->trimmed('nickname');
+ $user = User::staticGet('nickname', $nickname);
+ if (!$user) {
+ common_user_error(_('No such user.'));
+ return;
+ }
+ $this->show_xrds($user);
+ }
+
+ function show_xrds($user) {
+
+ header('Content-Type: application/xrds+xml');
+
+ common_start_xml();
+ common_element_start('XRDS', array('xmlns' => 'xri://$xrds'));
+
+ common_element_start('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'oauth',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+
+ common_element('Type', NULL, 'xri://$xrds*simple');
+
+ $this->show_service(OAUTH_ENDPOINT_REQUEST,
+ common_local_url('requesttoken'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1),
+ $user->uri);
+
+ $this->show_service(OAUTH_ENDPOINT_AUTHORIZE,
+ common_local_url('userauthorization'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+
+ $this->show_service(OAUTH_ENDPOINT_ACCESS,
+ common_local_url('accesstoken'),
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+
+ $this->show_service(OAUTH_ENDPOINT_RESOURCE,
+ NULL,
+ array(OAUTH_AUTH_HEADER, OAUTH_POST_BODY),
+ array(OAUTH_HMAC_SHA1));
+
+ common_element_end('XRD');
+
+ # XXX: decide whether to include user's ID/nickname in postNotice URL
+
+ common_element_start('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'xml:id' => 'omb',
+ 'xmlns:simple' => 'http://xrds-simple.net/core/1.0',
+ 'version' => '2.0'));
+
+ common_element('Type', NULL, 'xri://$xrds*simple');
+
+ $this->show_service(OMB_ENDPOINT_POSTNOTICE,
+ common_local_url('postnotice'));
+
+ $this->show_service(OMB_ENDPOINT_UPDATEPROFILE,
+ common_local_url('updateprofile'));
+
+ common_element_end('XRD');
+
+ common_element_start('XRD', array('xmlns' => 'xri://$xrd*($v*2.0)',
+ 'version' => '2.0'));
+
+ common_element('Type', NULL, 'xri://$xrds*simple');
+
+ $this->show_service(OAUTH_DISCOVERY,
+ '#oauth');
+ $this->show_service(OMB_NAMESPACE,
+ '#omb');
+
+ common_element_end('XRD');
+
+ common_element_end('XRDS');
+ common_end_xml();
+ }
+
+ function show_service($type, $uri, $params=NULL, $sigs=NULL, $localId=NULL) {
+ common_element_start('Service');
+ if ($uri) {
+ common_element('URI', NULL, $uri);
+ }
+ common_element('Type', NULL, $type);
+ if ($params) {
+ foreach ($params as $param) {
+ common_element('Type', NULL, $param);
+ }
+ }
+ if ($sigs) {
+ foreach ($sigs as $sig) {
+ common_element('Type', NULL, $sig);
+ }
+ }
+ if ($localId) {
+ common_element('LocalID', NULL, $localId);
+ }
+ common_element_end('Service');
+ }
+} \ No newline at end of file
diff --git a/classes/Avatar.php b/classes/Avatar.php
new file mode 100644
index 000000000..901c47c51
--- /dev/null
+++ b/classes/Avatar.php
@@ -0,0 +1,95 @@
+<?php
+/**
+ * Table Definition for avatar
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Avatar extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'avatar'; // table name
+ public $profile_id; // int(4) primary_key not_null
+ public $original; // tinyint(1)
+ public $width; // int(4) primary_key not_null
+ public $height; // int(4) primary_key not_null
+ public $mediatype; // varchar(32) not_null
+ public $filename; // varchar(255)
+ public $url; // varchar(255) unique_key
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Avatar',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ # We clean up the file, too
+
+ function delete() {
+ $filename = $this->filename;
+ if (parent::delete()) {
+ @unlink(common_avatar_path($filename));
+ }
+ }
+
+ # Create and save scaled version of this avatar
+ # XXX: maybe break into different methods
+
+ function scale($size) {
+
+ $image_s = imagecreatetruecolor($size, $size);
+ $image_a = $this->to_image();
+ $square = min($this->width, $this->height);
+ imagecolortransparent($image_s, imagecolorallocate($image_s, 0, 0, 0));
+ imagealphablending($image_s, false);
+ imagesavealpha($image_s, true);
+ imagecopyresampled($image_s, $image_a, 0, 0, 0, 0,
+ $size, $size, $square, $square);
+
+ $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png";
+
+ $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp());
+
+ if ($this->mediatype == 'image/jpeg') {
+ imagejpeg($image_s, common_avatar_path($filename));
+ } else {
+ imagepng($image_s, common_avatar_path($filename));
+ }
+
+ $scaled = DB_DataObject::factory('avatar');
+ $scaled->profile_id = $this->profile_id;
+ $scaled->width = $size;
+ $scaled->height = $size;
+ $scaled->original = false;
+ $scaled->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png';
+ $scaled->filename = $filename;
+ $scaled->url = common_avatar_url($filename);
+ $scaled->created = DB_DataObject_Cast::dateTime(); # current time
+
+ if ($scaled->insert()) {
+ return $scaled;
+ } else {
+ return NULL;
+ }
+ }
+
+ function to_image() {
+ $filepath = common_avatar_path($this->filename);
+ if ($this->mediatype == 'image/gif') {
+ return imagecreatefromgif($filepath);
+ } else if ($this->mediatype == 'image/jpeg') {
+ return imagecreatefromjpeg($filepath);
+ } else if ($this->mediatype == 'image/png') {
+ return imagecreatefrompng($filepath);
+ } else {
+ return NULL;
+ }
+ }
+
+ function &pkeyGet($kv) {
+ return Memcached_DataObject::pkeyGet('Avatar', $kv);
+ }
+}
diff --git a/classes/Channel.php b/classes/Channel.php
new file mode 100644
index 000000000..bcc0c36b5
--- /dev/null
+++ b/classes/Channel.php
@@ -0,0 +1,200 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class Channel {
+
+ function on($user) {
+ return false;
+ }
+
+ function off($user) {
+ return false;
+ }
+
+ function output($user, $text) {
+ return false;
+ }
+
+ function error($user, $text) {
+ return false;
+ }
+
+ function source() {
+ return NULL;
+ }
+}
+
+class XMPPChannel extends Channel {
+
+ var $conn = NULL;
+
+ function source() {
+ return 'xmpp';
+ }
+
+ function __construct($conn) {
+ $this->conn = $conn;
+ }
+
+ function on($user) {
+ return $this->set_notify($user, 1);
+ }
+
+ function off($user) {
+ return $this->set_notify($user, 0);
+ }
+
+ function output($user, $text) {
+ $text = '['.common_config('site', 'name') . '] ' . $text;
+ jabber_send_message($user->jabber, $text);
+ }
+
+ function error($user, $text) {
+ $text = '['.common_config('site', 'name') . '] ' . $text;
+ jabber_send_message($user->jabber, $text);
+ }
+
+ function set_notify(&$user, $notify) {
+ $orig = clone($user);
+ $user->jabbernotify = $notify;
+ $result = $user->update($orig);
+ if (!$result) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR,
+ 'Could not set notify flag to ' . $notify .
+ ' for user ' . common_log_objstring($user) .
+ ': ' . $last_error->message);
+ return false;
+ } else {
+ common_log(LOG_INFO,
+ 'User ' . $user->nickname . ' set notify flag to ' . $notify);
+ return true;
+ }
+ }
+}
+
+
+class WebChannel extends Channel {
+
+ function source() {
+ return 'web';
+ }
+
+ function on($user) {
+ return false;
+ }
+
+ function off($user) {
+ return false;
+ }
+
+ function output($user, $text) {
+ # XXX: buffer all output and send it at the end
+ # XXX: even better, redirect to appropriate page
+ # depending on what command was run
+ common_show_header(_('Command results'));
+ common_element('p', NULL, $text);
+ common_show_footer();
+ }
+
+ function error($user, $text) {
+ common_user_error($text);
+ }
+}
+
+
+class AjaxWebChannel extends WebChannel {
+
+ function output($user, $text) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Command results'));
+ common_element_end('head');
+ common_element_start('body');
+ common_element('p', array('id' => 'command_result'), $text);
+ common_element_end('body');
+ common_element_end('html');
+ }
+
+ function error($user, $text) {
+ common_start_html('text/xml;charset=utf-8', true);
+ common_element_start('head');
+ common_element('title', null, _('Ajax Error'));
+ common_element_end('head');
+ common_element_start('body');
+ common_element('p', array('id' => 'error'), $text);
+ common_element_end('body');
+ common_element_end('html');
+ }
+}
+
+
+class MailChannel extends Channel {
+
+ var $addr = NULL;
+
+ function source() {
+ return 'mail';
+ }
+
+ function __construct($addr=NULL) {
+ $this->addr = $addr;
+ }
+
+ function on($user) {
+ return $this->set_notify($user, 1);
+ }
+
+ function off($user) {
+ return $this->set_notify($user, 0);
+ }
+
+ function output($user, $text) {
+
+ $headers['From'] = $user->incomingemail;
+ $headers['To'] = $this->addr;
+
+ $headers['Subject'] = _('Command complete');
+
+ return mail_send(array($this->addr), $headers, $text);
+ }
+
+ function error($user, $text) {
+
+ $headers['From'] = $user->incomingemail;
+ $headers['To'] = $this->addr;
+
+ $headers['Subject'] = _('Command failed');
+
+ return mail_send(array($this->addr), $headers, $text);
+ }
+
+ function set_notify($user, $value) {
+ $orig = clone($user);
+ $user->smsnotify = $value;
+ $result = $user->update($orig);
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/classes/Command.php b/classes/Command.php
new file mode 100644
index 000000000..c2409d140
--- /dev/null
+++ b/classes/Command.php
@@ -0,0 +1,376 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/classes/Channel.php');
+
+class Command {
+
+ var $user = NULL;
+
+ function __construct($user=NULL) {
+ $this->user = $user;
+ }
+
+ function execute($channel) {
+ return false;
+ }
+}
+
+class UnimplementedCommand extends Command {
+ function execute($channel) {
+ $channel->error($this->user, _("Sorry, this command is not yet implemented."));
+ }
+}
+
+class TrackingCommand extends UnimplementedCommand {
+}
+
+class TrackOffCommand extends UnimplementedCommand {
+}
+
+class TrackCommand extends UnimplementedCommand {
+ var $word = NULL;
+ function __construct($user, $word) {
+ parent::__construct($user);
+ $this->word = $word;
+ }
+}
+
+class UntrackCommand extends UnimplementedCommand {
+ var $word = NULL;
+ function __construct($user, $word) {
+ parent::__construct($user);
+ $this->word = $word;
+ }
+}
+
+class NudgeCommand extends UnimplementedCommand {
+ var $other = NULL;
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+}
+
+class InviteCommand extends UnimplementedCommand {
+ var $other = NULL;
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+}
+
+class StatsCommand extends Command {
+ function execute($channel) {
+
+ $subs = new Subscription();
+ $subs->subscriber = $this->user->id;
+ $subs_count = (int) $subs->count() - 1;
+
+ $subbed = new Subscription();
+ $subbed->subscribed = $this->user->id;
+ $subbed_count = (int) $subbed->count() - 1;
+
+ $notices = new Notice();
+ $notices->profile_id = $this->user->id;
+ $notice_count = (int) $notices->count();
+
+ $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
+ "Subscribers: %2\$s\n".
+ "Notices: %3\$s"),
+ $subs_count,
+ $subbed_count,
+ $notice_count));
+ }
+}
+
+class FavCommand extends Command {
+
+ var $other = NULL;
+
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel) {
+
+ $recipient =
+ common_relative_profile($this->user, common_canonical_nickname($this->other));
+
+ if (!$recipient) {
+ $channel->error($this->user, _('No such user.'));
+ return;
+ }
+ $notice = $recipient->getCurrentNotice();
+ if (!$notice) {
+ $channel->error($this->user, _('User has no last notice'));
+ return;
+ }
+
+ $fave = Fave::addNew($this->user, $notice);
+
+ if (!$fave) {
+ $channel->error($this->user, _('Could not create favorite.'));
+ return;
+ }
+
+ $other = User::staticGet('id', $recipient->id);
+
+ if ($other && $other->id != $user->id) {
+ if ($other->email && $other->emailnotifyfav) {
+ mail_notify_fave($other, $this->user, $notice);
+ }
+ }
+
+ $this->user->blowFavesCache();
+
+ $channel->output($this->user, _('Notice marked as fave.'));
+ }
+}
+
+class WhoisCommand extends Command {
+ var $other = NULL;
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel) {
+ $recipient =
+ common_relative_profile($this->user, common_canonical_nickname($this->other));
+
+ if (!$recipient) {
+ $channel->error($this->user, _('No such user.'));
+ return;
+ }
+
+ $whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
+ $recipient->profileurl);
+ if ($recipient->fullname) {
+ $whois .= "\n" . sprintf(_('Fullname: %s'), $recipient->fullname);
+ }
+ if ($recipient->location) {
+ $whois .= "\n" . sprintf(_('Location: %s'), $recipient->location);
+ }
+ if ($recipient->homepage) {
+ $whois .= "\n" . sprintf(_('Homepage: %s'), $recipient->homepage);
+ }
+ if ($recipient->bio) {
+ $whois .= "\n" . sprintf(_('About: %s'), $recipient->bio);
+ }
+ $channel->output($this->user, $whois);
+ }
+}
+
+class MessageCommand extends Command {
+ var $other = NULL;
+ var $text = NULL;
+ function __construct($user, $other, $text) {
+ parent::__construct($user);
+ $this->other = $other;
+ $this->text = $text;
+ }
+
+ function execute($channel) {
+ $other = User::staticGet('nickname', common_canonical_nickname($this->other));
+ $len = mb_strlen($this->text);
+ if ($len == 0) {
+ $channel->error($this->user, _('No content!'));
+ return;
+ } else if ($len > 140) {
+ $content = common_shorten_links($content);
+ if (mb_strlen($content) > 140) {
+ $channel->error($this->user, sprintf(_('Message too long - maximum is 140 characters, you sent %d'), $len));
+ return;
+ }
+ }
+
+ if (!$other) {
+ $channel->error($this->user, _('No such user.'));
+ return;
+ } else if (!$this->user->mutuallySubscribed($other)) {
+ $channel->error($this->user, _('You can\'t send a message to this user.'));
+ return;
+ } else if ($this->user->id == $other->id) {
+ $channel->error($this->user, _('Don\'t send a message to yourself; just say it to yourself quietly instead.'));
+ return;
+ }
+ $message = Message::saveNew($this->user->id, $other->id, $this->text, $channel->source());
+ if ($message) {
+ $channel->output($this->user, sprintf(_('Direct message to %s sent'), $this->other));
+ } else {
+ $channel->error($this->user, _('Error sending direct message.'));
+ }
+ }
+}
+
+class GetCommand extends Command {
+
+ var $other = NULL;
+
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel) {
+ $target_nickname = common_canonical_nickname($this->other);
+
+ $target =
+ common_relative_profile($this->user, $target_nickname);
+
+ if (!$target) {
+ $channel->error($this->user, _('No such user.'));
+ return;
+ }
+ $notice = $target->getCurrentNotice();
+ if (!$notice) {
+ $channel->error($this->user, _('User has no last notice'));
+ return;
+ }
+ $notice_content = $notice->content;
+
+ $channel->output($this->user, $target_nickname . ": " . $notice_content);
+ }
+}
+
+class SubCommand extends Command {
+
+ var $other = NULL;
+
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel) {
+
+ if (!$this->other) {
+ $channel->error($this->user, _('Specify the name of the user to subscribe to'));
+ return;
+ }
+
+ $result = subs_subscribe_user($this->user, $this->other);
+
+ if ($result == 'true') {
+ $channel->output($this->user, sprintf(_('Subscribed to %s'), $this->other));
+ } else {
+ $channel->error($this->user, $result);
+ }
+ }
+}
+
+class UnsubCommand extends Command {
+
+ var $other = NULL;
+
+ function __construct($user, $other) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel) {
+ if(!$this->other) {
+ $channel->error($this->user, _('Specify the name of the user to unsubscribe from'));
+ return;
+ }
+
+ $result=subs_unsubscribe_user($this->user, $this->other);
+
+ if ($result) {
+ $channel->output($this->user, sprintf(_('Unsubscribed from %s'), $this->other));
+ } else {
+ $channel->error($this->user, $result);
+ }
+ }
+}
+
+class OffCommand extends Command {
+ var $other = NULL;
+ function __construct($user, $other=NULL) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+ function execute($channel) {
+ if ($other) {
+ $channel->error($this->user, _("Command not yet implemented."));
+ } else {
+ if ($channel->off($this->user)) {
+ $channel->output($this->user, _('Notification off.'));
+ } else {
+ $channel->error($this->user, _('Can\'t turn off notification.'));
+ }
+ }
+ }
+}
+
+class OnCommand extends Command {
+ var $other = NULL;
+ function __construct($user, $other=NULL) {
+ parent::__construct($user);
+ $this->other = $other;
+ }
+
+ function execute($channel) {
+ if ($other) {
+ $channel->error($this->user, _("Command not yet implemented."));
+ } else {
+ if ($channel->on($this->user)) {
+ $channel->output($this->user, _('Notification on.'));
+ } else {
+ $channel->error($this->user, _('Can\'t turn on notification.'));
+ }
+ }
+ }
+}
+
+class HelpCommand extends Command {
+ function execute($channel) {
+ $channel->output($this->user,
+ _("Commands:\n".
+ "on - turn on notifications\n".
+ "off - turn off notifications\n".
+ "help - show this help\n".
+ "follow <nickname> - subscribe to user\n".
+ "leave <nickname> - unsubscribe from user\n".
+ "d <nickname> <text> - direct message to user\n".
+ "get <nickname> - get last notice from user\n".
+ "whois <nickname> - get profile info on user\n".
+ "fav <nickname> - add user's last notice as a 'fave'\n".
+ "stats - get your stats\n".
+ "stop - same as 'off'\n".
+ "quit - same as 'off'\n".
+ "sub <nickname> - same as 'follow'\n".
+ "unsub <nickname> - same as 'leave'\n".
+ "last <nickname> - same as 'get'\n".
+ "on <nickname> - not yet implemented.\n".
+ "off <nickname> - not yet implemented.\n".
+ "nudge <nickname> - not yet implemented.\n".
+ "invite <phone number> - not yet implemented.\n".
+ "track <word> - not yet implemented.\n".
+ "untrack <word> - not yet implemented.\n".
+ "track off - not yet implemented.\n".
+ "untrack all - not yet implemented.\n".
+ "tracks - not yet implemented.\n".
+ "tracking - not yet implemented.\n"));
+ }
+}
diff --git a/classes/CommandInterpreter.php b/classes/CommandInterpreter.php
new file mode 100644
index 000000000..eae315cb6
--- /dev/null
+++ b/classes/CommandInterpreter.php
@@ -0,0 +1,196 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/classes/Command.php');
+
+class CommandInterpreter {
+
+ function handle_command($user, $text) {
+ # XXX: localise
+
+ $text = preg_replace('/\s+/', ' ', trim($text));
+ list($cmd, $arg) = explode(' ', $text, 2);
+
+ # We try to support all the same commands as Twitter, see
+ # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
+ # There are a few compatibility commands from earlier versions of
+ # Laconica
+
+ switch(strtolower($cmd)) {
+ case 'help':
+ if ($arg) {
+ return NULL;
+ }
+ return new HelpCommand($user);
+ case 'on':
+ if ($arg) {
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new OnCommand($user, $other);
+ }
+ } else {
+ return new OnCommand($user);
+ }
+ case 'off':
+ if ($arg) {
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new OffCommand($user, $other);
+ }
+ } else {
+ return new OffCommand($user);
+ }
+ case 'stop':
+ case 'quit':
+ if ($arg) {
+ return NULL;
+ } else {
+ return new OffCommand($user);
+ }
+ case 'follow':
+ case 'sub':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new SubCommand($user, $other);
+ }
+ case 'leave':
+ case 'unsub':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new UnsubCommand($user, $other);
+ }
+ case 'get':
+ case 'last':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new GetCommand($user, $other);
+ }
+ case 'd':
+ case 'dm':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if (!$extra) {
+ return NULL;
+ } else {
+ return new MessageCommand($user, $other, $extra);
+ }
+ case 'whois':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new WhoisCommand($user, $other);
+ }
+ case 'fav':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new FavCommand($user, $other);
+ }
+ case 'nudge':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new NudgeCommand($user, $other);
+ }
+ case 'stats':
+ if ($arg) {
+ return NULL;
+ }
+ return new StatsCommand($user);
+ case 'invite':
+ if (!$arg) {
+ return NULL;
+ }
+ list($other, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else {
+ return new InviteCommand($user, $other);
+ }
+ case 'track':
+ if (!$arg) {
+ return NULL;
+ }
+ list($word, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else if ($word == 'off') {
+ return new TrackOffCommand($user);
+ } else {
+ return new TrackCommand($user, $word);
+ }
+ case 'untrack':
+ if (!$arg) {
+ return NULL;
+ }
+ list($word, $extra) = explode(' ', $arg, 2);
+ if ($extra) {
+ return NULL;
+ } else if ($word == 'all') {
+ return new TrackOffCommand($user);
+ } else {
+ return new UntrackCommand($user, $word);
+ }
+ case 'tracks':
+ case 'tracking':
+ if ($arg) {
+ return NULL;
+ }
+ return new TrackingCommand($user);
+ default:
+ return false;
+ }
+ }
+}
+
diff --git a/classes/Confirm_address.php b/classes/Confirm_address.php
new file mode 100644
index 000000000..10661ff5c
--- /dev/null
+++ b/classes/Confirm_address.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Table Definition for confirm_address
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Confirm_address extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'confirm_address'; // table name
+ public $code; // varchar(32) primary_key not_null
+ public $user_id; // int(4) not_null
+ public $address; // varchar(255) not_null
+ public $address_extra; // varchar(255) not_null
+ public $address_type; // varchar(8) not_null
+ public $claimed; // datetime()
+ public $sent; // datetime()
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Confirm_address',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function sequenceKey() { return array(false, false); }
+}
diff --git a/classes/Consumer.php b/classes/Consumer.php
new file mode 100644
index 000000000..d18e6feeb
--- /dev/null
+++ b/classes/Consumer.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Table Definition for consumer
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Consumer extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'consumer'; // table name
+ public $consumer_key; // varchar(255) primary_key not_null
+ public $seed; // char(32) not_null
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Consumer',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Fave.php b/classes/Fave.php
new file mode 100644
index 000000000..7cc3f585e
--- /dev/null
+++ b/classes/Fave.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Table Definition for fave
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Fave extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'fave'; // table name
+ public $notice_id; // int(4) primary_key not_null
+ public $user_id; // int(4) primary_key not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Fave',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ static function addNew($user, $notice) {
+ $fave = new Fave();
+ $fave->user_id = $user->id;
+ $fave->notice_id = $notice->id;
+ if (!$fave->insert()) {
+ common_log_db_error($fave, 'INSERT', __FILE__);
+ return false;
+ }
+ return $fave;
+ }
+
+ function &pkeyGet($kv) {
+ return Memcached_DataObject::pkeyGet('Fave', $kv);
+ }
+}
diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php
new file mode 100644
index 000000000..7a625a209
--- /dev/null
+++ b/classes/Foreign_link.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Table Definition for foreign_link
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Foreign_link extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'foreign_link'; // table name
+ public $user_id; // int(4) primary_key not_null
+ public $foreign_id; // int(4) primary_key not_null
+ public $service; // int(4) primary_key not_null
+ public $credentials; // varchar(255)
+ public $noticesync; // tinyint(1) not_null default_1
+ public $friendsync; // tinyint(1) not_null default_2
+ public $profilesync; // tinyint(1) not_null default_1
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_link',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ // XXX: This only returns a 1->1 single obj mapping. Change? Or make
+ // a getForeignUsers() that returns more than one? --Zach
+ static function getByUserID($user_id, $service) {
+ $flink = new Foreign_link();
+ $flink->service = $service;
+ $flink->user_id = $user_id;
+ $flink->limit(1);
+
+ if ($flink->find(TRUE)) {
+ return $flink;
+ }
+
+ return NULL;
+ }
+
+ static function getByForeignID($foreign_id, $service) {
+ $flink = new Foreign_link();
+ $flink->service = $service;
+ $flink->foreign_id = $foreign_id;
+ $flink->limit(1);
+
+ if ($flink->find(TRUE)) {
+ return $flink;
+ }
+
+ return NULL;
+ }
+
+ # Convenience methods
+ function getForeignUser() {
+ $fuser = new Foreign_user();
+ $fuser->service = $this->service;
+ $fuser->id = $this->foreign_id;
+
+ $fuser->limit(1);
+
+ if ($fuser->find(TRUE)) {
+ return $fuser;
+ }
+
+ return NULL;
+ }
+
+ function getUser() {
+ return User::staticGet($this->user_id);
+ }
+
+}
diff --git a/classes/Foreign_service.php b/classes/Foreign_service.php
new file mode 100644
index 000000000..18ef83d69
--- /dev/null
+++ b/classes/Foreign_service.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Table Definition for foreign_service
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Foreign_service extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'foreign_service'; // table name
+ public $id; // int(4) primary_key not_null
+ public $name; // varchar(32) unique_key not_null
+ public $description; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_service',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Foreign_subscription.php b/classes/Foreign_subscription.php
new file mode 100644
index 000000000..315064067
--- /dev/null
+++ b/classes/Foreign_subscription.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Table Definition for foreign_subscription
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Foreign_subscription extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'foreign_subscription'; // table name
+ public $service; // int(4) primary_key not_null
+ public $subscriber; // int(4) primary_key not_null
+ public $subscribed; // int(4) primary_key not_null
+ public $created; // datetime() not_null
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_subscription',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Foreign_user.php b/classes/Foreign_user.php
new file mode 100644
index 000000000..027fae69d
--- /dev/null
+++ b/classes/Foreign_user.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Table Definition for foreign_user
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Foreign_user extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'foreign_user'; // table name
+ public $id; // int(4) primary_key not_null
+ public $service; // int(4) primary_key not_null
+ public $uri; // varchar(255) unique_key not_null
+ public $nickname; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Foreign_user',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ // XXX: This only returns a 1->1 single obj mapping. Change? Or make
+ // a getForeignUsers() that returns more than one? --Zach
+ static function getForeignUser($id, $service) {
+ $fuser = new Foreign_user();
+ $fuser->whereAdd("service = $service");
+ $fuser->whereAdd("id = $id");
+ $fuser->limit(1);
+
+ if ($fuser->find()) {
+ $fuser->fetch();
+ return $fuser;
+ }
+
+ return NULL;
+ }
+
+ function updateKeys(&$orig) {
+ $parts = array();
+ foreach (array('id', 'service', 'uri', 'nickname') as $k) {
+ if (strcmp($this->$k, $orig->$k) != 0) {
+ $parts[] = $k . ' = ' . $this->_quote($this->$k);
+ }
+ }
+ if (count($parts) == 0) {
+ # No changes
+ return true;
+ }
+ $toupdate = implode(', ', $parts);
+
+ $table = $this->tableName();
+ if(common_config('db','quote_identifiers')) {
+ $table = '"' . $table . '"';
+ }
+ $qry = 'UPDATE ' . $table . ' SET ' . $toupdate .
+ ' WHERE id = ' . $this->id;
+ $orig->decache();
+ $result = $this->query($qry);
+ if ($result) {
+ $this->encache();
+ }
+ return $result;
+ }
+
+
+}
diff --git a/classes/Invitation.php b/classes/Invitation.php
new file mode 100644
index 000000000..1477391b0
--- /dev/null
+++ b/classes/Invitation.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Table Definition for invitation
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Invitation extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'invitation'; // table name
+ public $code; // varchar(32) primary_key not_null
+ public $user_id; // int(4) not_null
+ public $address; // varchar(255) multiple_key not_null
+ public $address_type; // varchar(8) multiple_key not_null
+ public $created; // datetime() not_null
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Invitation',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php
new file mode 100644
index 000000000..7a33e158d
--- /dev/null
+++ b/classes/Memcached_DataObject.php
@@ -0,0 +1,194 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Memcached_DataObject extends DB_DataObject
+{
+ function &staticGet($cls, $k, $v=NULL) {
+ if (is_null($v)) {
+ $v = $k;
+ # XXX: HACK!
+ $i = new $cls;
+ $keys = $i->keys();
+ $k = $keys[0];
+ unset($i);
+ }
+ $i = Memcached_DataObject::getcached($cls, $k, $v);
+ if ($i) {
+ return $i;
+ } else {
+ $i = DB_DataObject::staticGet($cls, $k, $v);
+ if ($i) {
+ $i->encache();
+ }
+ return $i;
+ }
+ }
+
+ function &pkeyGet($cls, $kv) {
+ $i = Memcached_DataObject::multicache($cls, $kv);
+ if ($i) {
+ return $i;
+ } else {
+ $i = new $cls();
+ foreach ($kv as $k => $v) {
+ $i->$k = $v;
+ }
+ if ($i->find(true)) {
+ $i->encache();
+ } else {
+ $i = NULL;
+ }
+ return $i;
+ }
+ }
+
+ function insert() {
+ $result = parent::insert();
+ return $result;
+ }
+
+ function update($orig=NULL) {
+ if (is_object($orig) && $orig instanceof Memcached_DataObject) {
+ $orig->decache(); # might be different keys
+ }
+ $result = parent::update($orig);
+ if ($result) {
+ $this->encache();
+ }
+ return $result;
+ }
+
+ function delete() {
+ $this->decache(); # while we still have the values!
+ return parent::delete();
+ }
+
+ static function memcache() {
+ return common_memcache();
+ }
+
+ static function cacheKey($cls, $k, $v) {
+ return common_cache_key(strtolower($cls).':'.$k.':'.$v);
+ }
+
+ static function getcached($cls, $k, $v) {
+ $c = Memcached_DataObject::memcache();
+ if (!$c) {
+ return false;
+ } else {
+ return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
+ }
+ }
+
+ function keyTypes() {
+ global $_DB_DATAOBJECT;
+ if (!isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
+ $this->databaseStructure();
+
+ }
+ return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
+ }
+
+ function encache() {
+ $c = $this->memcache();
+ if (!$c) {
+ return false;
+ } else {
+ $pkey = array();
+ $pval = array();
+ $types = $this->keyTypes();
+ ksort($types);
+ foreach ($types as $key => $type) {
+ if ($type == 'K') {
+ $pkey[] = $key;
+ $pval[] = $this->$key;
+ } else {
+ $c->set($this->cacheKey($this->tableName(), $key, $this->$key), $this);
+ }
+ }
+ # XXX: should work for both compound and scalar pkeys
+ $pvals = implode(',', $pval);
+ $pkeys = implode(',', $pkey);
+ $c->set($this->cacheKey($this->tableName(), $pkeys, $pvals), $this);
+ }
+ }
+
+ function decache() {
+ $c = $this->memcache();
+ if (!$c) {
+ return false;
+ } else {
+ $pkey = array();
+ $pval = array();
+ $types = $this->keyTypes();
+ ksort($types);
+ foreach ($types as $key => $type) {
+ if ($type == 'K') {
+ $pkey[] = $key;
+ $pval[] = $this->$key;
+ } else {
+ $c->delete($this->cacheKey($this->tableName(), $key, $this->$key));
+ }
+ }
+ # should work for both compound and scalar pkeys
+ # XXX: comma works for now but may not be safe separator for future keys
+ $pvals = implode(',', $pval);
+ $pkeys = implode(',', $pkey);
+ $c->delete($this->cacheKey($this->tableName(), $pkeys, $pvals));
+ }
+ }
+
+ function multicache($cls, $kv) {
+ ksort($kv);
+ $c = Memcached_DataObject::memcache();
+ if (!$c) {
+ return false;
+ } else {
+ $pkeys = implode(',', array_keys($kv));
+ $pvals = implode(',', array_values($kv));
+ return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals));
+ }
+ }
+
+ function getSearchEngine($table) {
+ require_once INSTALLDIR.'/lib/search_engines.php';
+ static $search_engine;
+ if (!isset($search_engine)) {
+ $connected = false;
+ if (common_config('sphinx', 'enabled')) {
+ $search_engine = new SphinxSearch($this, $table);
+ $connected = $search_engine->is_connected();
+ }
+
+ // unable to connect to sphinx' search daemon
+ if (!$connected) {
+ if ('mysql' === common_config('db', 'type')) {
+ $search_engine = new MySQLSearch($this, $table);
+ } else {
+ $search_engine = new PGSearch($this, $table);
+ }
+ }
+ }
+ return $search_engine;
+ }
+}
diff --git a/classes/Message.php b/classes/Message.php
new file mode 100644
index 000000000..ef4bd0316
--- /dev/null
+++ b/classes/Message.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Table Definition for message
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Message extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'message'; // table name
+ public $id; // int(4) primary_key not_null
+ public $uri; // varchar(255) unique_key
+ public $from_profile; // int(4) not_null
+ public $to_profile; // int(4) not_null
+ public $content; // varchar(140)
+ public $rendered; // text()
+ public $url; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+ public $source; // varchar(32)
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Message',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function getFrom() {
+ return Profile::staticGet('id', $this->from_profile);
+ }
+
+ function getTo() {
+ return Profile::staticGet('id', $this->to_profile);
+ }
+
+ static function saveNew($from, $to, $content, $source) {
+
+ $msg = new Message();
+
+ $msg->from_profile = $from;
+ $msg->to_profile = $to;
+ $msg->content = common_shorten_links($content);
+ $msg->rendered = common_render_text($content);
+ $msg->created = common_sql_now();
+ $msg->source = $source;
+
+ $result = $msg->insert();
+
+ if (!$result) {
+ common_log_db_error($msg, 'INSERT', __FILE__);
+ return _('Could not insert message.');
+ }
+
+ $orig = clone($msg);
+ $msg->uri = common_local_url('showmessage', array('message' => $msg->id));
+
+ $result = $msg->update($orig);
+
+ if (!$result) {
+ common_log_db_error($msg, 'UPDATE', __FILE__);
+ return _('Could not update message with new URI.');
+ }
+
+ return $msg;
+ }
+}
diff --git a/classes/Nonce.php b/classes/Nonce.php
new file mode 100644
index 000000000..89d673c53
--- /dev/null
+++ b/classes/Nonce.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Table Definition for nonce
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Nonce extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'nonce'; // table name
+ public $consumer_key; // varchar(255) primary_key not_null
+ public $tok; // char(32) primary_key not_null
+ public $nonce; // char(32) primary_key not_null
+ public $ts; // datetime() not_null
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Nonce',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Notice.php b/classes/Notice.php
new file mode 100644
index 000000000..ca8283bce
--- /dev/null
+++ b/classes/Notice.php
@@ -0,0 +1,539 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for notice
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+/* We keep the first three 20-notice pages, plus one for pagination check,
+ * in the memcached cache. */
+
+define('NOTICE_CACHE_WINDOW', 61);
+
+class Notice extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'notice'; // table name
+ public $id; // int(4) primary_key not_null
+ public $profile_id; // int(4) not_null
+ public $uri; // varchar(255) unique_key
+ public $content; // varchar(140)
+ public $rendered; // text()
+ public $url; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+ public $reply_to; // int(4)
+ public $is_local; // tinyint(1)
+ public $source; // varchar(32)
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function getProfile() {
+ return Profile::staticGet('id', $this->profile_id);
+ }
+
+ function delete() {
+ $this->blowCaches(true);
+ $this->blowFavesCache(true);
+ $this->blowInboxes();
+ return parent::delete();
+ }
+
+ function saveTags() {
+ /* extract all #hastags */
+ $count = preg_match_all('/(?:^|\s)#([A-Za-z0-9_\-\.]{1,64})/', strtolower($this->content), $match);
+ if (!$count) {
+ return true;
+ }
+
+ /* elide characters we don't want in the tag */
+ $match[1] = str_replace(array('-', '_', '.'), '', $match[1]);
+
+ /* Add them to the database */
+ foreach(array_unique($match[1]) as $hashtag) {
+ $tag = DB_DataObject::factory('Notice_tag');
+ $tag->notice_id = $this->id;
+ $tag->tag = $hashtag;
+ $tag->created = $this->created;
+ $id = $tag->insert();
+ if (!$id) {
+ $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, 'DB error inserting hashtag: ' . $last_error->message);
+ common_server_error(sprintf(_('DB error inserting hashtag: %s'), $last_error->message));
+ return;
+ }
+ }
+ return true;
+ }
+
+ static function saveNew($profile_id, $content, $source=NULL, $is_local=1, $reply_to=NULL, $uri=NULL) {
+
+ $profile = Profile::staticGet($profile_id);
+
+ if (!$profile) {
+ common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
+ return _('Problem saving notice. Unknown user.');
+ }
+
+ if (common_config('throttle', 'enabled') && !Notice::checkEditThrottle($profile_id)) {
+ common_log(LOG_WARNING, 'Excessive posting by profile #' . $profile_id . '; throttled.');
+ return _('Too many notices too fast; take a breather and post again in a few minutes.');
+ }
+
+ $banned = common_config('profile', 'banned');
+
+ if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) {
+ common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id).");
+ return _('You are banned from posting notices on this site.');
+ }
+
+ $notice = new Notice();
+ $notice->profile_id = $profile_id;
+
+ $blacklist = common_config('public', 'blacklist');
+
+ # Blacklisted are non-false, but not 1, either
+
+ if ($blacklist && in_array($profile_id, $blacklist)) {
+ $notice->is_local = -1;
+ } else {
+ $notice->is_local = $is_local;
+ }
+
+ $notice->reply_to = $reply_to;
+ $notice->created = common_sql_now();
+ $notice->content = common_shorten_links($content);
+ $notice->rendered = common_render_content($notice->content, $notice);
+ $notice->source = $source;
+ $notice->uri = $uri;
+
+ $id = $notice->insert();
+
+ if (!$id) {
+ common_log_db_error($notice, 'INSERT', __FILE__);
+ return _('Problem saving notice.');
+ }
+
+ # Update the URI after the notice is in the database
+ if (!$uri) {
+ $orig = clone($notice);
+ $notice->uri = common_notice_uri($notice);
+
+ if (!$notice->update($orig)) {
+ common_log_db_error($notice, 'UPDATE', __FILE__);
+ return _('Problem saving notice.');
+ }
+ }
+
+ # XXX: do we need to change this for remote users?
+
+ common_save_replies($notice);
+ $notice->saveTags();
+
+ # Clear the cache for subscribed users, so they'll update at next request
+ # XXX: someone clever could prepend instead of clearing the cache
+
+ if (common_config('memcached', 'enabled')) {
+ $notice->blowCaches();
+ }
+
+ $notice->addToInboxes();
+ return $notice;
+ }
+
+ static function checkEditThrottle($profile_id) {
+ $profile = Profile::staticGet($profile_id);
+ if (!$profile) {
+ return false;
+ }
+ # Get the Nth notice
+ $notice = $profile->getNotices(common_config('throttle', 'count') - 1, 1);
+ if ($notice && $notice->fetch()) {
+ # If the Nth notice was posted less than timespan seconds ago
+ if (time() - strtotime($notice->created) <= common_config('throttle', 'timespan')) {
+ # Then we throttle
+ return false;
+ }
+ }
+ # Either not N notices in the stream, OR the Nth was not posted within timespan seconds
+ return true;
+ }
+
+ function blowCaches($blowLast=false) {
+ $this->blowSubsCache($blowLast);
+ $this->blowNoticeCache($blowLast);
+ $this->blowRepliesCache($blowLast);
+ $this->blowPublicCache($blowLast);
+ $this->blowTagCache($blowLast);
+ }
+
+ function blowTagCache($blowLast=false) {
+ $cache = common_memcache();
+ if ($cache) {
+ $tag = new Notice_tag();
+ $tag->notice_id = $this->id;
+ if ($tag->find()) {
+ while ($tag->fetch()) {
+ $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag));
+ if ($blowLast) {
+ $cache->delete(common_cache_key('notice_tag:notice_stream:' . $tag->tag . ';last'));
+ }
+ }
+ }
+ $tag->free();
+ unset($tag);
+ }
+ }
+
+ function blowSubsCache($blowLast=false) {
+ $cache = common_memcache();
+ if ($cache) {
+ $user = new User();
+
+ $user->query('SELECT id ' .
+ 'FROM user JOIN subscription ON user.id = subscription.subscriber ' .
+ 'WHERE subscription.subscribed = ' . $this->profile_id);
+
+ while ($user->fetch()) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
+ if ($blowLast) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last'));
+ }
+ }
+ $user->free();
+ unset($user);
+ }
+ }
+
+ function blowNoticeCache($blowLast=false) {
+ if ($this->is_local) {
+ $cache = common_memcache();
+ if ($cache) {
+ $cache->delete(common_cache_key('profile:notices:'.$this->profile_id));
+ if ($blowLast) {
+ $cache->delete(common_cache_key('profile:notices:'.$this->profile_id.';last'));
+ }
+ }
+ }
+ }
+
+ function blowRepliesCache($blowLast=false) {
+ $cache = common_memcache();
+ if ($cache) {
+ $reply = new Reply();
+ $reply->notice_id = $this->id;
+ if ($reply->find()) {
+ while ($reply->fetch()) {
+ $cache->delete(common_cache_key('user:replies:'.$reply->profile_id));
+ if ($blowLast) {
+ $cache->delete(common_cache_key('user:replies:'.$reply->profile_id.';last'));
+ }
+ }
+ }
+ $reply->free();
+ unset($reply);
+ }
+ }
+
+ function blowPublicCache($blowLast=false) {
+ if ($this->is_local == 1) {
+ $cache = common_memcache();
+ if ($cache) {
+ $cache->delete(common_cache_key('public'));
+ if ($blowLast) {
+ $cache->delete(common_cache_key('public').';last');
+ }
+ }
+ }
+ }
+
+ function blowFavesCache($blowLast=false) {
+ $cache = common_memcache();
+ if ($cache) {
+ $fave = new Fave();
+ $fave->notice_id = $this->id;
+ if ($fave->find()) {
+ while ($fave->fetch()) {
+ $cache->delete(common_cache_key('user:faves:'.$fave->user_id));
+ if ($blowLast) {
+ $cache->delete(common_cache_key('user:faves:'.$fave->user_id.';last'));
+ }
+ }
+ }
+ $fave->free();
+ unset($fave);
+ }
+ }
+
+ # XXX: too many args; we need to move to named params or even a separate
+ # class for notice streams
+
+ static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=NULL, $since=NULL) {
+
+ if (common_config('memcached', 'enabled')) {
+
+ # Skip the cache if this is a since, since_id or before_id qry
+ if ($since_id > 0 || $before_id > 0 || $since) {
+ return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+ } else {
+ return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
+ }
+ }
+
+ return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+ }
+
+ static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) {
+
+ $needAnd = FALSE;
+ $needWhere = TRUE;
+
+ if (preg_match('/\bWHERE\b/i', $qry)) {
+ $needWhere = FALSE;
+ $needAnd = TRUE;
+ }
+
+ if ($since_id > 0) {
+
+ if ($needWhere) {
+ $qry .= ' WHERE ';
+ $needWhere = FALSE;
+ } else {
+ $qry .= ' AND ';
+ }
+
+ $qry .= ' notice.id > ' . $since_id;
+ }
+
+ if ($before_id > 0) {
+
+ if ($needWhere) {
+ $qry .= ' WHERE ';
+ $needWhere = FALSE;
+ } else {
+ $qry .= ' AND ';
+ }
+
+ $qry .= ' notice.id < ' . $before_id;
+ }
+
+ if ($since) {
+
+ if ($needWhere) {
+ $qry .= ' WHERE ';
+ $needWhere = FALSE;
+ } else {
+ $qry .= ' AND ';
+ }
+
+ $qry .= ' notice.created > \'' . date('Y-m-d H:i:s', $since) . '\'';
+ }
+
+ # Allow ORDER override
+
+ if ($order) {
+ $qry .= $order;
+ } else {
+ $qry .= ' ORDER BY notice.created DESC, notice.id DESC ';
+ }
+
+ if (common_config('db','type') == 'pgsql') {
+ $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ $notice = new Notice();
+
+ $notice->query($qry);
+
+ return $notice;
+ }
+
+ # XXX: this is pretty long and should probably be broken up into
+ # some helper functions
+
+ static function getCachedStream($qry, $cachekey, $offset, $limit, $order) {
+
+ # If outside our cache window, just go to the DB
+
+ if ($offset + $limit > NOTICE_CACHE_WINDOW) {
+ return Notice::getStreamDirect($qry, $offset, $limit, NULL, NULL, $order, NULL);
+ }
+
+ # Get the cache; if we can't, just go to the DB
+
+ $cache = common_memcache();
+
+ if (!$cache) {
+ return Notice::getStreamDirect($qry, $offset, $limit, NULL, NULL, $order, NULL);
+ }
+
+ # Get the notices out of the cache
+
+ $notices = $cache->get(common_cache_key($cachekey));
+
+ # On a cache hit, return a DB-object-like wrapper
+
+ if ($notices !== FALSE) {
+ $wrapper = new NoticeWrapper(array_slice($notices, $offset, $limit));
+ return $wrapper;
+ }
+
+ # If the cache was invalidated because of new data being
+ # added, we can try and just get the new stuff. We keep an additional
+ # copy of the data at the key + ';last'
+
+ # No cache hit. Try to get the *last* cached version
+
+ $last_notices = $cache->get(common_cache_key($cachekey) . ';last');
+
+ if ($last_notices) {
+
+ # Reverse-chron order, so last ID is last.
+
+ $last_id = $last_notices[0]->id;
+
+ # XXX: this assumes monotonically increasing IDs; a fair
+ # bet with our DB.
+
+ $new_notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW,
+ $last_id, NULL, $order, NULL);
+
+ if ($new_notice) {
+ $new_notices = array();
+ while ($new_notice->fetch()) {
+ $new_notices[] = clone($new_notice);
+ }
+ $new_notice->free();
+ $notices = array_slice(array_merge($new_notices, $last_notices),
+ 0, NOTICE_CACHE_WINDOW);
+
+ # Store the array in the cache for next time
+
+ $result = $cache->set(common_cache_key($cachekey), $notices);
+ $result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
+
+ # return a wrapper of the array for use now
+
+ return new NoticeWrapper(array_slice($notices, $offset, $limit));
+ }
+ }
+
+ # Otherwise, get the full cache window out of the DB
+
+ $notice = Notice::getStreamDirect($qry, 0, NOTICE_CACHE_WINDOW, NULL, NULL, $order, NULL);
+
+ # If there are no hits, just return the value
+
+ if (!$notice) {
+ return $notice;
+ }
+
+ # Pack results into an array
+
+ $notices = array();
+
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ $notice->free();
+
+ # Store the array in the cache for next time
+
+ $result = $cache->set(common_cache_key($cachekey), $notices);
+ $result = $cache->set(common_cache_key($cachekey) . ';last', $notices);
+
+ # return a wrapper of the array for use now
+
+ $wrapper = new NoticeWrapper(array_slice($notices, $offset, $limit));
+
+ return $wrapper;
+ }
+
+ function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=NULL) {
+
+ $parts = array();
+
+ $qry = 'SELECT * FROM notice ';
+
+ if (common_config('public', 'localonly')) {
+ $parts[] = 'is_local = 1';
+ } else {
+ # -1 == blacklisted
+ $parts[] = 'is_local != -1';
+ }
+
+ if ($parts) {
+ $qry .= ' WHERE ' . implode(' AND ', $parts);
+ }
+
+ return Notice::getStream($qry,
+ 'public',
+ $offset, $limit, $since_id, $before_id, NULL, $since);
+ }
+
+ function addToInboxes() {
+ $enabled = common_config('inboxes', 'enabled');
+
+ if ($enabled === true || $enabled === 'transitional') {
+ $inbox = new Notice_inbox();
+ $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' .
+ 'SELECT user.id, ' . $this->id . ', "' . $this->created . '" ' .
+ 'FROM user JOIN subscription ON user.id = subscription.subscriber ' .
+ 'WHERE subscription.subscribed = ' . $this->profile_id . ' ' .
+ 'AND NOT EXISTS (SELECT user_id, notice_id ' .
+ 'FROM notice_inbox ' .
+ 'WHERE user_id = user.id ' .
+ 'AND notice_id = ' . $this->id . ' )';
+ if ($enabled === 'transitional') {
+ $qry .= ' AND user.inboxed = 1';
+ }
+ $inbox->query($qry);
+ }
+ return;
+ }
+
+ # Delete from inboxes if we're deleted.
+
+ function blowInboxes() {
+
+ $enabled = common_config('inboxes', 'enabled');
+
+ if ($enabled === true || $enabled === 'transitional') {
+ $inbox = new Notice_inbox();
+ $inbox->notice_id = $this->id;
+ $inbox->delete();
+ }
+
+ return;
+ }
+
+}
+
diff --git a/classes/NoticeWrapper.php b/classes/NoticeWrapper.php
new file mode 100644
index 000000000..f8c0aa381
--- /dev/null
+++ b/classes/NoticeWrapper.php
@@ -0,0 +1,59 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/classes/Notice.php');
+
+class NoticeWrapper extends Notice {
+
+ public $id; // int(4) primary_key not_null
+ public $profile_id; // int(4) not_null
+ public $uri; // varchar(255) unique_key
+ public $content; // varchar(140)
+ public $rendered; // text()
+ public $url; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+ public $reply_to; // int(4)
+ public $is_local; // tinyint(1)
+ public $source; // varchar(32)
+
+ var $notices = NULL;
+ var $i = -1;
+
+ function __construct($arr) {
+ $this->notices = $arr;
+ }
+
+ function fetch() {
+ static $fields = array('id', 'profile_id', 'uri', 'content', 'rendered',
+ 'url', 'created', 'modified', 'reply_to', 'is_local', 'source');
+ $this->i++;
+ if ($this->i >= count($this->notices)) {
+ return false;
+ } else {
+ $n = $this->notices[$this->i];
+ foreach ($fields as $f) {
+ $this->$f = $n->$f;
+ }
+ return true;
+ }
+ }
+} \ No newline at end of file
diff --git a/classes/Notice_inbox.php b/classes/Notice_inbox.php
new file mode 100644
index 000000000..cc482bd19
--- /dev/null
+++ b/classes/Notice_inbox.php
@@ -0,0 +1,40 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Notice_inbox extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'notice_inbox'; // table name
+ public $user_id; // int(4) primary_key not_null
+ public $notice_id; // int(4) primary_key not_null
+ public $created; // datetime() not_null
+ public $source; // tinyint(1) default_1
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice_inbox',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Notice_source.php b/classes/Notice_source.php
new file mode 100644
index 000000000..e0a41b927
--- /dev/null
+++ b/classes/Notice_source.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Table Definition for notice_source
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Notice_source extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'notice_source'; // table name
+ public $code; // varchar(32) primary_key not_null
+ public $name; // varchar(255) not_null
+ public $url; // varchar(255) not_null
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice_source',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Notice_tag.php b/classes/Notice_tag.php
new file mode 100644
index 000000000..5b75ff13f
--- /dev/null
+++ b/classes/Notice_tag.php
@@ -0,0 +1,55 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Notice_tag extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'notice_tag'; // table name
+ public $tag; // varchar(64) primary_key not_null
+ public $notice_id; // int(4) primary_key not_null
+ public $created; // datetime() not_null
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice_tag',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ static function getStream($tag, $offset=0, $limit=20) {
+ $qry =
+ 'SELECT notice.* ' .
+ 'FROM notice JOIN notice_tag ON notice.id = notice_tag.notice_id ' .
+ 'WHERE notice_tag.tag = "%s" ';
+
+ return Notice::getStream(sprintf($qry, $tag),
+ 'notice_tag:notice_stream:' . common_keyize($tag),
+ $offset, $limit);
+ }
+
+ function blowCache() {
+ $cache = common_memcache();
+ if ($cache) {
+ $cache->delete(common_cache_key('notice_tag:notice_stream:' . $this->tag));
+ }
+ }
+}
diff --git a/classes/Profile.php b/classes/Profile.php
new file mode 100644
index 000000000..b57d7e38d
--- /dev/null
+++ b/classes/Profile.php
@@ -0,0 +1,159 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for profile
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Profile extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'profile'; // table name
+ public $id; // int(4) primary_key not_null
+ public $nickname; // varchar(64) multiple_key not_null
+ public $fullname; // varchar(255) multiple_key
+ public $profileurl; // varchar(255)
+ public $homepage; // varchar(255) multiple_key
+ public $bio; // varchar(140) multiple_key
+ public $location; // varchar(255) multiple_key
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function getAvatar($width, $height=NULL) {
+ if (is_null($height)) {
+ $height = $width;
+ }
+ return Avatar::pkeyGet(array('profile_id' => $this->id,
+ 'width' => $width,
+ 'height' => $height));
+ }
+
+ function getOriginalAvatar() {
+ $avatar = DB_DataObject::factory('avatar');
+ $avatar->profile_id = $this->id;
+ $avatar->original = true;
+ if ($avatar->find(true)) {
+ return $avatar;
+ } else {
+ return NULL;
+ }
+ }
+
+ function setOriginal($source) {
+
+ $info = @getimagesize($source);
+
+ if (!$info) {
+ return NULL;
+ }
+
+ $filename = common_avatar_filename($this->id,
+ image_type_to_extension($info[2]),
+ NULL, common_timestamp());
+ $filepath = common_avatar_path($filename);
+
+ copy($source, $filepath);
+
+ $avatar = new Avatar();
+
+ $avatar->profile_id = $this->id;
+ $avatar->width = $info[0];
+ $avatar->height = $info[1];
+ $avatar->mediatype = image_type_to_mime_type($info[2]);
+ $avatar->filename = $filename;
+ $avatar->original = true;
+ $avatar->url = common_avatar_url($filename);
+ $avatar->created = DB_DataObject_Cast::dateTime(); # current time
+
+ # XXX: start a transaction here
+
+ if (!$this->delete_avatars()) {
+ @unlink($filepath);
+ return NULL;
+ }
+
+ if (!$avatar->insert()) {
+ @unlink($filepath);
+ return NULL;
+ }
+
+ foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) {
+ # We don't do a scaled one if original is our scaled size
+ if (!($avatar->width == $size && $avatar->height == $size)) {
+ $s = $avatar->scale($size);
+ if (!$s) {
+ return NULL;
+ }
+ }
+ }
+
+ return $avatar;
+ }
+
+ function delete_avatars() {
+ $avatar = new Avatar();
+ $avatar->profile_id = $this->id;
+ $avatar->find();
+ while ($avatar->fetch()) {
+ $avatar->delete();
+ }
+ return true;
+ }
+
+ function getBestName() {
+ return ($this->fullname) ? $this->fullname : $this->nickname;
+ }
+
+ # Get latest notice on or before date; default now
+ function getCurrentNotice($dt=NULL) {
+ $notice = new Notice();
+ $notice->profile_id = $this->id;
+ if ($dt) {
+ $notice->whereAdd('created < "' . $dt . '"');
+ }
+ $notice->orderBy('created DESC, notice.id DESC');
+ $notice->limit(1);
+ if ($notice->find(true)) {
+ return $notice;
+ }
+ return NULL;
+ }
+
+ function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0) {
+ $qry =
+ 'SELECT * ' .
+ 'FROM notice ' .
+ 'WHERE profile_id = %d ';
+
+ return Notice::getStream(sprintf($qry, $this->id),
+ 'profile:notices:'.$this->id,
+ $offset, $limit, $since_id, $before_id);
+ }
+}
diff --git a/classes/Profile_block.php b/classes/Profile_block.php
new file mode 100644
index 000000000..6ea26a3bc
--- /dev/null
+++ b/classes/Profile_block.php
@@ -0,0 +1,49 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for profile_block
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Profile_block extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'profile_block'; // table name
+ public $blocker; // int(4) primary_key not_null
+ public $blocked; // int(4) primary_key not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile_block',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function get($blocker, $blocked) {
+ return Memcached_DataObject::pkeyGet('Profile_block',
+ array('blocker' => $blocker,
+ 'blocked' => $blocked));
+ }
+}
diff --git a/classes/Profile_tag.php b/classes/Profile_tag.php
new file mode 100644
index 000000000..dde19aea2
--- /dev/null
+++ b/classes/Profile_tag.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Table Definition for profile_tag
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Profile_tag extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'profile_tag'; // table name
+ public $tagger; // int(4) primary_key not_null
+ public $tagged; // int(4) primary_key not_null
+ public $tag; // varchar(64) primary_key not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile_tag',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ static function getTags($tagger, $tagged) {
+
+ $tags = array();
+
+ # XXX: store this in memcached
+
+ $profile_tag = new Profile_tag();
+ $profile_tag->tagger = $tagger;
+ $profile_tag->tagged = $tagged;
+
+ $profile_tag->find();
+
+ while ($profile_tag->fetch()) {
+ $tags[] = $profile_tag->tag;
+ }
+
+ $profile_tag->free();
+
+ return $tags;
+ }
+
+ static function setTags($tagger, $tagged, $newtags) {
+
+ $oldtags = Profile_tag::getTags($tagger, $tagged);
+
+ # Delete stuff that's old that not in new
+
+ $to_delete = array_diff($oldtags, $newtags);
+
+ # Insert stuff that's in new and not in old
+
+ $to_insert = array_diff($newtags, $oldtags);
+
+ $profile_tag = new Profile_tag();
+
+ $profile_tag->tagger = $tagger;
+ $profile_tag->tagged = $tagged;
+
+ $profile_tag->query('BEGIN');
+
+ foreach ($to_delete as $deltag) {
+ $profile_tag->tag = $deltag;
+ $result = $profile_tag->delete();
+ if (!$result) {
+ common_log_db_error($profile_tag, 'DELETE', __FILE__);
+ return false;
+ }
+ }
+
+ foreach ($to_insert as $instag) {
+ $profile_tag->tag = $instag;
+ $result = $profile_tag->insert();
+ if (!$result) {
+ common_log_db_error($profile_tag, 'INSERT', __FILE__);
+ return false;
+ }
+ }
+
+ $profile_tag->query('COMMIT');
+
+ return true;
+ }
+
+ # Return profiles with a given tag
+ static function getTagged($tagger, $tag) {
+ $profile = new Profile();
+ $profile->query('SELECT profile.* ' .
+ 'FROM profile JOIN profile_tag ' .
+ 'ON profile.id = profile_tag.tagged ' .
+ 'WHERE profile_tag.tagger = ' . $tagger . ' ' .
+ 'AND profile_tag.tag = "' . $tag . '" ');
+ $tagged = array();
+ while ($profile->fetch()) {
+ $tagged[] = clone($profile);
+ }
+ return $tagged;
+ }
+}
diff --git a/classes/Queue_item.php b/classes/Queue_item.php
new file mode 100644
index 000000000..8ba3281de
--- /dev/null
+++ b/classes/Queue_item.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Table Definition for queue_item
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Queue_item extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'queue_item'; // table name
+ public $notice_id; // int(4) primary_key not_null
+ public $transport; // varchar(8) primary_key not_null
+ public $created; // datetime() not_null
+ public $claimed; // datetime()
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Queue_item',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function sequenceKey() { return array(false, false); }
+
+ static function top($transport) {
+
+ $qi = new Queue_item();
+ $qi->transport = $transport;
+ $qi->orderBy('created');
+ $qi->whereAdd('claimed is NULL');
+
+ $qi->limit(1);
+
+ $cnt = $qi->find(TRUE);
+
+ if ($cnt) {
+ # XXX: potential race condition
+ # can we force it to only update if claimed is still NULL
+ # (or old)?
+ common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $transport);
+ $orig = clone($qi);
+ $qi->claimed = common_sql_now();
+ $result = $qi->update($orig);
+ if ($result) {
+ common_log(LOG_INFO, 'claim succeeded.');
+ return $qi;
+ } else {
+ common_log(LOG_INFO, 'claim failed.');
+ }
+ }
+ $qi = NULL;
+ return NULL;
+ }
+}
diff --git a/classes/Remember_me.php b/classes/Remember_me.php
new file mode 100644
index 000000000..5bbd6cf17
--- /dev/null
+++ b/classes/Remember_me.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Table Definition for remember_me
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Remember_me extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'remember_me'; // table name
+ public $code; // varchar(32) primary_key not_null
+ public $user_id; // int(4) not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Remember_me',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function sequenceKey() { return array(false, false); }
+}
diff --git a/classes/Remote_profile.php b/classes/Remote_profile.php
new file mode 100644
index 000000000..c961dcca2
--- /dev/null
+++ b/classes/Remote_profile.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for remote_profile
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Remote_profile extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'remote_profile'; // table name
+ public $id; // int(4) primary_key not_null
+ public $uri; // varchar(255) unique_key
+ public $postnoticeurl; // varchar(255)
+ public $updateprofileurl; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Remote_profile',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Reply.php b/classes/Reply.php
new file mode 100644
index 000000000..d71ce3afb
--- /dev/null
+++ b/classes/Reply.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Table Definition for reply
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Reply extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'reply'; // table name
+ public $notice_id; // int(4) primary_key not_null
+ public $profile_id; // int(4) primary_key not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+ public $replied_id; // int(4)
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Reply',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Sms_carrier.php b/classes/Sms_carrier.php
new file mode 100644
index 000000000..6ecb51346
--- /dev/null
+++ b/classes/Sms_carrier.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Table Definition for sms_carrier
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Sms_carrier extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'sms_carrier'; // table name
+ public $id; // int(4) primary_key not_null
+ public $name; // varchar(64) unique_key
+ public $email_pattern; // varchar(255) not_null
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Sms_carrier',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function toEmailAddress($sms) {
+ return sprintf($this->email_pattern, $sms);
+ }
+}
diff --git a/classes/Subscription.php b/classes/Subscription.php
new file mode 100644
index 000000000..cc174fcce
--- /dev/null
+++ b/classes/Subscription.php
@@ -0,0 +1,51 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for subscription
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Subscription extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'subscription'; // table name
+ public $subscriber; // int(4) primary_key not_null
+ public $subscribed; // int(4) primary_key not_null
+ public $jabber; // tinyint(1) default_1
+ public $sms; // tinyint(1) default_1
+ public $token; // varchar(255)
+ public $secret; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Subscription',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function &pkeyGet($kv) {
+ return Memcached_DataObject::pkeyGet('Subscription', $kv);
+ }
+}
diff --git a/classes/Token.php b/classes/Token.php
new file mode 100644
index 000000000..d180ecebe
--- /dev/null
+++ b/classes/Token.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Table Definition for token
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Token extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'token'; // table name
+ public $consumer_key; // varchar(255) primary_key not_null
+ public $tok; // char(32) primary_key not_null
+ public $secret; // char(32) not_null
+ public $type; // tinyint(1) not_null
+ public $state; // tinyint(1)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Token',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/User.php b/classes/User.php
new file mode 100644
index 000000000..32d5bedde
--- /dev/null
+++ b/classes/User.php
@@ -0,0 +1,473 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for user
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+require_once 'Validate.php';
+
+class User extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'user'; // table name
+ public $id; // int(4) primary_key not_null
+ public $nickname; // varchar(64) unique_key
+ public $password; // varchar(255)
+ public $email; // varchar(255) unique_key
+ public $incomingemail; // varchar(255) unique_key
+ public $emailnotifysub; // tinyint(1) default_1
+ public $emailnotifyfav; // tinyint(1) default_1
+ public $emailnotifynudge; // tinyint(1) default_1
+ public $emailnotifymsg; // tinyint(1) default_1
+ public $emailmicroid; // tinyint(1) default_1
+ public $language; // varchar(50)
+ public $timezone; // varchar(50)
+ public $emailpost; // tinyint(1) default_1
+ public $jabber; // varchar(255) unique_key
+ public $jabbernotify; // tinyint(1)
+ public $jabberreplies; // tinyint(1)
+ public $jabbermicroid; // tinyint(1) default_1
+ public $updatefrompresence; // tinyint(1)
+ public $sms; // varchar(64) unique_key
+ public $carrier; // int(4)
+ public $smsnotify; // tinyint(1)
+ public $smsreplies; // tinyint(1)
+ public $smsemail; // varchar(255)
+ public $uri; // varchar(255) unique_key
+ public $autosubscribe; // tinyint(1)
+ public $urlshorteningservice; // varchar(50) default_ur1.ca
+ public $inboxed; // tinyint(1)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function getProfile() {
+ return Profile::staticGet('id', $this->id);
+ }
+
+ function isSubscribed($other) {
+ assert(!is_null($other));
+ # XXX: cache results of this query
+ $sub = Subscription::pkeyGet(array('subscriber' => $this->id,
+ 'subscribed' => $other->id));
+ return (is_null($sub)) ? false : true;
+ }
+
+ # 'update' won't write key columns, so we have to do it ourselves.
+
+ function updateKeys(&$orig) {
+ $parts = array();
+ foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) {
+ if (strcmp($this->$k, $orig->$k) != 0) {
+ $parts[] = $k . ' = ' . $this->_quote($this->$k);
+ }
+ }
+ if (count($parts) == 0) {
+ # No changes
+ return true;
+ }
+ $toupdate = implode(', ', $parts);
+
+ $table = $this->tableName();
+ if(common_config('db','quote_identifiers')) {
+ $table = '"' . $table . '"';
+ }
+ $qry = 'UPDATE ' . $table . ' SET ' . $toupdate .
+ ' WHERE id = ' . $this->id;
+ $orig->decache();
+ $result = $this->query($qry);
+ if ($result) {
+ $this->encache();
+ }
+ return $result;
+ }
+
+ function allowed_nickname($nickname) {
+ # XXX: should already be validated for size, content, etc.
+ static $blacklist = array('rss', 'xrds', 'doc', 'main',
+ 'settings', 'notice', 'user',
+ 'search', 'avatar', 'tag', 'tags',
+ 'api', 'message');
+ $merged = array_merge($blacklist, common_config('nickname', 'blacklist'));
+ return !in_array($nickname, $merged);
+ }
+
+ function getCurrentNotice($dt=NULL) {
+ $profile = $this->getProfile();
+ if (!$profile) {
+ return NULL;
+ }
+ return $profile->getCurrentNotice($dt);
+ }
+
+ function getCarrier() {
+ return Sms_carrier::staticGet('id', $this->carrier);
+ }
+
+ function subscribeTo($other) {
+ $sub = new Subscription();
+ $sub->subscriber = $this->id;
+ $sub->subscribed = $other->id;
+
+ $sub->created = common_sql_now(); # current time
+
+ if (!$sub->insert()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function hasBlocked($other) {
+
+ $block = Profile_block::get($this->id, $other->id);
+
+ if (is_null($block)) {
+ $result = false;
+ } else {
+ $result = true;
+ $block->free();
+ }
+
+ return $result;
+ }
+
+ static function register($fields) {
+
+ # MAGICALLY put fields into current scope
+
+ extract($fields);
+
+ $profile = new Profile();
+
+ $profile->query('BEGIN');
+
+ $profile->nickname = $nickname;
+ $profile->profileurl = common_profile_url($nickname);
+
+ if ($fullname) {
+ $profile->fullname = $fullname;
+ }
+ if ($homepage) {
+ $profile->homepage = $homepage;
+ }
+ if ($bio) {
+ $profile->bio = $bio;
+ }
+ if ($location) {
+ $profile->location = $location;
+ }
+
+ $profile->created = common_sql_now();
+
+ $id = $profile->insert();
+
+ if (!$id) {
+ common_log_db_error($profile, 'INSERT', __FILE__);
+ return FALSE;
+ }
+
+ $user = new User();
+
+ $user->id = $id;
+ $user->nickname = $nickname;
+
+ if ($password) { # may not have a password for OpenID users
+ $user->password = common_munge_password($password, $id);
+ }
+
+ # Users who respond to invite email have proven their ownership of that address
+
+ if ($code) {
+ $invite = Invitation::staticGet($code);
+ if ($invite && $invite->address && $invite->address_type == 'email' && $invite->address == $email) {
+ $user->email = $invite->address;
+ }
+ }
+
+ $inboxes = common_config('inboxes', 'enabled');
+
+ if ($inboxes === true || $inboxes == 'transitional') {
+ $user->inboxed = 1;
+ }
+
+ $user->created = common_sql_now();
+ $user->uri = common_user_uri($user);
+
+ $result = $user->insert();
+
+ if (!$result) {
+ common_log_db_error($user, 'INSERT', __FILE__);
+ return FALSE;
+ }
+
+ # Everyone is subscribed to themself
+
+ $subscription = new Subscription();
+ $subscription->subscriber = $user->id;
+ $subscription->subscribed = $user->id;
+ $subscription->created = $user->created;
+
+ $result = $subscription->insert();
+
+ if (!$result) {
+ common_log_db_error($subscription, 'INSERT', __FILE__);
+ return FALSE;
+ }
+
+ if ($email && !$user->email) {
+
+ $confirm = new Confirm_address();
+ $confirm->code = common_confirmation_code(128);
+ $confirm->user_id = $user->id;
+ $confirm->address = $email;
+ $confirm->address_type = 'email';
+
+ $result = $confirm->insert();
+ if (!$result) {
+ common_log_db_error($confirm, 'INSERT', __FILE__);
+ return FALSE;
+ }
+ }
+
+ if ($code && $user->email) {
+ $user->emailChanged();
+ }
+
+ $profile->query('COMMIT');
+
+ if ($email && !$user->email) {
+ mail_confirm_address($user, $confirm->code, $profile->nickname, $email);
+ }
+
+ return $user;
+ }
+
+ # Things we do when the email changes
+
+ function emailChanged() {
+
+ $invites = new Invitation();
+ $invites->address = $this->email;
+ $invites->address_type = 'email';
+
+ if ($invites->find()) {
+ while ($invites->fetch()) {
+ $other = User::staticGet($invites->user_id);
+ subs_subscribe_to($other, $this);
+ }
+ }
+ }
+
+ function hasFave($notice) {
+ $cache = common_memcache();
+
+ # XXX: Kind of a hack.
+ if ($cache) {
+ # This is the stream of favorite notices, in rev chron
+ # order. This forces it into cache.
+ $faves = $this->favoriteNotices(0, NOTICE_CACHE_WINDOW);
+ $cnt = 0;
+ while ($faves->fetch()) {
+ if ($faves->id < $notice->id) {
+ # If we passed it, it's not a fave
+ return false;
+ } else if ($faves->id == $notice->id) {
+ # If it matches a cached notice, then it's a fave
+ return true;
+ }
+ $cnt++;
+ }
+ # If we're not past the end of the cache window,
+ # then the cache has all available faves, so this one
+ # is not a fave.
+ if ($cnt < NOTICE_CACHE_WINDOW) {
+ return false;
+ }
+ # Otherwise, cache doesn't have all faves;
+ # fall through to the default
+ }
+ $fave = Fave::pkeyGet(array('user_id' => $this->id,
+ 'notice_id' => $notice->id));
+ return ((is_null($fave)) ? false : true);
+ }
+ function mutuallySubscribed($other) {
+ return $this->isSubscribed($other) &&
+ $other->isSubscribed($this);
+ }
+
+ function mutuallySubscribedUsers() {
+
+ # 3-way join; probably should get cached
+ $qry = 'SELECT user.* ' .
+ 'FROM subscription sub1 JOIN user ON sub1.subscribed = user.id ' .
+ 'JOIN subscription sub2 ON user.id = sub2.subscriber ' .
+ 'WHERE sub1.subscriber = %d and sub2.subscribed = %d ' .
+ 'ORDER BY user.nickname';
+ $user = new User();
+ $user->query(sprintf($qry, $this->id, $this->id));
+
+ return $user;
+ }
+
+ function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) {
+ $qry =
+ 'SELECT notice.* ' .
+ 'FROM notice JOIN reply ON notice.id = reply.notice_id ' .
+ 'WHERE reply.profile_id = %d ';
+ return Notice::getStream(sprintf($qry, $this->id),
+ 'user:replies:'.$this->id,
+ $offset, $limit, $since_id, $before_id, NULL, $since);
+ }
+
+ function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) {
+ $profile = $this->getProfile();
+ if (!$profile) {
+ return NULL;
+ } else {
+ return $profile->getNotices($offset, $limit, $since_id, $before_id);
+ }
+ }
+
+ function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE) {
+ $qry =
+ 'SELECT notice.* ' .
+ 'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
+ 'WHERE fave.user_id = %d ';
+ return Notice::getStream(sprintf($qry, $this->id),
+ 'user:faves:'.$this->id,
+ $offset, $limit);
+ }
+
+ function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=NULL) {
+ $enabled = common_config('inboxes', 'enabled');
+
+ # Complicated code, depending on whether we support inboxes yet
+ # XXX: make this go away when inboxes become mandatory
+
+ if ($enabled === false ||
+ ($enabled == 'transitional' && $this->inboxed == 0)) {
+ $qry =
+ 'SELECT notice.* ' .
+ 'FROM notice JOIN subscription ON notice.profile_id = subscription.subscribed ' .
+ 'WHERE subscription.subscriber = %d ';
+ $order = NULL;
+ } else if ($enabled === true ||
+ ($enabled == 'transitional' && $this->inboxed == 1)) {
+
+ $qry =
+ 'SELECT notice.* ' .
+ 'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
+ 'WHERE notice_inbox.user_id = %d ';
+ # NOTE: we override ORDER
+ $order = 'ORDER BY notice_inbox.created DESC, notice_inbox.notice_id DESC ';
+ }
+ return Notice::getStream(sprintf($qry, $this->id),
+ 'user:notices_with_friends:' . $this->id,
+ $offset, $limit, $since_id, $before_id,
+ $order, $since);
+ }
+
+ function blowFavesCache() {
+ $cache = common_memcache();
+ if ($cache) {
+ # Faves don't happen chronologically, so we need to blow
+ # ;last cache, too
+ $cache->delete(common_cache_key('user:faves:'.$this->id));
+ $cache->delete(common_cache_key('user:faves:'.$this->id).';last');
+ }
+ }
+
+ function getSelfTags() {
+ return Profile_tag::getTags($this->id, $this->id);
+ }
+
+ function setSelfTags($newtags) {
+ return Profile_tag::setTags($this->id, $this->id, $newtags);
+ }
+
+ function block($other) {
+
+ # Add a new block record
+
+ $block = new Profile_block();
+
+ # Begin a transaction
+
+ $block->query('BEGIN');
+
+ $block->blocker = $this->id;
+ $block->blocked = $other->id;
+
+ $result = $block->insert();
+
+ if (!$result) {
+ common_log_db_error($block, 'INSERT', __FILE__);
+ return false;
+ }
+
+ # Cancel their subscription, if it exists
+
+ $sub = Subscription::pkeyGet(array('subscriber' => $other->id,
+ 'subscribed' => $this->id));
+
+ if ($sub) {
+ $result = $sub->delete();
+ if (!$result) {
+ common_log_db_error($sub, 'DELETE', __FILE__);
+ return false;
+ }
+ }
+
+ $block->query('COMMIT');
+
+ return true;
+ }
+
+ function unblock($other) {
+
+ # Get the block record
+
+ $block = Profile_block::get($this->id, $other->id);
+
+ if (!$block) {
+ return false;
+ }
+
+ $result = $block->delete();
+
+ if (!$result) {
+ common_log_db_error($block, 'DELETE', __FILE__);
+ return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/classes/User_openid.php b/classes/User_openid.php
new file mode 100644
index 000000000..ad68f7402
--- /dev/null
+++ b/classes/User_openid.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Table Definition for user_openid
+ */
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class User_openid extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'user_openid'; // table name
+ public $canonical; // varchar(255) primary_key not_null
+ public $display; // varchar(255) unique_key not_null
+ public $user_id; // int(4) not_null
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_openid',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/laconica.ini b/classes/laconica.ini
new file mode 100644
index 000000000..db76b2dee
--- /dev/null
+++ b/classes/laconica.ini
@@ -0,0 +1,344 @@
+
+[avatar]
+profile_id = 129
+original = 17
+width = 129
+height = 129
+mediatype = 130
+filename = 2
+url = 2
+created = 142
+modified = 384
+
+[avatar__keys]
+profile_id = K
+width = K
+height = K
+url = U
+
+[confirm_address]
+code = 130
+user_id = 129
+address = 130
+address_extra = 130
+address_type = 130
+claimed = 14
+sent = 14
+modified = 384
+
+[confirm_address__keys]
+code = K
+
+[consumer]
+consumer_key = 130
+seed = 130
+created = 142
+modified = 384
+
+[consumer__keys]
+consumer_key = K
+
+[fave]
+notice_id = 129
+user_id = 129
+modified = 384
+
+[fave__keys]
+notice_id = K
+user_id = K
+
+[foreign_link]
+user_id = 129
+foreign_id = 129
+service = 129
+credentials = 2
+noticesync = 145
+friendsync = 145
+profilesync = 145
+created = 142
+modified = 384
+
+[foreign_link__keys]
+user_id = K
+foreign_id = K
+service = K
+
+[foreign_service]
+id = 129
+name = 130
+description = 2
+created = 142
+modified = 384
+
+[foreign_service__keys]
+id = K
+name = U
+
+[foreign_subscription]
+service = 129
+subscriber = 129
+subscribed = 129
+created = 142
+
+[foreign_subscription__keys]
+service = K
+subscriber = K
+subscribed = K
+
+[foreign_user]
+id = 129
+service = 129
+uri = 130
+nickname = 2
+created = 142
+modified = 384
+
+[foreign_user__keys]
+id = K
+service = K
+uri = U
+
+[invitation]
+code = 130
+user_id = 129
+address = 130
+address_type = 130
+created = 142
+
+[invitation__keys]
+code = K
+
+[message]
+id = 129
+uri = 2
+from_profile = 129
+to_profile = 129
+content = 2
+rendered = 34
+url = 2
+created = 142
+modified = 384
+source = 2
+
+[message__keys]
+id = N
+
+[nonce]
+consumer_key = 130
+tok = 130
+nonce = 130
+ts = 142
+created = 142
+modified = 384
+
+[nonce__keys]
+consumer_key = K
+tok = K
+nonce = K
+
+[notice]
+id = 129
+profile_id = 129
+uri = 2
+content = 2
+rendered = 34
+url = 2
+created = 142
+modified = 384
+reply_to = 1
+is_local = 17
+source = 2
+
+[notice__keys]
+id = N
+
+[notice_inbox]
+user_id = 129
+notice_id = 129
+created = 142
+source = 17
+
+[notice_inbox__keys]
+user_id = K
+notice_id = K
+
+[notice_source]
+code = 130
+name = 130
+url = 130
+created = 142
+modified = 384
+
+[notice_source__keys]
+code = K
+
+[notice_tag]
+tag = 130
+notice_id = 129
+created = 142
+
+[notice_tag__keys]
+tag = K
+notice_id = K
+
+[profile]
+id = 129
+nickname = 130
+fullname = 2
+profileurl = 2
+homepage = 2
+bio = 2
+location = 2
+created = 142
+modified = 384
+
+[profile__keys]
+id = N
+
+[profile_block]
+blocker = 129
+blocked = 129
+modified = 384
+
+[profile_block__keys]
+blocker = K
+blocked = K
+
+[profile_tag]
+tagger = 129
+tagged = 129
+tag = 130
+modified = 384
+
+[profile_tag__keys]
+tagger = K
+tagged = K
+tag = K
+
+[queue_item]
+notice_id = 129
+transport = 130
+created = 142
+claimed = 14
+
+[queue_item__keys]
+notice_id = K
+transport = K
+
+[remember_me]
+code = 130
+user_id = 129
+modified = 384
+
+[remember_me__keys]
+code = K
+
+[remote_profile]
+id = 129
+uri = 2
+postnoticeurl = 2
+updateprofileurl = 2
+created = 142
+modified = 384
+
+[remote_profile__keys]
+id = K
+uri = U
+
+[reply]
+notice_id = 129
+profile_id = 129
+modified = 384
+replied_id = 1
+
+[reply__keys]
+notice_id = K
+profile_id = K
+
+[sms_carrier]
+id = 129
+name = 2
+email_pattern = 130
+created = 142
+modified = 384
+
+[sms_carrier__keys]
+id = N
+
+[subscription]
+subscriber = 129
+subscribed = 129
+jabber = 17
+sms = 17
+token = 2
+secret = 2
+created = 142
+modified = 384
+
+[subscription__keys]
+subscriber = K
+subscribed = K
+
+[token]
+consumer_key = 130
+tok = 130
+secret = 130
+type = 145
+state = 17
+created = 142
+modified = 384
+
+[token__keys]
+consumer_key = K
+tok = K
+
+[user]
+id = 129
+nickname = 2
+password = 2
+email = 2
+incomingemail = 2
+emailnotifysub = 17
+emailnotifyfav = 17
+emailnotifynudge = 17
+emailnotifymsg = 17
+emailmicroid = 17
+language = 2
+timezone = 2
+emailpost = 17
+jabber = 2
+jabbernotify = 17
+jabberreplies = 17
+jabbermicroid = 17
+updatefrompresence = 17
+sms = 2
+carrier = 1
+smsnotify = 17
+smsreplies = 17
+smsemail = 2
+uri = 2
+autosubscribe = 17
+urlshorteningservice = 2
+inboxed = 17
+created = 142
+modified = 384
+
+[user__keys]
+id = K
+nickname = U
+email = U
+incomingemail = U
+jabber = U
+sms = U
+uri = U
+
+[user_openid]
+canonical = 130
+display = 130
+user_id = 129
+created = 142
+modified = 384
+
+[user_openid__keys]
+canonical = K
+display = U
diff --git a/classes/laconica.links.ini b/classes/laconica.links.ini
new file mode 100644
index 000000000..173b18726
--- /dev/null
+++ b/classes/laconica.links.ini
@@ -0,0 +1,43 @@
+[avatar]
+profile_id = profile:id
+
+[user]
+id = profile:id
+carrier = sms_carrier:id
+
+[remote_profile]
+id = profile:id
+
+[notice]
+profile_id = profile:id
+reply_to = notice:id
+
+[reply]
+notice_id = notice:id
+profile_id = profile:id
+
+[token]
+consumer_key = consumer:consumer_key
+
+[nonce]
+consumer_key,token = token:consumer_key,token
+
+[user_openid]
+user_id = user:id
+
+[confirm_address]
+user_id = user:id
+
+[remember_me]
+user_id = user:id
+
+[queue_item]
+notice_id = notice:id
+
+[subscription]
+subscriber = profile:id
+subscribed = profile:id
+
+[fave]
+notice_id = notice:id
+user_id = user:id
diff --git a/config.php.sample b/config.php.sample
new file mode 100644
index 000000000..db1a21663
--- /dev/null
+++ b/config.php.sample
@@ -0,0 +1,144 @@
+<?php
+/* -*- mode: php -*- */
+
+if (!defined('LACONICA')) { exit(1); }
+
+#If you have downloaded libraries in random little places, you
+#can add the paths here
+
+#$extra_path = array("/opt/php-openid-2.0.1", "/usr/local/share/php");
+#set_include_path(implode(PATH_SEPARATOR, $extra_path) . PATH_SEPARATOR . get_include_path());
+
+# We get called by common.php, $config is a tree with lots of config
+# options
+# These are for configuring your URLs
+
+$config['site']['name'] = 'Just another Laconica microblog';
+$config['site']['server'] = 'localhost';
+$config['site']['path'] = 'laconica';
+#$config['site']['fancy'] = false;
+#$config['site']['theme'] = 'default';
+#For contact email, defaults to $_SERVER["SERVER_ADMIN"]
+#$config['site']['email'] = 'admin@example.net';
+#Brought by...
+#$config['site']['broughtby'] = 'Individual or Company';
+#$config['site']['broughtbyurl'] = 'http://example.net/';
+#If you don't want to let users register (say, for a one-person install)
+#Crude but effective -- register everybody, then lock down
+#$config['site']['closed'] = true;
+#Only allow registration for people invited by another user
+#$config['site']['inviteonly'] = true;
+
+# If you want logging sent to a file instead of syslog
+#$config['site']['logfile'] = '/tmp/laconica.log';
+
+# This is a PEAR DB DSN, see http://pear.php.net/manual/en/package.database.db.intro-dsn.php
+# Set it to match your actual database
+
+$config['db']['database'] = 'mysql://laconica:microblog@localhost/laconica';
+#$config['db']['ini_your_db_name'] = $config['db']['schema_location'].'/laconica.ini';
+# *** WARNING *** WARNING *** WARNING *** WARNING ***
+# Setting debug to a non-zero value will expose your DATABASE PASSWORD to Web users.
+# !!!!!! DO NOT SET THIS ON PRODUCTION SERVERS !!!!!! DB_DataObject's bug, btw, not
+# ours.
+# *** WARNING *** WARNING *** WARNING *** WARNING ***
+#$config['db']['debug'] = 0;
+#$config['db']['db_driver'] = 'MDB2';
+
+#Database type. For mysql, these defaults are fine. For postgresql, set
+#'quote_identifiers' to true and 'type' to 'pgsql':
+#$config['db']['quote_identifiers'] = false;
+#$config['db']['type'] = 'mysql';
+
+#session_set_cookie_params(0, '/'. $config['site']['path'] .'/');
+
+#Standard fancy-url clashes prevented by not allowing nicknames on a blacklist
+#Add your own here. Note: empty array by default
+#$config['nickname']['blacklist'][] = 'scobleizer';
+
+# sphinx search
+$config['sphinx']['enabled'] = false;
+$config['sphinx']['server'] = 'localhost';
+$config['sphinx']['port'] = 3312;
+
+# Users to populate the 'Featured' tab
+#$config['nickname']['featured'][] = 'scobleizer';
+
+# xmpp
+#$config['xmpp']['enabled'] = false;
+#$config['xmpp']['server'] = 'server.example.net';
+#$config['xmpp']['host'] = NULL; # Only set if different from server
+#$config['xmpp']['port'] = 5222;
+#$config['xmpp']['user'] = 'update';
+#$config['xmpp']['encryption'] = false;
+#$config['xmpp']['resource'] = 'uniquename';
+#$config['xmpp']['password'] = 'blahblahblah';
+#$config['xmpp']['public'][] = 'someindexer@example.net';
+#$config['xmpp']['debug'] = false;
+
+#Default locale info
+#$config['site']['timezone'] = 'Pacific/Auckland';
+#$config['site']['language'] = 'en_NZ';
+
+#Email info, used for all outbound email
+#$config['mail']['notifyfrom'] = 'microblog@example.net';
+#$config['mail']['domain'] = 'microblog.example.net';
+# See http://pear.php.net/manual/en/package.mail.mail.factory.php for options
+#$config['mail']['backend'] = 'smtp';
+#$config['mail']['params'] = array(
+# 'host' => 'localhost',
+# 'port' => 25,
+# );
+#For incoming email, if enabled. Defaults to site server name.
+#$config['mail']['domain'] = 'incoming.example.net';
+
+#exponential decay factor for tags, default 10 days
+#raise this if traffic is slow, lower it if it's fast
+#$config['tag']['dropoff'] = 86400.0 * 10;
+
+#exponential decay factor for popular (most favorited notices)
+#default 10 days -- similar to tag dropoff
+#$config['popular']['dropoff'] = 86400.0 * 10;
+
+#optionally show non-local messages in public timeline
+#$config['public']['localonly'] = false;
+
+#hide certain users from public pages, by ID
+#$config['public']['blacklist'][] = 123;
+#$config['public']['blacklist'][] = 2307;
+
+#Do notice broadcasts offline
+#If you use this, you must run the six offline daemons in the
+#background. See the README for details.
+#$config['queue']['enabled'] = true;
+
+#The following customise the behaviour of the various daemons:
+#$config['daemon']['piddir'] = '/var/run';
+#$config['daemon']['user'] = false;
+#$config['daemon']['group'] = false;
+
+#For installations with high traffic, laconica can use MemCached to cache
+#frequently requested information. Only enable the following if you have
+#MemCached up and running:
+#$config['memcached']['enabled'] = false;
+#$config['memcached']['server'] = 'localhost';
+#$config['memcached']['port'] = 11211;
+
+#Twitter integration source attribute. Note: default is Laconica
+#$config['integration']['source'] = 'Laconica';
+
+# Edit throttling. Off by default. If turned on, you can only post 20 notices
+# every 10 minutes. Admins may want to play with the settings to minimize inconvenience for
+# real users without getting uncontrollable floods from spammers or runaway bots.
+
+#$config['throttle']['enabled'] = true;
+#$config['throttle']['count'] = 100;
+#$config['throttle']['timespan'] = 3600;
+
+# List of users banned from posting (nicknames and/or IDs)
+#$config['profile']['banned'][] = 'hacker';
+#$config['profile']['banned'][] = 12345;
+
+# config section for the built-in Facebook application
+#$config['facebook']['apikey'] = 'APIKEY';
+#$config['facebook']['secret'] = 'SECRET';
diff --git a/db/carrier.sql b/db/carrier.sql
new file mode 100644
index 000000000..932f7c8bb
--- /dev/null
+++ b/db/carrier.sql
@@ -0,0 +1,61 @@
+insert into sms_carrier
+ (name, email_pattern, created)
+values
+ ('3 River Wireless', '%s@sms.3rivers.net', now()),
+ ('7-11 Speakout', '%s@cingularme.com', now()),
+ ('Airtel (Karnataka, India)', '%s@airtelkk.com', now()),
+ ('Alaska Communications Systems', '%s@msg.acsalaska.com', now()),
+ ('Alltel Wireless', '%s@message.alltel.com', now()),
+ ('AT&T Wireless', '%s@txt.att.net', now()),
+ ('Bell Mobility (Canada)', '%s@txt.bell.ca', now()),
+ ('Boost Mobile', '%s@myboostmobile.com', now()),
+ ('Cellular One (Dobson)', '%s@mobile.celloneusa.com', now()),
+ ('Cincinnati Bell Wireless', '%s@gocbw.com', now()),
+ ('Cingular (Postpaid)', '%s@cingularme.com', now()),
+ ('Centennial Wireless', '%s@cwemail.com', now()),
+ ('Cingular (GoPhone prepaid)', '%s@cingularme.com', now()),
+ ('Claro (Nicaragua)', '%s@ideasclaro-ca.com', now()),
+ ('Comcel', '%s@comcel.com.co', now()),
+ ('Cricket', '%s@sms.mycricket.com', now()),
+ ('CTI', '%s@sms.ctimovil.com.ar', now()),
+ ('Emtel (Mauritius)', '%s@emtelworld.net', now()),
+ ('Fido (Canada)', '%s@fido.ca', now()),
+ ('General Communications Inc.', '%s@msg.gci.net', now()),
+ ('Globalstar', '%s@msg.globalstarusa.com', now()),
+ ('Helio', '%s@myhelio.com', now()),
+ ('Illinois Valley Cellular', '%s@ivctext.com', now()),
+ ('i wireless', '%s.iws@iwspcs.net', now()),
+ ('Meteor (Ireland)', '%s@sms.mymeteor.ie', now()),
+ ('Mero Mobile (Nepal)', '%s@sms.spicenepal.com', now()),
+ ('MetroPCS', '%s@mymetropcs.com', now()),
+ ('Movicom', '%s@movimensaje.com.ar', now()),
+ ('Mobitel (Sri Lanka)', '%s@sms.mobitel.lk', now()),
+ ('Movistar (Colombia)', '%s@movistar.com.co', now()),
+ ('MTN (South Africa)', '%s@sms.co.za', now()),
+ ('MTS (Canada)', '%s@text.mtsmobility.com', now()),
+ ('Nextel (Argentina)', '%s@nextel.net.ar', now()),
+ ('Orange (Poland)', '%s@orange.pl', now()),
+ ('Orange (UK)', '%s@orange.net', now()),
+ ('Personal (Argentina)', '%s@personal-net.com.ar', now()),
+ ('Plus GSM (Poland)', '%s@text.plusgsm.pl', now()),
+ ('President''s Choice (Canada)', '%s@txt.bell.ca', now()),
+ ('Qwest', '%s@qwestmp.com', now()),
+ ('Rogers (Canada)', '%s@pcs.rogers.com', now()),
+ ('Sasktel (Canada)', '%s@sms.sasktel.com', now()),
+ ('Setar Mobile email (Aruba)', '%s@mas.aw', now()),
+ ('Solo Mobile', '%s@txt.bell.ca', now()),
+ ('Sprint (PCS)', '%s@messaging.sprintpcs.com', now()),
+ ('Sprint (Nextel)', '%s@page.nextel.com', now()),
+ ('Suncom', '%s@tms.suncom.com', now()),
+ ('T-Mobile', '%s@tmomail.net', now()),
+ ('T-Mobile (Austria)', '%s@sms.t-mobile.at', now()),
+ ('Telus Mobility (Canada)', '%s@msg.telus.com', now()),
+ ('Thumb Cellular', '%s@sms.thumbcellular.com', now()),
+ ('Tigo (Formerly Ola)', '%s@sms.tigo.com.co', now()),
+ ('Unicel', '%s@utext.com', now()),
+ ('US Cellular', '%s@email.uscc.net', now()),
+ ('Verizon', '%s@vtext.com', now()),
+ ('Virgin Mobile (Canada)', '%s@vmobile.ca', now()),
+ ('Virgin Mobile (USA)', '%s@vmobl.com', now()),
+ ('Vodafone NZ (txt ''R'' to 901 to enable first)', '%s@sms.vodafone.net.nz', now()),
+ ('YCC', '%s@sms.ycc.ru', now());
diff --git a/db/foreign_services.sql b/db/foreign_services.sql
new file mode 100644
index 000000000..512d42513
--- /dev/null
+++ b/db/foreign_services.sql
@@ -0,0 +1,8 @@
+insert into foreign_service
+ (id, name, description, created)
+values
+ ('1','Twitter', 'Twitter Micro-blogging service', now());
+insert into foreign_service
+ (id, name, description, created)
+values
+ ('2','Facebook', 'Facebook', now());
diff --git a/db/laconica.sql b/db/laconica.sql
new file mode 100644
index 000000000..a366a6bcb
--- /dev/null
+++ b/db/laconica.sql
@@ -0,0 +1,370 @@
+/* local and remote users have profiles */
+
+create table profile (
+ id integer auto_increment primary key comment 'unique identifier',
+ nickname varchar(64) not null comment 'nickname or username',
+ fullname varchar(255) comment 'display name',
+ profileurl varchar(255) comment 'URL, cached so we dont regenerate',
+ homepage varchar(255) comment 'identifying URL',
+ bio varchar(140) comment 'descriptive biography',
+ location varchar(255) comment 'physical location',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ index profile_nickname_idx (nickname),
+ FULLTEXT(nickname, fullname, location, bio, homepage)
+) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table avatar (
+ profile_id integer not null comment 'foreign key to profile table' references profile (id),
+ original boolean default false comment 'uploaded by user or generated?',
+ width integer not null comment 'image width',
+ height integer not null comment 'image height',
+ mediatype varchar(32) not null comment 'file type',
+ filename varchar(255) null comment 'local filename, if local',
+ url varchar(255) unique key comment 'avatar location',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (profile_id, width, height),
+ index avatar_profile_id_idx (profile_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table sms_carrier (
+ id integer auto_increment primary key comment 'primary key for SMS carrier',
+ name varchar(64) unique key comment 'name of the carrier',
+ email_pattern varchar(255) not null comment 'sprintf pattern for making an email address from a phone number',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* local users */
+
+create table user (
+ id integer primary key comment 'foreign key to profile table' references profile (id),
+ nickname varchar(64) unique key comment 'nickname or username, duped in profile',
+ password varchar(255) comment 'salted password, can be null for OpenID users',
+ email varchar(255) unique key comment 'email address for password recovery etc.',
+ incomingemail varchar(255) unique key comment 'email address for post-by-email',
+ emailnotifysub tinyint default 1 comment 'Notify by email of subscriptions',
+ emailnotifyfav tinyint default 1 comment 'Notify by email of favorites',
+ emailnotifynudge tinyint default 1 comment 'Notify by email of nudges',
+ emailnotifymsg tinyint default 1 comment 'Notify by email of direct messages',
+ emailmicroid tinyint default 1 comment 'whether to publish email microid',
+ language varchar(50) comment 'preferred language',
+ timezone varchar(50) comment 'timezone',
+ emailpost tinyint default 1 comment 'Post by email',
+ jabber varchar(255) unique key comment 'jabber ID for notices',
+ jabbernotify tinyint default 0 comment 'whether to send notices to jabber',
+ jabberreplies tinyint default 0 comment 'whether to send notices to jabber on replies',
+ jabbermicroid tinyint default 1 comment 'whether to publish xmpp microid',
+ updatefrompresence tinyint default 0 comment 'whether to record updates from Jabber presence notices',
+ sms varchar(64) unique key comment 'sms phone number',
+ carrier integer comment 'foreign key to sms_carrier' references sms_carrier (id),
+ smsnotify tinyint default 0 comment 'whether to send notices to SMS',
+ smsreplies tinyint default 0 comment 'whether to send notices to SMS on replies',
+ smsemail varchar(255) comment 'built from sms and carrier',
+ uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
+ autosubscribe tinyint default 0 comment 'automatically subscribe to users who subscribe to us',
+ urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs',
+ inboxed tinyint default 0 comment 'has an inbox been created for this user?',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ index user_smsemail_idx (smsemail)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* remote people */
+
+create table remote_profile (
+ id integer primary key comment 'foreign key to profile table' references profile (id),
+ uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
+ postnoticeurl varchar(255) comment 'URL we use for posting notices',
+ updateprofileurl varchar(255) comment 'URL we use for updates to this profile',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table subscription (
+ subscriber integer not null comment 'profile listening',
+ subscribed integer not null comment 'profile being listened to',
+ jabber tinyint default 1 comment 'deliver jabber messages',
+ sms tinyint default 1 comment 'deliver sms messages',
+ token varchar(255) comment 'authorization token',
+ secret varchar(255) comment 'token secret',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (subscriber, subscribed),
+ index subscription_subscriber_idx (subscriber),
+ index subscription_subscribed_idx (subscribed),
+ index subscription_token_idx (token)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table notice (
+
+ id integer auto_increment primary key comment 'unique identifier',
+ profile_id integer not null comment 'who made the update' references profile (id),
+ uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
+ content varchar(140) comment 'update content',
+ rendered text comment 'HTML version of the content',
+ url varchar(255) comment 'URL of any attachment (image, video, bookmark, whatever)',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+ reply_to integer comment 'notice replied to (usually a guess)' references notice (id),
+ is_local tinyint default 0 comment 'notice was generated by a user',
+ source varchar(32) comment 'source of comment, like "web", "im", or "clientname"',
+
+ index notice_profile_id_idx (profile_id),
+ index notice_created_idx (created),
+ FULLTEXT(content)
+) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table notice_source (
+ code varchar(32) primary key not null comment 'source code',
+ name varchar(255) not null comment 'name of the source',
+ url varchar(255) not null comment 'url to link to',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table reply (
+
+ notice_id integer not null comment 'notice that is the reply' references notice (id),
+ profile_id integer not null comment 'profile replied to' references profile (id),
+ modified timestamp not null comment 'date this record was modified',
+ replied_id integer comment 'notice replied to (not used, see notice.reply_to)',
+
+ constraint primary key (notice_id, profile_id),
+ index reply_notice_id_idx (notice_id),
+ index reply_profile_id_idx (profile_id),
+ index reply_replied_id_idx (replied_id)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table fave (
+
+ notice_id integer not null comment 'notice that is the favorite' references notice (id),
+ user_id integer not null comment 'user who likes this notice' references user (id),
+ modified timestamp not null comment 'date this record was modified',
+
+ constraint primary key (notice_id, user_id),
+ index fave_notice_id_idx (notice_id),
+ index fave_user_id_idx (user_id),
+ index fave_modified_idx (modified)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* tables for OAuth */
+
+create table consumer (
+ consumer_key varchar(255) primary key comment 'unique identifier, root URL',
+ seed char(32) not null comment 'seed for new tokens by this consumer',
+
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table token (
+ consumer_key varchar(255) not null comment 'unique identifier, root URL' references consumer (consumer_key),
+ tok char(32) not null comment 'identifying value',
+ secret char(32) not null comment 'secret value',
+ type tinyint not null default 0 comment 'request or access',
+ state tinyint default 0 comment 'for requests; 0 = initial, 1 = authorized, 2 = used',
+
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (consumer_key, tok)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table nonce (
+ consumer_key varchar(255) not null comment 'unique identifier, root URL',
+ tok char(32) not null comment 'identifying value',
+ nonce char(32) not null comment 'nonce',
+ ts datetime not null comment 'timestamp sent',
+
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (consumer_key, tok, nonce),
+ constraint foreign key (consumer_key, tok) references token (consumer_key, tok)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* One-to-many relationship of user to openid_url */
+
+create table user_openid (
+ canonical varchar(255) primary key comment 'Canonical true URL',
+ display varchar(255) not null unique key comment 'URL for viewing, may be different from canonical',
+ user_id integer not null comment 'user owning this URL' references user (id),
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ index user_openid_user_id_idx (user_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* These are used by JanRain OpenID library */
+
+create table oid_associations (
+ server_url BLOB,
+ handle VARCHAR(255) character set latin1,
+ secret BLOB,
+ issued INTEGER,
+ lifetime INTEGER,
+ assoc_type VARCHAR(64),
+ PRIMARY KEY (server_url(255), handle)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table oid_nonces (
+ server_url VARCHAR(2047),
+ timestamp INTEGER,
+ salt CHAR(40),
+ UNIQUE (server_url(255), timestamp, salt)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table confirm_address (
+ code varchar(32) not null primary key comment 'good random code',
+ user_id integer not null comment 'user who requested confirmation' references user (id),
+ address varchar(255) not null comment 'address (email, Jabber, SMS, etc.)',
+ address_extra varchar(255) not null comment 'carrier ID, for SMS',
+ address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")',
+ claimed datetime comment 'date this was claimed for queueing',
+ sent datetime comment 'date this was sent for queueing',
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table remember_me (
+ code varchar(32) not null primary key comment 'good random code',
+ user_id integer not null comment 'user who is logged in' references user (id),
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table queue_item (
+
+ notice_id integer not null comment 'notice queued' references notice (id),
+ transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...',
+ created datetime not null comment 'date this record was created',
+ claimed datetime comment 'date this item was claimed',
+
+ constraint primary key (notice_id, transport),
+ index queue_item_created_idx (created)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* Hash tags */
+create table notice_tag (
+ tag varchar( 64 ) not null comment 'hash tag associated with this notice',
+ notice_id integer not null comment 'notice tagged' references notice (id),
+ created datetime not null comment 'date this record was created',
+
+ constraint primary key (tag, notice_id),
+ index notice_tag_created_idx (created)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+/* Synching with foreign services */
+
+create table foreign_service (
+ id int not null primary key comment 'numeric key for service',
+ name varchar(32) not null unique key comment 'name of the service',
+ description varchar(255) comment 'description',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table foreign_user (
+ id int not null comment 'unique numeric key on foreign service',
+ service int not null comment 'foreign key to service' references foreign_service(id),
+ uri varchar(255) not null unique key comment 'identifying URI',
+ nickname varchar(255) comment 'nickname on foreign service',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (id, service)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table foreign_link (
+ user_id int comment 'link to user on this system, if exists' references user (id),
+ foreign_id int comment 'link ' references foreign_user(id),
+ service int not null comment 'foreign key to service' references foreign_service(id),
+ credentials varchar(255) comment 'authc credentials, typically a password',
+ noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies',
+ friendsync tinyint not null default 2 comment 'friend synchronization, bit 1 = sync outgoing, bit 2 = sync incoming',
+ profilesync tinyint not null default 1 comment 'profile synchronization, bit 1 = sync outgoing, bit 2 = sync incoming',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+
+ constraint primary key (user_id, foreign_id, service),
+ index foreign_user_user_id_idx (user_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table foreign_subscription (
+ service int not null comment 'service where relationship happens' references foreign_service(id),
+ subscriber int not null comment 'subscriber on foreign service' references foreign_user (id),
+ subscribed int not null comment 'subscribed user' references foreign_user (id),
+ created datetime not null comment 'date this record was created',
+
+ constraint primary key (service, subscriber, subscribed),
+ index foreign_subscription_subscriber_idx (subscriber),
+ index foreign_subscription_subscribed_idx (subscribed)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table invitation (
+ code varchar(32) not null primary key comment 'random code for an invitation',
+ user_id int not null comment 'who sent the invitation' references user (id),
+ address varchar(255) not null comment 'invitation sent to',
+ address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")',
+ created datetime not null comment 'date this record was created',
+
+ index invitation_address_idx (address, address_type),
+ index invitation_user_id_idx (user_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table message (
+
+ id integer auto_increment primary key comment 'unique identifier',
+ uri varchar(255) unique key comment 'universally unique identifier',
+ from_profile integer not null comment 'who the message is from' references profile (id),
+ to_profile integer not null comment 'who the message is to' references profile (id),
+ content varchar(140) comment 'message content',
+ rendered text comment 'HTML version of the content',
+ url varchar(255) comment 'URL of any attachment (image, video, bookmark, whatever)',
+ created datetime not null comment 'date this record was created',
+ modified timestamp comment 'date this record was modified',
+ source varchar(32) comment 'source of comment, like "web", "im", or "clientname"',
+
+ index message_from_idx (from_profile),
+ index message_to_idx (to_profile),
+ index message_created_idx (created)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table notice_inbox (
+
+ user_id integer not null comment 'user receiving the message' references user (id),
+ notice_id integer not null comment 'notice received' references notice (id),
+ created datetime not null comment 'date the notice was created',
+ source tinyint default 1 comment 'reason it is in the inbox; 1=subscription',
+
+ constraint primary key (user_id, notice_id),
+ index notice_inbox_notice_id_idx (notice_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table profile_tag (
+ tagger integer not null comment 'user making the tag' references user (id),
+ tagged integer not null comment 'profile tagged' references profile (id),
+ tag varchar(64) not null comment 'hash tag associated with this notice',
+ modified timestamp comment 'date the tag was added',
+
+ constraint primary key (tagger, tagged, tag),
+ index profile_tag_modified_idx (modified),
+ index profile_tag_tagger_tag_idx (tagger, tag)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table profile_block (
+
+ blocker integer not null comment 'user making the block' references user (id),
+ blocked integer not null comment 'profile that is blocked' references profile (id),
+ modified timestamp comment 'date of blocking',
+
+ constraint primary key (blocker, blocked)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
diff --git a/db/laconica_pg.sql b/db/laconica_pg.sql
new file mode 100644
index 000000000..e784bb169
--- /dev/null
+++ b/db/laconica_pg.sql
@@ -0,0 +1,329 @@
+/* local and remote users have profiles */
+
+create table profile (
+ id serial primary key /* comment 'unique identifier' */,
+ nickname varchar(64) not null /* comment 'nickname or username' */,
+ fullname varchar(255) /* comment 'display name' */,
+ profileurl varchar(255) /* comment 'URL, cached so we dont regenerate' */,
+ homepage varchar(255) /* comment 'identifying URL' */,
+ bio varchar(140) /* comment 'descriptive biography' */,
+ location varchar(255) /* comment 'physical location' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+
+ textsearch tsvector
+);
+create index profile_nickname_idx on profile using btree(nickname);
+
+create table avatar (
+ profile_id integer not null /* comment 'foreign key to profile table' */ references profile (id) ,
+ original integer default 0 /* comment 'uploaded by user or generated?' */,
+ width integer not null /* comment 'image width' */,
+ height integer not null /* comment 'image height' */,
+ mediatype varchar(32) not null /* comment 'file type' */,
+ filename varchar(255) null /* comment 'local filename, if local' */,
+ url varchar(255) unique /* comment 'avatar location' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+
+ primary key(profile_id, width, height)
+);
+create index avatar_profile_id_idx on avatar using btree(profile_id);
+
+create table sms_carrier (
+ id serial primary key /* comment 'primary key for SMS carrier' */,
+ name varchar(64) unique /* comment 'name of the carrier' */,
+ email_pattern varchar(255) not null /* comment 'sprintf pattern for making an email address from a phone number' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified ' */
+);
+
+/* local users */
+
+create table "user" (
+ id integer primary key /* comment 'foreign key to profile table' */ references profile (id) ,
+ nickname varchar(64) unique /* comment 'nickname or username, duped in profile' */,
+ password varchar(255) /* comment 'salted password, can be null for OpenID users' */,
+ email varchar(255) unique /* comment 'email address for password recovery etc.' */,
+ incomingemail varchar(255) unique /* comment 'email address for post-by-email' */,
+ emailnotifysub integer default 1 /* comment 'Notify by email of subscriptions' */,
+ emailnotifyfav integer default 1 /* comment 'Notify by email of favorites' */,
+ emailnotifynudge integer default 1 /* comment 'Notify by email of nudges' */,
+ emailmicroid integer default 1 /* comment 'whether to publish email microid' */,
+ language varchar(50) /* comment 'preferred language' */,
+ timezone varchar(50) /* comment 'timezone' */,
+ emailpost integer default 1 /* comment 'Post by email' */,
+ jabber varchar(255) unique /* comment 'jabber ID for notices' */,
+ jabbernotify integer default 0 /* comment 'whether to send notices to jabber' */,
+ jabberreplies integer default 0 /* comment 'whether to send notices to jabber on replies' */,
+ jabbermicroid integer default 1 /* comment 'whether to publish xmpp microid' */,
+ updatefrompresence integer default 0 /* comment 'whether to record updates from Jabber presence notices' */,
+ sms varchar(64) unique /* comment 'sms phone number' */,
+ carrier integer /* comment 'foreign key to sms_carrier' */ references sms_carrier (id) ,
+ smsnotify integer default 0 /* comment 'whether to send notices to SMS' */,
+ smsreplies integer default 0 /* comment 'whether to send notices to SMS on replies' */,
+ smsemail varchar(255) /* comment 'built from sms and carrier' */,
+ uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */,
+ autosubscribe integer default 0 /* comment 'automatically subscribe to users who subscribe to us' */,
+ urlshorteningservice varchar(50) default 'ur1.ca' /* comment 'service to use for auto-shortening URLs' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */
+
+);
+create index user_smsemail_idx on "user" using btree(smsemail);
+
+/* remote people */
+
+create table remote_profile (
+ id integer primary key /* comment 'foreign key to profile table' */ references profile (id) ,
+ uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */,
+ postnoticeurl varchar(255) /* comment 'URL we use for posting notices' */,
+ updateprofileurl varchar(255) /* comment 'URL we use for updates to this profile' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */
+);
+
+create table subscription (
+ subscriber integer not null /* comment 'profile listening' */,
+ subscribed integer not null /* comment 'profile being listened to' */,
+ token varchar(255) /* comment 'authorization token' */,
+ secret varchar(255) /* comment 'token secret' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+
+ primary key (subscriber, subscribed)
+);
+create index subscription_subscriber_idx on subscription using btree(subscriber);
+create index subscription_subscribed_idx on subscription using btree(subscribed);
+
+create table notice (
+
+ id serial primary key /* comment 'unique identifier' */,
+ profile_id integer not null /* comment 'who made the update' */ references profile (id) ,
+ uri varchar(255) unique /* comment 'universally unique identifier, usually a tag URI' */,
+ content varchar(140) /* comment 'update content' */,
+ rendered text /* comment 'HTML version of the content' */,
+ url varchar(255) /* comment 'URL of any attachment (image, video, bookmark, whatever)' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+ reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) ,
+ is_local integer default 0 /* comment 'notice was generated by a user' */,
+ source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */
+
+/* FULLTEXT(content) */
+);
+create index notice_profile_id_idx on notice using btree(profile_id);
+create index notice_created_idx on notice using btree(created);
+
+create table notice_source (
+ code varchar(32) primary key not null /* comment 'source code' */,
+ name varchar(255) not null /* comment 'name of the source' */,
+ url varchar(255) not null /* comment 'url to link to' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */
+);
+
+create table reply (
+
+ notice_id integer not null /* comment 'notice that is the reply' */ references notice (id) ,
+ profile_id integer not null /* comment 'profile replied to' */ references profile (id) ,
+ modified timestamp not null default 'now' /* comment 'date this record was modified' */,
+ replied_id integer /* comment 'notice replied to (not used, see notice.reply_to)' */,
+
+ primary key (notice_id, profile_id)
+
+);
+create index reply_notice_id_idx on reply using btree(notice_id);
+create index reply_profile_id_idx on reply using btree(profile_id);
+create index reply_replied_id_idx on reply using btree(replied_id);
+
+create table fave (
+
+ notice_id integer not null /* comment 'notice that is the favorite' */ references notice (id),
+ user_id integer not null /* comment 'user who likes this notice' */ references "user" (id) ,
+ modified timestamp not null /* comment 'date this record was modified' */,
+
+ primary key (notice_id, user_id)
+
+);
+create index fave_notice_id_idx on fave using btree(notice_id);
+create index fave_user_id_idx on fave using btree(user_id);
+create index fave_modified_idx on fave using btree(modified);
+
+/* tables for OAuth */
+
+create table consumer (
+ consumer_key varchar(255) primary key /* comment 'unique identifier, root URL' */,
+ seed char(32) not null /* comment 'seed for new tokens by this consumer' */,
+
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */
+);
+
+create table token (
+ consumer_key varchar(255) not null /* comment 'unique identifier, root URL' */ references consumer (consumer_key),
+ tok char(32) not null /* comment 'identifying value' */,
+ secret char(32) not null /* comment 'secret value' */,
+ type integer not null default 0 /* comment 'request or access' */,
+ state integer default 0 /* comment 'for requests; 0 = initial, 1 = authorized, 2 = used' */,
+
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+
+ primary key (consumer_key, tok)
+);
+
+create table nonce (
+ consumer_key varchar(255) not null /* comment 'unique identifier, root URL' */,
+ tok char(32) not null /* comment 'identifying value' */,
+ nonce char(32) not null /* comment 'nonce' */,
+ ts timestamp not null /* comment 'timestamp sent' */,
+
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+
+ primary key (consumer_key, tok, nonce),
+ foreign key (consumer_key, tok) references token (consumer_key, tok)
+);
+
+/* One-to-many relationship of user to openid_url */
+
+create table user_openid (
+ canonical varchar(255) primary key /* comment 'Canonical true URL' */,
+ display varchar(255) not null unique /* comment 'URL for viewing, may be different from canonical' */,
+ user_id integer not null /* comment 'user owning this URL' */ references "user" (id) ,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */
+
+);
+create index user_openid_user_id_idx on user_openid using btree(user_id);
+
+/* These are used by JanRain OpenID library */
+
+create table oid_associations (
+ server_url varchar(2047),
+ handle varchar(255),
+ secret bytea,
+ issued integer,
+ lifetime integer,
+ assoc_type varchar(64),
+ primary key (server_url, handle)
+);
+
+create table oid_nonces (
+ server_url varchar(2047),
+ "timestamp" integer,
+ salt character(40),
+ unique (server_url, "timestamp", salt)
+);
+
+create table confirm_address (
+ code varchar(32) not null primary key /* comment 'good random code' */,
+ user_id integer not null /* comment 'user who requested confirmation' */ references "user" (id),
+ address varchar(255) not null /* comment 'address (email, Jabber, SMS, etc.)' */,
+ address_extra varchar(255) not null default '' /* comment 'carrier ID, for SMS' */,
+ address_type varchar(8) not null /* comment 'address type ("email", "jabber", "sms")' */,
+ claimed timestamp /* comment 'date this was claimed for queueing' */,
+ sent timestamp /* comment 'date this was sent for queueing' */,
+ modified timestamp /* comment 'date this record was modified' */
+);
+
+create table remember_me (
+ code varchar(32) not null primary key /* comment 'good random code' */,
+ user_id integer not null /* comment 'user who is logged in' */ references "user" (id),
+ modified timestamp /* comment 'date this record was modified' */
+);
+
+create table queue_item (
+
+ notice_id integer not null /* comment 'notice queued' */ references notice (id) ,
+ transport varchar(8) not null /* comment 'queue for what? "email", "jabber", "sms", "irc", ...' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ claimed timestamp /* comment 'date this item was claimed' */,
+
+ primary key (notice_id, transport)
+
+);
+create index queue_item_created_idx on queue_item using btree(created);
+
+/* Hash tags */
+create table notice_tag (
+ tag varchar( 64 ) not null /* comment 'hash tag associated with this notice' */,
+ notice_id integer not null /* comment 'notice tagged' */ references notice (id) ,
+ created timestamp not null /* comment 'date this record was created' */,
+
+ primary key (tag, notice_id)
+);
+create index notice_tag_created_idx on notice_tag using btree(created);
+
+/* Synching with foreign services */
+
+create table foreign_service (
+ id int not null primary key /* comment 'numeric key for service' */,
+ name varchar(32) not null unique /* comment 'name of the service' */,
+ description varchar(255) /* comment 'description' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */
+);
+
+create table foreign_user (
+ id int not null /* comment 'unique numeric key on foreign service' */,
+ service int not null /* comment 'foreign key to service' */ references foreign_service(id) ,
+ uri varchar(255) not null unique /* comment 'identifying URI' */,
+ nickname varchar(255) /* comment 'nickname on foreign service' */,
+ user_id int /* comment 'link to user on this system, if exists' */ references "user" (id),
+ credentials varchar(255) /* comment 'authc credentials, typically a password' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+
+ primary key (id, service)
+);
+create index foreign_user_user_id_idx on foreign_user using btree(user_id);
+
+create table foreign_subscription (
+ service int not null /* comment 'service where relationship happens' */ references foreign_service(id) ,
+ subscriber int not null /* comment 'subscriber on foreign service' */ ,
+ subscribed int not null /* comment 'subscribed user' */ ,
+ created timestamp not null /* comment 'date this record was created' */,
+
+ primary key (service, subscriber, subscribed)
+);
+create index foreign_subscription_subscriber_idx on foreign_subscription using btree(subscriber);
+create index foreign_subscription_subscribed_idx on foreign_subscription using btree(subscribed);
+
+create table invitation (
+ code varchar(32) not null primary key /* comment 'random code for an invitation' */,
+ user_id int not null /* comment 'who sent the invitation' */ references "user" (id),
+ address varchar(255) not null /* comment 'invitation sent to' */,
+ address_type varchar(8) not null /* comment 'address type ("email", "jabber", "sms") '*/,
+ created timestamp not null /* comment 'date this record was created' */
+
+);
+create index invitation_address_idx on invitation using btree(address,address_type);
+create index invitation_user_id_idx on invitation using btree(user_id);
+
+create table message (
+
+ id serial primary key /* comment 'unique identifier' */,
+ uri varchar(255) unique /* comment 'universally unique identifier' */,
+ from_profile integer not null /* comment 'who the message is from' */ references profile (id),
+ to_profile integer not null /* comment 'who the message is to' */ references profile (id),
+ content varchar(140) /* comment 'message content' */,
+ rendered text /* comment 'HTML version of the content' */,
+ url varchar(255) /* comment 'URL of any attachment (image, video, bookmark, whatever)' */,
+ created timestamp not null /* comment 'date this record was created' */,
+ modified timestamp /* comment 'date this record was modified' */,
+ source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */
+
+);
+create index message_from_idx on message using btree(from_profile);
+create index message_to_idx on message using btree(to_profile);
+create index message_created_idx on message using btree(created);
+
+/* Textsearch stuff */
+
+create index textsearch_idx on profile using gist(textsearch);
+create index noticecontent_idx on notice using gist(to_tsvector('english',content));
+create trigger textsearchupdate before insert or update on profile for each row
+execute procedure tsvector_update_trigger(textsearch, 'pg_catalog.english', nickname, fullname, location, bio, homepage);
+
diff --git a/doc/about b/doc/about
new file mode 100644
index 000000000..3036a51b9
--- /dev/null
+++ b/doc/about
@@ -0,0 +1,10 @@
+%%site.name%% is a
+[micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service
+based on the Free Software [Laconica](http://laconi.ca/) tool.
+
+If you [register](%%action.register%%) for an account,
+you can post small (140 chars or less) text notices
+about yourself, where you are, what you're doing, or practically
+anything you want. You can also subscribe to the notices of your
+friends, or other people you're interested in, and follow them on the
+Web or in an [RSS](http://en.wikipedia.org/wiki/RSS) feed.
diff --git a/doc/contact b/doc/contact
new file mode 100644
index 000000000..a8efc456a
--- /dev/null
+++ b/doc/contact
@@ -0,0 +1,24 @@
+There are a number of options for getting in contact with responsible
+people for %%site.name%%.
+
+Post a notice
+-------------
+
+If you have a question about how to do something, just post a notice
+with your question. People here like to answer messages. Watch the
+[public timeline](%%action.public%%) for answers; they'll usually start
+with "@" plus your user name.
+
+Bugs
+----
+
+If you think you've found a bug in the [Laconica](http://laconi.ca/) software,
+or if there's a new feature you'd like to see, add it into the [Laconica bug database](http://laconi.ca/PITS/HomePage). Don't forget to check the list of
+existing bugs to make sure it hasn't already been reported!
+
+Email
+-----
+
+You can reach the responsible party for this server at [%%site.email%%](mailto:%%site.email%%).
+
+
diff --git a/doc/faq b/doc/faq
new file mode 100644
index 000000000..5699f3635
--- /dev/null
+++ b/doc/faq
@@ -0,0 +1,42 @@
+These are some *Frequently Asked Questions* about this service, with
+some answers.
+
+What is %%site.name%%?
+----------------------
+
+%%site.name%% is a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service.
+You can use it to write short notices about yourself, where you are,
+and what you're doing, and those notices will be sent to all your friends
+and fans.
+
+How is %%site.name%% different from Twitter, Jaiku, Pownce, Plurk, others?
+--------------------------------------------------------------------------
+
+%%site.name%% is an [Open Network Service](http://opendefinition.org/osd). Our main
+goal is to provide a fair and transparent service that preserves users' autonomy. In
+particular, all the software used for %%site.name%% is [Free Software](http://en.wikipedia.org/wiki/Free_Software), and all the data is available
+under the [%%license.title%%](%%license.url%%) license, making it Open Data.
+
+The software also implements the [OpenMicroBlogging](http://openmicroblogging.org/) protocol, meaning that you can have friends on other microblogging services
+that can receive your notices.
+
+The goal here is *autonomy* -- you deserve the right to manage your own on-line
+presence. If you don't like how %%site.name%% works, you can take your data and the source code and set up your own server (or move your account to another one).
+
+Where is feature X?
+-------------------
+
+The software we run, [Laconica](http://laconi.ca/), is still in its early stages,
+and many features people expect from microblogging sites are not yet implemented. Some important ones that are expected "soon":
+
+* More [AJAX](http://en.wikipedia.org/wiki/AJAX)-y interface
+* Maps
+* Cross-post to Pownce, Jaiku, etc.
+* Pull messages from Twitter, Pownce, Jaiku, etc.
+* [Facebook](http://www.facebook.com/) integration
+* Image, video, audio notices
+
+There is [a list of bugs and features](http://laconi.ca/trac/) that you may find
+interesting. New ideas or complaints are very welcome.
+
+
diff --git a/doc/help b/doc/help
new file mode 100644
index 000000000..5b60072e2
--- /dev/null
+++ b/doc/help
@@ -0,0 +1,29 @@
+%%site.name%% is a **microblogging service**. Users post short (140
+character) notices which are broadcast to their friends and fans using
+the Web, RSS, or instant messages.
+
+If you'd like to try it out, first [register](%%action.register%%) a new account.
+Then, on the [public timeline](%%action.public%%), enter your message into
+the textbox at the top of the page, and click "Send". It will go out on the
+public timeline and to anyone who is subscribed to your notices (probably nobody,
+at first).
+
+To subscribe to other people's notifications, go to their profile page
+and click the "subscribe" button. They'll get a notice that you're now
+subscribed to their notifications, and, who knows?, they might subscribe
+back.
+
+More help
+---------
+
+Here are some documents that you might find helpful in understanding
+%%site.name%% and how to use it.
+
+* [About](%%doc.about%%) - an overview of the service
+* [FAQ](%%doc.faq%%) - frequently-asked questions about %%site.name%%
+* [Contact](%%doc.contact%%) - who to contact with questions about the service
+* [IM](%%doc.im%%) - using the instant-message (IM) features of %%site.name%%
+* [OpenID](%%doc.openid%%) - what OpenID is and how to use it with this service
+* [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users
+* [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy
+* [Source](%%doc.source%%) - How to get the Laconica source code
diff --git a/doc/im b/doc/im
new file mode 100644
index 000000000..da07f9fe7
--- /dev/null
+++ b/doc/im
@@ -0,0 +1,35 @@
+You can post messages to %%site.name%% using a [Jabber](http://jabber.org/) client
+on your computer, mobile phone, or other platform. ([GTalk](http://talk.google.com/),
+Google's Jabber program, will also work.) This can be a convenient way to keep
+up with your friends on %%site.name%%.
+
+If you don't already have a Jabber account, you can use GTalk or one of the other
+[public Jabber services](http://www.jabber.org/im-services). You'll probably also
+need an IM client like [Pidgin](http://www.pidgin.im/).
+
+Managing your IM settings
+-------------------------
+
+Use the [IM settings](%%action.imsettings%%) page to set your IM preferences. You can add or change your Jabber address and set the flags for Jabber update.
+
+When you add or change your address, you'll receive a message from **%%xmpp.user%%@%%xmpp.server%%** asking you to confirm the change. (You may need to
+add %%xmpp.user%%@%%xmpp.server%% to your buddy list *before* changing your IM
+settings; this is definitely true for GTalk.)
+
+Sending updates
+---------------
+
+You send updates by sending messages to %%xmpp.user%%@%%xmpp.server%%. Messages
+should be less than 140 characters; longer messages will be truncated.
+
+Commands
+--------
+
+You can do some minor management of your account through Jabber. These are the
+currently-implemented commands:
+
+* **on**: Turn on notifications. You'll receive copies of messages by people
+ you subscribe to.
+* **off**: Turn off notifications. You'll no longer receive Jabber
+ notifications.
+
diff --git a/doc/openid b/doc/openid
new file mode 100644
index 000000000..c741e3674
--- /dev/null
+++ b/doc/openid
@@ -0,0 +1,11 @@
+%%site.name%% supports the [OpenID](http://openid.net/) standard for single signon between Web sites. OpenID lets you log into many different Web sites without using a different password for each. (See [Wikipedia's OpenID article](http://en.wikipedia.org/wiki/OpenID) for more information.)
+
+If you already have an account on %%site.name%%, you can [login](%%action.login%%) with your username and password as usual.
+To use OpenID in the future, you can [add an OpenID to your account](%%action.openidsettings%%) after you have logged in normally.
+
+There are many [Public OpenID providers](http://wiki.openid.net/Public_OpenID_providers), and you may already have an OpenID-enabled account on another service.
+
+* On wikis: If you have an account on an OpenID-enabled wiki, like [Wikitravel](http://wikitravel.org/), [wikiHow](http://www.wikihow.com/), [Vinismo](http://vinismo.com/), [AboutUs](http://aboutus.org/) or [Keiki](http://kei.ki/), you can log in to %%site.name%% by entering the **full URL** of your user page on that other wiki in the box above. For example, *http://kei.ki/en/User:Evan*.
+* [Yahoo!](http://openid.yahoo.com/) : If you have an account with Yahoo!, you can log in to this site by entering your Yahoo!-provided OpenID in the box above. Yahoo! OpenID URLs have the form *https://me.yahoo.com/yourusername*.
+* [AOL](http://dev.aol.com/aol-and-63-million-openids) : If you have an account with [AOL](http://www.aol.com/), like an [AIM](http://www.aim.com/) account, you can log in to %%site.name%% by entering your AOL-provided OpenID in the box above. AOL OpenID URLs have the form *http://openid.aol.com/yourusername*. Your username should be all lowercase, no spaces.
+* [Blogger](http://bloggerindraft.blogspot.com/2008/01/new-feature-blogger-as-openid-provider.html), [Wordpress.com](http://faq.wordpress.com/2007/03/06/what-is-openid/), [LiveJournal](http://www.livejournal.com/openid/about.bml), [Vox](http://bradfitz.vox.com/library/post/openid-for-vox.html) : If you have a blog on any of these services, enter your blog URL in the box above. For example, *http://yourusername.blogspot.com/*, *http://yourusername.wordpress.com/*, *http://yourusername.livejournal.com/*, or *http://yourusername.vox.com/*.
diff --git a/doc/openmublog b/doc/openmublog
new file mode 100644
index 000000000..6e3abee42
--- /dev/null
+++ b/doc/openmublog
@@ -0,0 +1,25 @@
+[OpenMicroBlogging](http://openmicroblogging.org/) is a protocol that
+lets users of one [microblogging](http://en.wikipedia.org/wiki/microblogging) service
+subscribe to notices by users of another service. The protocol, based on
+[OAuth](http://oauth.net/), is open and free, and doesn't depend on any
+central authority to maintain the federated microblogs.
+
+The [Laconica](http://laconi.ca/) software that runs %%site.name%% supports
+OpenMicroBlogging 0.1. Anyone can make a new installation of Laconica on their
+own servers, and users of that new installation can subscribe to notices from
+%%site.name%%.
+
+Remote subscription
+-------------------
+
+If you have an account on a remote site that supports OpenMicroBlogging, and you
+want to subscribe to the notices of a user on this site, click on the "Subscribe"
+link under their avatar on their profile page. This should take you to the
+[remote subscription](%%action.remotesubscribe%%) page. Make sure that you've got the
+right nickname registered, and enter your profile URL on the other microblogging
+service.
+
+You'll be taken to your microblogging service, where you'll be asked to confirm the
+subscription. When you confirm, your service will receive new notifications from
+the user on %%site.name%%, and your service will forward them to you (using IM, SMS,
+the Web, or whatever else).
diff --git a/doc/privacy b/doc/privacy
new file mode 100644
index 000000000..90c7b3c7f
--- /dev/null
+++ b/doc/privacy
@@ -0,0 +1,45 @@
+This document outlines this service's respect for your personal
+privacy as a user of the service.
+
+- Almost all the text and files that users upload to this site is
+ available under the site license (see the license block at the bottom
+ of this page). Users agree to the license when they register to use
+ the site for the first time. Typically that means that the data can
+ be copied far and wide, for commercial and non-commercial purposes,
+ and in modified or unmodified form. If you're not OK with that,
+ don't use the service.
+- The following data items are considered *private data* that won't be
+ shared with other users, business partners, or the public at large:
+ * your password
+ * your email address
+ * your IM address (AIM, Jabber, or other instant messaging address)
+ * your phone number
+ * your "private messages"
+ * your login credentials (username and password) for other services (Twitter, Facebook, etc.)
+- Some private data may be published in aggregate, e.g. "30% of our
+ users are registered with Hotmail addresses."
+- Your notices (including files) can be downloaded and re-used by
+ other services, either one-by-one or in bulk as
+ [RSS](http://en.wikipedia.org/wiki/RSS) files.
+- Your profile information (including subscriptions and avatars) can be
+ downloaded and re-used by other services, either scraped from the HTML
+ interface or in bulk as [FOAF](http://en.wikipedia.org/wiki/FOAF) files.
+- Your notices will be forwarded to users who subscribe to them,
+ including users on another microblogging service.
+- Your profile information will be sent to microblogging services for
+ users who subscribe to you or to whom you subscribe.
+- Based on your email preferences, you may receive automated email
+ messages for important system events, such as when others subscribe
+ to your notices.
+- Based on your email preferences, you may receive an email
+ newsletter. You can opt out of the newsletter if you don't want to
+ receive it.
+- In urgent situations, administrators may send you email directly to
+ your registered email address, even if you've requested no notices
+ or newsletter. *Administrators will use digitally-signed email.*
+- This service will comply with court orders to turn over your private
+ information.
+
+
+
+
diff --git a/doc/source b/doc/source
new file mode 100644
index 000000000..83debbe53
--- /dev/null
+++ b/doc/source
@@ -0,0 +1,12 @@
+This service uses a Free microblogging tool called **Laconica**.
+Laconica is available under the [GNU Affero General Public License
+Version 3.0](http://www.fsf.org/licensing/licenses/agpl-3.0.html), a
+Free Software license for network services.
+
+You can get a copy of the software from the
+[Laconica](http://laconi.ca/) main site. The version of the software
+that runs on *this* site is unmodified from that version. The site
+also depends on certain libraries and other software; you can get
+those at the Laconica site, too.
+
+
diff --git a/extlib/Apache2.0.txt b/extlib/Apache2.0.txt
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/extlib/Apache2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/extlib/Auth/OpenID.php b/extlib/Auth/OpenID.php
new file mode 100644
index 000000000..6556b5b01
--- /dev/null
+++ b/extlib/Auth/OpenID.php
@@ -0,0 +1,552 @@
+<?php
+
+/**
+ * This is the PHP OpenID library by JanRain, Inc.
+ *
+ * This module contains core utility functionality used by the
+ * library. See Consumer.php and Server.php for the consumer and
+ * server implementations.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * The library version string
+ */
+define('Auth_OpenID_VERSION', '2.1.2');
+
+/**
+ * Require the fetcher code.
+ */
+require_once "Auth/Yadis/PlainHTTPFetcher.php";
+require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
+require_once "Auth/OpenID/BigMath.php";
+require_once "Auth/OpenID/URINorm.php";
+
+/**
+ * Status code returned by the server when the only option is to show
+ * an error page, since we do not have enough information to redirect
+ * back to the consumer. The associated value is an error message that
+ * should be displayed on an HTML error page.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_LOCAL_ERROR', 'local_error');
+
+/**
+ * Status code returned when there is an error to return in key-value
+ * form to the consumer. The caller should return a 400 Bad Request
+ * response with content-type text/plain and the value as the body.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_REMOTE_ERROR', 'remote_error');
+
+/**
+ * Status code returned when there is a key-value form OK response to
+ * the consumer. The value associated with this code is the
+ * response. The caller should return a 200 OK response with
+ * content-type text/plain and the value as the body.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_REMOTE_OK', 'remote_ok');
+
+/**
+ * Status code returned when there is a redirect back to the
+ * consumer. The value is the URL to redirect back to. The caller
+ * should return a 302 Found redirect with a Location: header
+ * containing the URL.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_REDIRECT', 'redirect');
+
+/**
+ * Status code returned when the caller needs to authenticate the
+ * user. The associated value is a {@link Auth_OpenID_ServerRequest}
+ * object that can be used to complete the authentication. If the user
+ * has taken some authentication action, use the retry() method of the
+ * {@link Auth_OpenID_ServerRequest} object to complete the request.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_DO_AUTH', 'do_auth');
+
+/**
+ * Status code returned when there were no OpenID arguments
+ * passed. This code indicates that the caller should return a 200 OK
+ * response and display an HTML page that says that this is an OpenID
+ * server endpoint.
+ *
+ * @see Auth_OpenID_Server
+ */
+define('Auth_OpenID_DO_ABOUT', 'do_about');
+
+/**
+ * Defines for regexes and format checking.
+ */
+define('Auth_OpenID_letters',
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+define('Auth_OpenID_digits',
+ "0123456789");
+
+define('Auth_OpenID_punct',
+ "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
+
+if (Auth_OpenID_getMathLib() === null) {
+ Auth_OpenID_setNoMathSupport();
+}
+
+/**
+ * The OpenID utility function class.
+ *
+ * @package OpenID
+ * @access private
+ */
+class Auth_OpenID {
+
+ /**
+ * Return true if $thing is an Auth_OpenID_FailureResponse object;
+ * false if not.
+ *
+ * @access private
+ */
+ function isFailure($thing)
+ {
+ return is_a($thing, 'Auth_OpenID_FailureResponse');
+ }
+
+ /**
+ * Gets the query data from the server environment based on the
+ * request method used. If GET was used, this looks at
+ * $_SERVER['QUERY_STRING'] directly. If POST was used, this
+ * fetches data from the special php://input file stream.
+ *
+ * Returns an associative array of the query arguments.
+ *
+ * Skips invalid key/value pairs (i.e. keys with no '=value'
+ * portion).
+ *
+ * Returns an empty array if neither GET nor POST was used, or if
+ * POST was used but php://input cannot be opened.
+ *
+ * @access private
+ */
+ function getQuery($query_str=null)
+ {
+ $data = array();
+
+ if ($query_str !== null) {
+ $data = Auth_OpenID::params_from_string($query_str);
+ } else if (!array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ // Do nothing.
+ } else {
+ // XXX HACK FIXME HORRIBLE.
+ //
+ // POSTing to a URL with query parameters is acceptable, but
+ // we don't have a clean way to distinguish those parameters
+ // when we need to do things like return_to verification
+ // which only want to look at one kind of parameter. We're
+ // going to emulate the behavior of some other environments
+ // by defaulting to GET and overwriting with POST if POST
+ // data is available.
+ $data = Auth_OpenID::params_from_string($_SERVER['QUERY_STRING']);
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $str = file_get_contents('php://input');
+
+ if ($str === false) {
+ $post = array();
+ } else {
+ $post = Auth_OpenID::params_from_string($str);
+ }
+
+ $data = array_merge($data, $post);
+ }
+ }
+
+ return $data;
+ }
+
+ function params_from_string($str)
+ {
+ $chunks = explode("&", $str);
+
+ $data = array();
+ foreach ($chunks as $chunk) {
+ $parts = explode("=", $chunk, 2);
+
+ if (count($parts) != 2) {
+ continue;
+ }
+
+ list($k, $v) = $parts;
+ $data[$k] = urldecode($v);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Create dir_name as a directory if it does not exist. If it
+ * exists, make sure that it is, in fact, a directory. Returns
+ * true if the operation succeeded; false if not.
+ *
+ * @access private
+ */
+ function ensureDir($dir_name)
+ {
+ if (is_dir($dir_name) || @mkdir($dir_name)) {
+ return true;
+ } else {
+ $parent_dir = dirname($dir_name);
+
+ // Terminal case; there is no parent directory to create.
+ if ($parent_dir == $dir_name) {
+ return true;
+ }
+
+ return (Auth_OpenID::ensureDir($parent_dir) && @mkdir($dir_name));
+ }
+ }
+
+ /**
+ * Adds a string prefix to all values of an array. Returns a new
+ * array containing the prefixed values.
+ *
+ * @access private
+ */
+ function addPrefix($values, $prefix)
+ {
+ $new_values = array();
+ foreach ($values as $s) {
+ $new_values[] = $prefix . $s;
+ }
+ return $new_values;
+ }
+
+ /**
+ * Convenience function for getting array values. Given an array
+ * $arr and a key $key, get the corresponding value from the array
+ * or return $default if the key is absent.
+ *
+ * @access private
+ */
+ function arrayGet($arr, $key, $fallback = null)
+ {
+ if (is_array($arr)) {
+ if (array_key_exists($key, $arr)) {
+ return $arr[$key];
+ } else {
+ return $fallback;
+ }
+ } else {
+ trigger_error("Auth_OpenID::arrayGet (key = ".$key.") expected " .
+ "array as first parameter, got " .
+ gettype($arr), E_USER_WARNING);
+
+ return false;
+ }
+ }
+
+ /**
+ * Replacement for PHP's broken parse_str.
+ */
+ function parse_str($query)
+ {
+ if ($query === null) {
+ return null;
+ }
+
+ $parts = explode('&', $query);
+
+ $new_parts = array();
+ for ($i = 0; $i < count($parts); $i++) {
+ $pair = explode('=', $parts[$i]);
+
+ if (count($pair) != 2) {
+ continue;
+ }
+
+ list($key, $value) = $pair;
+ $new_parts[$key] = urldecode($value);
+ }
+
+ return $new_parts;
+ }
+
+ /**
+ * Implements the PHP 5 'http_build_query' functionality.
+ *
+ * @access private
+ * @param array $data Either an array key/value pairs or an array
+ * of arrays, each of which holding two values: a key and a value,
+ * sequentially.
+ * @return string $result The result of url-encoding the key/value
+ * pairs from $data into a URL query string
+ * (e.g. "username=bob&id=56").
+ */
+ function httpBuildQuery($data)
+ {
+ $pairs = array();
+ foreach ($data as $key => $value) {
+ if (is_array($value)) {
+ $pairs[] = urlencode($value[0])."=".urlencode($value[1]);
+ } else {
+ $pairs[] = urlencode($key)."=".urlencode($value);
+ }
+ }
+ return implode("&", $pairs);
+ }
+
+ /**
+ * "Appends" query arguments onto a URL. The URL may or may not
+ * already have arguments (following a question mark).
+ *
+ * @access private
+ * @param string $url A URL, which may or may not already have
+ * arguments.
+ * @param array $args Either an array key/value pairs or an array of
+ * arrays, each of which holding two values: a key and a value,
+ * sequentially. If $args is an ordinary key/value array, the
+ * parameters will be added to the URL in sorted alphabetical order;
+ * if $args is an array of arrays, their order will be preserved.
+ * @return string $url The original URL with the new parameters added.
+ *
+ */
+ function appendArgs($url, $args)
+ {
+ if (count($args) == 0) {
+ return $url;
+ }
+
+ // Non-empty array; if it is an array of arrays, use
+ // multisort; otherwise use sort.
+ if (array_key_exists(0, $args) &&
+ is_array($args[0])) {
+ // Do nothing here.
+ } else {
+ $keys = array_keys($args);
+ sort($keys);
+ $new_args = array();
+ foreach ($keys as $key) {
+ $new_args[] = array($key, $args[$key]);
+ }
+ $args = $new_args;
+ }
+
+ $sep = '?';
+ if (strpos($url, '?') !== false) {
+ $sep = '&';
+ }
+
+ return $url . $sep . Auth_OpenID::httpBuildQuery($args);
+ }
+
+ /**
+ * Implements python's urlunparse, which is not available in PHP.
+ * Given the specified components of a URL, this function rebuilds
+ * and returns the URL.
+ *
+ * @access private
+ * @param string $scheme The scheme (e.g. 'http'). Defaults to 'http'.
+ * @param string $host The host. Required.
+ * @param string $port The port.
+ * @param string $path The path.
+ * @param string $query The query.
+ * @param string $fragment The fragment.
+ * @return string $url The URL resulting from assembling the
+ * specified components.
+ */
+ function urlunparse($scheme, $host, $port = null, $path = '/',
+ $query = '', $fragment = '')
+ {
+
+ if (!$scheme) {
+ $scheme = 'http';
+ }
+
+ if (!$host) {
+ return false;
+ }
+
+ if (!$path) {
+ $path = '';
+ }
+
+ $result = $scheme . "://" . $host;
+
+ if ($port) {
+ $result .= ":" . $port;
+ }
+
+ $result .= $path;
+
+ if ($query) {
+ $result .= "?" . $query;
+ }
+
+ if ($fragment) {
+ $result .= "#" . $fragment;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Given a URL, this "normalizes" it by adding a trailing slash
+ * and / or a leading http:// scheme where necessary. Returns
+ * null if the original URL is malformed and cannot be normalized.
+ *
+ * @access private
+ * @param string $url The URL to be normalized.
+ * @return mixed $new_url The URL after normalization, or null if
+ * $url was malformed.
+ */
+ function normalizeUrl($url)
+ {
+ @$parsed = parse_url($url);
+
+ if (!$parsed) {
+ return null;
+ }
+
+ if (isset($parsed['scheme']) &&
+ isset($parsed['host'])) {
+ $scheme = strtolower($parsed['scheme']);
+ if (!in_array($scheme, array('http', 'https'))) {
+ return null;
+ }
+ } else {
+ $url = 'http://' . $url;
+ }
+
+ $normalized = Auth_OpenID_urinorm($url);
+ if ($normalized === null) {
+ return null;
+ }
+ list($defragged, $frag) = Auth_OpenID::urldefrag($normalized);
+ return $defragged;
+ }
+
+ /**
+ * Replacement (wrapper) for PHP's intval() because it's broken.
+ *
+ * @access private
+ */
+ function intval($value)
+ {
+ $re = "/^\\d+$/";
+
+ if (!preg_match($re, $value)) {
+ return false;
+ }
+
+ return intval($value);
+ }
+
+ /**
+ * Count the number of bytes in a string independently of
+ * multibyte support conditions.
+ *
+ * @param string $str The string of bytes to count.
+ * @return int The number of bytes in $str.
+ */
+ function bytes($str)
+ {
+ return strlen(bin2hex($str)) / 2;
+ }
+
+ /**
+ * Get the bytes in a string independently of multibyte support
+ * conditions.
+ */
+ function toBytes($str)
+ {
+ $hex = bin2hex($str);
+
+ if (!$hex) {
+ return array();
+ }
+
+ $b = array();
+ for ($i = 0; $i < strlen($hex); $i += 2) {
+ $b[] = chr(base_convert(substr($hex, $i, 2), 16, 10));
+ }
+
+ return $b;
+ }
+
+ function urldefrag($url)
+ {
+ $parts = explode("#", $url, 2);
+
+ if (count($parts) == 1) {
+ return array($parts[0], "");
+ } else {
+ return $parts;
+ }
+ }
+
+ function filter($callback, &$sequence)
+ {
+ $result = array();
+
+ foreach ($sequence as $item) {
+ if (call_user_func_array($callback, array($item))) {
+ $result[] = $item;
+ }
+ }
+
+ return $result;
+ }
+
+ function update(&$dest, &$src)
+ {
+ foreach ($src as $k => $v) {
+ $dest[$k] = $v;
+ }
+ }
+
+ /**
+ * Wrap PHP's standard error_log functionality. Use this to
+ * perform all logging. It will interpolate any additional
+ * arguments into the format string before logging.
+ *
+ * @param string $format_string The sprintf format for the message
+ */
+ function log($format_string)
+ {
+ $args = func_get_args();
+ $message = call_user_func_array('sprintf', $args);
+ error_log($message);
+ }
+
+ function autoSubmitHTML($form, $title="OpenId transaction in progress")
+ {
+ return("<html>".
+ "<head><title>".
+ $title .
+ "</title></head>".
+ "<body onload='document.forms[0].submit();'>".
+ $form .
+ "<script>".
+ "var elements = document.forms[0].elements;".
+ "for (var i = 0; i < elements.length; i++) {".
+ " elements[i].style.display = \"none\";".
+ "}".
+ "</script>".
+ "</body>".
+ "</html>");
+ }
+}
+?>
diff --git a/extlib/Auth/OpenID/AX.php b/extlib/Auth/OpenID/AX.php
new file mode 100644
index 000000000..4a617ae30
--- /dev/null
+++ b/extlib/Auth/OpenID/AX.php
@@ -0,0 +1,1023 @@
+<?php
+
+/**
+ * Implements the OpenID attribute exchange specification, version 1.0
+ * as of svn revision 370 from openid.net svn.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID/Extension.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/TrustRoot.php";
+
+define('Auth_OpenID_AX_NS_URI',
+ 'http://openid.net/srv/ax/1.0');
+
+// Use this as the 'count' value for an attribute in a FetchRequest to
+// ask for as many values as the OP can provide.
+define('Auth_OpenID_AX_UNLIMITED_VALUES', 'unlimited');
+
+// Minimum supported alias length in characters. Here for
+// completeness.
+define('Auth_OpenID_AX_MINIMUM_SUPPORTED_ALIAS_LENGTH', 32);
+
+/**
+ * AX utility class.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX {
+ /**
+ * @param mixed $thing Any object which may be an
+ * Auth_OpenID_AX_Error object.
+ *
+ * @return bool true if $thing is an Auth_OpenID_AX_Error; false
+ * if not.
+ */
+ function isError($thing)
+ {
+ return is_a($thing, 'Auth_OpenID_AX_Error');
+ }
+}
+
+/**
+ * Check an alias for invalid characters; raise AXError if any are
+ * found. Return None if the alias is valid.
+ */
+function Auth_OpenID_AX_checkAlias($alias)
+{
+ if (strpos($alias, ',') !== false) {
+ return new Auth_OpenID_AX_Error(sprintf(
+ "Alias %s must not contain comma", $alias));
+ }
+ if (strpos($alias, '.') !== false) {
+ return new Auth_OpenID_AX_Error(sprintf(
+ "Alias %s must not contain period", $alias));
+ }
+
+ return true;
+}
+
+/**
+ * Results from data that does not meet the attribute exchange 1.0
+ * specification
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_Error {
+ function Auth_OpenID_AX_Error($message=null)
+ {
+ $this->message = $message;
+ }
+}
+
+/**
+ * Abstract class containing common code for attribute exchange
+ * messages.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_Message extends Auth_OpenID_Extension {
+ /**
+ * ns_alias: The preferred namespace alias for attribute exchange
+ * messages
+ */
+ var $ns_alias = 'ax';
+
+ /**
+ * mode: The type of this attribute exchange message. This must be
+ * overridden in subclasses.
+ */
+ var $mode = null;
+
+ var $ns_uri = Auth_OpenID_AX_NS_URI;
+
+ /**
+ * Return Auth_OpenID_AX_Error if the mode in the attribute
+ * exchange arguments does not match what is expected for this
+ * class; true otherwise.
+ *
+ * @access private
+ */
+ function _checkMode($ax_args)
+ {
+ $mode = Auth_OpenID::arrayGet($ax_args, 'mode');
+ if ($mode != $this->mode) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "Expected mode '%s'; got '%s'",
+ $this->mode, $mode));
+ }
+
+ return true;
+ }
+
+ /**
+ * Return a set of attribute exchange arguments containing the
+ * basic information that must be in every attribute exchange
+ * message.
+ *
+ * @access private
+ */
+ function _newArgs()
+ {
+ return array('mode' => $this->mode);
+ }
+}
+
+/**
+ * Represents a single attribute in an attribute exchange
+ * request. This should be added to an AXRequest object in order to
+ * request the attribute.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_AttrInfo {
+ /**
+ * Construct an attribute information object. Do not call this
+ * directly; call make(...) instead.
+ *
+ * @param string $type_uri The type URI for this attribute.
+ *
+ * @param int $count The number of values of this type to request.
+ *
+ * @param bool $required Whether the attribute will be marked as
+ * required in the request.
+ *
+ * @param string $alias The name that should be given to this
+ * attribute in the request.
+ */
+ function Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
+ $alias)
+ {
+ /**
+ * required: Whether the attribute will be marked as required
+ * when presented to the subject of the attribute exchange
+ * request.
+ */
+ $this->required = $required;
+
+ /**
+ * count: How many values of this type to request from the
+ * subject. Defaults to one.
+ */
+ $this->count = $count;
+
+ /**
+ * type_uri: The identifier that determines what the attribute
+ * represents and how it is serialized. For example, one type
+ * URI representing dates could represent a Unix timestamp in
+ * base 10 and another could represent a human-readable
+ * string.
+ */
+ $this->type_uri = $type_uri;
+
+ /**
+ * alias: The name that should be given to this attribute in
+ * the request. If it is not supplied, a generic name will be
+ * assigned. For example, if you want to call a Unix timestamp
+ * value 'tstamp', set its alias to that value. If two
+ * attributes in the same message request to use the same
+ * alias, the request will fail to be generated.
+ */
+ $this->alias = $alias;
+ }
+
+ /**
+ * Construct an attribute information object. For parameter
+ * details, see the constructor.
+ */
+ function make($type_uri, $count=1, $required=false,
+ $alias=null)
+ {
+ if ($alias !== null) {
+ $result = Auth_OpenID_AX_checkAlias($alias);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+ }
+
+ return new Auth_OpenID_AX_AttrInfo($type_uri, $count, $required,
+ $alias);
+ }
+
+ /**
+ * When processing a request for this attribute, the OP should
+ * call this method to determine whether all available attribute
+ * values were requested. If self.count == UNLIMITED_VALUES, this
+ * returns True. Otherwise this returns False, in which case
+ * self.count is an integer.
+ */
+ function wantsUnlimitedValues()
+ {
+ return $this->count === Auth_OpenID_AX_UNLIMITED_VALUES;
+ }
+}
+
+/**
+ * Given a namespace mapping and a string containing a comma-separated
+ * list of namespace aliases, return a list of type URIs that
+ * correspond to those aliases.
+ *
+ * @param $namespace_map The mapping from namespace URI to alias
+ * @param $alias_list_s The string containing the comma-separated
+ * list of aliases. May also be None for convenience.
+ *
+ * @return $seq The list of namespace URIs that corresponds to the
+ * supplied list of aliases. If the string was zero-length or None, an
+ * empty list will be returned.
+ *
+ * return null If an alias is present in the list of aliases but
+ * is not present in the namespace map.
+ */
+function Auth_OpenID_AX_toTypeURIs(&$namespace_map, $alias_list_s)
+{
+ $uris = array();
+
+ if ($alias_list_s) {
+ foreach (explode(',', $alias_list_s) as $alias) {
+ $type_uri = $namespace_map->getNamespaceURI($alias);
+ if ($type_uri === null) {
+ // raise KeyError(
+ // 'No type is defined for attribute name %r' % (alias,))
+ return new Auth_OpenID_AX_Error(
+ sprintf('No type is defined for attribute name %s',
+ $alias)
+ );
+ } else {
+ $uris[] = $type_uri;
+ }
+ }
+ }
+
+ return $uris;
+}
+
+/**
+ * An attribute exchange 'fetch_request' message. This message is sent
+ * by a relying party when it wishes to obtain attributes about the
+ * subject of an OpenID authentication request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_FetchRequest extends Auth_OpenID_AX_Message {
+
+ var $mode = 'fetch_request';
+
+ function Auth_OpenID_AX_FetchRequest($update_url=null)
+ {
+ /**
+ * requested_attributes: The attributes that have been
+ * requested thus far, indexed by the type URI.
+ */
+ $this->requested_attributes = array();
+
+ /**
+ * update_url: A URL that will accept responses for this
+ * attribute exchange request, even in the absence of the user
+ * who made this request.
+ */
+ $this->update_url = $update_url;
+ }
+
+ /**
+ * Add an attribute to this attribute exchange request.
+ *
+ * @param attribute: The attribute that is being requested
+ * @return true on success, false when the requested attribute is
+ * already present in this fetch request.
+ */
+ function add($attribute)
+ {
+ if ($this->contains($attribute->type_uri)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("The attribute %s has already been requested",
+ $attribute->type_uri));
+ }
+
+ $this->requested_attributes[$attribute->type_uri] = $attribute;
+
+ return true;
+ }
+
+ /**
+ * Get the serialized form of this attribute fetch request.
+ *
+ * @returns Auth_OpenID_AX_FetchRequest The fetch request message parameters
+ */
+ function getExtensionArgs()
+ {
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ $required = array();
+ $if_available = array();
+
+ $ax_args = $this->_newArgs();
+
+ foreach ($this->requested_attributes as $type_uri => $attribute) {
+ if ($attribute->alias === null) {
+ $alias = $aliases->add($type_uri);
+ } else {
+ $alias = $aliases->addAlias($type_uri, $attribute->alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $attribute->alias, $type_uri
+ ));
+ }
+ }
+
+ if ($attribute->required) {
+ $required[] = $alias;
+ } else {
+ $if_available[] = $alias;
+ }
+
+ if ($attribute->count != 1) {
+ $ax_args['count.' . $alias] = strval($attribute->count);
+ }
+
+ $ax_args['type.' . $alias] = $type_uri;
+ }
+
+ if ($required) {
+ $ax_args['required'] = implode(',', $required);
+ }
+
+ if ($if_available) {
+ $ax_args['if_available'] = implode(',', $if_available);
+ }
+
+ return $ax_args;
+ }
+
+ /**
+ * Get the type URIs for all attributes that have been marked as
+ * required.
+ *
+ * @return A list of the type URIs for attributes that have been
+ * marked as required.
+ */
+ function getRequiredAttrs()
+ {
+ $required = array();
+ foreach ($this->requested_attributes as $type_uri => $attribute) {
+ if ($attribute->required) {
+ $required[] = $type_uri;
+ }
+ }
+
+ return $required;
+ }
+
+ /**
+ * Extract a FetchRequest from an OpenID message
+ *
+ * @param request: The OpenID request containing the attribute
+ * fetch request
+ *
+ * @returns mixed An Auth_OpenID_AX_Error or the
+ * Auth_OpenID_AX_FetchRequest extracted from the request message if
+ * successful
+ */
+ function &fromOpenIDRequest($request)
+ {
+ $m = $request->message;
+ $obj = new Auth_OpenID_AX_FetchRequest();
+ $ax_args = $m->getArgs($obj->ns_uri);
+
+ $result = $obj->parseExtensionArgs($ax_args);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ if ($obj->update_url) {
+ // Update URL must match the openid.realm of the
+ // underlying OpenID 2 message.
+ $realm = $m->getArg(Auth_OpenID_OPENID_NS, 'realm',
+ $m->getArg(
+ Auth_OpenID_OPENID_NS,
+ 'return_to'));
+
+ if (!$realm) {
+ $obj = new Auth_OpenID_AX_Error(
+ sprintf("Cannot validate update_url %s " .
+ "against absent realm", $obj->update_url));
+ } else if (!Auth_OpenID_TrustRoot::match($realm,
+ $obj->update_url)) {
+ $obj = new Auth_OpenID_AX_Error(
+ sprintf("Update URL %s failed validation against realm %s",
+ $obj->update_url, $realm));
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Given attribute exchange arguments, populate this FetchRequest.
+ *
+ * @return $result Auth_OpenID_AX_Error if the data to be parsed
+ * does not follow the attribute exchange specification. At least
+ * when 'if_available' or 'required' is not specified for a
+ * particular attribute type. Returns true otherwise.
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = $this->_checkMode($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ foreach ($ax_args as $key => $value) {
+ if (strpos($key, 'type.') === 0) {
+ $alias = substr($key, 5);
+ $type_uri = $value;
+
+ $alias = $aliases->addAlias($type_uri, $alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $alias, $type_uri)
+ );
+ }
+
+ $count_s = Auth_OpenID::arrayGet($ax_args, 'count.' . $alias);
+ if ($count_s) {
+ $count = Auth_OpenID::intval($count_s);
+ if (($count === false) &&
+ ($count_s === Auth_OpenID_AX_UNLIMITED_VALUES)) {
+ $count = $count_s;
+ }
+ } else {
+ $count = 1;
+ }
+
+ if ($count === false) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Integer value expected for %s, got %s",
+ 'count.' . $alias, $count_s));
+ }
+
+ $attrinfo = Auth_OpenID_AX_AttrInfo::make($type_uri, $count,
+ false, $alias);
+
+ if (Auth_OpenID_AX::isError($attrinfo)) {
+ return $attrinfo;
+ }
+
+ $this->add($attrinfo);
+ }
+ }
+
+ $required = Auth_OpenID_AX_toTypeURIs($aliases,
+ Auth_OpenID::arrayGet($ax_args, 'required'));
+
+ foreach ($required as $type_uri) {
+ $attrib =& $this->requested_attributes[$type_uri];
+ $attrib->required = true;
+ }
+
+ $if_available = Auth_OpenID_AX_toTypeURIs($aliases,
+ Auth_OpenID::arrayGet($ax_args, 'if_available'));
+
+ $all_type_uris = array_merge($required, $if_available);
+
+ foreach ($aliases->iterNamespaceURIs() as $type_uri) {
+ if (!in_array($type_uri, $all_type_uris)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf('Type URI %s was in the request but not ' .
+ 'present in "required" or "if_available"',
+ $type_uri));
+
+ }
+ }
+
+ $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+ return true;
+ }
+
+ /**
+ * Iterate over the AttrInfo objects that are contained in this
+ * fetch_request.
+ */
+ function iterAttrs()
+ {
+ return array_values($this->requested_attributes);
+ }
+
+ function iterTypes()
+ {
+ return array_keys($this->requested_attributes);
+ }
+
+ /**
+ * Is the given type URI present in this fetch_request?
+ */
+ function contains($type_uri)
+ {
+ return in_array($type_uri, $this->iterTypes());
+ }
+}
+
+/**
+ * An abstract class that implements a message that has attribute keys
+ * and values. It contains the common code between fetch_response and
+ * store_request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_KeyValueMessage extends Auth_OpenID_AX_Message {
+
+ function Auth_OpenID_AX_KeyValueMessage()
+ {
+ $this->data = array();
+ }
+
+ /**
+ * Add a single value for the given attribute type to the
+ * message. If there are already values specified for this type,
+ * this value will be sent in addition to the values already
+ * specified.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param value: The value to add to the response to the relying
+ * party for this attribute
+ * @return null
+ */
+ function addValue($type_uri, $value)
+ {
+ if (!array_key_exists($type_uri, $this->data)) {
+ $this->data[$type_uri] = array();
+ }
+
+ $values =& $this->data[$type_uri];
+ $values[] = $value;
+ }
+
+ /**
+ * Set the values for the given attribute type. This replaces any
+ * values that have already been set for this attribute.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param values: A list of values to send for this attribute.
+ */
+ function setValues($type_uri, &$values)
+ {
+ $this->data[$type_uri] =& $values;
+ }
+
+ /**
+ * Get the extension arguments for the key/value pairs contained
+ * in this message.
+ *
+ * @param aliases: An alias mapping. Set to None if you don't care
+ * about the aliases for this request.
+ *
+ * @access private
+ */
+ function _getExtensionKVArgs(&$aliases)
+ {
+ if ($aliases === null) {
+ $aliases = new Auth_OpenID_NamespaceMap();
+ }
+
+ $ax_args = array();
+
+ foreach ($this->data as $type_uri => $values) {
+ $alias = $aliases->add($type_uri);
+
+ $ax_args['type.' . $alias] = $type_uri;
+ $ax_args['count.' . $alias] = strval(count($values));
+
+ foreach ($values as $i => $value) {
+ $key = sprintf('value.%s.%d', $alias, $i + 1);
+ $ax_args[$key] = $value;
+ }
+ }
+
+ return $ax_args;
+ }
+
+ /**
+ * Parse attribute exchange key/value arguments into this object.
+ *
+ * @param ax_args: The attribute exchange fetch_response
+ * arguments, with namespacing removed.
+ *
+ * @return Auth_OpenID_AX_Error or true
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = $this->_checkMode($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ foreach ($ax_args as $key => $value) {
+ if (strpos($key, 'type.') === 0) {
+ $type_uri = $value;
+ $alias = substr($key, 5);
+
+ $result = Auth_OpenID_AX_checkAlias($alias);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $alias = $aliases->addAlias($type_uri, $alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $alias, $type_uri)
+ );
+ }
+ }
+ }
+
+ foreach ($aliases->iteritems() as $pair) {
+ list($type_uri, $alias) = $pair;
+
+ if (array_key_exists('count.' . $alias, $ax_args)) {
+
+ $count_key = 'count.' . $alias;
+ $count_s = $ax_args[$count_key];
+
+ $count = Auth_OpenID::intval($count_s);
+
+ if ($count === false) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Integer value expected for %s, got %s",
+ 'count. %s' . $alias, $count_s,
+ Auth_OpenID_AX_UNLIMITED_VALUES)
+ );
+ }
+
+ $values = array();
+ for ($i = 1; $i < $count + 1; $i++) {
+ $value_key = sprintf('value.%s.%d', $alias, $i);
+
+ if (!array_key_exists($value_key, $ax_args)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "No value found for key %s",
+ $value_key));
+ }
+
+ $value = $ax_args[$value_key];
+ $values[] = $value;
+ }
+ } else {
+ $key = 'value.' . $alias;
+
+ if (!array_key_exists($key, $ax_args)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf(
+ "No value found for key %s",
+ $key));
+ }
+
+ $value = $ax_args['value.' . $alias];
+
+ if ($value == '') {
+ $values = array();
+ } else {
+ $values = array($value);
+ }
+ }
+
+ $this->data[$type_uri] = $values;
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a single value for an attribute. If no value was sent for
+ * this attribute, use the supplied default. If there is more than
+ * one value for this attribute, this method will fail.
+ *
+ * @param type_uri: The URI for the attribute
+ * @param default: The value to return if the attribute was not
+ * sent in the fetch_response.
+ *
+ * @return $value Auth_OpenID_AX_Error on failure or the value of
+ * the attribute in the fetch_response message, or the default
+ * supplied
+ */
+ function getSingle($type_uri, $default=null)
+ {
+ $values = Auth_OpenID::arrayGet($this->data, $type_uri);
+ if (!$values) {
+ return $default;
+ } else if (count($values) == 1) {
+ return $values[0];
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf('More than one value present for %s',
+ $type_uri)
+ );
+ }
+ }
+
+ /**
+ * Get the list of values for this attribute in the
+ * fetch_response.
+ *
+ * XXX: what to do if the values are not present? default
+ * parameter? this is funny because it's always supposed to return
+ * a list, so the default may break that, though it's provided by
+ * the user's code, so it might be okay. If no default is
+ * supplied, should the return be None or []?
+ *
+ * @param type_uri: The URI of the attribute
+ *
+ * @return $values The list of values for this attribute in the
+ * response. May be an empty list. If the attribute was not sent
+ * in the response, returns Auth_OpenID_AX_Error.
+ */
+ function get($type_uri)
+ {
+ if (array_key_exists($type_uri, $this->data)) {
+ return $this->data[$type_uri];
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Type URI %s not found in response",
+ $type_uri)
+ );
+ }
+ }
+
+ /**
+ * Get the number of responses for a particular attribute in this
+ * fetch_response message.
+ *
+ * @param type_uri: The URI of the attribute
+ *
+ * @returns int The number of values sent for this attribute. If
+ * the attribute was not sent in the response, returns
+ * Auth_OpenID_AX_Error.
+ */
+ function count($type_uri)
+ {
+ if (array_key_exists($type_uri, $this->data)) {
+ return count($this->get($type_uri));
+ } else {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Type URI %s not found in response",
+ $type_uri)
+ );
+ }
+ }
+}
+
+/**
+ * A fetch_response attribute exchange message.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_FetchResponse extends Auth_OpenID_AX_KeyValueMessage {
+ var $mode = 'fetch_response';
+
+ function Auth_OpenID_AX_FetchResponse($update_url=null)
+ {
+ $this->Auth_OpenID_AX_KeyValueMessage();
+ $this->update_url = $update_url;
+ }
+
+ /**
+ * Serialize this object into arguments in the attribute exchange
+ * namespace
+ *
+ * @return $args The dictionary of unqualified attribute exchange
+ * arguments that represent this fetch_response, or
+ * Auth_OpenID_AX_Error on error.
+ */
+ function getExtensionArgs($request=null)
+ {
+ $aliases = new Auth_OpenID_NamespaceMap();
+
+ $zero_value_types = array();
+
+ if ($request !== null) {
+ // Validate the data in the context of the request (the
+ // same attributes should be present in each, and the
+ // counts in the response must be no more than the counts
+ // in the request)
+
+ foreach ($this->data as $type_uri => $unused) {
+ if (!$request->contains($type_uri)) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Response attribute not present in request: %s",
+ $type_uri)
+ );
+ }
+ }
+
+ foreach ($request->iterAttrs() as $attr_info) {
+ // Copy the aliases from the request so that reading
+ // the response in light of the request is easier
+ if ($attr_info->alias === null) {
+ $aliases->add($attr_info->type_uri);
+ } else {
+ $alias = $aliases->addAlias($attr_info->type_uri,
+ $attr_info->alias);
+
+ if ($alias === null) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("Could not add alias %s for URI %s",
+ $attr_info->alias, $attr_info->type_uri)
+ );
+ }
+ }
+
+ if (array_key_exists($attr_info->type_uri, $this->data)) {
+ $values = $this->data[$attr_info->type_uri];
+ } else {
+ $values = array();
+ $zero_value_types[] = $attr_info;
+ }
+
+ if (($attr_info->count != Auth_OpenID_AX_UNLIMITED_VALUES) &&
+ ($attr_info->count < count($values))) {
+ return new Auth_OpenID_AX_Error(
+ sprintf("More than the number of requested values " .
+ "were specified for %s",
+ $attr_info->type_uri)
+ );
+ }
+ }
+ }
+
+ $kv_args = $this->_getExtensionKVArgs($aliases);
+
+ // Add the KV args into the response with the args that are
+ // unique to the fetch_response
+ $ax_args = $this->_newArgs();
+
+ // For each requested attribute, put its type/alias and count
+ // into the response even if no data were returned.
+ foreach ($zero_value_types as $attr_info) {
+ $alias = $aliases->getAlias($attr_info->type_uri);
+ $kv_args['type.' . $alias] = $attr_info->type_uri;
+ $kv_args['count.' . $alias] = '0';
+ }
+
+ $update_url = null;
+ if ($request) {
+ $update_url = $request->update_url;
+ } else {
+ $update_url = $this->update_url;
+ }
+
+ if ($update_url) {
+ $ax_args['update_url'] = $update_url;
+ }
+
+ Auth_OpenID::update(&$ax_args, $kv_args);
+
+ return $ax_args;
+ }
+
+ /**
+ * @return $result Auth_OpenID_AX_Error on failure or true on
+ * success.
+ */
+ function parseExtensionArgs($ax_args)
+ {
+ $result = parent::parseExtensionArgs($ax_args);
+
+ if (Auth_OpenID_AX::isError($result)) {
+ return $result;
+ }
+
+ $this->update_url = Auth_OpenID::arrayGet($ax_args, 'update_url');
+
+ return true;
+ }
+
+ /**
+ * Construct a FetchResponse object from an OpenID library
+ * SuccessResponse object.
+ *
+ * @param success_response: A successful id_res response object
+ *
+ * @param signed: Whether non-signed args should be processsed. If
+ * True (the default), only signed arguments will be processsed.
+ *
+ * @return $response A FetchResponse containing the data from the
+ * OpenID message
+ */
+ function fromSuccessResponse($success_response, $signed=true)
+ {
+ $obj = new Auth_OpenID_AX_FetchResponse();
+ if ($signed) {
+ $ax_args = $success_response->getSignedNS($obj->ns_uri);
+ } else {
+ $ax_args = $success_response->message->getArgs($obj->ns_uri);
+ }
+ if ($ax_args === null || Auth_OpenID::isFailure($ax_args) ||
+ sizeof($ax_args) == 0) {
+ return null;
+ }
+
+ $result = $obj->parseExtensionArgs($ax_args);
+ if (Auth_OpenID_AX::isError($result)) {
+ #XXX log me
+ return null;
+ }
+ return $obj;
+ }
+}
+
+/**
+ * A store request attribute exchange message representation.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_StoreRequest extends Auth_OpenID_AX_KeyValueMessage {
+ var $mode = 'store_request';
+
+ /**
+ * @param array $aliases The namespace aliases to use when making
+ * this store response. Leave as None to use defaults.
+ */
+ function getExtensionArgs($aliases=null)
+ {
+ $ax_args = $this->_newArgs();
+ $kv_args = $this->_getExtensionKVArgs($aliases);
+ Auth_OpenID::update(&$ax_args, $kv_args);
+ return $ax_args;
+ }
+}
+
+/**
+ * An indication that the store request was processed along with this
+ * OpenID transaction. Use make(), NOT the constructor, to create
+ * response objects.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AX_StoreResponse extends Auth_OpenID_AX_Message {
+ var $SUCCESS_MODE = 'store_response_success';
+ var $FAILURE_MODE = 'store_response_failure';
+
+ /**
+ * Returns Auth_OpenID_AX_Error on error or an
+ * Auth_OpenID_AX_StoreResponse object on success.
+ */
+ function &make($succeeded=true, $error_message=null)
+ {
+ if (($succeeded) && ($error_message !== null)) {
+ return new Auth_OpenID_AX_Error('An error message may only be '.
+ 'included in a failing fetch response');
+ }
+
+ return new Auth_OpenID_AX_StoreResponse($succeeded, $error_message);
+ }
+
+ function Auth_OpenID_AX_StoreResponse($succeeded=true, $error_message=null)
+ {
+ if ($succeeded) {
+ $this->mode = $this->SUCCESS_MODE;
+ } else {
+ $this->mode = $this->FAILURE_MODE;
+ }
+
+ $this->error_message = $error_message;
+ }
+
+ /**
+ * Was this response a success response?
+ */
+ function succeeded()
+ {
+ return $this->mode == $this->SUCCESS_MODE;
+ }
+
+ function getExtensionArgs()
+ {
+ $ax_args = $this->_newArgs();
+ if ((!$this->succeeded()) && $this->error_message) {
+ $ax_args['error'] = $this->error_message;
+ }
+
+ return $ax_args;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/Association.php b/extlib/Auth/OpenID/Association.php
new file mode 100644
index 000000000..37ce0cbf4
--- /dev/null
+++ b/extlib/Auth/OpenID/Association.php
@@ -0,0 +1,613 @@
+<?php
+
+/**
+ * This module contains code for dealing with associations between
+ * consumers and servers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/KVForm.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/HMAC.php';
+
+/**
+ * This class represents an association between a server and a
+ * consumer. In general, users of this library will never see
+ * instances of this object. The only exception is if you implement a
+ * custom {@link Auth_OpenID_OpenIDStore}.
+ *
+ * If you do implement such a store, it will need to store the values
+ * of the handle, secret, issued, lifetime, and assoc_type instance
+ * variables.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Association {
+
+ /**
+ * This is a HMAC-SHA1 specific value.
+ *
+ * @access private
+ */
+ var $SIG_LENGTH = 20;
+
+ /**
+ * The ordering and name of keys as stored by serialize.
+ *
+ * @access private
+ */
+ var $assoc_keys = array(
+ 'version',
+ 'handle',
+ 'secret',
+ 'issued',
+ 'lifetime',
+ 'assoc_type'
+ );
+
+ var $_macs = array(
+ 'HMAC-SHA1' => 'Auth_OpenID_HMACSHA1',
+ 'HMAC-SHA256' => 'Auth_OpenID_HMACSHA256'
+ );
+
+ /**
+ * This is an alternate constructor (factory method) used by the
+ * OpenID consumer library to create associations. OpenID store
+ * implementations shouldn't use this constructor.
+ *
+ * @access private
+ *
+ * @param integer $expires_in This is the amount of time this
+ * association is good for, measured in seconds since the
+ * association was issued.
+ *
+ * @param string $handle This is the handle the server gave this
+ * association.
+ *
+ * @param string secret This is the shared secret the server
+ * generated for this association.
+ *
+ * @param assoc_type This is the type of association this
+ * instance represents. The only valid values of this field at
+ * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
+ * be defined in the future.
+ *
+ * @return association An {@link Auth_OpenID_Association}
+ * instance.
+ */
+ function fromExpiresIn($expires_in, $handle, $secret, $assoc_type)
+ {
+ $issued = time();
+ $lifetime = $expires_in;
+ return new Auth_OpenID_Association($handle, $secret,
+ $issued, $lifetime, $assoc_type);
+ }
+
+ /**
+ * This is the standard constructor for creating an association.
+ * The library should create all of the necessary associations, so
+ * this constructor is not part of the external API.
+ *
+ * @access private
+ *
+ * @param string $handle This is the handle the server gave this
+ * association.
+ *
+ * @param string $secret This is the shared secret the server
+ * generated for this association.
+ *
+ * @param integer $issued This is the time this association was
+ * issued, in seconds since 00:00 GMT, January 1, 1970. (ie, a
+ * unix timestamp)
+ *
+ * @param integer $lifetime This is the amount of time this
+ * association is good for, measured in seconds since the
+ * association was issued.
+ *
+ * @param string $assoc_type This is the type of association this
+ * instance represents. The only valid values of this field at
+ * this time is 'HMAC-SHA1' and 'HMAC-SHA256', but new types may
+ * be defined in the future.
+ */
+ function Auth_OpenID_Association(
+ $handle, $secret, $issued, $lifetime, $assoc_type)
+ {
+ if (!in_array($assoc_type,
+ Auth_OpenID_getSupportedAssociationTypes())) {
+ $fmt = 'Unsupported association type (%s)';
+ trigger_error(sprintf($fmt, $assoc_type), E_USER_ERROR);
+ }
+
+ $this->handle = $handle;
+ $this->secret = $secret;
+ $this->issued = $issued;
+ $this->lifetime = $lifetime;
+ $this->assoc_type = $assoc_type;
+ }
+
+ /**
+ * This returns the number of seconds this association is still
+ * valid for, or 0 if the association is no longer valid.
+ *
+ * @return integer $seconds The number of seconds this association
+ * is still valid for, or 0 if the association is no longer valid.
+ */
+ function getExpiresIn($now = null)
+ {
+ if ($now == null) {
+ $now = time();
+ }
+
+ return max(0, $this->issued + $this->lifetime - $now);
+ }
+
+ /**
+ * This checks to see if two {@link Auth_OpenID_Association}
+ * instances represent the same association.
+ *
+ * @return bool $result true if the two instances represent the
+ * same association, false otherwise.
+ */
+ function equal($other)
+ {
+ return ((gettype($this) == gettype($other))
+ && ($this->handle == $other->handle)
+ && ($this->secret == $other->secret)
+ && ($this->issued == $other->issued)
+ && ($this->lifetime == $other->lifetime)
+ && ($this->assoc_type == $other->assoc_type));
+ }
+
+ /**
+ * Convert an association to KV form.
+ *
+ * @return string $result String in KV form suitable for
+ * deserialization by deserialize.
+ */
+ function serialize()
+ {
+ $data = array(
+ 'version' => '2',
+ 'handle' => $this->handle,
+ 'secret' => base64_encode($this->secret),
+ 'issued' => strval(intval($this->issued)),
+ 'lifetime' => strval(intval($this->lifetime)),
+ 'assoc_type' => $this->assoc_type
+ );
+
+ assert(array_keys($data) == $this->assoc_keys);
+
+ return Auth_OpenID_KVForm::fromArray($data, $strict = true);
+ }
+
+ /**
+ * Parse an association as stored by serialize(). This is the
+ * inverse of serialize.
+ *
+ * @param string $assoc_s Association as serialized by serialize()
+ * @return Auth_OpenID_Association $result instance of this class
+ */
+ function deserialize($class_name, $assoc_s)
+ {
+ $pairs = Auth_OpenID_KVForm::toArray($assoc_s, $strict = true);
+ $keys = array();
+ $values = array();
+ foreach ($pairs as $key => $value) {
+ if (is_array($value)) {
+ list($key, $value) = $value;
+ }
+ $keys[] = $key;
+ $values[] = $value;
+ }
+
+ $class_vars = get_class_vars($class_name);
+ $class_assoc_keys = $class_vars['assoc_keys'];
+
+ sort($keys);
+ sort($class_assoc_keys);
+
+ if ($keys != $class_assoc_keys) {
+ trigger_error('Unexpected key values: ' . var_export($keys, true),
+ E_USER_WARNING);
+ return null;
+ }
+
+ $version = $pairs['version'];
+ $handle = $pairs['handle'];
+ $secret = $pairs['secret'];
+ $issued = $pairs['issued'];
+ $lifetime = $pairs['lifetime'];
+ $assoc_type = $pairs['assoc_type'];
+
+ if ($version != '2') {
+ trigger_error('Unknown version: ' . $version, E_USER_WARNING);
+ return null;
+ }
+
+ $issued = intval($issued);
+ $lifetime = intval($lifetime);
+ $secret = base64_decode($secret);
+
+ return new $class_name(
+ $handle, $secret, $issued, $lifetime, $assoc_type);
+ }
+
+ /**
+ * Generate a signature for a sequence of (key, value) pairs
+ *
+ * @access private
+ * @param array $pairs The pairs to sign, in order. This is an
+ * array of two-tuples.
+ * @return string $signature The binary signature of this sequence
+ * of pairs
+ */
+ function sign($pairs)
+ {
+ $kv = Auth_OpenID_KVForm::fromArray($pairs);
+
+ /* Invalid association types should be caught at constructor */
+ $callback = $this->_macs[$this->assoc_type];
+
+ return call_user_func_array($callback, array($this->secret, $kv));
+ }
+
+ /**
+ * Generate a signature for some fields in a dictionary
+ *
+ * @access private
+ * @param array $fields The fields to sign, in order; this is an
+ * array of strings.
+ * @param array $data Dictionary of values to sign (an array of
+ * string => string pairs).
+ * @return string $signature The signature, base64 encoded
+ */
+ function signMessage($message)
+ {
+ if ($message->hasKey(Auth_OpenID_OPENID_NS, 'sig') ||
+ $message->hasKey(Auth_OpenID_OPENID_NS, 'signed')) {
+ // Already has a sig
+ return null;
+ }
+
+ $extant_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+
+ if ($extant_handle && ($extant_handle != $this->handle)) {
+ // raise ValueError("Message has a different association handle")
+ return null;
+ }
+
+ $signed_message = $message;
+ $signed_message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
+ $this->handle);
+
+ $message_keys = array_keys($signed_message->toPostArgs());
+ $signed_list = array();
+ $signed_prefix = 'openid.';
+
+ foreach ($message_keys as $k) {
+ if (strpos($k, $signed_prefix) === 0) {
+ $signed_list[] = substr($k, strlen($signed_prefix));
+ }
+ }
+
+ $signed_list[] = 'signed';
+ sort($signed_list);
+
+ $signed_message->setArg(Auth_OpenID_OPENID_NS, 'signed',
+ implode(',', $signed_list));
+ $sig = $this->getMessageSignature($signed_message);
+ $signed_message->setArg(Auth_OpenID_OPENID_NS, 'sig', $sig);
+ return $signed_message;
+ }
+
+ /**
+ * Given a {@link Auth_OpenID_Message}, return the key/value pairs
+ * to be signed according to the signed list in the message. If
+ * the message lacks a signed list, return null.
+ *
+ * @access private
+ */
+ function _makePairs(&$message)
+ {
+ $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+ if (!$signed || Auth_OpenID::isFailure($signed)) {
+ // raise ValueError('Message has no signed list: %s' % (message,))
+ return null;
+ }
+
+ $signed_list = explode(',', $signed);
+ $pairs = array();
+ $data = $message->toPostArgs();
+ foreach ($signed_list as $field) {
+ $pairs[] = array($field, Auth_OpenID::arrayGet($data,
+ 'openid.' .
+ $field, ''));
+ }
+ return $pairs;
+ }
+
+ /**
+ * Given an {@link Auth_OpenID_Message}, return the signature for
+ * the signed list in the message.
+ *
+ * @access private
+ */
+ function getMessageSignature(&$message)
+ {
+ $pairs = $this->_makePairs($message);
+ return base64_encode($this->sign($pairs));
+ }
+
+ /**
+ * Confirm that the signature of these fields matches the
+ * signature contained in the data.
+ *
+ * @access private
+ */
+ function checkMessageSignature(&$message)
+ {
+ $sig = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'sig');
+
+ if (!$sig || Auth_OpenID::isFailure($sig)) {
+ return false;
+ }
+
+ $calculated_sig = $this->getMessageSignature($message);
+ return $calculated_sig == $sig;
+ }
+}
+
+function Auth_OpenID_getSecretSize($assoc_type)
+{
+ if ($assoc_type == 'HMAC-SHA1') {
+ return 20;
+ } else if ($assoc_type == 'HMAC-SHA256') {
+ return 32;
+ } else {
+ return null;
+ }
+}
+
+function Auth_OpenID_getAllAssociationTypes()
+{
+ return array('HMAC-SHA1', 'HMAC-SHA256');
+}
+
+function Auth_OpenID_getSupportedAssociationTypes()
+{
+ $a = array('HMAC-SHA1');
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $a[] = 'HMAC-SHA256';
+ }
+
+ return $a;
+}
+
+function Auth_OpenID_getSessionTypes($assoc_type)
+{
+ $assoc_to_session = array(
+ 'HMAC-SHA1' => array('DH-SHA1', 'no-encryption'));
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $assoc_to_session['HMAC-SHA256'] =
+ array('DH-SHA256', 'no-encryption');
+ }
+
+ return Auth_OpenID::arrayGet($assoc_to_session, $assoc_type, array());
+}
+
+function Auth_OpenID_checkSessionType($assoc_type, $session_type)
+{
+ if (!in_array($session_type,
+ Auth_OpenID_getSessionTypes($assoc_type))) {
+ return false;
+ }
+
+ return true;
+}
+
+function Auth_OpenID_getDefaultAssociationOrder()
+{
+ $order = array();
+
+ if (!Auth_OpenID_noMathSupport()) {
+ $order[] = array('HMAC-SHA1', 'DH-SHA1');
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $order[] = array('HMAC-SHA256', 'DH-SHA256');
+ }
+ }
+
+ $order[] = array('HMAC-SHA1', 'no-encryption');
+
+ if (Auth_OpenID_HMACSHA256_SUPPORTED) {
+ $order[] = array('HMAC-SHA256', 'no-encryption');
+ }
+
+ return $order;
+}
+
+function Auth_OpenID_getOnlyEncryptedOrder()
+{
+ $result = array();
+
+ foreach (Auth_OpenID_getDefaultAssociationOrder() as $pair) {
+ list($assoc, $session) = $pair;
+
+ if ($session != 'no-encryption') {
+ if (Auth_OpenID_HMACSHA256_SUPPORTED &&
+ ($assoc == 'HMAC-SHA256')) {
+ $result[] = $pair;
+ } else if ($assoc != 'HMAC-SHA256') {
+ $result[] = $pair;
+ }
+ }
+ }
+
+ return $result;
+}
+
+function &Auth_OpenID_getDefaultNegotiator()
+{
+ $x = new Auth_OpenID_SessionNegotiator(
+ Auth_OpenID_getDefaultAssociationOrder());
+ return $x;
+}
+
+function &Auth_OpenID_getEncryptedNegotiator()
+{
+ $x = new Auth_OpenID_SessionNegotiator(
+ Auth_OpenID_getOnlyEncryptedOrder());
+ return $x;
+}
+
+/**
+ * A session negotiator controls the allowed and preferred association
+ * types and association session types. Both the {@link
+ * Auth_OpenID_Consumer} and {@link Auth_OpenID_Server} use
+ * negotiators when creating associations.
+ *
+ * You can create and use negotiators if you:
+
+ * - Do not want to do Diffie-Hellman key exchange because you use
+ * transport-layer encryption (e.g. SSL)
+ *
+ * - Want to use only SHA-256 associations
+ *
+ * - Do not want to support plain-text associations over a non-secure
+ * channel
+ *
+ * It is up to you to set a policy for what kinds of associations to
+ * accept. By default, the library will make any kind of association
+ * that is allowed in the OpenID 2.0 specification.
+ *
+ * Use of negotiators in the library
+ * =================================
+ *
+ * When a consumer makes an association request, it calls {@link
+ * getAllowedType} to get the preferred association type and
+ * association session type.
+ *
+ * The server gets a request for a particular association/session type
+ * and calls {@link isAllowed} to determine if it should create an
+ * association. If it is supported, negotiation is complete. If it is
+ * not, the server calls {@link getAllowedType} to get an allowed
+ * association type to return to the consumer.
+ *
+ * If the consumer gets an error response indicating that the
+ * requested association/session type is not supported by the server
+ * that contains an assocation/session type to try, it calls {@link
+ * isAllowed} to determine if it should try again with the given
+ * combination of association/session type.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SessionNegotiator {
+ function Auth_OpenID_SessionNegotiator($allowed_types)
+ {
+ $this->allowed_types = array();
+ $this->setAllowedTypes($allowed_types);
+ }
+
+ /**
+ * Set the allowed association types, checking to make sure each
+ * combination is valid.
+ *
+ * @access private
+ */
+ function setAllowedTypes($allowed_types)
+ {
+ foreach ($allowed_types as $pair) {
+ list($assoc_type, $session_type) = $pair;
+ if (!Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
+ return false;
+ }
+ }
+
+ $this->allowed_types = $allowed_types;
+ return true;
+ }
+
+ /**
+ * Add an association type and session type to the allowed types
+ * list. The assocation/session pairs are tried in the order that
+ * they are added.
+ *
+ * @access private
+ */
+ function addAllowedType($assoc_type, $session_type = null)
+ {
+ if ($this->allowed_types === null) {
+ $this->allowed_types = array();
+ }
+
+ if ($session_type === null) {
+ $available = Auth_OpenID_getSessionTypes($assoc_type);
+
+ if (!$available) {
+ return false;
+ }
+
+ foreach ($available as $session_type) {
+ $this->addAllowedType($assoc_type, $session_type);
+ }
+ } else {
+ if (Auth_OpenID_checkSessionType($assoc_type, $session_type)) {
+ $this->allowed_types[] = array($assoc_type, $session_type);
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Is this combination of association type and session type allowed?
+ function isAllowed($assoc_type, $session_type)
+ {
+ $assoc_good = in_array(array($assoc_type, $session_type),
+ $this->allowed_types);
+
+ $matches = in_array($session_type,
+ Auth_OpenID_getSessionTypes($assoc_type));
+
+ return ($assoc_good && $matches);
+ }
+
+ /**
+ * Get a pair of assocation type and session type that are
+ * supported.
+ */
+ function getAllowedType()
+ {
+ if (!$this->allowed_types) {
+ return array(null, null);
+ }
+
+ return $this->allowed_types[0];
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/BigMath.php b/extlib/Auth/OpenID/BigMath.php
new file mode 100644
index 000000000..45104947d
--- /dev/null
+++ b/extlib/Auth/OpenID/BigMath.php
@@ -0,0 +1,471 @@
+<?php
+
+/**
+ * BigMath: A math library wrapper that abstracts out the underlying
+ * long integer library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Needed for random number generation
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * Need Auth_OpenID::bytes().
+ */
+require_once 'Auth/OpenID.php';
+
+/**
+ * The superclass of all big-integer math implementations
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_MathLibrary {
+ /**
+ * Given a long integer, returns the number converted to a binary
+ * string. This function accepts long integer values of arbitrary
+ * magnitude and uses the local large-number math library when
+ * available.
+ *
+ * @param integer $long The long number (can be a normal PHP
+ * integer or a number created by one of the available long number
+ * libraries)
+ * @return string $binary The binary version of $long
+ */
+ function longToBinary($long)
+ {
+ $cmp = $this->cmp($long, 0);
+ if ($cmp < 0) {
+ $msg = __FUNCTION__ . " takes only positive integers.";
+ trigger_error($msg, E_USER_ERROR);
+ return null;
+ }
+
+ if ($cmp == 0) {
+ return "\x00";
+ }
+
+ $bytes = array();
+
+ while ($this->cmp($long, 0) > 0) {
+ array_unshift($bytes, $this->mod($long, 256));
+ $long = $this->div($long, pow(2, 8));
+ }
+
+ if ($bytes && ($bytes[0] > 127)) {
+ array_unshift($bytes, 0);
+ }
+
+ $string = '';
+ foreach ($bytes as $byte) {
+ $string .= pack('C', $byte);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Given a binary string, returns the binary string converted to a
+ * long number.
+ *
+ * @param string $binary The binary version of a long number,
+ * probably as a result of calling longToBinary
+ * @return integer $long The long number equivalent of the binary
+ * string $str
+ */
+ function binaryToLong($str)
+ {
+ if ($str === null) {
+ return null;
+ }
+
+ // Use array_merge to return a zero-indexed array instead of a
+ // one-indexed array.
+ $bytes = array_merge(unpack('C*', $str));
+
+ $n = $this->init(0);
+
+ if ($bytes && ($bytes[0] > 127)) {
+ trigger_error("bytesToNum works only for positive integers.",
+ E_USER_WARNING);
+ return null;
+ }
+
+ foreach ($bytes as $byte) {
+ $n = $this->mul($n, pow(2, 8));
+ $n = $this->add($n, $byte);
+ }
+
+ return $n;
+ }
+
+ function base64ToLong($str)
+ {
+ $b64 = base64_decode($str);
+
+ if ($b64 === false) {
+ return false;
+ }
+
+ return $this->binaryToLong($b64);
+ }
+
+ function longToBase64($str)
+ {
+ return base64_encode($this->longToBinary($str));
+ }
+
+ /**
+ * Returns a random number in the specified range. This function
+ * accepts $start, $stop, and $step values of arbitrary magnitude
+ * and will utilize the local large-number math library when
+ * available.
+ *
+ * @param integer $start The start of the range, or the minimum
+ * random number to return
+ * @param integer $stop The end of the range, or the maximum
+ * random number to return
+ * @param integer $step The step size, such that $result - ($step
+ * * N) = $start for some N
+ * @return integer $result The resulting randomly-generated number
+ */
+ function rand($stop)
+ {
+ static $duplicate_cache = array();
+
+ // Used as the key for the duplicate cache
+ $rbytes = $this->longToBinary($stop);
+
+ if (array_key_exists($rbytes, $duplicate_cache)) {
+ list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
+ } else {
+ if ($rbytes[0] == "\x00") {
+ $nbytes = Auth_OpenID::bytes($rbytes) - 1;
+ } else {
+ $nbytes = Auth_OpenID::bytes($rbytes);
+ }
+
+ $mxrand = $this->pow(256, $nbytes);
+
+ // If we get a number less than this, then it is in the
+ // duplicated range.
+ $duplicate = $this->mod($mxrand, $stop);
+
+ if (count($duplicate_cache) > 10) {
+ $duplicate_cache = array();
+ }
+
+ $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
+ }
+
+ do {
+ $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
+ $n = $this->binaryToLong($bytes);
+ // Keep looping if this value is in the low duplicated range
+ } while ($this->cmp($n, $duplicate) < 0);
+
+ return $this->mod($n, $stop);
+ }
+}
+
+/**
+ * Exposes BCmath math library functionality.
+ *
+ * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided
+ * by the BCMath extension.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{
+ var $type = 'bcmath';
+
+ function add($x, $y)
+ {
+ return bcadd($x, $y);
+ }
+
+ function sub($x, $y)
+ {
+ return bcsub($x, $y);
+ }
+
+ function pow($base, $exponent)
+ {
+ return bcpow($base, $exponent);
+ }
+
+ function cmp($x, $y)
+ {
+ return bccomp($x, $y);
+ }
+
+ function init($number, $base = 10)
+ {
+ return $number;
+ }
+
+ function mod($base, $modulus)
+ {
+ return bcmod($base, $modulus);
+ }
+
+ function mul($x, $y)
+ {
+ return bcmul($x, $y);
+ }
+
+ function div($x, $y)
+ {
+ return bcdiv($x, $y);
+ }
+
+ /**
+ * Same as bcpowmod when bcpowmod is missing
+ *
+ * @access private
+ */
+ function _powmod($base, $exponent, $modulus)
+ {
+ $square = $this->mod($base, $modulus);
+ $result = 1;
+ while($this->cmp($exponent, 0) > 0) {
+ if ($this->mod($exponent, 2)) {
+ $result = $this->mod($this->mul($result, $square), $modulus);
+ }
+ $square = $this->mod($this->mul($square, $square), $modulus);
+ $exponent = $this->div($exponent, 2);
+ }
+ return $result;
+ }
+
+ function powmod($base, $exponent, $modulus)
+ {
+ if (function_exists('bcpowmod')) {
+ return bcpowmod($base, $exponent, $modulus);
+ } else {
+ return $this->_powmod($base, $exponent, $modulus);
+ }
+ }
+
+ function toString($num)
+ {
+ return $num;
+ }
+}
+
+/**
+ * Exposes GMP math library functionality.
+ *
+ * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided
+ * by the GMP extension.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{
+ var $type = 'gmp';
+
+ function add($x, $y)
+ {
+ return gmp_add($x, $y);
+ }
+
+ function sub($x, $y)
+ {
+ return gmp_sub($x, $y);
+ }
+
+ function pow($base, $exponent)
+ {
+ return gmp_pow($base, $exponent);
+ }
+
+ function cmp($x, $y)
+ {
+ return gmp_cmp($x, $y);
+ }
+
+ function init($number, $base = 10)
+ {
+ return gmp_init($number, $base);
+ }
+
+ function mod($base, $modulus)
+ {
+ return gmp_mod($base, $modulus);
+ }
+
+ function mul($x, $y)
+ {
+ return gmp_mul($x, $y);
+ }
+
+ function div($x, $y)
+ {
+ return gmp_div_q($x, $y);
+ }
+
+ function powmod($base, $exponent, $modulus)
+ {
+ return gmp_powm($base, $exponent, $modulus);
+ }
+
+ function toString($num)
+ {
+ return gmp_strval($num);
+ }
+}
+
+/**
+ * Define the supported extensions. An extension array has keys
+ * 'modules', 'extension', and 'class'. 'modules' is an array of PHP
+ * module names which the loading code will attempt to load. These
+ * values will be suffixed with a library file extension (e.g. ".so").
+ * 'extension' is the name of a PHP extension which will be tested
+ * before 'modules' are loaded. 'class' is the string name of a
+ * {@link Auth_OpenID_MathWrapper} subclass which should be
+ * instantiated if a given extension is present.
+ *
+ * You can define new math library implementations and add them to
+ * this array.
+ */
+function Auth_OpenID_math_extensions()
+{
+ $result = array();
+
+ if (!defined('Auth_OpenID_BUGGY_GMP')) {
+ $result[] =
+ array('modules' => array('gmp', 'php_gmp'),
+ 'extension' => 'gmp',
+ 'class' => 'Auth_OpenID_GmpMathWrapper');
+ }
+
+ $result[] = array(
+ 'modules' => array('bcmath', 'php_bcmath'),
+ 'extension' => 'bcmath',
+ 'class' => 'Auth_OpenID_BcMathWrapper');
+
+ return $result;
+}
+
+/**
+ * Detect which (if any) math library is available
+ */
+function Auth_OpenID_detectMathLibrary($exts)
+{
+ $loaded = false;
+
+ foreach ($exts as $extension) {
+ // See if the extension specified is already loaded.
+ if ($extension['extension'] &&
+ extension_loaded($extension['extension'])) {
+ $loaded = true;
+ }
+
+ // Try to load dynamic modules.
+ if (!$loaded) {
+ foreach ($extension['modules'] as $module) {
+ if (@dl($module . "." . PHP_SHLIB_SUFFIX)) {
+ $loaded = true;
+ break;
+ }
+ }
+ }
+
+ // If the load succeeded, supply an instance of
+ // Auth_OpenID_MathWrapper which wraps the specified
+ // module's functionality.
+ if ($loaded) {
+ return $extension;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * {@link Auth_OpenID_getMathLib} checks for the presence of long
+ * number extension modules and returns an instance of
+ * {@link Auth_OpenID_MathWrapper} which exposes the module's
+ * functionality.
+ *
+ * Checks for the existence of an extension module described by the
+ * result of {@link Auth_OpenID_math_extensions()} and returns an
+ * instance of a wrapper for that extension module. If no extension
+ * module is found, an instance of {@link Auth_OpenID_MathWrapper} is
+ * returned, which wraps the native PHP integer implementation. The
+ * proper calling convention for this method is $lib =&
+ * Auth_OpenID_getMathLib().
+ *
+ * This function checks for the existence of specific long number
+ * implementations in the following order: GMP followed by BCmath.
+ *
+ * @return Auth_OpenID_MathWrapper $instance An instance of
+ * {@link Auth_OpenID_MathWrapper} or one of its subclasses
+ *
+ * @package OpenID
+ */
+function &Auth_OpenID_getMathLib()
+{
+ // The instance of Auth_OpenID_MathWrapper that we choose to
+ // supply will be stored here, so that subseqent calls to this
+ // method will return a reference to the same object.
+ static $lib = null;
+
+ if (isset($lib)) {
+ return $lib;
+ }
+
+ if (Auth_OpenID_noMathSupport()) {
+ $null = null;
+ return $null;
+ }
+
+ // If this method has not been called before, look at
+ // Auth_OpenID_math_extensions and try to find an extension that
+ // works.
+ $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
+ if ($ext === false) {
+ $tried = array();
+ foreach (Auth_OpenID_math_extensions() as $extinfo) {
+ $tried[] = $extinfo['extension'];
+ }
+ $triedstr = implode(", ", $tried);
+
+ Auth_OpenID_setNoMathSupport();
+
+ $result = null;
+ return $result;
+ }
+
+ // Instantiate a new wrapper
+ $class = $ext['class'];
+ $lib = new $class();
+
+ return $lib;
+}
+
+function Auth_OpenID_setNoMathSupport()
+{
+ if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
+ define('Auth_OpenID_NO_MATH_SUPPORT', true);
+ }
+}
+
+function Auth_OpenID_noMathSupport()
+{
+ return defined('Auth_OpenID_NO_MATH_SUPPORT');
+}
+
+?>
diff --git a/extlib/Auth/OpenID/Consumer.php b/extlib/Auth/OpenID/Consumer.php
new file mode 100644
index 000000000..a72684c6b
--- /dev/null
+++ b/extlib/Auth/OpenID/Consumer.php
@@ -0,0 +1,2229 @@
+<?php
+
+/**
+ * This module documents the main interface with the OpenID consumer
+ * library. The only part of the library which has to be used and
+ * isn't documented in full here is the store required to create an
+ * Auth_OpenID_Consumer instance. More on the abstract store type and
+ * concrete implementations of it that are provided in the
+ * documentation for the Auth_OpenID_Consumer constructor.
+ *
+ * OVERVIEW
+ *
+ * The OpenID identity verification process most commonly uses the
+ * following steps, as visible to the user of this library:
+ *
+ * 1. The user enters their OpenID into a field on the consumer's
+ * site, and hits a login button.
+ * 2. The consumer site discovers the user's OpenID server using the
+ * YADIS protocol.
+ * 3. The consumer site sends the browser a redirect to the identity
+ * server. This is the authentication request as described in
+ * the OpenID specification.
+ * 4. The identity server's site sends the browser a redirect back
+ * to the consumer site. This redirect contains the server's
+ * response to the authentication request.
+ *
+ * The most important part of the flow to note is the consumer's site
+ * must handle two separate HTTP requests in order to perform the full
+ * identity check.
+ *
+ * LIBRARY DESIGN
+ *
+ * This consumer library is designed with that flow in mind. The goal
+ * is to make it as easy as possible to perform the above steps
+ * securely.
+ *
+ * At a high level, there are two important parts in the consumer
+ * library. The first important part is this module, which contains
+ * the interface to actually use this library. The second is the
+ * Auth_OpenID_Interface class, which describes the interface to use
+ * if you need to create a custom method for storing the state this
+ * library needs to maintain between requests.
+ *
+ * In general, the second part is less important for users of the
+ * library to know about, as several implementations are provided
+ * which cover a wide variety of situations in which consumers may use
+ * the library.
+ *
+ * This module contains a class, Auth_OpenID_Consumer, with methods
+ * corresponding to the actions necessary in each of steps 2, 3, and 4
+ * described in the overview. Use of this library should be as easy
+ * as creating an Auth_OpenID_Consumer instance and calling the
+ * methods appropriate for the action the site wants to take.
+ *
+ * STORES AND DUMB MODE
+ *
+ * OpenID is a protocol that works best when the consumer site is able
+ * to store some state. This is the normal mode of operation for the
+ * protocol, and is sometimes referred to as smart mode. There is
+ * also a fallback mode, known as dumb mode, which is available when
+ * the consumer site is not able to store state. This mode should be
+ * avoided when possible, as it leaves the implementation more
+ * vulnerable to replay attacks.
+ *
+ * The mode the library works in for normal operation is determined by
+ * the store that it is given. The store is an abstraction that
+ * handles the data that the consumer needs to manage between http
+ * requests in order to operate efficiently and securely.
+ *
+ * Several store implementation are provided, and the interface is
+ * fully documented so that custom stores can be used as well. See
+ * the documentation for the Auth_OpenID_Consumer class for more
+ * information on the interface for stores. The implementations that
+ * are provided allow the consumer site to store the necessary data in
+ * several different ways, including several SQL databases and normal
+ * files on disk.
+ *
+ * There is an additional concrete store provided that puts the system
+ * in dumb mode. This is not recommended, as it removes the library's
+ * ability to stop replay attacks reliably. It still uses time-based
+ * checking to make replay attacks only possible within a small
+ * window, but they remain possible within that window. This store
+ * should only be used if the consumer site has no way to retain data
+ * between requests at all.
+ *
+ * IMMEDIATE MODE
+ *
+ * In the flow described above, the user may need to confirm to the
+ * lidentity server that it's ok to authorize his or her identity.
+ * The server may draw pages asking for information from the user
+ * before it redirects the browser back to the consumer's site. This
+ * is generally transparent to the consumer site, so it is typically
+ * ignored as an implementation detail.
+ *
+ * There can be times, however, where the consumer site wants to get a
+ * response immediately. When this is the case, the consumer can put
+ * the library in immediate mode. In immediate mode, there is an
+ * extra response possible from the server, which is essentially the
+ * server reporting that it doesn't have enough information to answer
+ * the question yet.
+ *
+ * USING THIS LIBRARY
+ *
+ * Integrating this library into an application is usually a
+ * relatively straightforward process. The process should basically
+ * follow this plan:
+ *
+ * Add an OpenID login field somewhere on your site. When an OpenID
+ * is entered in that field and the form is submitted, it should make
+ * a request to the your site which includes that OpenID URL.
+ *
+ * First, the application should instantiate the Auth_OpenID_Consumer
+ * class using the store of choice (Auth_OpenID_FileStore or one of
+ * the SQL-based stores). If the application has a custom
+ * session-management implementation, an object implementing the
+ * {@link Auth_Yadis_PHPSession} interface should be passed as the
+ * second parameter. Otherwise, the default uses $_SESSION.
+ *
+ * Next, the application should call the Auth_OpenID_Consumer object's
+ * 'begin' method. This method takes the OpenID URL. The 'begin'
+ * method returns an Auth_OpenID_AuthRequest object.
+ *
+ * Next, the application should call the 'redirectURL' method of the
+ * Auth_OpenID_AuthRequest object. The 'return_to' URL parameter is
+ * the URL that the OpenID server will send the user back to after
+ * attempting to verify his or her identity. The 'trust_root' is the
+ * URL (or URL pattern) that identifies your web site to the user when
+ * he or she is authorizing it. Send a redirect to the resulting URL
+ * to the user's browser.
+ *
+ * That's the first half of the authentication process. The second
+ * half of the process is done after the user's ID server sends the
+ * user's browser a redirect back to your site to complete their
+ * login.
+ *
+ * When that happens, the user will contact your site at the URL given
+ * as the 'return_to' URL to the Auth_OpenID_AuthRequest::redirectURL
+ * call made above. The request will have several query parameters
+ * added to the URL by the identity server as the information
+ * necessary to finish the request.
+ *
+ * Lastly, instantiate an Auth_OpenID_Consumer instance as above and
+ * call its 'complete' method, passing in all the received query
+ * arguments.
+ *
+ * There are multiple possible return types possible from that
+ * method. These indicate the whether or not the login was successful,
+ * and include any additional information appropriate for their type.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require utility classes and functions for the consumer.
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/HMAC.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/Nonce.php";
+require_once "Auth/OpenID/Discover.php";
+require_once "Auth/OpenID/URINorm.php";
+require_once "Auth/Yadis/Manager.php";
+require_once "Auth/Yadis/XRI.php";
+
+/**
+ * This is the status code returned when the complete method returns
+ * successfully.
+ */
+define('Auth_OpenID_SUCCESS', 'success');
+
+/**
+ * Status to indicate cancellation of OpenID authentication.
+ */
+define('Auth_OpenID_CANCEL', 'cancel');
+
+/**
+ * This is the status code completeAuth returns when the value it
+ * received indicated an invalid login.
+ */
+define('Auth_OpenID_FAILURE', 'failure');
+
+/**
+ * This is the status code completeAuth returns when the
+ * {@link Auth_OpenID_Consumer} instance is in immediate mode, and the
+ * identity server sends back a URL to send the user to to complete his
+ * or her login.
+ */
+define('Auth_OpenID_SETUP_NEEDED', 'setup needed');
+
+/**
+ * This is the status code beginAuth returns when the page fetched
+ * from the entered OpenID URL doesn't contain the necessary link tags
+ * to function as an identity page.
+ */
+define('Auth_OpenID_PARSE_ERROR', 'parse error');
+
+/**
+ * An OpenID consumer implementation that performs discovery and does
+ * session management. See the Consumer.php file documentation for
+ * more information.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Consumer {
+
+ /**
+ * @access private
+ */
+ var $discoverMethod = 'Auth_OpenID_discover';
+
+ /**
+ * @access private
+ */
+ var $session_key_prefix = "_openid_consumer_";
+
+ /**
+ * @access private
+ */
+ var $_token_suffix = "last_token";
+
+ /**
+ * Initialize a Consumer instance.
+ *
+ * You should create a new instance of the Consumer object with
+ * every HTTP request that handles OpenID transactions.
+ *
+ * @param Auth_OpenID_OpenIDStore $store This must be an object
+ * that implements the interface in {@link
+ * Auth_OpenID_OpenIDStore}. Several concrete implementations are
+ * provided, to cover most common use cases. For stores backed by
+ * MySQL, PostgreSQL, or SQLite, see the {@link
+ * Auth_OpenID_SQLStore} class and its sublcasses. For a
+ * filesystem-backed store, see the {@link Auth_OpenID_FileStore}
+ * module. As a last resort, if it isn't possible for the server
+ * to store state at all, an instance of {@link
+ * Auth_OpenID_DumbStore} can be used.
+ *
+ * @param mixed $session An object which implements the interface
+ * of the {@link Auth_Yadis_PHPSession} class. Particularly, this
+ * object is expected to have these methods: get($key), set($key),
+ * $value), and del($key). This defaults to a session object
+ * which wraps PHP's native session machinery. You should only
+ * need to pass something here if you have your own sessioning
+ * implementation.
+ *
+ * @param str $consumer_cls The name of the class to instantiate
+ * when creating the internal consumer object. This is used for
+ * testing.
+ */
+ function Auth_OpenID_Consumer(&$store, $session = null,
+ $consumer_cls = null)
+ {
+ if ($session === null) {
+ $session = new Auth_Yadis_PHPSession();
+ }
+
+ $this->session =& $session;
+
+ if ($consumer_cls !== null) {
+ $this->consumer =& new $consumer_cls($store);
+ } else {
+ $this->consumer =& new Auth_OpenID_GenericConsumer($store);
+ }
+
+ $this->_token_key = $this->session_key_prefix . $this->_token_suffix;
+ }
+
+ /**
+ * Used in testing to define the discovery mechanism.
+ *
+ * @access private
+ */
+ function getDiscoveryObject(&$session, $openid_url,
+ $session_key_prefix)
+ {
+ return new Auth_Yadis_Discovery($session, $openid_url,
+ $session_key_prefix);
+ }
+
+ /**
+ * Start the OpenID authentication process. See steps 1-2 in the
+ * overview at the top of this file.
+ *
+ * @param string $user_url Identity URL given by the user. This
+ * method performs a textual transformation of the URL to try and
+ * make sure it is normalized. For example, a user_url of
+ * example.com will be normalized to http://example.com/
+ * normalizing and resolving any redirects the server might issue.
+ *
+ * @param bool $anonymous True if the OpenID request is to be sent
+ * to the server without any identifier information. Use this
+ * when you want to transport data but don't want to do OpenID
+ * authentication with identifiers.
+ *
+ * @return Auth_OpenID_AuthRequest $auth_request An object
+ * containing the discovered information will be returned, with a
+ * method for building a redirect URL to the server, as described
+ * in step 3 of the overview. This object may also be used to add
+ * extension arguments to the request, using its 'addExtensionArg'
+ * method.
+ */
+ function begin($user_url, $anonymous=false)
+ {
+ $openid_url = $user_url;
+
+ $disco = $this->getDiscoveryObject($this->session,
+ $openid_url,
+ $this->session_key_prefix);
+
+ // Set the 'stale' attribute of the manager. If discovery
+ // fails in a fatal way, the stale flag will cause the manager
+ // to be cleaned up next time discovery is attempted.
+
+ $m = $disco->getManager();
+ $loader = new Auth_Yadis_ManagerLoader();
+
+ if ($m) {
+ if ($m->stale) {
+ $disco->destroyManager();
+ } else {
+ $m->stale = true;
+ $disco->session->set($disco->session_key,
+ serialize($loader->toSession($m)));
+ }
+ }
+
+ $endpoint = $disco->getNextService($this->discoverMethod,
+ $this->consumer->fetcher);
+
+ // Reset the 'stale' attribute of the manager.
+ $m =& $disco->getManager();
+ if ($m) {
+ $m->stale = false;
+ $disco->session->set($disco->session_key,
+ serialize($loader->toSession($m)));
+ }
+
+ if ($endpoint === null) {
+ return null;
+ } else {
+ return $this->beginWithoutDiscovery($endpoint,
+ $anonymous);
+ }
+ }
+
+ /**
+ * Start OpenID verification without doing OpenID server
+ * discovery. This method is used internally by Consumer.begin
+ * after discovery is performed, and exists to provide an
+ * interface for library users needing to perform their own
+ * discovery.
+ *
+ * @param Auth_OpenID_ServiceEndpoint $endpoint an OpenID service
+ * endpoint descriptor.
+ *
+ * @param bool anonymous Set to true if you want to perform OpenID
+ * without identifiers.
+ *
+ * @return Auth_OpenID_AuthRequest $auth_request An OpenID
+ * authentication request object.
+ */
+ function &beginWithoutDiscovery($endpoint, $anonymous=false)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $auth_req = $this->consumer->begin($endpoint);
+ $this->session->set($this->_token_key,
+ $loader->toSession($auth_req->endpoint));
+ if (!$auth_req->setAnonymous($anonymous)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "OpenID 1 requests MUST include the identifier " .
+ "in the request.");
+ }
+ return $auth_req;
+ }
+
+ /**
+ * Called to interpret the server's response to an OpenID
+ * request. It is called in step 4 of the flow described in the
+ * consumer overview.
+ *
+ * @param string $current_url The URL used to invoke the application.
+ * Extract the URL from your application's web
+ * request framework and specify it here to have it checked
+ * against the openid.current_url value in the response. If
+ * the current_url URL check fails, the status of the
+ * completion will be FAILURE.
+ *
+ * @param array $query An array of the query parameters (key =>
+ * value pairs) for this HTTP request. Defaults to null. If
+ * null, the GET or POST data are automatically gotten from the
+ * PHP environment. It is only useful to override $query for
+ * testing.
+ *
+ * @return Auth_OpenID_ConsumerResponse $response A instance of an
+ * Auth_OpenID_ConsumerResponse subclass. The type of response is
+ * indicated by the status attribute, which will be one of
+ * SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED.
+ */
+ function complete($current_url, $query=null)
+ {
+ if ($current_url && !is_string($current_url)) {
+ // This is ugly, but we need to complain loudly when
+ // someone uses the API incorrectly.
+ trigger_error("current_url must be a string; see NEWS file " .
+ "for upgrading notes.",
+ E_USER_ERROR);
+ }
+
+ if ($query === null) {
+ $query = Auth_OpenID::getQuery();
+ }
+
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $endpoint_data = $this->session->get($this->_token_key);
+ $endpoint =
+ $loader->fromSession($endpoint_data);
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $response = $this->consumer->complete($message, $endpoint,
+ $current_url);
+ $this->session->del($this->_token_key);
+
+ if (in_array($response->status, array(Auth_OpenID_SUCCESS,
+ Auth_OpenID_CANCEL))) {
+ if ($response->identity_url !== null) {
+ $disco = $this->getDiscoveryObject($this->session,
+ $response->identity_url,
+ $this->session_key_prefix);
+ $disco->cleanup(true);
+ }
+ }
+
+ return $response;
+ }
+}
+
+/**
+ * A class implementing HMAC/DH-SHA1 consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+ var $session_type = 'DH-SHA1';
+ var $hash_func = 'Auth_OpenID_SHA1';
+ var $secret_size = 20;
+ var $allowed_assoc_types = array('HMAC-SHA1');
+
+ function Auth_OpenID_DiffieHellmanSHA1ConsumerSession($dh = null)
+ {
+ if ($dh === null) {
+ $dh = new Auth_OpenID_DiffieHellman();
+ }
+
+ $this->dh = $dh;
+ }
+
+ function getRequest()
+ {
+ $math =& Auth_OpenID_getMathLib();
+
+ $cpub = $math->longToBase64($this->dh->public);
+
+ $args = array('dh_consumer_public' => $cpub);
+
+ if (!$this->dh->usingDefaultValues()) {
+ $args = array_merge($args, array(
+ 'dh_modulus' =>
+ $math->longToBase64($this->dh->mod),
+ 'dh_gen' =>
+ $math->longToBase64($this->dh->gen)));
+ }
+
+ return $args;
+ }
+
+ function extractSecret($response)
+ {
+ if (!$response->hasKey(Auth_OpenID_OPENID_NS,
+ 'dh_server_public')) {
+ return null;
+ }
+
+ if (!$response->hasKey(Auth_OpenID_OPENID_NS,
+ 'enc_mac_key')) {
+ return null;
+ }
+
+ $math =& Auth_OpenID_getMathLib();
+
+ $spub = $math->base64ToLong($response->getArg(Auth_OpenID_OPENID_NS,
+ 'dh_server_public'));
+ $enc_mac_key = base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
+ 'enc_mac_key'));
+
+ return $this->dh->xorSecret($spub, $enc_mac_key, $this->hash_func);
+ }
+}
+
+/**
+ * A class implementing HMAC/DH-SHA256 consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA256ConsumerSession extends
+ Auth_OpenID_DiffieHellmanSHA1ConsumerSession {
+ var $session_type = 'DH-SHA256';
+ var $hash_func = 'Auth_OpenID_SHA256';
+ var $secret_size = 32;
+ var $allowed_assoc_types = array('HMAC-SHA256');
+}
+
+/**
+ * A class implementing plaintext consumer sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainTextConsumerSession {
+ var $session_type = 'no-encryption';
+ var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
+
+ function getRequest()
+ {
+ return array();
+ }
+
+ function extractSecret($response)
+ {
+ if (!$response->hasKey(Auth_OpenID_OPENID_NS, 'mac_key')) {
+ return null;
+ }
+
+ return base64_decode($response->getArg(Auth_OpenID_OPENID_NS,
+ 'mac_key'));
+ }
+}
+
+/**
+ * Returns available session types.
+ */
+function Auth_OpenID_getAvailableSessionTypes()
+{
+ $types = array(
+ 'no-encryption' => 'Auth_OpenID_PlainTextConsumerSession',
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ConsumerSession',
+ 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ConsumerSession');
+
+ return $types;
+}
+
+/**
+ * This class is the interface to the OpenID consumer logic.
+ * Instances of it maintain no per-request state, so they can be
+ * reused (or even used by multiple threads concurrently) as needed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_GenericConsumer {
+ /**
+ * @access private
+ */
+ var $discoverMethod = 'Auth_OpenID_discover';
+
+ /**
+ * This consumer's store object.
+ */
+ var $store;
+
+ /**
+ * @access private
+ */
+ var $_use_assocs;
+
+ /**
+ * @access private
+ */
+ var $openid1_nonce_query_arg_name = 'janrain_nonce';
+
+ /**
+ * Another query parameter that gets added to the return_to for
+ * OpenID 1; if the user's session state is lost, use this claimed
+ * identifier to do discovery when verifying the response.
+ */
+ var $openid1_return_to_identifier_name = 'openid1_claimed_id';
+
+ /**
+ * This method initializes a new {@link Auth_OpenID_Consumer}
+ * instance to access the library.
+ *
+ * @param Auth_OpenID_OpenIDStore $store This must be an object
+ * that implements the interface in {@link Auth_OpenID_OpenIDStore}.
+ * Several concrete implementations are provided, to cover most common use
+ * cases. For stores backed by MySQL, PostgreSQL, or SQLite, see
+ * the {@link Auth_OpenID_SQLStore} class and its sublcasses. For a
+ * filesystem-backed store, see the {@link Auth_OpenID_FileStore} module.
+ * As a last resort, if it isn't possible for the server to store
+ * state at all, an instance of {@link Auth_OpenID_DumbStore} can be used.
+ *
+ * @param bool $immediate This is an optional boolean value. It
+ * controls whether the library uses immediate mode, as explained
+ * in the module description. The default value is False, which
+ * disables immediate mode.
+ */
+ function Auth_OpenID_GenericConsumer(&$store)
+ {
+ $this->store =& $store;
+ $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
+ $this->_use_assocs = ($this->store ? true : false);
+
+ $this->fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ $this->session_types = Auth_OpenID_getAvailableSessionTypes();
+ }
+
+ /**
+ * Called to begin OpenID authentication using the specified
+ * {@link Auth_OpenID_ServiceEndpoint}.
+ *
+ * @access private
+ */
+ function begin($service_endpoint)
+ {
+ $assoc = $this->_getAssociation($service_endpoint);
+ $r = new Auth_OpenID_AuthRequest($service_endpoint, $assoc);
+ $r->return_to_args[$this->openid1_nonce_query_arg_name] =
+ Auth_OpenID_mkNonce();
+
+ if ($r->message->isOpenID1()) {
+ $r->return_to_args[$this->openid1_return_to_identifier_name] =
+ $r->endpoint->claimed_id;
+ }
+
+ return $r;
+ }
+
+ /**
+ * Given an {@link Auth_OpenID_Message}, {@link
+ * Auth_OpenID_ServiceEndpoint} and optional return_to URL,
+ * complete OpenID authentication.
+ *
+ * @access private
+ */
+ function complete($message, $endpoint, $return_to)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+ '<no mode set>');
+
+ $mode_methods = array(
+ 'cancel' => '_complete_cancel',
+ 'error' => '_complete_error',
+ 'setup_needed' => '_complete_setup_needed',
+ 'id_res' => '_complete_id_res',
+ );
+
+ $method = Auth_OpenID::arrayGet($mode_methods, $mode,
+ '_completeInvalid');
+
+ return call_user_func_array(array(&$this, $method),
+ array($message, $endpoint, $return_to));
+ }
+
+ /**
+ * @access private
+ */
+ function _completeInvalid($message, &$endpoint, $unused)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode',
+ '<No mode set>');
+
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf("Invalid openid.mode '%s'", $mode));
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_cancel($message, &$endpoint, $unused)
+ {
+ return new Auth_OpenID_CancelResponse($endpoint);
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_error($message, &$endpoint, $unused)
+ {
+ $error = $message->getArg(Auth_OpenID_OPENID_NS, 'error');
+ $contact = $message->getArg(Auth_OpenID_OPENID_NS, 'contact');
+ $reference = $message->getArg(Auth_OpenID_OPENID_NS, 'reference');
+
+ return new Auth_OpenID_FailureResponse($endpoint, $error,
+ $contact, $reference);
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_setup_needed($message, &$endpoint, $unused)
+ {
+ if (!$message->isOpenID2()) {
+ return $this->_completeInvalid($message, $endpoint);
+ }
+
+ $user_setup_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'user_setup_url');
+ return new Auth_OpenID_SetupNeededResponse($endpoint, $user_setup_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _complete_id_res($message, &$endpoint, $return_to)
+ {
+ $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+ 'user_setup_url');
+
+ if ($this->_checkSetupNeeded($message)) {
+ return new Auth_OpenID_SetupNeededResponse(
+ $endpoint, $user_setup_url);
+ } else {
+ return $this->_doIdRes($message, $endpoint, $return_to);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _checkSetupNeeded($message)
+ {
+ // In OpenID 1, we check to see if this is a cancel from
+ // immediate mode by the presence of the user_setup_url
+ // parameter.
+ if ($message->isOpenID1()) {
+ $user_setup_url = $message->getArg(Auth_OpenID_OPENID1_NS,
+ 'user_setup_url');
+ if ($user_setup_url !== null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _doIdRes($message, $endpoint, $return_to)
+ {
+ // Checks for presence of appropriate fields (and checks
+ // signed list fields)
+ $result = $this->_idResCheckForFields($message);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ if (!$this->_checkReturnTo($message, $return_to)) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("return_to does not match return URL. Expected %s, got %s",
+ $return_to,
+ $message->getArg(Auth_OpenID_OPENID_NS, 'return_to')));
+ }
+
+ // Verify discovery information:
+ $result = $this->_verifyDiscoveryResults($message, $endpoint);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ $endpoint = $result;
+
+ $result = $this->_idResCheckSignature($message,
+ $endpoint->server_url);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ $result = $this->_idResCheckNonce($message, $endpoint);
+
+ if (Auth_OpenID::isFailure($result)) {
+ return $result;
+ }
+
+ $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS, 'signed',
+ Auth_OpenID_NO_DEFAULT);
+ if (Auth_OpenID::isFailure($signed_list_str)) {
+ return $signed_list_str;
+ }
+ $signed_list = explode(',', $signed_list_str);
+
+ $signed_fields = Auth_OpenID::addPrefix($signed_list, "openid.");
+
+ return new Auth_OpenID_SuccessResponse($endpoint, $message,
+ $signed_fields);
+
+ }
+
+ /**
+ * @access private
+ */
+ function _checkReturnTo($message, $return_to)
+ {
+ // Check an OpenID message and its openid.return_to value
+ // against a return_to URL from an application. Return True
+ // on success, False on failure.
+
+ // Check the openid.return_to args against args in the
+ // original message.
+ $result = Auth_OpenID_GenericConsumer::_verifyReturnToArgs(
+ $message->toPostArgs());
+ if (Auth_OpenID::isFailure($result)) {
+ return false;
+ }
+
+ // Check the return_to base URL against the one in the
+ // message.
+ $msg_return_to = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'return_to');
+ if (Auth_OpenID::isFailure($return_to)) {
+ // XXX log me
+ return false;
+ }
+
+ $return_to_parts = parse_url(Auth_OpenID_urinorm($return_to));
+ $msg_return_to_parts = parse_url(Auth_OpenID_urinorm($msg_return_to));
+
+ // If port is absent from both, add it so it's equal in the
+ // check below.
+ if ((!array_key_exists('port', $return_to_parts)) &&
+ (!array_key_exists('port', $msg_return_to_parts))) {
+ $return_to_parts['port'] = null;
+ $msg_return_to_parts['port'] = null;
+ }
+
+ // If path is absent from both, add it so it's equal in the
+ // check below.
+ if ((!array_key_exists('path', $return_to_parts)) &&
+ (!array_key_exists('path', $msg_return_to_parts))) {
+ $return_to_parts['path'] = null;
+ $msg_return_to_parts['path'] = null;
+ }
+
+ // The URL scheme, authority, and path MUST be the same
+ // between the two URLs.
+ foreach (array('scheme', 'host', 'port', 'path') as $component) {
+ // If the url component is absent in either URL, fail.
+ // There should always be a scheme, host, port, and path.
+ if (!array_key_exists($component, $return_to_parts)) {
+ return false;
+ }
+
+ if (!array_key_exists($component, $msg_return_to_parts)) {
+ return false;
+ }
+
+ if (Auth_OpenID::arrayGet($return_to_parts, $component) !==
+ Auth_OpenID::arrayGet($msg_return_to_parts, $component)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyReturnToArgs($query)
+ {
+ // Verify that the arguments in the return_to URL are present in this
+ // response.
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ $return_to = $message->getArg(Auth_OpenID_OPENID_NS, 'return_to');
+
+ if (Auth_OpenID::isFailure($return_to)) {
+ return $return_to;
+ }
+ // XXX: this should be checked by _idResCheckForFields
+ if (!$return_to) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Response has no return_to");
+ }
+
+ $parsed_url = parse_url($return_to);
+
+ $q = array();
+ if (array_key_exists('query', $parsed_url)) {
+ $rt_query = $parsed_url['query'];
+ $q = Auth_OpenID::parse_str($rt_query);
+ }
+
+ foreach ($q as $rt_key => $rt_value) {
+ if (!array_key_exists($rt_key, $query)) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("return_to parameter %s absent from query", $rt_key));
+ } else {
+ $value = $query[$rt_key];
+ if ($rt_value != $value) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("parameter %s value %s does not match " .
+ "return_to value %s", $rt_key,
+ $value, $rt_value));
+ }
+ }
+ }
+
+ // Make sure all non-OpenID arguments in the response are also
+ // in the signed return_to.
+ $bare_args = $message->getArgs(Auth_OpenID_BARE_NS);
+ foreach ($bare_args as $key => $value) {
+ if (Auth_OpenID::arrayGet($q, $key) != $value) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("Parameter %s = %s not in return_to URL",
+ $key, $value));
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _idResCheckSignature($message, $server_url)
+ {
+ $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+ if (Auth_OpenID::isFailure($assoc_handle)) {
+ return $assoc_handle;
+ }
+
+ $assoc = $this->store->getAssociation($server_url, $assoc_handle);
+
+ if ($assoc) {
+ if ($assoc->getExpiresIn() <= 0) {
+ // XXX: It might be a good idea sometimes to re-start
+ // the authentication with a new association. Doing it
+ // automatically opens the possibility for
+ // denial-of-service by a server that just returns
+ // expired associations (or really short-lived
+ // associations)
+ return new Auth_OpenID_FailureResponse(null,
+ 'Association with ' . $server_url . ' expired');
+ }
+
+ if (!$assoc->checkMessageSignature($message)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Bad signature");
+ }
+ } else {
+ // It's not an association we know about. Stateless mode
+ // is our only possible path for recovery. XXX - async
+ // framework will not want to block on this call to
+ // _checkAuth.
+ if (!$this->_checkAuth($message, $server_url)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Server denied check_authentication");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryResults($message, $endpoint=null)
+ {
+ if ($message->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS) {
+ return $this->_verifyDiscoveryResultsOpenID2($message,
+ $endpoint);
+ } else {
+ return $this->_verifyDiscoveryResultsOpenID1($message,
+ $endpoint);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryResultsOpenID1($message, $endpoint)
+ {
+ $claimed_id = $message->getArg(Auth_OpenID_BARE_NS,
+ $this->openid1_return_to_identifier_name);
+
+ if (($endpoint === null) && ($claimed_id === null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ 'When using OpenID 1, the claimed ID must be supplied, ' .
+ 'either by passing it through as a return_to parameter ' .
+ 'or by using a session, and supplied to the GenericConsumer ' .
+ 'as the argument to complete()');
+ } else if (($endpoint !== null) && ($claimed_id === null)) {
+ $claimed_id = $endpoint->claimed_id;
+ }
+
+ $to_match = new Auth_OpenID_ServiceEndpoint();
+ $to_match->type_uris = array(Auth_OpenID_TYPE_1_1);
+ $to_match->local_id = $message->getArg(Auth_OpenID_OPENID1_NS,
+ 'identity');
+
+ // Restore delegate information from the initiation phase
+ $to_match->claimed_id = $claimed_id;
+
+ if ($to_match->local_id === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Missing required field openid.identity");
+ }
+
+ $to_match_1_0 = $to_match->copy();
+ $to_match_1_0->type_uris = array(Auth_OpenID_TYPE_1_0);
+
+ if ($endpoint !== null) {
+ $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+
+ if (is_a($result, 'Auth_OpenID_TypeURIMismatch')) {
+ $result = $this->_verifyDiscoverySingle($endpoint,
+ $to_match_1_0);
+ }
+
+ if (Auth_OpenID::isFailure($result)) {
+ // oidutil.log("Error attempting to use stored
+ // discovery information: " + str(e))
+ // oidutil.log("Attempting discovery to
+ // verify endpoint")
+ } else {
+ return $endpoint;
+ }
+ }
+
+ // Endpoint is either bad (failed verification) or None
+ return $this->_discoverAndVerify($to_match->claimed_id,
+ array($to_match, $to_match_1_0));
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoverySingle($endpoint, $to_match)
+ {
+ // Every type URI that's in the to_match endpoint has to be
+ // present in the discovered endpoint.
+ foreach ($to_match->type_uris as $type_uri) {
+ if (!$endpoint->usesExtension($type_uri)) {
+ return new Auth_OpenID_TypeURIMismatch($endpoint,
+ "Required type ".$type_uri." not present");
+ }
+ }
+
+ // Fragments do not influence discovery, so we can't compare a
+ // claimed identifier with a fragment to discovered
+ // information.
+ list($defragged_claimed_id, $_) =
+ Auth_OpenID::urldefrag($to_match->claimed_id);
+
+ if ($defragged_claimed_id != $endpoint->claimed_id) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf('Claimed ID does not match (different subjects!), ' .
+ 'Expected %s, got %s', $defragged_claimed_id,
+ $endpoint->claimed_id));
+ }
+
+ if ($to_match->getLocalID() != $endpoint->getLocalID()) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf('local_id mismatch. Expected %s, got %s',
+ $to_match->getLocalID(), $endpoint->getLocalID()));
+ }
+
+ // If the server URL is None, this must be an OpenID 1
+ // response, because op_endpoint is a required parameter in
+ // OpenID 2. In that case, we don't actually care what the
+ // discovered server_url is, because signature checking or
+ // check_auth should take care of that check for us.
+ if ($to_match->server_url === null) {
+ if ($to_match->preferredNamespace() != Auth_OpenID_OPENID1_NS) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Preferred namespace mismatch (bug)");
+ }
+ } else if ($to_match->server_url != $endpoint->server_url) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ sprintf('OP Endpoint mismatch. Expected %s, got %s',
+ $to_match->server_url, $endpoint->server_url));
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryResultsOpenID2($message, $endpoint)
+ {
+ $to_match = new Auth_OpenID_ServiceEndpoint();
+ $to_match->type_uris = array(Auth_OpenID_TYPE_2_0);
+ $to_match->claimed_id = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'claimed_id');
+
+ $to_match->local_id = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'identity');
+
+ $to_match->server_url = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'op_endpoint');
+
+ if ($to_match->server_url === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "OP Endpoint URL missing");
+ }
+
+ // claimed_id and identifier must both be present or both be
+ // absent
+ if (($to_match->claimed_id === null) &&
+ ($to_match->local_id !== null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ 'openid.identity is present without openid.claimed_id');
+ }
+
+ if (($to_match->claimed_id !== null) &&
+ ($to_match->local_id === null)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ 'openid.claimed_id is present without openid.identity');
+ }
+
+ if ($to_match->claimed_id === null) {
+ // This is a response without identifiers, so there's
+ // really no checking that we can do, so return an
+ // endpoint that's for the specified `openid.op_endpoint'
+ return Auth_OpenID_ServiceEndpoint::fromOPEndpointURL(
+ $to_match->server_url);
+ }
+
+ if (!$endpoint) {
+ // The claimed ID doesn't match, so we have to do
+ // discovery again. This covers not using sessions, OP
+ // identifier endpoints and responses that didn't match
+ // the original request.
+ // oidutil.log('No pre-discovered information supplied.')
+ return $this->_discoverAndVerify($to_match->claimed_id,
+ array($to_match));
+ } else {
+
+ // The claimed ID matches, so we use the endpoint that we
+ // discovered in initiation. This should be the most
+ // common case.
+ $result = $this->_verifyDiscoverySingle($endpoint, $to_match);
+
+ if (Auth_OpenID::isFailure($result)) {
+ $endpoint = $this->_discoverAndVerify($to_match->claimed_id,
+ array($to_match));
+ if (Auth_OpenID::isFailure($endpoint)) {
+ return $endpoint;
+ }
+ }
+ }
+
+ // The endpoint we return should have the claimed ID from the
+ // message we just verified, fragment and all.
+ if ($endpoint->claimed_id != $to_match->claimed_id) {
+ $endpoint->claimed_id = $to_match->claimed_id;
+ }
+
+ return $endpoint;
+ }
+
+ /**
+ * @access private
+ */
+ function _discoverAndVerify($claimed_id, $to_match_endpoints)
+ {
+ // oidutil.log('Performing discovery on %s' % (claimed_id,))
+ list($unused, $services) = call_user_func($this->discoverMethod,
+ $claimed_id,
+ $this->fetcher);
+
+ if (!$services) {
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf("No OpenID information found at %s",
+ $claimed_id));
+ }
+
+ return $this->_verifyDiscoveryServices($claimed_id, $services,
+ $to_match_endpoints);
+ }
+
+ /**
+ * @access private
+ */
+ function _verifyDiscoveryServices($claimed_id,
+ &$services, &$to_match_endpoints)
+ {
+ // Search the services resulting from discovery to find one
+ // that matches the information from the assertion
+
+ foreach ($services as $endpoint) {
+ foreach ($to_match_endpoints as $to_match_endpoint) {
+ $result = $this->_verifyDiscoverySingle($endpoint,
+ $to_match_endpoint);
+
+ if (!Auth_OpenID::isFailure($result)) {
+ // It matches, so discover verification has
+ // succeeded. Return this endpoint.
+ return $endpoint;
+ }
+ }
+ }
+
+ return new Auth_OpenID_FailureResponse(null,
+ sprintf('No matching endpoint found after discovering %s',
+ $claimed_id));
+ }
+
+ /**
+ * Extract the nonce from an OpenID 1 response. Return the nonce
+ * from the BARE_NS since we independently check the return_to
+ * arguments are the same as those in the response message.
+ *
+ * See the openid1_nonce_query_arg_name class variable
+ *
+ * @returns $nonce The nonce as a string or null
+ *
+ * @access private
+ */
+ function _idResGetNonceOpenID1($message, $endpoint)
+ {
+ return $message->getArg(Auth_OpenID_BARE_NS,
+ $this->openid1_nonce_query_arg_name);
+ }
+
+ /**
+ * @access private
+ */
+ function _idResCheckNonce($message, $endpoint)
+ {
+ if ($message->isOpenID1()) {
+ // This indicates that the nonce was generated by the consumer
+ $nonce = $this->_idResGetNonceOpenID1($message, $endpoint);
+ $server_url = '';
+ } else {
+ $nonce = $message->getArg(Auth_OpenID_OPENID2_NS,
+ 'response_nonce');
+
+ $server_url = $endpoint->server_url;
+ }
+
+ if ($nonce === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Nonce missing from response");
+ }
+
+ $parts = Auth_OpenID_splitNonce($nonce);
+
+ if ($parts === null) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Malformed nonce in response");
+ }
+
+ list($timestamp, $salt) = $parts;
+
+ if (!$this->store->useNonce($server_url, $timestamp, $salt)) {
+ return new Auth_OpenID_FailureResponse($endpoint,
+ "Nonce already used or out of range");
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _idResCheckForFields($message)
+ {
+ $basic_fields = array('return_to', 'assoc_handle', 'sig', 'signed');
+ $basic_sig_fields = array('return_to', 'identity');
+
+ $require_fields = array(
+ Auth_OpenID_OPENID2_NS => array_merge($basic_fields,
+ array('op_endpoint')),
+
+ Auth_OpenID_OPENID1_NS => array_merge($basic_fields,
+ array('identity'))
+ );
+
+ $require_sigs = array(
+ Auth_OpenID_OPENID2_NS => array_merge($basic_sig_fields,
+ array('response_nonce',
+ 'claimed_id',
+ 'assoc_handle')),
+ Auth_OpenID_OPENID1_NS => array_merge($basic_sig_fields,
+ array('nonce'))
+ );
+
+ foreach ($require_fields[$message->getOpenIDNamespace()] as $field) {
+ if (!$message->hasKey(Auth_OpenID_OPENID_NS, $field)) {
+ return new Auth_OpenID_FailureResponse(null,
+ "Missing required field '".$field."'");
+ }
+ }
+
+ $signed_list_str = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'signed',
+ Auth_OpenID_NO_DEFAULT);
+ if (Auth_OpenID::isFailure($signed_list_str)) {
+ return $signed_list_str;
+ }
+ $signed_list = explode(',', $signed_list_str);
+
+ foreach ($require_sigs[$message->getOpenIDNamespace()] as $field) {
+ // Field is present and not in signed list
+ if ($message->hasKey(Auth_OpenID_OPENID_NS, $field) &&
+ (!in_array($field, $signed_list))) {
+ return new Auth_OpenID_FailureResponse(null,
+ "'".$field."' not signed");
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _checkAuth($message, $server_url)
+ {
+ $request = $this->_createCheckAuthRequest($message);
+ if ($request === null) {
+ return false;
+ }
+
+ $resp_message = $this->_makeKVPost($request, $server_url);
+ if (($resp_message === null) ||
+ (is_a($resp_message, 'Auth_OpenID_ServerErrorContainer'))) {
+ return false;
+ }
+
+ return $this->_processCheckAuthResponse($resp_message, $server_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _createCheckAuthRequest($message)
+ {
+ $signed = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+ if ($signed) {
+ foreach (explode(',', $signed) as $k) {
+ $value = $message->getAliasedArg($k);
+ if ($value === null) {
+ return null;
+ }
+ }
+ }
+ $ca_message = $message->copy();
+ $ca_message->setArg(Auth_OpenID_OPENID_NS, 'mode',
+ 'check_authentication');
+ return $ca_message;
+ }
+
+ /**
+ * @access private
+ */
+ function _processCheckAuthResponse($response, $server_url)
+ {
+ $is_valid = $response->getArg(Auth_OpenID_OPENID_NS, 'is_valid',
+ 'false');
+
+ $invalidate_handle = $response->getArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle');
+
+ if ($invalidate_handle !== null) {
+ $this->store->removeAssociation($server_url,
+ $invalidate_handle);
+ }
+
+ if ($is_valid == 'true') {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Adapt a POST response to a Message.
+ *
+ * @param $response Result of a POST to an OpenID endpoint.
+ *
+ * @access private
+ */
+ function _httpResponseToMessage($response, $server_url)
+ {
+ // Should this function be named Message.fromHTTPResponse instead?
+ $response_message = Auth_OpenID_Message::fromKVForm($response->body);
+
+ if ($response->status == 400) {
+ return Auth_OpenID_ServerErrorContainer::fromMessage(
+ $response_message);
+ } else if ($response->status != 200 and $response->status != 206) {
+ return null;
+ }
+
+ return $response_message;
+ }
+
+ /**
+ * @access private
+ */
+ function _makeKVPost($message, $server_url)
+ {
+ $body = $message->toURLEncoded();
+ $resp = $this->fetcher->post($server_url, $body);
+
+ if ($resp === null) {
+ return null;
+ }
+
+ return $this->_httpResponseToMessage($resp, $server_url);
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($endpoint)
+ {
+ if (!$this->_use_assocs) {
+ return null;
+ }
+
+ $assoc = $this->store->getAssociation($endpoint->server_url);
+
+ if (($assoc === null) ||
+ ($assoc->getExpiresIn() <= 0)) {
+
+ $assoc = $this->_negotiateAssociation($endpoint);
+
+ if ($assoc !== null) {
+ $this->store->storeAssociation($endpoint->server_url,
+ $assoc);
+ }
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * Handle ServerErrors resulting from association requests.
+ *
+ * @return $result If server replied with an C{unsupported-type}
+ * error, return a tuple of supported C{association_type},
+ * C{session_type}. Otherwise logs the error and returns null.
+ *
+ * @access private
+ */
+ function _extractSupportedAssociationType(&$server_error, &$endpoint,
+ $assoc_type)
+ {
+ // Any error message whose code is not 'unsupported-type'
+ // should be considered a total failure.
+ if (($server_error->error_code != 'unsupported-type') ||
+ ($server_error->message->isOpenID1())) {
+ return null;
+ }
+
+ // The server didn't like the association/session type that we
+ // sent, and it sent us back a message that might tell us how
+ // to handle it.
+
+ // Extract the session_type and assoc_type from the error
+ // message
+ $assoc_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type');
+
+ $session_type = $server_error->message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+
+ if (($assoc_type === null) || ($session_type === null)) {
+ return null;
+ } else if (!$this->negotiator->isAllowed($assoc_type,
+ $session_type)) {
+ return null;
+ } else {
+ return array($assoc_type, $session_type);
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _negotiateAssociation($endpoint)
+ {
+ // Get our preferred session/association type from the negotiatior.
+ list($assoc_type, $session_type) = $this->negotiator->getAllowedType();
+
+ $assoc = $this->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (Auth_OpenID::isFailure($assoc)) {
+ return null;
+ }
+
+ if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
+ $why = $assoc;
+
+ $supportedTypes = $this->_extractSupportedAssociationType(
+ $why, $endpoint, $assoc_type);
+
+ if ($supportedTypes !== null) {
+ list($assoc_type, $session_type) = $supportedTypes;
+
+ // Attempt to create an association from the assoc_type
+ // and session_type that the server told us it
+ // supported.
+ $assoc = $this->_requestAssociation(
+ $endpoint, $assoc_type, $session_type);
+
+ if (is_a($assoc, 'Auth_OpenID_ServerErrorContainer')) {
+ // Do not keep trying, since it rejected the
+ // association type that it told us to use.
+ // oidutil.log('Server %s refused its suggested association
+ // 'type: session_type=%s, assoc_type=%s'
+ // % (endpoint.server_url, session_type,
+ // assoc_type))
+ return null;
+ } else {
+ return $assoc;
+ }
+ } else {
+ return null;
+ }
+ } else {
+ return $assoc;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _requestAssociation($endpoint, $assoc_type, $session_type)
+ {
+ list($assoc_session, $args) = $this->_createAssociateRequest(
+ $endpoint, $assoc_type, $session_type);
+
+ $response_message = $this->_makeKVPost($args, $endpoint->server_url);
+
+ if ($response_message === null) {
+ // oidutil.log('openid.associate request failed: %s' % (why[0],))
+ return null;
+ } else if (is_a($response_message,
+ 'Auth_OpenID_ServerErrorContainer')) {
+ return $response_message;
+ }
+
+ return $this->_extractAssociation($response_message, $assoc_session);
+ }
+
+ /**
+ * @access private
+ */
+ function _extractAssociation(&$assoc_response, &$assoc_session)
+ {
+ // Extract the common fields from the response, raising an
+ // exception if they are not found
+ $assoc_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_type',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($assoc_type)) {
+ return $assoc_type;
+ }
+
+ $assoc_handle = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'assoc_handle',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($assoc_handle)) {
+ return $assoc_handle;
+ }
+
+ // expires_in is a base-10 string. The Python parsing will
+ // accept literals that have whitespace around them and will
+ // accept negative values. Neither of these are really in-spec,
+ // but we think it's OK to accept them.
+ $expires_in_str = $assoc_response->getArg(
+ Auth_OpenID_OPENID_NS, 'expires_in',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($expires_in_str)) {
+ return $expires_in_str;
+ }
+
+ $expires_in = Auth_OpenID::intval($expires_in_str);
+ if ($expires_in === false) {
+
+ $err = sprintf("Could not parse expires_in from association ".
+ "response %s", print_r($assoc_response, true));
+ return new Auth_OpenID_FailureResponse(null, $err);
+ }
+
+ // OpenID 1 has funny association session behaviour.
+ if ($assoc_response->isOpenID1()) {
+ $session_type = $this->_getOpenID1SessionType($assoc_response);
+ } else {
+ $session_type = $assoc_response->getArg(
+ Auth_OpenID_OPENID2_NS, 'session_type',
+ Auth_OpenID_NO_DEFAULT);
+
+ if (Auth_OpenID::isFailure($session_type)) {
+ return $session_type;
+ }
+ }
+
+ // Session type mismatch
+ if ($assoc_session->session_type != $session_type) {
+ if ($assoc_response->isOpenID1() &&
+ ($session_type == 'no-encryption')) {
+ // In OpenID 1, any association request can result in
+ // a 'no-encryption' association response. Setting
+ // assoc_session to a new no-encryption session should
+ // make the rest of this function work properly for
+ // that case.
+ $assoc_session = new Auth_OpenID_PlainTextConsumerSession();
+ } else {
+ // Any other mismatch, regardless of protocol version
+ // results in the failure of the association session
+ // altogether.
+ return null;
+ }
+ }
+
+ // Make sure assoc_type is valid for session_type
+ if (!in_array($assoc_type, $assoc_session->allowed_assoc_types)) {
+ return null;
+ }
+
+ // Delegate to the association session to extract the secret
+ // from the response, however is appropriate for that session
+ // type.
+ $secret = $assoc_session->extractSecret($assoc_response);
+
+ if ($secret === null) {
+ return null;
+ }
+
+ return Auth_OpenID_Association::fromExpiresIn(
+ $expires_in, $assoc_handle, $secret, $assoc_type);
+ }
+
+ /**
+ * @access private
+ */
+ function _createAssociateRequest($endpoint, $assoc_type, $session_type)
+ {
+ if (array_key_exists($session_type, $this->session_types)) {
+ $session_type_class = $this->session_types[$session_type];
+
+ if (is_callable($session_type_class)) {
+ $assoc_session = $session_type_class();
+ } else {
+ $assoc_session = new $session_type_class();
+ }
+ } else {
+ return null;
+ }
+
+ $args = array(
+ 'mode' => 'associate',
+ 'assoc_type' => $assoc_type);
+
+ if (!$endpoint->compatibilityMode()) {
+ $args['ns'] = Auth_OpenID_OPENID2_NS;
+ }
+
+ // Leave out the session type if we're in compatibility mode
+ // *and* it's no-encryption.
+ if ((!$endpoint->compatibilityMode()) ||
+ ($assoc_session->session_type != 'no-encryption')) {
+ $args['session_type'] = $assoc_session->session_type;
+ }
+
+ $args = array_merge($args, $assoc_session->getRequest());
+ $message = Auth_OpenID_Message::fromOpenIDArgs($args);
+ return array($assoc_session, $message);
+ }
+
+ /**
+ * Given an association response message, extract the OpenID 1.X
+ * session type.
+ *
+ * This function mostly takes care of the 'no-encryption' default
+ * behavior in OpenID 1.
+ *
+ * If the association type is plain-text, this function will
+ * return 'no-encryption'
+ *
+ * @access private
+ * @return $typ The association type for this message
+ */
+ function _getOpenID1SessionType($assoc_response)
+ {
+ // If it's an OpenID 1 message, allow session_type to default
+ // to None (which signifies "no-encryption")
+ $session_type = $assoc_response->getArg(Auth_OpenID_OPENID1_NS,
+ 'session_type');
+
+ // Handle the differences between no-encryption association
+ // respones in OpenID 1 and 2:
+
+ // no-encryption is not really a valid session type for OpenID
+ // 1, but we'll accept it anyway, while issuing a warning.
+ if ($session_type == 'no-encryption') {
+ // oidutil.log('WARNING: OpenID server sent "no-encryption"'
+ // 'for OpenID 1.X')
+ } else if (($session_type == '') || ($session_type === null)) {
+ // Missing or empty session type is the way to flag a
+ // 'no-encryption' response. Change the session type to
+ // 'no-encryption' so that it can be handled in the same
+ // way as OpenID 2 'no-encryption' respones.
+ $session_type = 'no-encryption';
+ }
+
+ return $session_type;
+ }
+}
+
+/**
+ * This class represents an authentication request from a consumer to
+ * an OpenID server.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AuthRequest {
+
+ /**
+ * Initialize an authentication request with the specified token,
+ * association, and endpoint.
+ *
+ * Users of this library should not create instances of this
+ * class. Instances of this class are created by the library when
+ * needed.
+ */
+ function Auth_OpenID_AuthRequest(&$endpoint, $assoc)
+ {
+ $this->assoc = $assoc;
+ $this->endpoint =& $endpoint;
+ $this->return_to_args = array();
+ $this->message = new Auth_OpenID_Message(
+ $endpoint->preferredNamespace());
+ $this->_anonymous = false;
+ }
+
+ /**
+ * Add an extension to this checkid request.
+ *
+ * $extension_request: An object that implements the extension
+ * request interface for adding arguments to an OpenID message.
+ */
+ function addExtension(&$extension_request)
+ {
+ $extension_request->toMessage($this->message);
+ }
+
+ /**
+ * Add an extension argument to this OpenID authentication
+ * request.
+ *
+ * Use caution when adding arguments, because they will be
+ * URL-escaped and appended to the redirect URL, which can easily
+ * get quite long.
+ *
+ * @param string $namespace The namespace for the extension. For
+ * example, the simple registration extension uses the namespace
+ * 'sreg'.
+ *
+ * @param string $key The key within the extension namespace. For
+ * example, the nickname field in the simple registration
+ * extension's key is 'nickname'.
+ *
+ * @param string $value The value to provide to the server for
+ * this argument.
+ */
+ function addExtensionArg($namespace, $key, $value)
+ {
+ return $this->message->setArg($namespace, $key, $value);
+ }
+
+ /**
+ * Set whether this request should be made anonymously. If a
+ * request is anonymous, the identifier will not be sent in the
+ * request. This is only useful if you are making another kind of
+ * request with an extension in this request.
+ *
+ * Anonymous requests are not allowed when the request is made
+ * with OpenID 1.
+ */
+ function setAnonymous($is_anonymous)
+ {
+ if ($is_anonymous && $this->message->isOpenID1()) {
+ return false;
+ } else {
+ $this->_anonymous = $is_anonymous;
+ return true;
+ }
+ }
+
+ /**
+ * Produce a {@link Auth_OpenID_Message} representing this
+ * request.
+ *
+ * @param string $realm The URL (or URL pattern) that identifies
+ * your web site to the user when she is authorizing it.
+ *
+ * @param string $return_to The URL that the OpenID provider will
+ * send the user back to after attempting to verify her identity.
+ *
+ * Not specifying a return_to URL means that the user will not be
+ * returned to the site issuing the request upon its completion.
+ *
+ * @param bool $immediate If true, the OpenID provider is to send
+ * back a response immediately, useful for behind-the-scenes
+ * authentication attempts. Otherwise the OpenID provider may
+ * engage the user before providing a response. This is the
+ * default case, as the user may need to provide credentials or
+ * approve the request before a positive response can be sent.
+ */
+ function getMessage($realm, $return_to=null, $immediate=false)
+ {
+ if ($return_to) {
+ $return_to = Auth_OpenID::appendArgs($return_to,
+ $this->return_to_args);
+ } else if ($immediate) {
+ // raise ValueError(
+ // '"return_to" is mandatory when
+ //using "checkid_immediate"')
+ return new Auth_OpenID_FailureResponse(null,
+ "'return_to' is mandatory when using checkid_immediate");
+ } else if ($this->message->isOpenID1()) {
+ // raise ValueError('"return_to" is
+ // mandatory for OpenID 1 requests')
+ return new Auth_OpenID_FailureResponse(null,
+ "'return_to' is mandatory for OpenID 1 requests");
+ } else if ($this->return_to_args) {
+ // raise ValueError('extra "return_to" arguments
+ // were specified, but no return_to was specified')
+ return new Auth_OpenID_FailureResponse(null,
+ "extra 'return_to' arguments where specified, " .
+ "but no return_to was specified");
+ }
+
+ if ($immediate) {
+ $mode = 'checkid_immediate';
+ } else {
+ $mode = 'checkid_setup';
+ }
+
+ $message = $this->message->copy();
+ if ($message->isOpenID1()) {
+ $realm_key = 'trust_root';
+ } else {
+ $realm_key = 'realm';
+ }
+
+ $message->updateArgs(Auth_OpenID_OPENID_NS,
+ array(
+ $realm_key => $realm,
+ 'mode' => $mode,
+ 'return_to' => $return_to));
+
+ if (!$this->_anonymous) {
+ if ($this->endpoint->isOPIdentifier()) {
+ // This will never happen when we're in compatibility
+ // mode, as long as isOPIdentifier() returns False
+ // whenever preferredNamespace() returns OPENID1_NS.
+ $claimed_id = $request_identity =
+ Auth_OpenID_IDENTIFIER_SELECT;
+ } else {
+ $request_identity = $this->endpoint->getLocalID();
+ $claimed_id = $this->endpoint->claimed_id;
+ }
+
+ // This is true for both OpenID 1 and 2
+ $message->setArg(Auth_OpenID_OPENID_NS, 'identity',
+ $request_identity);
+
+ if ($message->isOpenID2()) {
+ $message->setArg(Auth_OpenID_OPENID2_NS, 'claimed_id',
+ $claimed_id);
+ }
+ }
+
+ if ($this->assoc) {
+ $message->setArg(Auth_OpenID_OPENID_NS, 'assoc_handle',
+ $this->assoc->handle);
+ }
+
+ return $message;
+ }
+
+ function redirectURL($realm, $return_to = null,
+ $immediate = false)
+ {
+ $message = $this->getMessage($realm, $return_to, $immediate);
+
+ if (Auth_OpenID::isFailure($message)) {
+ return $message;
+ }
+
+ return $message->toURL($this->endpoint->server_url);
+ }
+
+ /**
+ * Get html for a form to submit this request to the IDP.
+ *
+ * form_tag_attrs: An array of attributes to be added to the form
+ * tag. 'accept-charset' and 'enctype' have defaults that can be
+ * overridden. If a value is supplied for 'action' or 'method', it
+ * will be replaced.
+ */
+ function formMarkup($realm, $return_to=null, $immediate=false,
+ $form_tag_attrs=null)
+ {
+ $message = $this->getMessage($realm, $return_to, $immediate);
+
+ if (Auth_OpenID::isFailure($message)) {
+ return $message;
+ }
+
+ return $message->toFormMarkup($this->endpoint->server_url,
+ $form_tag_attrs);
+ }
+
+ /**
+ * Get a complete html document that will autosubmit the request
+ * to the IDP.
+ *
+ * Wraps formMarkup. See the documentation for that function.
+ */
+ function htmlMarkup($realm, $return_to=null, $immediate=false,
+ $form_tag_attrs=null)
+ {
+ $form = $this->formMarkup($realm, $return_to, $immediate,
+ $form_tag_attrs);
+
+ if (Auth_OpenID::isFailure($form)) {
+ return $form;
+ }
+ return Auth_OpenID::autoSubmitHTML($form);
+ }
+
+ function shouldSendRedirect()
+ {
+ return $this->endpoint->compatibilityMode();
+ }
+}
+
+/**
+ * The base class for responses from the Auth_OpenID_Consumer.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ConsumerResponse {
+ var $status = null;
+
+ function setEndpoint($endpoint)
+ {
+ $this->endpoint = $endpoint;
+ if ($endpoint === null) {
+ $this->identity_url = null;
+ } else {
+ $this->identity_url = $endpoint->claimed_id;
+ }
+ }
+
+ /**
+ * Return the display identifier for this response.
+ *
+ * The display identifier is related to the Claimed Identifier, but the
+ * two are not always identical. The display identifier is something the
+ * user should recognize as what they entered, whereas the response's
+ * claimed identifier (in the identity_url attribute) may have extra
+ * information for better persistence.
+ *
+ * URLs will be stripped of their fragments for display. XRIs will
+ * display the human-readable identifier (i-name) instead of the
+ * persistent identifier (i-number).
+ *
+ * Use the display identifier in your user interface. Use
+ * identity_url for querying your database or authorization server.
+ *
+ */
+ function getDisplayIdentifier()
+ {
+ if ($this->endpoint !== null) {
+ return $this->endpoint->getDisplayIdentifier();
+ }
+ return null;
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_SUCCESS. Indicates that
+ * this request is a successful acknowledgement from the OpenID server
+ * that the supplied URL is, indeed controlled by the requesting
+ * agent. This has three relevant attributes:
+ *
+ * claimed_id - The identity URL that has been authenticated
+ *
+ * signed_args - The arguments in the server's response that were
+ * signed and verified.
+ *
+ * status - Auth_OpenID_SUCCESS.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SuccessResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_SUCCESS;
+
+ /**
+ * @access private
+ */
+ function Auth_OpenID_SuccessResponse($endpoint, $message, $signed_args=null)
+ {
+ $this->endpoint = $endpoint;
+ $this->identity_url = $endpoint->claimed_id;
+ $this->signed_args = $signed_args;
+ $this->message = $message;
+
+ if ($this->signed_args === null) {
+ $this->signed_args = array();
+ }
+ }
+
+ /**
+ * Extract signed extension data from the server's response.
+ *
+ * @param string $prefix The extension namespace from which to
+ * extract the extension data.
+ */
+ function extensionResponse($namespace_uri, $require_signed)
+ {
+ if ($require_signed) {
+ return $this->getSignedNS($namespace_uri);
+ } else {
+ return $this->message->getArgs($namespace_uri);
+ }
+ }
+
+ function isOpenID1()
+ {
+ return $this->message->isOpenID1();
+ }
+
+ function isSigned($ns_uri, $ns_key)
+ {
+ // Return whether a particular key is signed, regardless of
+ // its namespace alias
+ return in_array($this->message->getKey($ns_uri, $ns_key),
+ $this->signed_args);
+ }
+
+ function getSigned($ns_uri, $ns_key, $default = null)
+ {
+ // Return the specified signed field if available, otherwise
+ // return default
+ if ($this->isSigned($ns_uri, $ns_key)) {
+ return $this->message->getArg($ns_uri, $ns_key, $default);
+ } else {
+ return $default;
+ }
+ }
+
+ function getSignedNS($ns_uri)
+ {
+ $args = array();
+
+ $msg_args = $this->message->getArgs($ns_uri);
+ if (Auth_OpenID::isFailure($msg_args)) {
+ return null;
+ }
+
+ foreach ($msg_args as $key => $value) {
+ if (!$this->isSigned($ns_uri, $key)) {
+ return null;
+ }
+ }
+
+ return $msg_args;
+ }
+
+ /**
+ * Get the openid.return_to argument from this response.
+ *
+ * This is useful for verifying that this request was initiated by
+ * this consumer.
+ *
+ * @return string $return_to The return_to URL supplied to the
+ * server on the initial request, or null if the response did not
+ * contain an 'openid.return_to' argument.
+ */
+ function getReturnTo()
+ {
+ return $this->getSigned(Auth_OpenID_OPENID_NS, 'return_to');
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_FAILURE. Indicates that the
+ * OpenID protocol has failed. This could be locally or remotely
+ * triggered. This has three relevant attributes:
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted, if it can be determined. Otherwise, null.
+ *
+ * message - A message indicating why the request failed, if one is
+ * supplied. Otherwise, null.
+ *
+ * status - Auth_OpenID_FAILURE.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_FailureResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_FAILURE;
+
+ function Auth_OpenID_FailureResponse($endpoint, $message = null,
+ $contact = null, $reference = null)
+ {
+ $this->setEndpoint($endpoint);
+ $this->message = $message;
+ $this->contact = $contact;
+ $this->reference = $reference;
+ }
+}
+
+/**
+ * A specific, internal failure used to detect type URI mismatch.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_TypeURIMismatch extends Auth_OpenID_FailureResponse {
+}
+
+/**
+ * Exception that is raised when the server returns a 400 response
+ * code to a direct request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerErrorContainer {
+ function Auth_OpenID_ServerErrorContainer($error_text,
+ $error_code,
+ $message)
+ {
+ $this->error_text = $error_text;
+ $this->error_code = $error_code;
+ $this->message = $message;
+ }
+
+ /**
+ * @access private
+ */
+ function fromMessage($message)
+ {
+ $error_text = $message->getArg(
+ Auth_OpenID_OPENID_NS, 'error', '<no error message supplied>');
+ $error_code = $message->getArg(Auth_OpenID_OPENID_NS, 'error_code');
+ return new Auth_OpenID_ServerErrorContainer($error_text,
+ $error_code,
+ $message);
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_CANCEL. Indicates that the
+ * user cancelled the OpenID authentication request. This has two
+ * relevant attributes:
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted, if it can be determined. Otherwise, null.
+ *
+ * status - Auth_OpenID_SUCCESS.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CancelResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_CANCEL;
+
+ function Auth_OpenID_CancelResponse($endpoint)
+ {
+ $this->setEndpoint($endpoint);
+ }
+}
+
+/**
+ * A response with a status of Auth_OpenID_SETUP_NEEDED. Indicates
+ * that the request was in immediate mode, and the server is unable to
+ * authenticate the user without further interaction.
+ *
+ * claimed_id - The identity URL for which authentication was
+ * attempted.
+ *
+ * setup_url - A URL that can be used to send the user to the server
+ * to set up for authentication. The user should be redirected in to
+ * the setup_url, either in the current window or in a new browser
+ * window. Null in OpenID 2.
+ *
+ * status - Auth_OpenID_SETUP_NEEDED.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SetupNeededResponse extends Auth_OpenID_ConsumerResponse {
+ var $status = Auth_OpenID_SETUP_NEEDED;
+
+ function Auth_OpenID_SetupNeededResponse($endpoint,
+ $setup_url = null)
+ {
+ $this->setEndpoint($endpoint);
+ $this->setup_url = $setup_url;
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/CryptUtil.php b/extlib/Auth/OpenID/CryptUtil.php
new file mode 100644
index 000000000..aacc3cd39
--- /dev/null
+++ b/extlib/Auth/OpenID/CryptUtil.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * CryptUtil: A suite of wrapper utility functions for the OpenID
+ * library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+if (!defined('Auth_OpenID_RAND_SOURCE')) {
+ /**
+ * The filename for a source of random bytes. Define this yourself
+ * if you have a different source of randomness.
+ */
+ define('Auth_OpenID_RAND_SOURCE', '/dev/urandom');
+}
+
+class Auth_OpenID_CryptUtil {
+ /**
+ * Get the specified number of random bytes.
+ *
+ * Attempts to use a cryptographically secure (not predictable)
+ * source of randomness if available. If there is no high-entropy
+ * randomness source available, it will fail. As a last resort,
+ * for non-critical systems, define
+ * <code>Auth_OpenID_RAND_SOURCE</code> as <code>null</code>, and
+ * the code will fall back on a pseudo-random number generator.
+ *
+ * @param int $num_bytes The length of the return value
+ * @return string $bytes random bytes
+ */
+ function getBytes($num_bytes)
+ {
+ static $f = null;
+ $bytes = '';
+ if ($f === null) {
+ if (Auth_OpenID_RAND_SOURCE === null) {
+ $f = false;
+ } else {
+ $f = @fopen(Auth_OpenID_RAND_SOURCE, "r");
+ if ($f === false) {
+ $msg = 'Define Auth_OpenID_RAND_SOURCE as null to ' .
+ ' continue with an insecure random number generator.';
+ trigger_error($msg, E_USER_ERROR);
+ }
+ }
+ }
+ if ($f === false) {
+ // pseudorandom used
+ $bytes = '';
+ for ($i = 0; $i < $num_bytes; $i += 4) {
+ $bytes .= pack('L', mt_rand());
+ }
+ $bytes = substr($bytes, 0, $num_bytes);
+ } else {
+ $bytes = fread($f, $num_bytes);
+ }
+ return $bytes;
+ }
+
+ /**
+ * Produce a string of length random bytes, chosen from chrs. If
+ * $chrs is null, the resulting string may contain any characters.
+ *
+ * @param integer $length The length of the resulting
+ * randomly-generated string
+ * @param string $chrs A string of characters from which to choose
+ * to build the new string
+ * @return string $result A string of randomly-chosen characters
+ * from $chrs
+ */
+ function randomString($length, $population = null)
+ {
+ if ($population === null) {
+ return Auth_OpenID_CryptUtil::getBytes($length);
+ }
+
+ $popsize = strlen($population);
+
+ if ($popsize > 256) {
+ $msg = 'More than 256 characters supplied to ' . __FUNCTION__;
+ trigger_error($msg, E_USER_ERROR);
+ }
+
+ $duplicate = 256 % $popsize;
+
+ $str = "";
+ for ($i = 0; $i < $length; $i++) {
+ do {
+ $n = ord(Auth_OpenID_CryptUtil::getBytes(1));
+ } while ($n < $duplicate);
+
+ $n %= $popsize;
+ $str .= $population[$n];
+ }
+
+ return $str;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/DatabaseConnection.php b/extlib/Auth/OpenID/DatabaseConnection.php
new file mode 100644
index 000000000..9db6e0eb3
--- /dev/null
+++ b/extlib/Auth/OpenID/DatabaseConnection.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * The Auth_OpenID_DatabaseConnection class, which is used to emulate
+ * a PEAR database connection.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * An empty base class intended to emulate PEAR connection
+ * functionality in applications that supply their own database
+ * abstraction mechanisms. See {@link Auth_OpenID_SQLStore} for more
+ * information. You should subclass this class if you need to create
+ * an SQL store that needs to access its database using an
+ * application's database abstraction layer instead of a PEAR database
+ * connection. Any subclass of Auth_OpenID_DatabaseConnection MUST
+ * adhere to the interface specified here.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DatabaseConnection {
+ /**
+ * Sets auto-commit mode on this database connection.
+ *
+ * @param bool $mode True if auto-commit is to be used; false if
+ * not.
+ */
+ function autoCommit($mode)
+ {
+ }
+
+ /**
+ * Run an SQL query with the specified parameters, if any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return mixed $result The result of calling this connection's
+ * internal query function. The type of result depends on the
+ * underlying database engine. This method is usually used when
+ * the result of a query is not important, like a DDL query.
+ */
+ function query($sql, $params = array())
+ {
+ }
+
+ /**
+ * Starts a transaction on this connection, if supported.
+ */
+ function begin()
+ {
+ }
+
+ /**
+ * Commits a transaction on this connection, if supported.
+ */
+ function commit()
+ {
+ }
+
+ /**
+ * Performs a rollback on this connection, if supported.
+ */
+ function rollback()
+ {
+ }
+
+ /**
+ * Run an SQL query and return the first column of the first row
+ * of the result set, if any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return mixed $result The value of the first column of the
+ * first row of the result set. False if no such result was
+ * found.
+ */
+ function getOne($sql, $params = array())
+ {
+ }
+
+ /**
+ * Run an SQL query and return the first row of the result set, if
+ * any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return array $result The first row of the result set, if any,
+ * keyed on column name. False if no such result was found.
+ */
+ function getRow($sql, $params = array())
+ {
+ }
+
+ /**
+ * Run an SQL query with the specified parameters, if any.
+ *
+ * @param string $sql An SQL string with placeholders. The
+ * placeholders are assumed to be specific to the database engine
+ * for this connection.
+ *
+ * @param array $params An array of parameters to insert into the
+ * SQL string using this connection's escaping mechanism.
+ *
+ * @return array $result An array of arrays representing the
+ * result of the query; each array is keyed on column name.
+ */
+ function getAll($sql, $params = array())
+ {
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/DiffieHellman.php b/extlib/Auth/OpenID/DiffieHellman.php
new file mode 100644
index 000000000..f4ded7eba
--- /dev/null
+++ b/extlib/Auth/OpenID/DiffieHellman.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * The OpenID library's Diffie-Hellman implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/BigMath.php';
+
+function Auth_OpenID_getDefaultMod()
+{
+ return '155172898181473697471232257763715539915724801'.
+ '966915404479707795314057629378541917580651227423'.
+ '698188993727816152646631438561595825688188889951'.
+ '272158842675419950341258706556549803580104870537'.
+ '681476726513255747040765857479291291572334510643'.
+ '245094715007229621094194349783925984760375594985'.
+ '848253359305585439638443';
+}
+
+function Auth_OpenID_getDefaultGen()
+{
+ return '2';
+}
+
+/**
+ * The Diffie-Hellman key exchange class. This class relies on
+ * {@link Auth_OpenID_MathLibrary} to perform large number operations.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellman {
+
+ var $mod;
+ var $gen;
+ var $private;
+ var $lib = null;
+
+ function Auth_OpenID_DiffieHellman($mod = null, $gen = null,
+ $private = null, $lib = null)
+ {
+ if ($lib === null) {
+ $this->lib =& Auth_OpenID_getMathLib();
+ } else {
+ $this->lib =& $lib;
+ }
+
+ if ($mod === null) {
+ $this->mod = $this->lib->init(Auth_OpenID_getDefaultMod());
+ } else {
+ $this->mod = $mod;
+ }
+
+ if ($gen === null) {
+ $this->gen = $this->lib->init(Auth_OpenID_getDefaultGen());
+ } else {
+ $this->gen = $gen;
+ }
+
+ if ($private === null) {
+ $r = $this->lib->rand($this->mod);
+ $this->private = $this->lib->add($r, 1);
+ } else {
+ $this->private = $private;
+ }
+
+ $this->public = $this->lib->powmod($this->gen, $this->private,
+ $this->mod);
+ }
+
+ function getSharedSecret($composite)
+ {
+ return $this->lib->powmod($composite, $this->private, $this->mod);
+ }
+
+ function getPublicKey()
+ {
+ return $this->public;
+ }
+
+ function usingDefaultValues()
+ {
+ return ($this->mod == Auth_OpenID_getDefaultMod() &&
+ $this->gen == Auth_OpenID_getDefaultGen());
+ }
+
+ function xorSecret($composite, $secret, $hash_func)
+ {
+ $dh_shared = $this->getSharedSecret($composite);
+ $dh_shared_str = $this->lib->longToBinary($dh_shared);
+ $hash_dh_shared = $hash_func($dh_shared_str);
+
+ $xsecret = "";
+ for ($i = 0; $i < Auth_OpenID::bytes($secret); $i++) {
+ $xsecret .= chr(ord($secret[$i]) ^ ord($hash_dh_shared[$i]));
+ }
+
+ return $xsecret;
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/Discover.php b/extlib/Auth/OpenID/Discover.php
new file mode 100644
index 000000000..62aeb1d2b
--- /dev/null
+++ b/extlib/Auth/OpenID/Discover.php
@@ -0,0 +1,548 @@
+<?php
+
+/**
+ * The OpenID and Yadis discovery implementation for OpenID 1.2.
+ */
+
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Parse.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/Yadis/XRIRes.php";
+require_once "Auth/Yadis/Yadis.php";
+
+// XML namespace value
+define('Auth_OpenID_XMLNS_1_0', 'http://openid.net/xmlns/1.0');
+
+// Yadis service types
+define('Auth_OpenID_TYPE_1_2', 'http://openid.net/signon/1.2');
+define('Auth_OpenID_TYPE_1_1', 'http://openid.net/signon/1.1');
+define('Auth_OpenID_TYPE_1_0', 'http://openid.net/signon/1.0');
+define('Auth_OpenID_TYPE_2_0_IDP', 'http://specs.openid.net/auth/2.0/server');
+define('Auth_OpenID_TYPE_2_0', 'http://specs.openid.net/auth/2.0/signon');
+define('Auth_OpenID_RP_RETURN_TO_URL_TYPE',
+ 'http://specs.openid.net/auth/2.0/return_to');
+
+function Auth_OpenID_getOpenIDTypeURIs()
+{
+ return array(Auth_OpenID_TYPE_2_0_IDP,
+ Auth_OpenID_TYPE_2_0,
+ Auth_OpenID_TYPE_1_2,
+ Auth_OpenID_TYPE_1_1,
+ Auth_OpenID_TYPE_1_0,
+ Auth_OpenID_RP_RETURN_TO_URL_TYPE);
+}
+
+/**
+ * Object representing an OpenID service endpoint.
+ */
+class Auth_OpenID_ServiceEndpoint {
+ function Auth_OpenID_ServiceEndpoint()
+ {
+ $this->claimed_id = null;
+ $this->server_url = null;
+ $this->type_uris = array();
+ $this->local_id = null;
+ $this->canonicalID = null;
+ $this->used_yadis = false; // whether this came from an XRDS
+ $this->display_identifier = null;
+ }
+
+ function getDisplayIdentifier()
+ {
+ if ($this->display_identifier) {
+ return $this->display_identifier;
+ }
+ if (! $this->claimed_id) {
+ return $this->claimed_id;
+ }
+ $parsed = parse_url($this->claimed_id);
+ $scheme = $parsed['scheme'];
+ $host = $parsed['host'];
+ $path = $parsed['path'];
+ if (array_key_exists('query', $parsed)) {
+ $query = $parsed['query'];
+ $no_frag = "$scheme://$host$path?$query";
+ } else {
+ $no_frag = "$scheme://$host$path";
+ }
+ return $no_frag;
+ }
+
+ function usesExtension($extension_uri)
+ {
+ return in_array($extension_uri, $this->type_uris);
+ }
+
+ function preferredNamespace()
+ {
+ if (in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris) ||
+ in_array(Auth_OpenID_TYPE_2_0, $this->type_uris)) {
+ return Auth_OpenID_OPENID2_NS;
+ } else {
+ return Auth_OpenID_OPENID1_NS;
+ }
+ }
+
+ /*
+ * Query this endpoint to see if it has any of the given type
+ * URIs. This is useful for implementing other endpoint classes
+ * that e.g. need to check for the presence of multiple versions
+ * of a single protocol.
+ *
+ * @param $type_uris The URIs that you wish to check
+ *
+ * @return all types that are in both in type_uris and
+ * $this->type_uris
+ */
+ function matchTypes($type_uris)
+ {
+ $result = array();
+ foreach ($type_uris as $test_uri) {
+ if ($this->supportsType($test_uri)) {
+ $result[] = $test_uri;
+ }
+ }
+
+ return $result;
+ }
+
+ function supportsType($type_uri)
+ {
+ // Does this endpoint support this type?
+ return ((in_array($type_uri, $this->type_uris)) ||
+ (($type_uri == Auth_OpenID_TYPE_2_0) &&
+ $this->isOPIdentifier()));
+ }
+
+ function compatibilityMode()
+ {
+ return $this->preferredNamespace() != Auth_OpenID_OPENID2_NS;
+ }
+
+ function isOPIdentifier()
+ {
+ return in_array(Auth_OpenID_TYPE_2_0_IDP, $this->type_uris);
+ }
+
+ function fromOPEndpointURL($op_endpoint_url)
+ {
+ // Construct an OP-Identifier OpenIDServiceEndpoint object for
+ // a given OP Endpoint URL
+ $obj = new Auth_OpenID_ServiceEndpoint();
+ $obj->server_url = $op_endpoint_url;
+ $obj->type_uris = array(Auth_OpenID_TYPE_2_0_IDP);
+ return $obj;
+ }
+
+ function parseService($yadis_url, $uri, $type_uris, $service_element)
+ {
+ // Set the state of this object based on the contents of the
+ // service element. Return true if successful, false if not
+ // (if findOPLocalIdentifier returns false).
+ $this->type_uris = $type_uris;
+ $this->server_url = $uri;
+ $this->used_yadis = true;
+
+ if (!$this->isOPIdentifier()) {
+ $this->claimed_id = $yadis_url;
+ $this->local_id = Auth_OpenID_findOPLocalIdentifier(
+ $service_element,
+ $this->type_uris);
+ if ($this->local_id === false) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ function getLocalID()
+ {
+ // Return the identifier that should be sent as the
+ // openid.identity_url parameter to the server.
+ if ($this->local_id === null && $this->canonicalID === null) {
+ return $this->claimed_id;
+ } else {
+ if ($this->local_id) {
+ return $this->local_id;
+ } else {
+ return $this->canonicalID;
+ }
+ }
+ }
+
+ /*
+ * Parse the given document as XRDS looking for OpenID services.
+ *
+ * @return array of Auth_OpenID_ServiceEndpoint or null if the
+ * document cannot be parsed.
+ */
+ function fromXRDS($uri, $xrds_text)
+ {
+ $xrds =& Auth_Yadis_XRDS::parseXRDS($xrds_text);
+
+ if ($xrds) {
+ $yadis_services =
+ $xrds->services(array('filter_MatchesAnyOpenIDType'));
+ return Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services);
+ }
+
+ return null;
+ }
+
+ /*
+ * Create endpoints from a DiscoveryResult.
+ *
+ * @param discoveryResult Auth_Yadis_DiscoveryResult
+ * @return array of Auth_OpenID_ServiceEndpoint or null if
+ * endpoints cannot be created.
+ */
+ function fromDiscoveryResult($discoveryResult)
+ {
+ if ($discoveryResult->isXRDS()) {
+ return Auth_OpenID_ServiceEndpoint::fromXRDS(
+ $discoveryResult->normalized_uri,
+ $discoveryResult->response_text);
+ } else {
+ return Auth_OpenID_ServiceEndpoint::fromHTML(
+ $discoveryResult->normalized_uri,
+ $discoveryResult->response_text);
+ }
+ }
+
+ function fromHTML($uri, $html)
+ {
+ $discovery_types = array(
+ array(Auth_OpenID_TYPE_2_0,
+ 'openid2.provider', 'openid2.local_id'),
+ array(Auth_OpenID_TYPE_1_1,
+ 'openid.server', 'openid.delegate')
+ );
+
+ $services = array();
+
+ foreach ($discovery_types as $triple) {
+ list($type_uri, $server_rel, $delegate_rel) = $triple;
+
+ $urls = Auth_OpenID_legacy_discover($html, $server_rel,
+ $delegate_rel);
+
+ if ($urls === false) {
+ continue;
+ }
+
+ list($delegate_url, $server_url) = $urls;
+
+ $service = new Auth_OpenID_ServiceEndpoint();
+ $service->claimed_id = $uri;
+ $service->local_id = $delegate_url;
+ $service->server_url = $server_url;
+ $service->type_uris = array($type_uri);
+
+ $services[] = $service;
+ }
+
+ return $services;
+ }
+
+ function copy()
+ {
+ $x = new Auth_OpenID_ServiceEndpoint();
+
+ $x->claimed_id = $this->claimed_id;
+ $x->server_url = $this->server_url;
+ $x->type_uris = $this->type_uris;
+ $x->local_id = $this->local_id;
+ $x->canonicalID = $this->canonicalID;
+ $x->used_yadis = $this->used_yadis;
+
+ return $x;
+ }
+}
+
+function Auth_OpenID_findOPLocalIdentifier($service, $type_uris)
+{
+ // Extract a openid:Delegate value from a Yadis Service element.
+ // If no delegate is found, returns null. Returns false on
+ // discovery failure (when multiple delegate/localID tags have
+ // different values).
+
+ $service->parser->registerNamespace('openid',
+ Auth_OpenID_XMLNS_1_0);
+
+ $service->parser->registerNamespace('xrd',
+ Auth_Yadis_XMLNS_XRD_2_0);
+
+ $parser =& $service->parser;
+
+ $permitted_tags = array();
+
+ if (in_array(Auth_OpenID_TYPE_1_1, $type_uris) ||
+ in_array(Auth_OpenID_TYPE_1_0, $type_uris)) {
+ $permitted_tags[] = 'openid:Delegate';
+ }
+
+ if (in_array(Auth_OpenID_TYPE_2_0, $type_uris)) {
+ $permitted_tags[] = 'xrd:LocalID';
+ }
+
+ $local_id = null;
+
+ foreach ($permitted_tags as $tag_name) {
+ $tags = $service->getElements($tag_name);
+
+ foreach ($tags as $tag) {
+ $content = $parser->content($tag);
+
+ if ($local_id === null) {
+ $local_id = $content;
+ } else if ($local_id != $content) {
+ return false;
+ }
+ }
+ }
+
+ return $local_id;
+}
+
+function filter_MatchesAnyOpenIDType(&$service)
+{
+ $uris = $service->getTypes();
+
+ foreach ($uris as $uri) {
+ if (in_array($uri, Auth_OpenID_getOpenIDTypeURIs())) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function Auth_OpenID_bestMatchingService($service, $preferred_types)
+{
+ // Return the index of the first matching type, or something
+ // higher if no type matches.
+ //
+ // This provides an ordering in which service elements that
+ // contain a type that comes earlier in the preferred types list
+ // come before service elements that come later. If a service
+ // element has more than one type, the most preferred one wins.
+
+ foreach ($preferred_types as $index => $typ) {
+ if (in_array($typ, $service->type_uris)) {
+ return $index;
+ }
+ }
+
+ return count($preferred_types);
+}
+
+function Auth_OpenID_arrangeByType($service_list, $preferred_types)
+{
+ // Rearrange service_list in a new list so services are ordered by
+ // types listed in preferred_types. Return the new list.
+
+ // Build a list with the service elements in tuples whose
+ // comparison will prefer the one with the best matching service
+ $prio_services = array();
+ foreach ($service_list as $index => $service) {
+ $prio_services[] = array(Auth_OpenID_bestMatchingService($service,
+ $preferred_types),
+ $index, $service);
+ }
+
+ sort($prio_services);
+
+ // Now that the services are sorted by priority, remove the sort
+ // keys from the list.
+ foreach ($prio_services as $index => $s) {
+ $prio_services[$index] = $prio_services[$index][2];
+ }
+
+ return $prio_services;
+}
+
+// Extract OP Identifier services. If none found, return the rest,
+// sorted with most preferred first according to
+// OpenIDServiceEndpoint.openid_type_uris.
+//
+// openid_services is a list of OpenIDServiceEndpoint objects.
+//
+// Returns a list of OpenIDServiceEndpoint objects."""
+function Auth_OpenID_getOPOrUserServices($openid_services)
+{
+ $op_services = Auth_OpenID_arrangeByType($openid_services,
+ array(Auth_OpenID_TYPE_2_0_IDP));
+
+ $openid_services = Auth_OpenID_arrangeByType($openid_services,
+ Auth_OpenID_getOpenIDTypeURIs());
+
+ if ($op_services) {
+ return $op_services;
+ } else {
+ return $openid_services;
+ }
+}
+
+function Auth_OpenID_makeOpenIDEndpoints($uri, $yadis_services)
+{
+ $s = array();
+
+ if (!$yadis_services) {
+ return $s;
+ }
+
+ foreach ($yadis_services as $service) {
+ $type_uris = $service->getTypes();
+ $uris = $service->getURIs();
+
+ // If any Type URIs match and there is an endpoint URI
+ // specified, then this is an OpenID endpoint
+ if ($type_uris &&
+ $uris) {
+ foreach ($uris as $service_uri) {
+ $openid_endpoint = new Auth_OpenID_ServiceEndpoint();
+ if ($openid_endpoint->parseService($uri,
+ $service_uri,
+ $type_uris,
+ $service)) {
+ $s[] = $openid_endpoint;
+ }
+ }
+ }
+ }
+
+ return $s;
+}
+
+function Auth_OpenID_discoverWithYadis($uri, &$fetcher,
+ $endpoint_filter='Auth_OpenID_getOPOrUserServices',
+ $discover_function=null)
+{
+ // Discover OpenID services for a URI. Tries Yadis and falls back
+ // on old-style <link rel='...'> discovery if Yadis fails.
+
+ // Might raise a yadis.discover.DiscoveryFailure if no document
+ // came back for that URI at all. I don't think falling back to
+ // OpenID 1.0 discovery on the same URL will help, so don't bother
+ // to catch it.
+ if ($discover_function === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $openid_services = array();
+
+ $response = call_user_func_array($discover_function,
+ array($uri, &$fetcher));
+
+ $yadis_url = $response->normalized_uri;
+ $yadis_services = array();
+
+ if ($response->isFailure()) {
+ return array($uri, array());
+ }
+
+ $openid_services = Auth_OpenID_ServiceEndpoint::fromXRDS(
+ $yadis_url,
+ $response->response_text);
+
+ if (!$openid_services) {
+ if ($response->isXRDS()) {
+ return Auth_OpenID_discoverWithoutYadis($uri,
+ $fetcher);
+ }
+
+ // Try to parse the response as HTML to get OpenID 1.0/1.1
+ // <link rel="...">
+ $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
+ $yadis_url,
+ $response->response_text);
+ }
+
+ $openid_services = call_user_func_array($endpoint_filter,
+ array(&$openid_services));
+
+ return array($yadis_url, $openid_services);
+}
+
+function Auth_OpenID_discoverURI($uri, &$fetcher)
+{
+ $uri = Auth_OpenID::normalizeUrl($uri);
+ return Auth_OpenID_discoverWithYadis($uri, $fetcher);
+}
+
+function Auth_OpenID_discoverWithoutYadis($uri, &$fetcher)
+{
+ $http_resp = @$fetcher->get($uri);
+
+ if ($http_resp->status != 200 and $http_resp->status != 206) {
+ return array($uri, array());
+ }
+
+ $identity_url = $http_resp->final_url;
+
+ // Try to parse the response as HTML to get OpenID 1.0/1.1 <link
+ // rel="...">
+ $openid_services = Auth_OpenID_ServiceEndpoint::fromHTML(
+ $identity_url,
+ $http_resp->body);
+
+ return array($identity_url, $openid_services);
+}
+
+function Auth_OpenID_discoverXRI($iname, &$fetcher)
+{
+ $resolver = new Auth_Yadis_ProxyResolver($fetcher);
+ list($canonicalID, $yadis_services) =
+ $resolver->query($iname,
+ Auth_OpenID_getOpenIDTypeURIs(),
+ array('filter_MatchesAnyOpenIDType'));
+
+ $openid_services = Auth_OpenID_makeOpenIDEndpoints($iname,
+ $yadis_services);
+
+ $openid_services = Auth_OpenID_getOPOrUserServices($openid_services);
+
+ for ($i = 0; $i < count($openid_services); $i++) {
+ $openid_services[$i]->canonicalID = $canonicalID;
+ $openid_services[$i]->claimed_id = $canonicalID;
+ $openid_services[$i]->display_identifier = $iname;
+ }
+
+ // FIXME: returned xri should probably be in some normal form
+ return array($iname, $openid_services);
+}
+
+function Auth_OpenID_discover($uri, &$fetcher)
+{
+ // If the fetcher (i.e., PHP) doesn't support SSL, we can't do
+ // discovery on an HTTPS URL.
+ if ($fetcher->isHTTPS($uri) && !$fetcher->supportsSSL()) {
+ return array($uri, array());
+ }
+
+ if (Auth_Yadis_identifierScheme($uri) == 'XRI') {
+ $result = Auth_OpenID_discoverXRI($uri, $fetcher);
+ } else {
+ $result = Auth_OpenID_discoverURI($uri, $fetcher);
+ }
+
+ // If the fetcher doesn't support SSL, we can't interact with
+ // HTTPS server URLs; remove those endpoints from the list.
+ if (!$fetcher->supportsSSL()) {
+ $http_endpoints = array();
+ list($new_uri, $endpoints) = $result;
+
+ foreach ($endpoints as $e) {
+ if (!$fetcher->isHTTPS($e->server_url)) {
+ $http_endpoints[] = $e;
+ }
+ }
+
+ $result = array($new_uri, $http_endpoints);
+ }
+
+ return $result;
+}
+
+?>
diff --git a/extlib/Auth/OpenID/DumbStore.php b/extlib/Auth/OpenID/DumbStore.php
new file mode 100644
index 000000000..22fd2d366
--- /dev/null
+++ b/extlib/Auth/OpenID/DumbStore.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * This file supplies a dumb store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Import the interface for creating a new store class.
+ */
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/HMAC.php';
+
+/**
+ * This is a store for use in the worst case, when you have no way of
+ * saving state on the consumer site. Using this store makes the
+ * consumer vulnerable to replay attacks, as it's unable to use
+ * nonces. Avoid using this store if it is at all possible.
+ *
+ * Most of the methods of this class are implementation details.
+ * Users of this class need to worry only about the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DumbStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Creates a new {@link Auth_OpenID_DumbStore} instance. For the security
+ * of the tokens generated by the library, this class attempts to
+ * at least have a secure implementation of getAuthKey.
+ *
+ * When you create an instance of this class, pass in a secret
+ * phrase. The phrase is hashed with sha1 to make it the correct
+ * length and form for an auth key. That allows you to use a long
+ * string as the secret phrase, which means you can make it very
+ * difficult to guess.
+ *
+ * Each {@link Auth_OpenID_DumbStore} instance that is created for use by
+ * your consumer site needs to use the same $secret_phrase.
+ *
+ * @param string secret_phrase The phrase used to create the auth
+ * key returned by getAuthKey
+ */
+ function Auth_OpenID_DumbStore($secret_phrase)
+ {
+ $this->auth_key = Auth_OpenID_SHA1($secret_phrase);
+ }
+
+ /**
+ * This implementation does nothing.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ }
+
+ /**
+ * This implementation always returns null.
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ return null;
+ }
+
+ /**
+ * This implementation always returns false.
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ return false;
+ }
+
+ /**
+ * In a system truly limited to dumb mode, nonces must all be
+ * accepted. This therefore always returns true, which makes
+ * replay attacks feasible.
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ return true;
+ }
+
+ /**
+ * This method returns the auth key generated by the constructor.
+ */
+ function getAuthKey()
+ {
+ return $this->auth_key;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/Extension.php b/extlib/Auth/OpenID/Extension.php
new file mode 100644
index 000000000..f362a4b38
--- /dev/null
+++ b/extlib/Auth/OpenID/Extension.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * An interface for OpenID extensions.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the Message implementation.
+ */
+require_once 'Auth/OpenID/Message.php';
+
+/**
+ * A base class for accessing extension request and response data for
+ * the OpenID 2 protocol.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Extension {
+ /**
+ * ns_uri: The namespace to which to add the arguments for this
+ * extension
+ */
+ var $ns_uri = null;
+ var $ns_alias = null;
+
+ /**
+ * Get the string arguments that should be added to an OpenID
+ * message for this extension.
+ */
+ function getExtensionArgs()
+ {
+ return null;
+ }
+
+ /**
+ * Add the arguments from this extension to the provided message.
+ *
+ * Returns the message with the extension arguments added.
+ */
+ function toMessage(&$message)
+ {
+ $implicit = $message->isOpenID1();
+ $added = $message->namespaces->addAlias($this->ns_uri,
+ $this->ns_alias,
+ $implicit);
+
+ if ($added === null) {
+ if ($message->namespaces->getAlias($this->ns_uri) !=
+ $this->ns_alias) {
+ return null;
+ }
+ }
+
+ $message->updateArgs($this->ns_uri,
+ $this->getExtensionArgs());
+ return $message;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/FileStore.php b/extlib/Auth/OpenID/FileStore.php
new file mode 100644
index 000000000..29d8d20e7
--- /dev/null
+++ b/extlib/Auth/OpenID/FileStore.php
@@ -0,0 +1,618 @@
+<?php
+
+/**
+ * This file supplies a Memcached store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require base class for creating a new interface.
+ */
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/HMAC.php';
+require_once 'Auth/OpenID/Nonce.php';
+
+/**
+ * This is a filesystem-based store for OpenID associations and
+ * nonces. This store should be safe for use in concurrent systems on
+ * both windows and unix (excluding NFS filesystems). There are a
+ * couple race conditions in the system, but those failure cases have
+ * been set up in such a way that the worst-case behavior is someone
+ * having to try to log in a second time.
+ *
+ * Most of the methods of this class are implementation details.
+ * People wishing to just use this store need only pay attention to
+ * the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Initializes a new {@link Auth_OpenID_FileStore}. This
+ * initializes the nonce and association directories, which are
+ * subdirectories of the directory passed in.
+ *
+ * @param string $directory This is the directory to put the store
+ * directories in.
+ */
+ function Auth_OpenID_FileStore($directory)
+ {
+ if (!Auth_OpenID::ensureDir($directory)) {
+ trigger_error('Not a directory and failed to create: '
+ . $directory, E_USER_ERROR);
+ }
+ $directory = realpath($directory);
+
+ $this->directory = $directory;
+ $this->active = true;
+
+ $this->nonce_dir = $directory . DIRECTORY_SEPARATOR . 'nonces';
+
+ $this->association_dir = $directory . DIRECTORY_SEPARATOR .
+ 'associations';
+
+ // Temp dir must be on the same filesystem as the assciations
+ // $directory.
+ $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp';
+
+ $this->max_nonce_age = 6 * 60 * 60; // Six hours, in seconds
+
+ if (!$this->_setup()) {
+ trigger_error('Failed to initialize OpenID file store in ' .
+ $directory, E_USER_ERROR);
+ }
+ }
+
+ function destroy()
+ {
+ Auth_OpenID_FileStore::_rmtree($this->directory);
+ $this->active = false;
+ }
+
+ /**
+ * Make sure that the directories in which we store our data
+ * exist.
+ *
+ * @access private
+ */
+ function _setup()
+ {
+ return (Auth_OpenID::ensureDir($this->nonce_dir) &&
+ Auth_OpenID::ensureDir($this->association_dir) &&
+ Auth_OpenID::ensureDir($this->temp_dir));
+ }
+
+ /**
+ * Create a temporary file on the same filesystem as
+ * $this->association_dir.
+ *
+ * The temporary directory should not be cleaned if there are any
+ * processes using the store. If there is no active process using
+ * the store, it is safe to remove all of the files in the
+ * temporary directory.
+ *
+ * @return array ($fd, $filename)
+ * @access private
+ */
+ function _mktemp()
+ {
+ $name = Auth_OpenID_FileStore::_mkstemp($dir = $this->temp_dir);
+ $file_obj = @fopen($name, 'wb');
+ if ($file_obj !== false) {
+ return array($file_obj, $name);
+ } else {
+ Auth_OpenID_FileStore::_removeIfPresent($name);
+ }
+ }
+
+ function cleanupNonces()
+ {
+ global $Auth_OpenID_SKEW;
+
+ $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir);
+ $now = time();
+
+ $removed = 0;
+ // Check all nonces for expiry
+ foreach ($nonces as $nonce_fname) {
+ $base = basename($nonce_fname);
+ $parts = explode('-', $base, 2);
+ $timestamp = $parts[0];
+ $timestamp = intval($timestamp, 16);
+ if (abs($timestamp - $now) > $Auth_OpenID_SKEW) {
+ Auth_OpenID_FileStore::_removeIfPresent($nonce_fname);
+ $removed += 1;
+ }
+ }
+ return $removed;
+ }
+
+ /**
+ * Create a unique filename for a given server url and
+ * handle. This implementation does not assume anything about the
+ * format of the handle. The filename that is returned will
+ * contain the domain name from the server URL for ease of human
+ * inspection of the data directory.
+ *
+ * @return string $filename
+ */
+ function getAssociationFilename($server_url, $handle)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ if (strpos($server_url, '://') === false) {
+ trigger_error(sprintf("Bad server URL: %s", $server_url),
+ E_USER_WARNING);
+ return null;
+ }
+
+ list($proto, $rest) = explode('://', $server_url, 2);
+ $parts = explode('/', $rest);
+ $domain = Auth_OpenID_FileStore::_filenameEscape($parts[0]);
+ $url_hash = Auth_OpenID_FileStore::_safe64($server_url);
+ if ($handle) {
+ $handle_hash = Auth_OpenID_FileStore::_safe64($handle);
+ } else {
+ $handle_hash = '';
+ }
+
+ $filename = sprintf('%s-%s-%s-%s', $proto, $domain, $url_hash,
+ $handle_hash);
+
+ return $this->association_dir. DIRECTORY_SEPARATOR . $filename;
+ }
+
+ /**
+ * Store an association in the association directory.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return false;
+ }
+
+ $association_s = $association->serialize();
+ $filename = $this->getAssociationFilename($server_url,
+ $association->handle);
+ list($tmp_file, $tmp) = $this->_mktemp();
+
+ if (!$tmp_file) {
+ trigger_error("_mktemp didn't return a valid file descriptor",
+ E_USER_WARNING);
+ return false;
+ }
+
+ fwrite($tmp_file, $association_s);
+
+ fflush($tmp_file);
+
+ fclose($tmp_file);
+
+ if (@rename($tmp, $filename)) {
+ return true;
+ } else {
+ // In case we are running on Windows, try unlinking the
+ // file in case it exists.
+ @unlink($filename);
+
+ // Now the target should not exist. Try renaming again,
+ // giving up if it fails.
+ if (@rename($tmp, $filename)) {
+ return true;
+ }
+ }
+
+ // If there was an error, don't leave the temporary file
+ // around.
+ Auth_OpenID_FileStore::_removeIfPresent($tmp);
+ return false;
+ }
+
+ /**
+ * Retrieve an association. If no handle is specified, return the
+ * association with the most recent issue time.
+ *
+ * @return mixed $association
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ if ($handle === null) {
+ $handle = '';
+ }
+
+ // The filename with the empty handle is a prefix of all other
+ // associations for the given server URL.
+ $filename = $this->getAssociationFilename($server_url, $handle);
+
+ if ($handle) {
+ return $this->_getAssociation($filename);
+ } else {
+ $association_files =
+ Auth_OpenID_FileStore::_listdir($this->association_dir);
+ $matching_files = array();
+
+ // strip off the path to do the comparison
+ $name = basename($filename);
+ foreach ($association_files as $association_file) {
+ $base = basename($association_file);
+ if (strpos($base, $name) === 0) {
+ $matching_files[] = $association_file;
+ }
+ }
+
+ $matching_associations = array();
+ // read the matching files and sort by time issued
+ foreach ($matching_files as $full_name) {
+ $association = $this->_getAssociation($full_name);
+ if ($association !== null) {
+ $matching_associations[] = array($association->issued,
+ $association);
+ }
+ }
+
+ $issued = array();
+ $assocs = array();
+ foreach ($matching_associations as $key => $assoc) {
+ $issued[$key] = $assoc[0];
+ $assocs[$key] = $assoc[1];
+ }
+
+ array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
+ $matching_associations);
+
+ // return the most recently issued one.
+ if ($matching_associations) {
+ list($issued, $assoc) = $matching_associations[0];
+ return $assoc;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _getAssociation($filename)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $assoc_file = @fopen($filename, 'rb');
+
+ if ($assoc_file === false) {
+ return null;
+ }
+
+ $assoc_s = fread($assoc_file, filesize($filename));
+ fclose($assoc_file);
+
+ if (!$assoc_s) {
+ return null;
+ }
+
+ $association =
+ Auth_OpenID_Association::deserialize('Auth_OpenID_Association',
+ $assoc_s);
+
+ if (!$association) {
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ return null;
+ }
+
+ if ($association->getExpiresIn() == 0) {
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ return null;
+ } else {
+ return $association;
+ }
+ }
+
+ /**
+ * Remove an association if it exists. Do nothing if it does not.
+ *
+ * @return bool $success
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $assoc = $this->getAssociation($server_url, $handle);
+ if ($assoc === null) {
+ return false;
+ } else {
+ $filename = $this->getAssociationFilename($server_url, $handle);
+ return Auth_OpenID_FileStore::_removeIfPresent($filename);
+ }
+ }
+
+ /**
+ * Return whether this nonce is present. As a side effect, mark it
+ * as no longer present.
+ *
+ * @return bool $present
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
+ return False;
+ }
+
+ if ($server_url) {
+ list($proto, $rest) = explode('://', $server_url, 2);
+ } else {
+ $proto = '';
+ $rest = '';
+ }
+
+ $parts = explode('/', $rest, 2);
+ $domain = $this->_filenameEscape($parts[0]);
+ $url_hash = $this->_safe64($server_url);
+ $salt_hash = $this->_safe64($salt);
+
+ $filename = sprintf('%08x-%s-%s-%s-%s', $timestamp, $proto,
+ $domain, $url_hash, $salt_hash);
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $filename;
+
+ $result = @fopen($filename, 'x');
+
+ if ($result === false) {
+ return false;
+ } else {
+ fclose($result);
+ return true;
+ }
+ }
+
+ /**
+ * Remove expired entries from the database. This is potentially
+ * expensive, so only run when it is acceptable to take time.
+ *
+ * @access private
+ */
+ function _allAssocs()
+ {
+ $all_associations = array();
+
+ $association_filenames =
+ Auth_OpenID_FileStore::_listdir($this->association_dir);
+
+ foreach ($association_filenames as $association_filename) {
+ $association_file = fopen($association_filename, 'rb');
+
+ if ($association_file !== false) {
+ $assoc_s = fread($association_file,
+ filesize($association_filename));
+ fclose($association_file);
+
+ // Remove expired or corrupted associations
+ $association =
+ Auth_OpenID_Association::deserialize(
+ 'Auth_OpenID_Association', $assoc_s);
+
+ if ($association === null) {
+ Auth_OpenID_FileStore::_removeIfPresent(
+ $association_filename);
+ } else {
+ if ($association->getExpiresIn() == 0) {
+ $all_associations[] = array($association_filename,
+ $association);
+ }
+ }
+ }
+ }
+
+ return $all_associations;
+ }
+
+ function clean()
+ {
+ if (!$this->active) {
+ trigger_error("FileStore no longer active", E_USER_ERROR);
+ return null;
+ }
+
+ $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir);
+ $now = time();
+
+ // Check all nonces for expiry
+ foreach ($nonces as $nonce) {
+ if (!Auth_OpenID_checkTimestamp($nonce, $now)) {
+ $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce;
+ Auth_OpenID_FileStore::_removeIfPresent($filename);
+ }
+ }
+
+ foreach ($this->_allAssocs() as $pair) {
+ list($assoc_filename, $assoc) = $pair;
+ if ($assoc->getExpiresIn() == 0) {
+ Auth_OpenID_FileStore::_removeIfPresent($assoc_filename);
+ }
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _rmtree($dir)
+ {
+ if ($dir[strlen($dir) - 1] != DIRECTORY_SEPARATOR) {
+ $dir .= DIRECTORY_SEPARATOR;
+ }
+
+ if ($handle = opendir($dir)) {
+ while ($item = readdir($handle)) {
+ if (!in_array($item, array('.', '..'))) {
+ if (is_dir($dir . $item)) {
+
+ if (!Auth_OpenID_FileStore::_rmtree($dir . $item)) {
+ return false;
+ }
+ } else if (is_file($dir . $item)) {
+ if (!unlink($dir . $item)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ closedir($handle);
+
+ if (!@rmdir($dir)) {
+ return false;
+ }
+
+ return true;
+ } else {
+ // Couldn't open directory.
+ return false;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _mkstemp($dir)
+ {
+ foreach (range(0, 4) as $i) {
+ $name = tempnam($dir, "php_openid_filestore_");
+
+ if ($name !== false) {
+ return $name;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _mkdtemp($dir)
+ {
+ foreach (range(0, 4) as $i) {
+ $name = $dir . strval(DIRECTORY_SEPARATOR) . strval(getmypid()) .
+ "-" . strval(rand(1, time()));
+ if (!mkdir($name, 0700)) {
+ return false;
+ } else {
+ return $name;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @access private
+ */
+ function _listdir($dir)
+ {
+ $handle = opendir($dir);
+ $files = array();
+ while (false !== ($filename = readdir($handle))) {
+ if (!in_array($filename, array('.', '..'))) {
+ $files[] = $dir . DIRECTORY_SEPARATOR . $filename;
+ }
+ }
+ return $files;
+ }
+
+ /**
+ * @access private
+ */
+ function _isFilenameSafe($char)
+ {
+ $_Auth_OpenID_filename_allowed = Auth_OpenID_letters .
+ Auth_OpenID_digits . ".";
+ return (strpos($_Auth_OpenID_filename_allowed, $char) !== false);
+ }
+
+ /**
+ * @access private
+ */
+ function _safe64($str)
+ {
+ $h64 = base64_encode(Auth_OpenID_SHA1($str));
+ $h64 = str_replace('+', '_', $h64);
+ $h64 = str_replace('/', '.', $h64);
+ $h64 = str_replace('=', '', $h64);
+ return $h64;
+ }
+
+ /**
+ * @access private
+ */
+ function _filenameEscape($str)
+ {
+ $filename = "";
+ $b = Auth_OpenID::toBytes($str);
+
+ for ($i = 0; $i < count($b); $i++) {
+ $c = $b[$i];
+ if (Auth_OpenID_FileStore::_isFilenameSafe($c)) {
+ $filename .= $c;
+ } else {
+ $filename .= sprintf("_%02X", ord($c));
+ }
+ }
+ return $filename;
+ }
+
+ /**
+ * Attempt to remove a file, returning whether the file existed at
+ * the time of the call.
+ *
+ * @access private
+ * @return bool $result True if the file was present, false if not.
+ */
+ function _removeIfPresent($filename)
+ {
+ return @unlink($filename);
+ }
+
+ function cleanupAssociations()
+ {
+ $removed = 0;
+ foreach ($this->_allAssocs() as $pair) {
+ list($assoc_filename, $assoc) = $pair;
+ if ($assoc->getExpiresIn() == 0) {
+ $this->_removeIfPresent($assoc_filename);
+ $removed += 1;
+ }
+ }
+ return $removed;
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/HMAC.php b/extlib/Auth/OpenID/HMAC.php
new file mode 100644
index 000000000..ec42db8df
--- /dev/null
+++ b/extlib/Auth/OpenID/HMAC.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * This is the HMACSHA1 implementation for the OpenID library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID.php';
+
+/**
+ * SHA1_BLOCKSIZE is this module's SHA1 blocksize used by the fallback
+ * implementation.
+ */
+define('Auth_OpenID_SHA1_BLOCKSIZE', 64);
+
+function Auth_OpenID_SHA1($text)
+{
+ if (function_exists('hash') &&
+ function_exists('hash_algos') &&
+ (in_array('sha1', hash_algos()))) {
+ // PHP 5 case (sometimes): 'hash' available and 'sha1' algo
+ // supported.
+ return hash('sha1', $text, true);
+ } else if (function_exists('sha1')) {
+ // PHP 4 case: 'sha1' available.
+ $hex = sha1($text);
+ $raw = '';
+ for ($i = 0; $i < 40; $i += 2) {
+ $hexcode = substr($hex, $i, 2);
+ $charcode = (int)base_convert($hexcode, 16, 10);
+ $raw .= chr($charcode);
+ }
+ return $raw;
+ } else {
+ // Explode.
+ trigger_error('No SHA1 function found', E_USER_ERROR);
+ }
+}
+
+/**
+ * Compute an HMAC/SHA1 hash.
+ *
+ * @access private
+ * @param string $key The HMAC key
+ * @param string $text The message text to hash
+ * @return string $mac The MAC
+ */
+function Auth_OpenID_HMACSHA1($key, $text)
+{
+ if (Auth_OpenID::bytes($key) > Auth_OpenID_SHA1_BLOCKSIZE) {
+ $key = Auth_OpenID_SHA1($key, true);
+ }
+
+ $key = str_pad($key, Auth_OpenID_SHA1_BLOCKSIZE, chr(0x00));
+ $ipad = str_repeat(chr(0x36), Auth_OpenID_SHA1_BLOCKSIZE);
+ $opad = str_repeat(chr(0x5c), Auth_OpenID_SHA1_BLOCKSIZE);
+ $hash1 = Auth_OpenID_SHA1(($key ^ $ipad) . $text, true);
+ $hmac = Auth_OpenID_SHA1(($key ^ $opad) . $hash1, true);
+ return $hmac;
+}
+
+if (function_exists('hash') &&
+ function_exists('hash_algos') &&
+ (in_array('sha256', hash_algos()))) {
+ function Auth_OpenID_SHA256($text)
+ {
+ // PHP 5 case: 'hash' available and 'sha256' algo supported.
+ return hash('sha256', $text, true);
+ }
+ define('Auth_OpenID_SHA256_SUPPORTED', true);
+} else {
+ define('Auth_OpenID_SHA256_SUPPORTED', false);
+}
+
+if (function_exists('hash_hmac') &&
+ function_exists('hash_algos') &&
+ (in_array('sha256', hash_algos()))) {
+
+ function Auth_OpenID_HMACSHA256($key, $text)
+ {
+ // Return raw MAC (not hex string).
+ return hash_hmac('sha256', $text, $key, true);
+ }
+
+ define('Auth_OpenID_HMACSHA256_SUPPORTED', true);
+} else {
+ define('Auth_OpenID_HMACSHA256_SUPPORTED', false);
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/Interface.php b/extlib/Auth/OpenID/Interface.php
new file mode 100644
index 000000000..f4c6062f8
--- /dev/null
+++ b/extlib/Auth/OpenID/Interface.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * This file specifies the interface for PHP OpenID store implementations.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * This is the interface for the store objects the OpenID library
+ * uses. It is a single class that provides all of the persistence
+ * mechanisms that the OpenID library needs, for both servers and
+ * consumers. If you want to create an SQL-driven store, please see
+ * then {@link Auth_OpenID_SQLStore} class.
+ *
+ * Change: Version 2.0 removed the storeNonce, getAuthKey, and isDumb
+ * methods, and changed the behavior of the useNonce method to support
+ * one-way nonces.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ */
+class Auth_OpenID_OpenIDStore {
+ /**
+ * This method puts an Association object into storage,
+ * retrievable by server URL and handle.
+ *
+ * @param string $server_url The URL of the identity server that
+ * this association is with. Because of the way the server portion
+ * of the library uses this interface, don't assume there are any
+ * limitations on the character set of the input string. In
+ * particular, expect to see unescaped non-url-safe characters in
+ * the server_url field.
+ *
+ * @param Association $association The Association to store.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::storeAssociation ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /*
+ * Remove expired nonces from the store.
+ *
+ * Discards any nonce from storage that is old enough that its
+ * timestamp would not pass useNonce().
+ *
+ * This method is not called in the normal operation of the
+ * library. It provides a way for store admins to keep their
+ * storage from filling up with expired data.
+ *
+ * @return the number of nonces expired
+ */
+ function cleanupNonces()
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::cleanupNonces ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /*
+ * Remove expired associations from the store.
+ *
+ * This method is not called in the normal operation of the
+ * library. It provides a way for store admins to keep their
+ * storage from filling up with expired data.
+ *
+ * @return the number of associations expired.
+ */
+ function cleanupAssociations()
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::cleanupAssociations ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /*
+ * Shortcut for cleanupNonces(), cleanupAssociations().
+ *
+ * This method is not called in the normal operation of the
+ * library. It provides a way for store admins to keep their
+ * storage from filling up with expired data.
+ */
+ function cleanup()
+ {
+ return array($this->cleanupNonces(),
+ $this->cleanupAssociations());
+ }
+
+ /**
+ * Report whether this storage supports cleanup
+ */
+ function supportsCleanup()
+ {
+ return true;
+ }
+
+ /**
+ * This method returns an Association object from storage that
+ * matches the server URL and, if specified, handle. It returns
+ * null if no such association is found or if the matching
+ * association is expired.
+ *
+ * If no handle is specified, the store may return any association
+ * which matches the server URL. If multiple associations are
+ * valid, the recommended return value for this method is the one
+ * most recently issued.
+ *
+ * This method is allowed (and encouraged) to garbage collect
+ * expired associations when found. This method must not return
+ * expired associations.
+ *
+ * @param string $server_url The URL of the identity server to get
+ * the association for. Because of the way the server portion of
+ * the library uses this interface, don't assume there are any
+ * limitations on the character set of the input string. In
+ * particular, expect to see unescaped non-url-safe characters in
+ * the server_url field.
+ *
+ * @param mixed $handle This optional parameter is the handle of
+ * the specific association to get. If no specific handle is
+ * provided, any valid association matching the server URL is
+ * returned.
+ *
+ * @return Association The Association for the given identity
+ * server.
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::getAssociation ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * This method removes the matching association if it's found, and
+ * returns whether the association was removed or not.
+ *
+ * @param string $server_url The URL of the identity server the
+ * association to remove belongs to. Because of the way the server
+ * portion of the library uses this interface, don't assume there
+ * are any limitations on the character set of the input
+ * string. In particular, expect to see unescaped non-url-safe
+ * characters in the server_url field.
+ *
+ * @param string $handle This is the handle of the association to
+ * remove. If there isn't an association found that matches both
+ * the given URL and handle, then there was no matching handle
+ * found.
+ *
+ * @return mixed Returns whether or not the given association existed.
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::removeAssociation ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Called when using a nonce.
+ *
+ * This method should return C{True} if the nonce has not been
+ * used before, and store it for a while to make sure nobody
+ * tries to use the same value again. If the nonce has already
+ * been used, return C{False}.
+ *
+ * Change: In earlier versions, round-trip nonces were used and a
+ * nonce was only valid if it had been previously stored with
+ * storeNonce. Version 2.0 uses one-way nonces, requiring a
+ * different implementation here that does not depend on a
+ * storeNonce call. (storeNonce is no longer part of the
+ * interface.
+ *
+ * @param string $nonce The nonce to use.
+ *
+ * @return bool Whether or not the nonce was valid.
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ trigger_error("Auth_OpenID_OpenIDStore::useNonce ".
+ "not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Removes all entries from the store; implementation is optional.
+ */
+ function reset()
+ {
+ }
+
+}
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/KVForm.php b/extlib/Auth/OpenID/KVForm.php
new file mode 100644
index 000000000..fb342a001
--- /dev/null
+++ b/extlib/Auth/OpenID/KVForm.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * OpenID protocol key-value/comma-newline format parsing and
+ * serialization
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Container for key-value/comma-newline OpenID format and parsing
+ */
+class Auth_OpenID_KVForm {
+ /**
+ * Convert an OpenID colon/newline separated string into an
+ * associative array
+ *
+ * @static
+ * @access private
+ */
+ function toArray($kvs, $strict=false)
+ {
+ $lines = explode("\n", $kvs);
+
+ $last = array_pop($lines);
+ if ($last !== '') {
+ array_push($lines, $last);
+ if ($strict) {
+ return false;
+ }
+ }
+
+ $values = array();
+
+ for ($lineno = 0; $lineno < count($lines); $lineno++) {
+ $line = $lines[$lineno];
+ $kv = explode(':', $line, 2);
+ if (count($kv) != 2) {
+ if ($strict) {
+ return false;
+ }
+ continue;
+ }
+
+ $key = $kv[0];
+ $tkey = trim($key);
+ if ($tkey != $key) {
+ if ($strict) {
+ return false;
+ }
+ }
+
+ $value = $kv[1];
+ $tval = trim($value);
+ if ($tval != $value) {
+ if ($strict) {
+ return false;
+ }
+ }
+
+ $values[$tkey] = $tval;
+ }
+
+ return $values;
+ }
+
+ /**
+ * Convert an array into an OpenID colon/newline separated string
+ *
+ * @static
+ * @access private
+ */
+ function fromArray($values)
+ {
+ if ($values === null) {
+ return null;
+ }
+
+ ksort($values);
+
+ $serialized = '';
+ foreach ($values as $key => $value) {
+ if (is_array($value)) {
+ list($key, $value) = array($value[0], $value[1]);
+ }
+
+ if (strpos($key, ':') !== false) {
+ return null;
+ }
+
+ if (strpos($key, "\n") !== false) {
+ return null;
+ }
+
+ if (strpos($value, "\n") !== false) {
+ return null;
+ }
+ $serialized .= "$key:$value\n";
+ }
+ return $serialized;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/MemcachedStore.php b/extlib/Auth/OpenID/MemcachedStore.php
new file mode 100644
index 000000000..d357c6b11
--- /dev/null
+++ b/extlib/Auth/OpenID/MemcachedStore.php
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * This file supplies a memcached store backend for OpenID servers and
+ * consumers.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author Artemy Tregubenko <me@arty.name>
+ * @copyright 2008 JanRain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ * Contributed by Open Web Technologies <http://openwebtech.ru/>
+ */
+
+/**
+ * Import the interface for creating a new store class.
+ */
+require_once 'Auth/OpenID/Interface.php';
+
+/**
+ * This is a memcached-based store for OpenID associations and
+ * nonces.
+ *
+ * As memcache has limit of 250 chars for key length,
+ * server_url, handle and salt are hashed with sha1().
+ *
+ * Most of the methods of this class are implementation details.
+ * People wishing to just use this store need only pay attention to
+ * the constructor.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MemcachedStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * Initializes a new {@link Auth_OpenID_MemcachedStore} instance.
+ * Just saves memcached object as property.
+ *
+ * @param resource connection Memcache connection resourse
+ */
+ function Auth_OpenID_MemcachedStore($connection, $compress = false)
+ {
+ $this->connection = $connection;
+ $this->compress = $compress ? MEMCACHE_COMPRESSED : 0;
+ }
+
+ /**
+ * Store association until its expiration time in memcached.
+ * Overwrites any existing association with same server_url and
+ * handle. Handles list of associations for every server.
+ */
+ function storeAssociation($server_url, $association)
+ {
+ // create memcached keys for association itself
+ // and list of associations for this server
+ $associationKey = $this->associationKey($server_url,
+ $association->handle);
+ $serverKey = $this->associationServerKey($server_url);
+
+ // get list of associations
+ $serverAssociations = $this->connection->get($serverKey);
+
+ // if no such list, initialize it with empty array
+ if (!$serverAssociations) {
+ $serverAssociations = array();
+ }
+ // and store given association key in it
+ $serverAssociations[$association->issued] = $associationKey;
+
+ // save associations' keys list
+ $this->connection->set(
+ $serverKey,
+ $serverAssociations,
+ $this->compress
+ );
+ // save association itself
+ $this->connection->set(
+ $associationKey,
+ $association,
+ $this->compress,
+ $association->issued + $association->lifetime);
+ }
+
+ /**
+ * Read association from memcached. If no handle given
+ * and multiple associations found, returns latest issued
+ */
+ function getAssociation($server_url, $handle = null)
+ {
+ // simple case: handle given
+ if ($handle !== null) {
+ // get association, return null if failed
+ $association = $this->connection->get(
+ $this->associationKey($server_url, $handle));
+ return $association ? $association : null;
+ }
+
+ // no handle given, working with list
+ // create key for list of associations
+ $serverKey = $this->associationServerKey($server_url);
+
+ // get list of associations
+ $serverAssociations = $this->connection->get($serverKey);
+ // return null if failed or got empty list
+ if (!$serverAssociations) {
+ return null;
+ }
+
+ // get key of most recently issued association
+ $keys = array_keys($serverAssociations);
+ sort($keys);
+ $lastKey = $serverAssociations[array_pop($keys)];
+
+ // get association, return null if failed
+ $association = $this->connection->get($lastKey);
+ return $association ? $association : null;
+ }
+
+ /**
+ * Immediately delete association from memcache.
+ */
+ function removeAssociation($server_url, $handle)
+ {
+ // create memcached keys for association itself
+ // and list of associations for this server
+ $serverKey = $this->associationServerKey($server_url);
+ $associationKey = $this->associationKey($server_url,
+ $handle);
+
+ // get list of associations
+ $serverAssociations = $this->connection->get($serverKey);
+ // return null if failed or got empty list
+ if (!$serverAssociations) {
+ return false;
+ }
+
+ // ensure that given association key exists in list
+ $serverAssociations = array_flip($serverAssociations);
+ if (!array_key_exists($associationKey, $serverAssociations)) {
+ return false;
+ }
+
+ // remove given association key from list
+ unset($serverAssociations[$associationKey]);
+ $serverAssociations = array_flip($serverAssociations);
+
+ // save updated list
+ $this->connection->set(
+ $serverKey,
+ $serverAssociations,
+ $this->compress
+ );
+
+ // delete association
+ return $this->connection->delete($associationKey);
+ }
+
+ /**
+ * Create nonce for server and salt, expiring after
+ * $Auth_OpenID_SKEW seconds.
+ */
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ // save one request to memcache when nonce obviously expired
+ if (abs($timestamp - time()) > $Auth_OpenID_SKEW) {
+ return false;
+ }
+
+ // returns false when nonce already exists
+ // otherwise adds nonce
+ return $this->connection->add(
+ 'openid_nonce_' . sha1($server_url) . '_' . sha1($salt),
+ 1, // any value here
+ $this->compress,
+ $Auth_OpenID_SKEW);
+ }
+
+ /**
+ * Memcache key is prefixed with 'openid_association_' string.
+ */
+ function associationKey($server_url, $handle = null)
+ {
+ return 'openid_association_' . sha1($server_url) . '_' . sha1($handle);
+ }
+
+ /**
+ * Memcache key is prefixed with 'openid_association_' string.
+ */
+ function associationServerKey($server_url)
+ {
+ return 'openid_association_server_' . sha1($server_url);
+ }
+
+ /**
+ * Report that this storage doesn't support cleanup
+ */
+ function supportsCleanup()
+ {
+ return false;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/Message.php b/extlib/Auth/OpenID/Message.php
new file mode 100644
index 000000000..fd23e67a3
--- /dev/null
+++ b/extlib/Auth/OpenID/Message.php
@@ -0,0 +1,915 @@
+<?php
+
+/**
+ * Extension argument processing code
+ *
+ * @package OpenID
+ */
+
+/**
+ * Import tools needed to deal with messages.
+ */
+require_once 'Auth/OpenID.php';
+require_once 'Auth/OpenID/KVForm.php';
+require_once 'Auth/Yadis/XML.php';
+require_once 'Auth/OpenID/Consumer.php'; // For Auth_OpenID_FailureResponse
+
+// This doesn't REALLY belong here, but where is better?
+define('Auth_OpenID_IDENTIFIER_SELECT',
+ "http://specs.openid.net/auth/2.0/identifier_select");
+
+// URI for Simple Registration extension, the only commonly deployed
+// OpenID 1.x extension, and so a special case
+define('Auth_OpenID_SREG_URI', 'http://openid.net/sreg/1.0');
+
+// The OpenID 1.X namespace URI
+define('Auth_OpenID_OPENID1_NS', 'http://openid.net/signon/1.0');
+define('Auth_OpenID_THE_OTHER_OPENID1_NS', 'http://openid.net/signon/1.1');
+
+function Auth_OpenID_isOpenID1($ns)
+{
+ return ($ns == Auth_OpenID_THE_OTHER_OPENID1_NS) ||
+ ($ns == Auth_OpenID_OPENID1_NS);
+}
+
+// The OpenID 2.0 namespace URI
+define('Auth_OpenID_OPENID2_NS', 'http://specs.openid.net/auth/2.0');
+
+// The namespace consisting of pairs with keys that are prefixed with
+// "openid." but not in another namespace.
+define('Auth_OpenID_NULL_NAMESPACE', 'Null namespace');
+
+// The null namespace, when it is an allowed OpenID namespace
+define('Auth_OpenID_OPENID_NS', 'OpenID namespace');
+
+// The top-level namespace, excluding all pairs with keys that start
+// with "openid."
+define('Auth_OpenID_BARE_NS', 'Bare namespace');
+
+// Sentinel for Message implementation to indicate that getArg should
+// return null instead of returning a default.
+define('Auth_OpenID_NO_DEFAULT', 'NO DEFAULT ALLOWED');
+
+// Limit, in bytes, of identity provider and return_to URLs, including
+// response payload. See OpenID 1.1 specification, Appendix D.
+define('Auth_OpenID_OPENID1_URL_LIMIT', 2047);
+
+// All OpenID protocol fields. Used to check namespace aliases.
+global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
+$Auth_OpenID_OPENID_PROTOCOL_FIELDS = array(
+ 'ns', 'mode', 'error', 'return_to', 'contact', 'reference',
+ 'signed', 'assoc_type', 'session_type', 'dh_modulus', 'dh_gen',
+ 'dh_consumer_public', 'claimed_id', 'identity', 'realm',
+ 'invalidate_handle', 'op_endpoint', 'response_nonce', 'sig',
+ 'assoc_handle', 'trust_root', 'openid');
+
+// Global namespace / alias registration map. See
+// Auth_OpenID_registerNamespaceAlias.
+global $Auth_OpenID_registered_aliases;
+$Auth_OpenID_registered_aliases = array();
+
+/**
+ * Registers a (namespace URI, alias) mapping in a global namespace
+ * alias map. Raises NamespaceAliasRegistrationError if either the
+ * namespace URI or alias has already been registered with a different
+ * value. This function is required if you want to use a namespace
+ * with an OpenID 1 message.
+ */
+function Auth_OpenID_registerNamespaceAlias($namespace_uri, $alias)
+{
+ global $Auth_OpenID_registered_aliases;
+
+ if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
+ $alias) == $namespace_uri) {
+ return true;
+ }
+
+ if (in_array($namespace_uri,
+ array_values($Auth_OpenID_registered_aliases))) {
+ return false;
+ }
+
+ if (in_array($alias, array_keys($Auth_OpenID_registered_aliases))) {
+ return false;
+ }
+
+ $Auth_OpenID_registered_aliases[$alias] = $namespace_uri;
+ return true;
+}
+
+/**
+ * Removes a (namespace_uri, alias) registration from the global
+ * namespace alias map. Returns true if the removal succeeded; false
+ * if not (if the mapping did not exist).
+ */
+function Auth_OpenID_removeNamespaceAlias($namespace_uri, $alias)
+{
+ global $Auth_OpenID_registered_aliases;
+
+ if (Auth_OpenID::arrayGet($Auth_OpenID_registered_aliases,
+ $alias) === $namespace_uri) {
+ unset($Auth_OpenID_registered_aliases[$alias]);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * An Auth_OpenID_Mapping maintains a mapping from arbitrary keys to
+ * arbitrary values. (This is unlike an ordinary PHP array, whose
+ * keys may be only simple scalars.)
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Mapping {
+ /**
+ * Initialize a mapping. If $classic_array is specified, its keys
+ * and values are used to populate the mapping.
+ */
+ function Auth_OpenID_Mapping($classic_array = null)
+ {
+ $this->keys = array();
+ $this->values = array();
+
+ if (is_array($classic_array)) {
+ foreach ($classic_array as $key => $value) {
+ $this->set($key, $value);
+ }
+ }
+ }
+
+ /**
+ * Returns true if $thing is an Auth_OpenID_Mapping object; false
+ * if not.
+ */
+ function isA($thing)
+ {
+ return (is_object($thing) &&
+ strtolower(get_class($thing)) == 'auth_openid_mapping');
+ }
+
+ /**
+ * Returns an array of the keys in the mapping.
+ */
+ function keys()
+ {
+ return $this->keys;
+ }
+
+ /**
+ * Returns an array of values in the mapping.
+ */
+ function values()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Returns an array of (key, value) pairs in the mapping.
+ */
+ function items()
+ {
+ $temp = array();
+
+ for ($i = 0; $i < count($this->keys); $i++) {
+ $temp[] = array($this->keys[$i],
+ $this->values[$i]);
+ }
+ return $temp;
+ }
+
+ /**
+ * Returns the "length" of the mapping, or the number of keys.
+ */
+ function len()
+ {
+ return count($this->keys);
+ }
+
+ /**
+ * Sets a key-value pair in the mapping. If the key already
+ * exists, its value is replaced with the new value.
+ */
+ function set($key, $value)
+ {
+ $index = array_search($key, $this->keys);
+
+ if ($index !== false) {
+ $this->values[$index] = $value;
+ } else {
+ $this->keys[] = $key;
+ $this->values[] = $value;
+ }
+ }
+
+ /**
+ * Gets a specified value from the mapping, associated with the
+ * specified key. If the key does not exist in the mapping,
+ * $default is returned instead.
+ */
+ function get($key, $default = null)
+ {
+ $index = array_search($key, $this->keys);
+
+ if ($index !== false) {
+ return $this->values[$index];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _reflow()
+ {
+ // PHP is broken yet again. Sort the arrays to remove the
+ // hole in the numeric indexes that make up the array.
+ $old_keys = $this->keys;
+ $old_values = $this->values;
+
+ $this->keys = array();
+ $this->values = array();
+
+ foreach ($old_keys as $k) {
+ $this->keys[] = $k;
+ }
+
+ foreach ($old_values as $v) {
+ $this->values[] = $v;
+ }
+ }
+
+ /**
+ * Deletes a key-value pair from the mapping with the specified
+ * key.
+ */
+ function del($key)
+ {
+ $index = array_search($key, $this->keys);
+
+ if ($index !== false) {
+ unset($this->keys[$index]);
+ unset($this->values[$index]);
+ $this->_reflow();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the specified value has a key in the mapping;
+ * false if not.
+ */
+ function contains($value)
+ {
+ return (array_search($value, $this->keys) !== false);
+ }
+}
+
+/**
+ * Maintains a bijective map between namespace uris and aliases.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_NamespaceMap {
+ function Auth_OpenID_NamespaceMap()
+ {
+ $this->alias_to_namespace = new Auth_OpenID_Mapping();
+ $this->namespace_to_alias = new Auth_OpenID_Mapping();
+ $this->implicit_namespaces = array();
+ }
+
+ function getAlias($namespace_uri)
+ {
+ return $this->namespace_to_alias->get($namespace_uri);
+ }
+
+ function getNamespaceURI($alias)
+ {
+ return $this->alias_to_namespace->get($alias);
+ }
+
+ function iterNamespaceURIs()
+ {
+ // Return an iterator over the namespace URIs
+ return $this->namespace_to_alias->keys();
+ }
+
+ function iterAliases()
+ {
+ // Return an iterator over the aliases"""
+ return $this->alias_to_namespace->keys();
+ }
+
+ function iteritems()
+ {
+ return $this->namespace_to_alias->items();
+ }
+
+ function isImplicit($namespace_uri)
+ {
+ return in_array($namespace_uri, $this->implicit_namespaces);
+ }
+
+ function addAlias($namespace_uri, $desired_alias, $implicit=false)
+ {
+ // Add an alias from this namespace URI to the desired alias
+ global $Auth_OpenID_OPENID_PROTOCOL_FIELDS;
+
+ // Check that desired_alias is not an openid protocol field as
+ // per the spec.
+ if (in_array($desired_alias, $Auth_OpenID_OPENID_PROTOCOL_FIELDS)) {
+ Auth_OpenID::log("\"%s\" is not an allowed namespace alias",
+ $desired_alias);
+ return null;
+ }
+
+ // Check that desired_alias does not contain a period as per
+ // the spec.
+ if (strpos($desired_alias, '.') !== false) {
+ Auth_OpenID::log('"%s" must not contain a dot', $desired_alias);
+ return null;
+ }
+
+ // Check that there is not a namespace already defined for the
+ // desired alias
+ $current_namespace_uri =
+ $this->alias_to_namespace->get($desired_alias);
+
+ if (($current_namespace_uri !== null) &&
+ ($current_namespace_uri != $namespace_uri)) {
+ Auth_OpenID::log('Cannot map "%s" because previous mapping exists',
+ $namespace_uri);
+ return null;
+ }
+
+ // Check that there is not already a (different) alias for
+ // this namespace URI
+ $alias = $this->namespace_to_alias->get($namespace_uri);
+
+ if (($alias !== null) && ($alias != $desired_alias)) {
+ Auth_OpenID::log('Cannot map %s to alias %s. ' .
+ 'It is already mapped to alias %s',
+ $namespace_uri, $desired_alias, $alias);
+ return null;
+ }
+
+ assert((Auth_OpenID_NULL_NAMESPACE === $desired_alias) ||
+ is_string($desired_alias));
+
+ $this->alias_to_namespace->set($desired_alias, $namespace_uri);
+ $this->namespace_to_alias->set($namespace_uri, $desired_alias);
+ if ($implicit) {
+ array_push($this->implicit_namespaces, $namespace_uri);
+ }
+
+ return $desired_alias;
+ }
+
+ function add($namespace_uri)
+ {
+ // Add this namespace URI to the mapping, without caring what
+ // alias it ends up with
+
+ // See if this namespace is already mapped to an alias
+ $alias = $this->namespace_to_alias->get($namespace_uri);
+
+ if ($alias !== null) {
+ return $alias;
+ }
+
+ // Fall back to generating a numerical alias
+ $i = 0;
+ while (1) {
+ $alias = 'ext' . strval($i);
+ if ($this->addAlias($namespace_uri, $alias) === null) {
+ $i += 1;
+ } else {
+ return $alias;
+ }
+ }
+
+ // Should NEVER be reached!
+ return null;
+ }
+
+ function contains($namespace_uri)
+ {
+ return $this->isDefined($namespace_uri);
+ }
+
+ function isDefined($namespace_uri)
+ {
+ return $this->namespace_to_alias->contains($namespace_uri);
+ }
+}
+
+/**
+ * In the implementation of this object, null represents the global
+ * namespace as well as a namespace with no key.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Message {
+
+ function Auth_OpenID_Message($openid_namespace = null)
+ {
+ // Create an empty Message
+ $this->allowed_openid_namespaces = array(
+ Auth_OpenID_OPENID1_NS,
+ Auth_OpenID_THE_OTHER_OPENID1_NS,
+ Auth_OpenID_OPENID2_NS);
+
+ $this->args = new Auth_OpenID_Mapping();
+ $this->namespaces = new Auth_OpenID_NamespaceMap();
+ if ($openid_namespace === null) {
+ $this->_openid_ns_uri = null;
+ } else {
+ $implicit = Auth_OpenID_isOpenID1($openid_namespace);
+ $this->setOpenIDNamespace($openid_namespace, $implicit);
+ }
+ }
+
+ function isOpenID1()
+ {
+ return Auth_OpenID_isOpenID1($this->getOpenIDNamespace());
+ }
+
+ function isOpenID2()
+ {
+ return $this->getOpenIDNamespace() == Auth_OpenID_OPENID2_NS;
+ }
+
+ function fromPostArgs($args)
+ {
+ // Construct a Message containing a set of POST arguments
+ $obj = new Auth_OpenID_Message();
+
+ // Partition into "openid." args and bare args
+ $openid_args = array();
+ foreach ($args as $key => $value) {
+
+ if (is_array($value)) {
+ return null;
+ }
+
+ $parts = explode('.', $key, 2);
+
+ if (count($parts) == 2) {
+ list($prefix, $rest) = $parts;
+ } else {
+ $prefix = null;
+ }
+
+ if ($prefix != 'openid') {
+ $obj->args->set(array(Auth_OpenID_BARE_NS, $key), $value);
+ } else {
+ $openid_args[$rest] = $value;
+ }
+ }
+
+ if ($obj->_fromOpenIDArgs($openid_args)) {
+ return $obj;
+ } else {
+ return null;
+ }
+ }
+
+ function fromOpenIDArgs($openid_args)
+ {
+ // Takes an array.
+
+ // Construct a Message from a parsed KVForm message
+ $obj = new Auth_OpenID_Message();
+ if ($obj->_fromOpenIDArgs($openid_args)) {
+ return $obj;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _fromOpenIDArgs($openid_args)
+ {
+ global $Auth_OpenID_registered_aliases;
+
+ // Takes an Auth_OpenID_Mapping instance OR an array.
+
+ if (!Auth_OpenID_Mapping::isA($openid_args)) {
+ $openid_args = new Auth_OpenID_Mapping($openid_args);
+ }
+
+ $ns_args = array();
+
+ // Resolve namespaces
+ foreach ($openid_args->items() as $pair) {
+ list($rest, $value) = $pair;
+
+ $parts = explode('.', $rest, 2);
+
+ if (count($parts) == 2) {
+ list($ns_alias, $ns_key) = $parts;
+ } else {
+ $ns_alias = Auth_OpenID_NULL_NAMESPACE;
+ $ns_key = $rest;
+ }
+
+ if ($ns_alias == 'ns') {
+ if ($this->namespaces->addAlias($value, $ns_key) === null) {
+ return false;
+ }
+ } else if (($ns_alias == Auth_OpenID_NULL_NAMESPACE) &&
+ ($ns_key == 'ns')) {
+ // null namespace
+ if ($this->setOpenIDNamespace($value, false) === false) {
+ return false;
+ }
+ } else {
+ $ns_args[] = array($ns_alias, $ns_key, $value);
+ }
+ }
+
+ if (!$this->getOpenIDNamespace()) {
+ if ($this->setOpenIDNamespace(Auth_OpenID_OPENID1_NS, true) ===
+ false) {
+ return false;
+ }
+ }
+
+ // Actually put the pairs into the appropriate namespaces
+ foreach ($ns_args as $triple) {
+ list($ns_alias, $ns_key, $value) = $triple;
+ $ns_uri = $this->namespaces->getNamespaceURI($ns_alias);
+ if ($ns_uri === null) {
+ $ns_uri = $this->_getDefaultNamespace($ns_alias);
+ if ($ns_uri === null) {
+
+ $ns_uri = Auth_OpenID_OPENID_NS;
+ $ns_key = sprintf('%s.%s', $ns_alias, $ns_key);
+ } else {
+ $this->namespaces->addAlias($ns_uri, $ns_alias, true);
+ }
+ }
+
+ $this->setArg($ns_uri, $ns_key, $value);
+ }
+
+ return true;
+ }
+
+ function _getDefaultNamespace($mystery_alias)
+ {
+ global $Auth_OpenID_registered_aliases;
+ if ($this->isOpenID1()) {
+ return @$Auth_OpenID_registered_aliases[$mystery_alias];
+ }
+ return null;
+ }
+
+ function setOpenIDNamespace($openid_ns_uri, $implicit)
+ {
+ if (!in_array($openid_ns_uri, $this->allowed_openid_namespaces)) {
+ Auth_OpenID::log('Invalid null namespace: "%s"', $openid_ns_uri);
+ return false;
+ }
+
+ $succeeded = $this->namespaces->addAlias($openid_ns_uri,
+ Auth_OpenID_NULL_NAMESPACE,
+ $implicit);
+ if ($succeeded === false) {
+ return false;
+ }
+
+ $this->_openid_ns_uri = $openid_ns_uri;
+
+ return true;
+ }
+
+ function getOpenIDNamespace()
+ {
+ return $this->_openid_ns_uri;
+ }
+
+ function fromKVForm($kvform_string)
+ {
+ // Create a Message from a KVForm string
+ return Auth_OpenID_Message::fromOpenIDArgs(
+ Auth_OpenID_KVForm::toArray($kvform_string));
+ }
+
+ function copy()
+ {
+ return $this;
+ }
+
+ function toPostArgs()
+ {
+ // Return all arguments with openid. in front of namespaced
+ // arguments.
+
+ $args = array();
+
+ // Add namespace definitions to the output
+ foreach ($this->namespaces->iteritems() as $pair) {
+ list($ns_uri, $alias) = $pair;
+ if ($this->namespaces->isImplicit($ns_uri)) {
+ continue;
+ }
+ if ($alias == Auth_OpenID_NULL_NAMESPACE) {
+ $ns_key = 'openid.ns';
+ } else {
+ $ns_key = 'openid.ns.' . $alias;
+ }
+ $args[$ns_key] = $ns_uri;
+ }
+
+ foreach ($this->args->items() as $pair) {
+ list($ns_parts, $value) = $pair;
+ list($ns_uri, $ns_key) = $ns_parts;
+ $key = $this->getKey($ns_uri, $ns_key);
+ $args[$key] = $value;
+ }
+
+ return $args;
+ }
+
+ function toArgs()
+ {
+ // Return all namespaced arguments, failing if any
+ // non-namespaced arguments exist.
+ $post_args = $this->toPostArgs();
+ $kvargs = array();
+ foreach ($post_args as $k => $v) {
+ if (strpos($k, 'openid.') !== 0) {
+ // raise ValueError(
+ // 'This message can only be encoded as a POST, because it '
+ // 'contains arguments that are not prefixed with "openid."')
+ return null;
+ } else {
+ $kvargs[substr($k, 7)] = $v;
+ }
+ }
+
+ return $kvargs;
+ }
+
+ function toFormMarkup($action_url, $form_tag_attrs = null,
+ $submit_text = "Continue")
+ {
+ $form = "<form accept-charset=\"UTF-8\" ".
+ "enctype=\"application/x-www-form-urlencoded\"";
+
+ if (!$form_tag_attrs) {
+ $form_tag_attrs = array();
+ }
+
+ $form_tag_attrs['action'] = $action_url;
+ $form_tag_attrs['method'] = 'post';
+
+ unset($form_tag_attrs['enctype']);
+ unset($form_tag_attrs['accept-charset']);
+
+ if ($form_tag_attrs) {
+ foreach ($form_tag_attrs as $name => $attr) {
+ $form .= sprintf(" %s=\"%s\"", $name, $attr);
+ }
+ }
+
+ $form .= ">\n";
+
+ foreach ($this->toPostArgs() as $name => $value) {
+ $form .= sprintf(
+ "<input type=\"hidden\" name=\"%s\" value=\"%s\" />\n",
+ $name, $value);
+ }
+
+ $form .= sprintf("<input type=\"submit\" value=\"%s\" />\n",
+ $submit_text);
+
+ $form .= "</form>\n";
+
+ return $form;
+ }
+
+ function toURL($base_url)
+ {
+ // Generate a GET URL with the parameters in this message
+ // attached as query parameters.
+ return Auth_OpenID::appendArgs($base_url, $this->toPostArgs());
+ }
+
+ function toKVForm()
+ {
+ // Generate a KVForm string that contains the parameters in
+ // this message. This will fail if the message contains
+ // arguments outside of the 'openid.' prefix.
+ return Auth_OpenID_KVForm::fromArray($this->toArgs());
+ }
+
+ function toURLEncoded()
+ {
+ // Generate an x-www-urlencoded string
+ $args = array();
+
+ foreach ($this->toPostArgs() as $k => $v) {
+ $args[] = array($k, $v);
+ }
+
+ sort($args);
+ return Auth_OpenID::httpBuildQuery($args);
+ }
+
+ /**
+ * @access private
+ */
+ function _fixNS($namespace)
+ {
+ // Convert an input value into the internally used values of
+ // this object
+
+ if ($namespace == Auth_OpenID_OPENID_NS) {
+ if ($this->_openid_ns_uri === null) {
+ return new Auth_OpenID_FailureResponse(null,
+ 'OpenID namespace not set');
+ } else {
+ $namespace = $this->_openid_ns_uri;
+ }
+ }
+
+ if (($namespace != Auth_OpenID_BARE_NS) &&
+ (!is_string($namespace))) {
+ //TypeError
+ $err_msg = sprintf("Namespace must be Auth_OpenID_BARE_NS, ".
+ "Auth_OpenID_OPENID_NS or a string. got %s",
+ print_r($namespace, true));
+ return new Auth_OpenID_FailureResponse(null, $err_msg);
+ }
+
+ if (($namespace != Auth_OpenID_BARE_NS) &&
+ (strpos($namespace, ':') === false)) {
+ // fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r'
+ // warnings.warn(fmt % (namespace,), DeprecationWarning)
+
+ if ($namespace == 'sreg') {
+ // fmt = 'Using %r instead of "sreg" as namespace'
+ // warnings.warn(fmt % (SREG_URI,), DeprecationWarning,)
+ return Auth_OpenID_SREG_URI;
+ }
+ }
+
+ return $namespace;
+ }
+
+ function hasKey($namespace, $ns_key)
+ {
+ $namespace = $this->_fixNS($namespace);
+ if (Auth_OpenID::isFailure($namespace)) {
+ // XXX log me
+ return false;
+ } else {
+ return $this->args->contains(array($namespace, $ns_key));
+ }
+ }
+
+ function getKey($namespace, $ns_key)
+ {
+ // Get the key for a particular namespaced argument
+ $namespace = $this->_fixNS($namespace);
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ }
+ if ($namespace == Auth_OpenID_BARE_NS) {
+ return $ns_key;
+ }
+
+ $ns_alias = $this->namespaces->getAlias($namespace);
+
+ // No alias is defined, so no key can exist
+ if ($ns_alias === null) {
+ return null;
+ }
+
+ if ($ns_alias == Auth_OpenID_NULL_NAMESPACE) {
+ $tail = $ns_key;
+ } else {
+ $tail = sprintf('%s.%s', $ns_alias, $ns_key);
+ }
+
+ return 'openid.' . $tail;
+ }
+
+ function getArg($namespace, $key, $default = null)
+ {
+ // Get a value for a namespaced key.
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ if ((!$this->args->contains(array($namespace, $key))) &&
+ ($default == Auth_OpenID_NO_DEFAULT)) {
+ $err_msg = sprintf("Namespace %s missing required field %s",
+ $namespace, $key);
+ return new Auth_OpenID_FailureResponse(null, $err_msg);
+ } else {
+ return $this->args->get(array($namespace, $key), $default);
+ }
+ }
+ }
+
+ function getArgs($namespace)
+ {
+ // Get the arguments that are defined for this namespace URI
+
+ $namespace = $this->_fixNS($namespace);
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ $stuff = array();
+ foreach ($this->args->items() as $pair) {
+ list($key, $value) = $pair;
+ list($pair_ns, $ns_key) = $key;
+ if ($pair_ns == $namespace) {
+ $stuff[$ns_key] = $value;
+ }
+ }
+
+ return $stuff;
+ }
+ }
+
+ function updateArgs($namespace, $updates)
+ {
+ // Set multiple key/value pairs in one call
+
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ foreach ($updates as $k => $v) {
+ $this->setArg($namespace, $k, $v);
+ }
+ return true;
+ }
+ }
+
+ function setArg($namespace, $key, $value)
+ {
+ // Set a single argument in this namespace
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ $this->args->set(array($namespace, $key), $value);
+ if ($namespace !== Auth_OpenID_BARE_NS) {
+ $this->namespaces->add($namespace);
+ }
+ return true;
+ }
+ }
+
+ function delArg($namespace, $key)
+ {
+ $namespace = $this->_fixNS($namespace);
+
+ if (Auth_OpenID::isFailure($namespace)) {
+ return $namespace;
+ } else {
+ return $this->args->del(array($namespace, $key));
+ }
+ }
+
+ function getAliasedArg($aliased_key, $default = null)
+ {
+ $parts = explode('.', $aliased_key, 2);
+
+ if (count($parts) != 2) {
+ $ns = null;
+ } else {
+ list($alias, $key) = $parts;
+
+ if ($alias == 'ns') {
+ // Return the namespace URI for a namespace alias
+ // parameter.
+ return $this->namespaces->getNamespaceURI($key);
+ } else {
+ $ns = $this->namespaces->getNamespaceURI($alias);
+ }
+ }
+
+ if ($ns === null) {
+ $key = $aliased_key;
+ $ns = $this->getOpenIDNamespace();
+ }
+
+ return $this->getArg($ns, $key, $default);
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/MySQLStore.php b/extlib/Auth/OpenID/MySQLStore.php
new file mode 100644
index 000000000..eb08af016
--- /dev/null
+++ b/extlib/Auth/OpenID/MySQLStore.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * A MySQL store.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the base class file.
+ */
+require_once "Auth/OpenID/SQLStore.php";
+
+/**
+ * An SQL store that uses MySQL as its backend.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MySQLStore extends Auth_OpenID_SQLStore {
+ /**
+ * @access private
+ */
+ function setSQL()
+ {
+ $this->sql['nonce_table'] =
+ "CREATE TABLE %s (\n".
+ " server_url VARCHAR(2047) NOT NULL,\n".
+ " timestamp INTEGER NOT NULL,\n".
+ " salt CHAR(40) NOT NULL,\n".
+ " UNIQUE (server_url(255), timestamp, salt)\n".
+ ") ENGINE=InnoDB";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (\n".
+ " server_url BLOB NOT NULL,\n".
+ " handle VARCHAR(255) NOT NULL,\n".
+ " secret BLOB NOT NULL,\n".
+ " issued INTEGER NOT NULL,\n".
+ " lifetime INTEGER NOT NULL,\n".
+ " assoc_type VARCHAR(64) NOT NULL,\n".
+ " PRIMARY KEY (server_url(255), handle)\n".
+ ") ENGINE=InnoDB";
+
+ $this->sql['set_assoc'] =
+ "REPLACE INTO %s (server_url, handle, secret, issued,\n".
+ " lifetime, assoc_type) VALUES (?, ?, !, ?, ?, ?)";
+
+ $this->sql['get_assocs'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ?";
+
+ $this->sql['get_assoc'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ? AND handle = ?";
+
+ $this->sql['remove_assoc'] =
+ "DELETE FROM %s WHERE server_url = ? AND handle = ?";
+
+ $this->sql['add_nonce'] =
+ "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
+
+ $this->sql['clean_nonce'] =
+ "DELETE FROM %s WHERE timestamp < ?";
+
+ $this->sql['clean_assoc'] =
+ "DELETE FROM %s WHERE issued + lifetime < ?";
+ }
+
+ /**
+ * @access private
+ */
+ function blobEncode($blob)
+ {
+ return "0x" . bin2hex($blob);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/Nonce.php b/extlib/Auth/OpenID/Nonce.php
new file mode 100644
index 000000000..effecac38
--- /dev/null
+++ b/extlib/Auth/OpenID/Nonce.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * Nonce-related functionality.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Need CryptUtil to generate random strings.
+ */
+require_once 'Auth/OpenID/CryptUtil.php';
+
+/**
+ * This is the characters that the nonces are made from.
+ */
+define('Auth_OpenID_Nonce_CHRS',"abcdefghijklmnopqrstuvwxyz" .
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
+
+// Keep nonces for five hours (allow five hours for the combination of
+// request time and clock skew). This is probably way more than is
+// necessary, but there is not much overhead in storing nonces.
+global $Auth_OpenID_SKEW;
+$Auth_OpenID_SKEW = 60 * 60 * 5;
+
+define('Auth_OpenID_Nonce_REGEX',
+ '/(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z(.*)/');
+
+define('Auth_OpenID_Nonce_TIME_FMT',
+ '%Y-%m-%dT%H:%M:%SZ');
+
+function Auth_OpenID_splitNonce($nonce_string)
+{
+ // Extract a timestamp from the given nonce string
+ $result = preg_match(Auth_OpenID_Nonce_REGEX, $nonce_string, $matches);
+ if ($result != 1 || count($matches) != 8) {
+ return null;
+ }
+
+ list($unused,
+ $tm_year,
+ $tm_mon,
+ $tm_mday,
+ $tm_hour,
+ $tm_min,
+ $tm_sec,
+ $uniquifier) = $matches;
+
+ $timestamp =
+ @gmmktime($tm_hour, $tm_min, $tm_sec, $tm_mon, $tm_mday, $tm_year);
+
+ if ($timestamp === false || $timestamp < 0) {
+ return null;
+ }
+
+ return array($timestamp, $uniquifier);
+}
+
+function Auth_OpenID_checkTimestamp($nonce_string,
+ $allowed_skew = null,
+ $now = null)
+{
+ // Is the timestamp that is part of the specified nonce string
+ // within the allowed clock-skew of the current time?
+ global $Auth_OpenID_SKEW;
+
+ if ($allowed_skew === null) {
+ $allowed_skew = $Auth_OpenID_SKEW;
+ }
+
+ $parts = Auth_OpenID_splitNonce($nonce_string);
+ if ($parts == null) {
+ return false;
+ }
+
+ if ($now === null) {
+ $now = time();
+ }
+
+ $stamp = $parts[0];
+
+ // Time after which we should not use the nonce
+ $past = $now - $allowed_skew;
+
+ // Time that is too far in the future for us to allow
+ $future = $now + $allowed_skew;
+
+ // the stamp is not too far in the future and is not too far
+ // in the past
+ return (($past <= $stamp) && ($stamp <= $future));
+}
+
+function Auth_OpenID_mkNonce($when = null)
+{
+ // Generate a nonce with the current timestamp
+ $salt = Auth_OpenID_CryptUtil::randomString(
+ 6, Auth_OpenID_Nonce_CHRS);
+ if ($when === null) {
+ // It's safe to call time() with no arguments; it returns a
+ // GMT unix timestamp on PHP 4 and PHP 5. gmmktime() with no
+ // args returns a local unix timestamp on PHP 4, so don't use
+ // that.
+ $when = time();
+ }
+ $time_str = gmstrftime(Auth_OpenID_Nonce_TIME_FMT, $when);
+ return $time_str . $salt;
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/PAPE.php b/extlib/Auth/OpenID/PAPE.php
new file mode 100644
index 000000000..62cba8a91
--- /dev/null
+++ b/extlib/Auth/OpenID/PAPE.php
@@ -0,0 +1,301 @@
+<?php
+
+/**
+ * An implementation of the OpenID Provider Authentication Policy
+ * Extension 1.0
+ *
+ * See:
+ * http://openid.net/developers/specs/
+ */
+
+require_once "Auth/OpenID/Extension.php";
+
+define('Auth_OpenID_PAPE_NS_URI',
+ "http://specs.openid.net/extensions/pape/1.0");
+
+define('PAPE_AUTH_MULTI_FACTOR_PHYSICAL',
+ 'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical');
+define('PAPE_AUTH_MULTI_FACTOR',
+ 'http://schemas.openid.net/pape/policies/2007/06/multi-factor');
+define('PAPE_AUTH_PHISHING_RESISTANT',
+ 'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant');
+
+define('PAPE_TIME_VALIDATOR',
+ '^[0-9]{4,4}-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z$');
+/**
+ * A Provider Authentication Policy request, sent from a relying party
+ * to a provider
+ *
+ * preferred_auth_policies: The authentication policies that
+ * the relying party prefers
+ *
+ * max_auth_age: The maximum time, in seconds, that the relying party
+ * wants to allow to have elapsed before the user must re-authenticate
+ */
+class Auth_OpenID_PAPE_Request extends Auth_OpenID_Extension {
+
+ var $ns_alias = 'pape';
+ var $ns_uri = Auth_OpenID_PAPE_NS_URI;
+
+ function Auth_OpenID_PAPE_Request($preferred_auth_policies=null,
+ $max_auth_age=null)
+ {
+ if ($preferred_auth_policies === null) {
+ $preferred_auth_policies = array();
+ }
+
+ $this->preferred_auth_policies = $preferred_auth_policies;
+ $this->max_auth_age = $max_auth_age;
+ }
+
+ /**
+ * Add an acceptable authentication policy URI to this request
+ *
+ * This method is intended to be used by the relying party to add
+ * acceptable authentication types to the request.
+ *
+ * policy_uri: The identifier for the preferred type of
+ * authentication.
+ */
+ function addPolicyURI($policy_uri)
+ {
+ if (!in_array($policy_uri, $this->preferred_auth_policies)) {
+ $this->preferred_auth_policies[] = $policy_uri;
+ }
+ }
+
+ function getExtensionArgs()
+ {
+ $ns_args = array(
+ 'preferred_auth_policies' =>
+ implode(' ', $this->preferred_auth_policies)
+ );
+
+ if ($this->max_auth_age !== null) {
+ $ns_args['max_auth_age'] = strval($this->max_auth_age);
+ }
+
+ return $ns_args;
+ }
+
+ /**
+ * Instantiate a Request object from the arguments in a checkid_*
+ * OpenID message
+ */
+ function fromOpenIDRequest($request)
+ {
+ $obj = new Auth_OpenID_PAPE_Request();
+ $args = $request->message->getArgs(Auth_OpenID_PAPE_NS_URI);
+
+ if ($args === null || $args === array()) {
+ return null;
+ }
+
+ $obj->parseExtensionArgs($args);
+ return $obj;
+ }
+
+ /**
+ * Set the state of this request to be that expressed in these
+ * PAPE arguments
+ *
+ * @param args: The PAPE arguments without a namespace
+ */
+ function parseExtensionArgs($args)
+ {
+ // preferred_auth_policies is a space-separated list of policy
+ // URIs
+ $this->preferred_auth_policies = array();
+
+ $policies_str = Auth_OpenID::arrayGet($args, 'preferred_auth_policies');
+ if ($policies_str) {
+ foreach (explode(' ', $policies_str) as $uri) {
+ if (!in_array($uri, $this->preferred_auth_policies)) {
+ $this->preferred_auth_policies[] = $uri;
+ }
+ }
+ }
+
+ // max_auth_age is base-10 integer number of seconds
+ $max_auth_age_str = Auth_OpenID::arrayGet($args, 'max_auth_age');
+ if ($max_auth_age_str) {
+ $this->max_auth_age = Auth_OpenID::intval($max_auth_age_str);
+ } else {
+ $this->max_auth_age = null;
+ }
+ }
+
+ /**
+ * Given a list of authentication policy URIs that a provider
+ * supports, this method returns the subsequence of those types
+ * that are preferred by the relying party.
+ *
+ * @param supported_types: A sequence of authentication policy
+ * type URIs that are supported by a provider
+ *
+ * @return array The sub-sequence of the supported types that are
+ * preferred by the relying party. This list will be ordered in
+ * the order that the types appear in the supported_types
+ * sequence, and may be empty if the provider does not prefer any
+ * of the supported authentication types.
+ */
+ function preferredTypes($supported_types)
+ {
+ $result = array();
+
+ foreach ($supported_types as $st) {
+ if (in_array($st, $this->preferred_auth_policies)) {
+ $result[] = $st;
+ }
+ }
+ return $result;
+ }
+}
+
+/**
+ * A Provider Authentication Policy response, sent from a provider to
+ * a relying party
+ */
+class Auth_OpenID_PAPE_Response extends Auth_OpenID_Extension {
+
+ var $ns_alias = 'pape';
+ var $ns_uri = Auth_OpenID_PAPE_NS_URI;
+
+ function Auth_OpenID_PAPE_Response($auth_policies=null, $auth_time=null,
+ $nist_auth_level=null)
+ {
+ if ($auth_policies) {
+ $this->auth_policies = $auth_policies;
+ } else {
+ $this->auth_policies = array();
+ }
+
+ $this->auth_time = $auth_time;
+ $this->nist_auth_level = $nist_auth_level;
+ }
+
+ /**
+ * Add a authentication policy to this response
+ *
+ * This method is intended to be used by the provider to add a
+ * policy that the provider conformed to when authenticating the
+ * user.
+ *
+ * @param policy_uri: The identifier for the preferred type of
+ * authentication.
+ */
+ function addPolicyURI($policy_uri)
+ {
+ if (!in_array($policy_uri, $this->auth_policies)) {
+ $this->auth_policies[] = $policy_uri;
+ }
+ }
+
+ /**
+ * Create an Auth_OpenID_PAPE_Response object from a successful
+ * OpenID library response.
+ *
+ * @param success_response $success_response A SuccessResponse
+ * from Auth_OpenID_Consumer::complete()
+ *
+ * @returns: A provider authentication policy response from the
+ * data that was supplied with the id_res response.
+ */
+ function fromSuccessResponse($success_response)
+ {
+ $obj = new Auth_OpenID_PAPE_Response();
+
+ // PAPE requires that the args be signed.
+ $args = $success_response->getSignedNS(Auth_OpenID_PAPE_NS_URI);
+
+ if ($args === null || $args === array()) {
+ return null;
+ }
+
+ $result = $obj->parseExtensionArgs($args);
+
+ if ($result === false) {
+ return null;
+ } else {
+ return $obj;
+ }
+ }
+
+ /**
+ * Parse the provider authentication policy arguments into the
+ * internal state of this object
+ *
+ * @param args: unqualified provider authentication policy
+ * arguments
+ *
+ * @param strict: Whether to return false when bad data is
+ * encountered
+ *
+ * @return null The data is parsed into the internal fields of
+ * this object.
+ */
+ function parseExtensionArgs($args, $strict=false)
+ {
+ $policies_str = Auth_OpenID::arrayGet($args, 'auth_policies');
+ if ($policies_str && $policies_str != "none") {
+ $this->auth_policies = explode(" ", $policies_str);
+ }
+
+ $nist_level_str = Auth_OpenID::arrayGet($args, 'nist_auth_level');
+ if ($nist_level_str !== null) {
+ $nist_level = Auth_OpenID::intval($nist_level_str);
+
+ if ($nist_level === false) {
+ if ($strict) {
+ return false;
+ } else {
+ $nist_level = null;
+ }
+ }
+
+ if (0 <= $nist_level && $nist_level < 5) {
+ $this->nist_auth_level = $nist_level;
+ } else if ($strict) {
+ return false;
+ }
+ }
+
+ $auth_time = Auth_OpenID::arrayGet($args, 'auth_time');
+ if ($auth_time !== null) {
+ if (ereg(PAPE_TIME_VALIDATOR, $auth_time)) {
+ $this->auth_time = $auth_time;
+ } else if ($strict) {
+ return false;
+ }
+ }
+ }
+
+ function getExtensionArgs()
+ {
+ $ns_args = array();
+ if (count($this->auth_policies) > 0) {
+ $ns_args['auth_policies'] = implode(' ', $this->auth_policies);
+ } else {
+ $ns_args['auth_policies'] = 'none';
+ }
+
+ if ($this->nist_auth_level !== null) {
+ if (!in_array($this->nist_auth_level, range(0, 4), true)) {
+ return false;
+ }
+ $ns_args['nist_auth_level'] = strval($this->nist_auth_level);
+ }
+
+ if ($this->auth_time !== null) {
+ if (!ereg(PAPE_TIME_VALIDATOR, $this->auth_time)) {
+ return false;
+ }
+
+ $ns_args['auth_time'] = $this->auth_time;
+ }
+
+ return $ns_args;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/Parse.php b/extlib/Auth/OpenID/Parse.php
new file mode 100644
index 000000000..546f34f6b
--- /dev/null
+++ b/extlib/Auth/OpenID/Parse.php
@@ -0,0 +1,352 @@
+<?php
+
+/**
+ * This module implements a VERY limited parser that finds <link> tags
+ * in the head of HTML or XHTML documents and parses out their
+ * attributes according to the OpenID spec. It is a liberal parser,
+ * but it requires these things from the data in order to work:
+ *
+ * - There must be an open <html> tag
+ *
+ * - There must be an open <head> tag inside of the <html> tag
+ *
+ * - Only <link>s that are found inside of the <head> tag are parsed
+ * (this is by design)
+ *
+ * - The parser follows the OpenID specification in resolving the
+ * attributes of the link tags. This means that the attributes DO
+ * NOT get resolved as they would by an XML or HTML parser. In
+ * particular, only certain entities get replaced, and href
+ * attributes do not get resolved relative to a base URL.
+ *
+ * From http://openid.net/specs.bml:
+ *
+ * - The openid.server URL MUST be an absolute URL. OpenID consumers
+ * MUST NOT attempt to resolve relative URLs.
+ *
+ * - The openid.server URL MUST NOT include entities other than &amp;,
+ * &lt;, &gt;, and &quot;.
+ *
+ * The parser ignores SGML comments and <![CDATA[blocks]]>. Both kinds
+ * of quoting are allowed for attributes.
+ *
+ * The parser deals with invalid markup in these ways:
+ *
+ * - Tag names are not case-sensitive
+ *
+ * - The <html> tag is accepted even when it is not at the top level
+ *
+ * - The <head> tag is accepted even when it is not a direct child of
+ * the <html> tag, but a <html> tag must be an ancestor of the
+ * <head> tag
+ *
+ * - <link> tags are accepted even when they are not direct children
+ * of the <head> tag, but a <head> tag must be an ancestor of the
+ * <link> tag
+ *
+ * - If there is no closing tag for an open <html> or <head> tag, the
+ * remainder of the document is viewed as being inside of the
+ * tag. If there is no closing tag for a <link> tag, the link tag is
+ * treated as a short tag. Exceptions to this rule are that <html>
+ * closes <html> and <body> or <head> closes <head>
+ *
+ * - Attributes of the <link> tag are not required to be quoted.
+ *
+ * - In the case of duplicated attribute names, the attribute coming
+ * last in the tag will be the value returned.
+ *
+ * - Any text that does not parse as an attribute within a link tag
+ * will be ignored. (e.g. <link pumpkin rel='openid.server' /> will
+ * ignore pumpkin)
+ *
+ * - If there are more than one <html> or <head> tag, the parser only
+ * looks inside of the first one.
+ *
+ * - The contents of <script> tags are ignored entirely, except
+ * unclosed <script> tags. Unclosed <script> tags are ignored.
+ *
+ * - Any other invalid markup is ignored, including unclosed SGML
+ * comments and unclosed <![CDATA[blocks.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @access private
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require Auth_OpenID::arrayGet().
+ */
+require_once "Auth/OpenID.php";
+
+class Auth_OpenID_Parse {
+
+ /**
+ * Specify some flags for use with regex matching.
+ */
+ var $_re_flags = "si";
+
+ /**
+ * Stuff to remove before we start looking for tags
+ */
+ var $_removed_re =
+ "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
+
+ /**
+ * Starts with the tag name at a word boundary, where the tag name
+ * is not a namespace
+ */
+ var $_tag_expr = "<%s\b(?!:)([^>]*?)(?:\/>|>(.*?)(?:<\/?%s\s*>|\Z))";
+
+ var $_attr_find = '\b(\w+)=("[^"]*"|\'[^\']*\'|[^\'"\s\/<>]+)';
+
+ var $_open_tag_expr = "<%s\b";
+ var $_close_tag_expr = "<((\/%s\b)|(%s[^>\/]*\/))>";
+
+ function Auth_OpenID_Parse()
+ {
+ $this->_link_find = sprintf("/<link\b(?!:)([^>]*)(?!<)>/%s",
+ $this->_re_flags);
+
+ $this->_entity_replacements = array(
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'quot' => '"'
+ );
+
+ $this->_attr_find = sprintf("/%s/%s",
+ $this->_attr_find,
+ $this->_re_flags);
+
+ $this->_removed_re = sprintf("/%s/%s",
+ $this->_removed_re,
+ $this->_re_flags);
+
+ $this->_ent_replace =
+ sprintf("&(%s);", implode("|",
+ $this->_entity_replacements));
+ }
+
+ /**
+ * Returns a regular expression that will match a given tag in an
+ * SGML string.
+ */
+ function tagMatcher($tag_name, $close_tags = null)
+ {
+ $expr = $this->_tag_expr;
+
+ if ($close_tags) {
+ $options = implode("|", array_merge(array($tag_name), $close_tags));
+ $closer = sprintf("(?:%s)", $options);
+ } else {
+ $closer = $tag_name;
+ }
+
+ $expr = sprintf($expr, $tag_name, $closer);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function openTag($tag_name)
+ {
+ $expr = sprintf($this->_open_tag_expr, $tag_name);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function closeTag($tag_name)
+ {
+ $expr = sprintf($this->_close_tag_expr, $tag_name, $tag_name);
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ function htmlBegin($s)
+ {
+ $matches = array();
+ $result = preg_match($this->openTag('html'), $s,
+ $matches, PREG_OFFSET_CAPTURE);
+ if ($result === false || !$matches) {
+ return false;
+ }
+ // Return the offset of the first match.
+ return $matches[0][1];
+ }
+
+ function htmlEnd($s)
+ {
+ $matches = array();
+ $result = preg_match($this->closeTag('html'), $s,
+ $matches, PREG_OFFSET_CAPTURE);
+ if ($result === false || !$matches) {
+ return false;
+ }
+ // Return the offset of the first match.
+ return $matches[count($matches) - 1][1];
+ }
+
+ function headFind()
+ {
+ return $this->tagMatcher('head', array('body', 'html'));
+ }
+
+ function replaceEntities($str)
+ {
+ foreach ($this->_entity_replacements as $old => $new) {
+ $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
+ }
+ return $str;
+ }
+
+ function removeQuotes($str)
+ {
+ $matches = array();
+ $double = '/^"(.*)"$/';
+ $single = "/^\'(.*)\'$/";
+
+ if (preg_match($double, $str, $matches)) {
+ return $matches[1];
+ } else if (preg_match($single, $str, $matches)) {
+ return $matches[1];
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * Find all link tags in a string representing a HTML document and
+ * return a list of their attributes.
+ *
+ * @param string $html The text to parse
+ * @return array $list An array of arrays of attributes, one for each
+ * link tag
+ */
+ function parseLinkAttrs($html)
+ {
+ $stripped = preg_replace($this->_removed_re,
+ "",
+ $html);
+
+ $html_begin = $this->htmlBegin($stripped);
+ $html_end = $this->htmlEnd($stripped);
+
+ if ($html_begin === false) {
+ return array();
+ }
+
+ if ($html_end === false) {
+ $html_end = strlen($stripped);
+ }
+
+ $stripped = substr($stripped, $html_begin,
+ $html_end - $html_begin);
+
+ // Try to find the <HEAD> tag.
+ $head_re = $this->headFind();
+ $head_matches = array();
+ if (!preg_match($head_re, $stripped, $head_matches)) {
+ return array();
+ }
+
+ $link_data = array();
+ $link_matches = array();
+
+ if (!preg_match_all($this->_link_find, $head_matches[0],
+ $link_matches)) {
+ return array();
+ }
+
+ foreach ($link_matches[0] as $link) {
+ $attr_matches = array();
+ preg_match_all($this->_attr_find, $link, $attr_matches);
+ $link_attrs = array();
+ foreach ($attr_matches[0] as $index => $full_match) {
+ $name = $attr_matches[1][$index];
+ $value = $this->replaceEntities(
+ $this->removeQuotes($attr_matches[2][$index]));
+
+ $link_attrs[strtolower($name)] = $value;
+ }
+ $link_data[] = $link_attrs;
+ }
+
+ return $link_data;
+ }
+
+ function relMatches($rel_attr, $target_rel)
+ {
+ // Does this target_rel appear in the rel_str?
+ // XXX: TESTME
+ $rels = preg_split("/\s+/", trim($rel_attr));
+ foreach ($rels as $rel) {
+ $rel = strtolower($rel);
+ if ($rel == $target_rel) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ function linkHasRel($link_attrs, $target_rel)
+ {
+ // Does this link have target_rel as a relationship?
+ // XXX: TESTME
+ $rel_attr = Auth_OpeniD::arrayGet($link_attrs, 'rel', null);
+ return ($rel_attr && $this->relMatches($rel_attr,
+ $target_rel));
+ }
+
+ function findLinksRel($link_attrs_list, $target_rel)
+ {
+ // Filter the list of link attributes on whether it has
+ // target_rel as a relationship.
+ // XXX: TESTME
+ $result = array();
+ foreach ($link_attrs_list as $attr) {
+ if ($this->linkHasRel($attr, $target_rel)) {
+ $result[] = $attr;
+ }
+ }
+
+ return $result;
+ }
+
+ function findFirstHref($link_attrs_list, $target_rel)
+ {
+ // Return the value of the href attribute for the first link
+ // tag in the list that has target_rel as a relationship.
+ // XXX: TESTME
+ $matches = $this->findLinksRel($link_attrs_list,
+ $target_rel);
+ if (!$matches) {
+ return null;
+ }
+ $first = $matches[0];
+ return Auth_OpenID::arrayGet($first, 'href', null);
+ }
+}
+
+function Auth_OpenID_legacy_discover($html_text, $server_rel,
+ $delegate_rel)
+{
+ $p = new Auth_OpenID_Parse();
+
+ $link_attrs = $p->parseLinkAttrs($html_text);
+
+ $server_url = $p->findFirstHref($link_attrs,
+ $server_rel);
+
+ if ($server_url === null) {
+ return false;
+ } else {
+ $delegate_url = $p->findFirstHref($link_attrs,
+ $delegate_rel);
+ return array($delegate_url, $server_url);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/PostgreSQLStore.php b/extlib/Auth/OpenID/PostgreSQLStore.php
new file mode 100644
index 000000000..69d95e7b8
--- /dev/null
+++ b/extlib/Auth/OpenID/PostgreSQLStore.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * A PostgreSQL store.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the base class file.
+ */
+require_once "Auth/OpenID/SQLStore.php";
+
+/**
+ * An SQL store that uses PostgreSQL as its backend.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PostgreSQLStore extends Auth_OpenID_SQLStore {
+ /**
+ * @access private
+ */
+ function setSQL()
+ {
+ $this->sql['nonce_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
+ "timestamp INTEGER NOT NULL, ".
+ "salt CHAR(40) NOT NULL, ".
+ "UNIQUE (server_url, timestamp, salt))";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047) NOT NULL, ".
+ "handle VARCHAR(255) NOT NULL, ".
+ "secret BYTEA NOT NULL, ".
+ "issued INTEGER NOT NULL, ".
+ "lifetime INTEGER NOT NULL, ".
+ "assoc_type VARCHAR(64) NOT NULL, ".
+ "PRIMARY KEY (server_url, handle), ".
+ "CONSTRAINT secret_length_constraint CHECK ".
+ "(LENGTH(secret) <= 128))";
+
+ $this->sql['set_assoc'] =
+ array(
+ 'insert_assoc' => "INSERT INTO %s (server_url, handle, ".
+ "secret, issued, lifetime, assoc_type) VALUES ".
+ "(?, ?, '!', ?, ?, ?)",
+ 'update_assoc' => "UPDATE %s SET secret = '!', issued = ?, ".
+ "lifetime = ?, assoc_type = ? WHERE server_url = ? AND ".
+ "handle = ?"
+ );
+
+ $this->sql['get_assocs'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ?";
+
+ $this->sql['get_assoc'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ? AND handle = ?";
+
+ $this->sql['remove_assoc'] =
+ "DELETE FROM %s WHERE server_url = ? AND handle = ?";
+
+ $this->sql['add_nonce'] =
+ "INSERT INTO %s (server_url, timestamp, salt) VALUES ".
+ "(?, ?, ?)"
+ ;
+
+ $this->sql['clean_nonce'] =
+ "DELETE FROM %s WHERE timestamp < ?";
+
+ $this->sql['clean_assoc'] =
+ "DELETE FROM %s WHERE issued + lifetime < ?";
+ }
+
+ /**
+ * @access private
+ */
+ function _set_assoc($server_url, $handle, $secret, $issued, $lifetime,
+ $assoc_type)
+ {
+ $result = $this->_get_assoc($server_url, $handle);
+ if ($result) {
+ // Update the table since this associations already exists.
+ $this->connection->query($this->sql['set_assoc']['update_assoc'],
+ array($secret, $issued, $lifetime,
+ $assoc_type, $server_url, $handle));
+ } else {
+ // Insert a new record because this association wasn't
+ // found.
+ $this->connection->query($this->sql['set_assoc']['insert_assoc'],
+ array($server_url, $handle, $secret,
+ $issued, $lifetime, $assoc_type));
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function blobEncode($blob)
+ {
+ return $this->_octify($blob);
+ }
+
+ /**
+ * @access private
+ */
+ function blobDecode($blob)
+ {
+ return $this->_unoctify($blob);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/SQLStore.php b/extlib/Auth/OpenID/SQLStore.php
new file mode 100644
index 000000000..da93c6aa2
--- /dev/null
+++ b/extlib/Auth/OpenID/SQLStore.php
@@ -0,0 +1,569 @@
+<?php
+
+/**
+ * SQL-backed OpenID stores.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require the PEAR DB module because we'll need it for the SQL-based
+ * stores implemented here. We silence any errors from the inclusion
+ * because it might not be present, and a user of the SQL stores may
+ * supply an Auth_OpenID_DatabaseConnection instance that implements
+ * its own storage.
+ */
+global $__Auth_OpenID_PEAR_AVAILABLE;
+$__Auth_OpenID_PEAR_AVAILABLE = @include_once 'DB.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/Interface.php';
+require_once 'Auth/OpenID/Nonce.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID.php';
+
+/**
+ * @access private
+ */
+require_once 'Auth/OpenID/Nonce.php';
+
+/**
+ * This is the parent class for the SQL stores, which contains the
+ * logic common to all of the SQL stores.
+ *
+ * The table names used are determined by the class variables
+ * associations_table_name and nonces_table_name. To change the name
+ * of the tables used, pass new table names into the constructor.
+ *
+ * To create the tables with the proper schema, see the createTables
+ * method.
+ *
+ * This class shouldn't be used directly. Use one of its subclasses
+ * instead, as those contain the code necessary to use a specific
+ * database. If you're an OpenID integrator and you'd like to create
+ * an SQL-driven store that wraps an application's database
+ * abstraction, be sure to create a subclass of
+ * {@link Auth_OpenID_DatabaseConnection} that calls the application's
+ * database abstraction calls. Then, pass an instance of your new
+ * database connection class to your SQLStore subclass constructor.
+ *
+ * All methods other than the constructor and createTables should be
+ * considered implementation details.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore {
+
+ /**
+ * This creates a new SQLStore instance. It requires an
+ * established database connection be given to it, and it allows
+ * overriding the default table names.
+ *
+ * @param connection $connection This must be an established
+ * connection to a database of the correct type for the SQLStore
+ * subclass you're using. This must either be an PEAR DB
+ * connection handle or an instance of a subclass of
+ * Auth_OpenID_DatabaseConnection.
+ *
+ * @param associations_table: This is an optional parameter to
+ * specify the name of the table used for storing associations.
+ * The default value is 'oid_associations'.
+ *
+ * @param nonces_table: This is an optional parameter to specify
+ * the name of the table used for storing nonces. The default
+ * value is 'oid_nonces'.
+ */
+ function Auth_OpenID_SQLStore($connection,
+ $associations_table = null,
+ $nonces_table = null)
+ {
+ global $__Auth_OpenID_PEAR_AVAILABLE;
+
+ $this->associations_table_name = "oid_associations";
+ $this->nonces_table_name = "oid_nonces";
+
+ // Check the connection object type to be sure it's a PEAR
+ // database connection.
+ if (!(is_object($connection) &&
+ (is_subclass_of($connection, 'db_common') ||
+ is_subclass_of($connection,
+ 'auth_openid_databaseconnection')))) {
+ trigger_error("Auth_OpenID_SQLStore expected PEAR connection " .
+ "object (got ".get_class($connection).")",
+ E_USER_ERROR);
+ return;
+ }
+
+ $this->connection = $connection;
+
+ // Be sure to set the fetch mode so the results are keyed on
+ // column name instead of column index. This is a PEAR
+ // constant, so only try to use it if PEAR is present. Note
+ // that Auth_Openid_Databaseconnection instances need not
+ // implement ::setFetchMode for this reason.
+ if ($__Auth_OpenID_PEAR_AVAILABLE) {
+ $this->connection->setFetchMode(DB_FETCHMODE_ASSOC);
+ }
+
+ if ($associations_table) {
+ $this->associations_table_name = $associations_table;
+ }
+
+ if ($nonces_table) {
+ $this->nonces_table_name = $nonces_table;
+ }
+
+ $this->max_nonce_age = 6 * 60 * 60;
+
+ // Be sure to run the database queries with auto-commit mode
+ // turned OFF, because we want every function to run in a
+ // transaction, implicitly. As a rule, methods named with a
+ // leading underscore will NOT control transaction behavior.
+ // Callers of these methods will worry about transactions.
+ $this->connection->autoCommit(false);
+
+ // Create an empty SQL strings array.
+ $this->sql = array();
+
+ // Call this method (which should be overridden by subclasses)
+ // to populate the $this->sql array with SQL strings.
+ $this->setSQL();
+
+ // Verify that all required SQL statements have been set, and
+ // raise an error if any expected SQL strings were either
+ // absent or empty.
+ list($missing, $empty) = $this->_verifySQL();
+
+ if ($missing) {
+ trigger_error("Expected keys in SQL query list: " .
+ implode(", ", $missing),
+ E_USER_ERROR);
+ return;
+ }
+
+ if ($empty) {
+ trigger_error("SQL list keys have no SQL strings: " .
+ implode(", ", $empty),
+ E_USER_ERROR);
+ return;
+ }
+
+ // Add table names to queries.
+ $this->_fixSQL();
+ }
+
+ function tableExists($table_name)
+ {
+ return !$this->isError(
+ $this->connection->query(
+ sprintf("SELECT * FROM %s LIMIT 0",
+ $table_name)));
+ }
+
+ /**
+ * Returns true if $value constitutes a database error; returns
+ * false otherwise.
+ */
+ function isError($value)
+ {
+ return PEAR::isError($value);
+ }
+
+ /**
+ * Converts a query result to a boolean. If the result is a
+ * database error according to $this->isError(), this returns
+ * false; otherwise, this returns true.
+ */
+ function resultToBool($obj)
+ {
+ if ($this->isError($obj)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * This method should be overridden by subclasses. This method is
+ * called by the constructor to set values in $this->sql, which is
+ * an array keyed on sql name.
+ */
+ function setSQL()
+ {
+ }
+
+ /**
+ * Resets the store by removing all records from the store's
+ * tables.
+ */
+ function reset()
+ {
+ $this->connection->query(sprintf("DELETE FROM %s",
+ $this->associations_table_name));
+
+ $this->connection->query(sprintf("DELETE FROM %s",
+ $this->nonces_table_name));
+ }
+
+ /**
+ * @access private
+ */
+ function _verifySQL()
+ {
+ $missing = array();
+ $empty = array();
+
+ $required_sql_keys = array(
+ 'nonce_table',
+ 'assoc_table',
+ 'set_assoc',
+ 'get_assoc',
+ 'get_assocs',
+ 'remove_assoc'
+ );
+
+ foreach ($required_sql_keys as $key) {
+ if (!array_key_exists($key, $this->sql)) {
+ $missing[] = $key;
+ } else if (!$this->sql[$key]) {
+ $empty[] = $key;
+ }
+ }
+
+ return array($missing, $empty);
+ }
+
+ /**
+ * @access private
+ */
+ function _fixSQL()
+ {
+ $replacements = array(
+ array(
+ 'value' => $this->nonces_table_name,
+ 'keys' => array('nonce_table',
+ 'add_nonce',
+ 'clean_nonce')
+ ),
+ array(
+ 'value' => $this->associations_table_name,
+ 'keys' => array('assoc_table',
+ 'set_assoc',
+ 'get_assoc',
+ 'get_assocs',
+ 'remove_assoc',
+ 'clean_assoc')
+ )
+ );
+
+ foreach ($replacements as $item) {
+ $value = $item['value'];
+ $keys = $item['keys'];
+
+ foreach ($keys as $k) {
+ if (is_array($this->sql[$k])) {
+ foreach ($this->sql[$k] as $part_key => $part_value) {
+ $this->sql[$k][$part_key] = sprintf($part_value,
+ $value);
+ }
+ } else {
+ $this->sql[$k] = sprintf($this->sql[$k], $value);
+ }
+ }
+ }
+ }
+
+ function blobDecode($blob)
+ {
+ return $blob;
+ }
+
+ function blobEncode($str)
+ {
+ return $str;
+ }
+
+ function createTables()
+ {
+ $this->connection->autoCommit(true);
+ $n = $this->create_nonce_table();
+ $a = $this->create_assoc_table();
+ $this->connection->autoCommit(false);
+
+ if ($n && $a) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function create_nonce_table()
+ {
+ if (!$this->tableExists($this->nonces_table_name)) {
+ $r = $this->connection->query($this->sql['nonce_table']);
+ return $this->resultToBool($r);
+ }
+ return true;
+ }
+
+ function create_assoc_table()
+ {
+ if (!$this->tableExists($this->associations_table_name)) {
+ $r = $this->connection->query($this->sql['assoc_table']);
+ return $this->resultToBool($r);
+ }
+ return true;
+ }
+
+ /**
+ * @access private
+ */
+ function _set_assoc($server_url, $handle, $secret, $issued,
+ $lifetime, $assoc_type)
+ {
+ return $this->connection->query($this->sql['set_assoc'],
+ array(
+ $server_url,
+ $handle,
+ $secret,
+ $issued,
+ $lifetime,
+ $assoc_type));
+ }
+
+ function storeAssociation($server_url, $association)
+ {
+ if ($this->resultToBool($this->_set_assoc(
+ $server_url,
+ $association->handle,
+ $this->blobEncode(
+ $association->secret),
+ $association->issued,
+ $association->lifetime,
+ $association->assoc_type
+ ))) {
+ $this->connection->commit();
+ } else {
+ $this->connection->rollback();
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _get_assoc($server_url, $handle)
+ {
+ $result = $this->connection->getRow($this->sql['get_assoc'],
+ array($server_url, $handle));
+ if ($this->isError($result)) {
+ return null;
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _get_assocs($server_url)
+ {
+ $result = $this->connection->getAll($this->sql['get_assocs'],
+ array($server_url));
+
+ if ($this->isError($result)) {
+ return array();
+ } else {
+ return $result;
+ }
+ }
+
+ function removeAssociation($server_url, $handle)
+ {
+ if ($this->_get_assoc($server_url, $handle) == null) {
+ return false;
+ }
+
+ if ($this->resultToBool($this->connection->query(
+ $this->sql['remove_assoc'],
+ array($server_url, $handle)))) {
+ $this->connection->commit();
+ } else {
+ $this->connection->rollback();
+ }
+
+ return true;
+ }
+
+ function getAssociation($server_url, $handle = null)
+ {
+ if ($handle !== null) {
+ $assoc = $this->_get_assoc($server_url, $handle);
+
+ $assocs = array();
+ if ($assoc) {
+ $assocs[] = $assoc;
+ }
+ } else {
+ $assocs = $this->_get_assocs($server_url);
+ }
+
+ if (!$assocs || (count($assocs) == 0)) {
+ return null;
+ } else {
+ $associations = array();
+
+ foreach ($assocs as $assoc_row) {
+ $assoc = new Auth_OpenID_Association($assoc_row['handle'],
+ $assoc_row['secret'],
+ $assoc_row['issued'],
+ $assoc_row['lifetime'],
+ $assoc_row['assoc_type']);
+
+ $assoc->secret = $this->blobDecode($assoc->secret);
+
+ if ($assoc->getExpiresIn() == 0) {
+ $this->removeAssociation($server_url, $assoc->handle);
+ } else {
+ $associations[] = array($assoc->issued, $assoc);
+ }
+ }
+
+ if ($associations) {
+ $issued = array();
+ $assocs = array();
+ foreach ($associations as $key => $assoc) {
+ $issued[$key] = $assoc[0];
+ $assocs[$key] = $assoc[1];
+ }
+
+ array_multisort($issued, SORT_DESC, $assocs, SORT_DESC,
+ $associations);
+
+ // return the most recently issued one.
+ list($issued, $assoc) = $associations[0];
+ return $assoc;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function _add_nonce($server_url, $timestamp, $salt)
+ {
+ $sql = $this->sql['add_nonce'];
+ $result = $this->connection->query($sql, array($server_url,
+ $timestamp,
+ $salt));
+ if ($this->isError($result)) {
+ $this->connection->rollback();
+ } else {
+ $this->connection->commit();
+ }
+ return $this->resultToBool($result);
+ }
+
+ function useNonce($server_url, $timestamp, $salt)
+ {
+ global $Auth_OpenID_SKEW;
+
+ if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
+ return False;
+ }
+
+ return $this->_add_nonce($server_url, $timestamp, $salt);
+ }
+
+ /**
+ * "Octifies" a binary string by returning a string with escaped
+ * octal bytes. This is used for preparing binary data for
+ * PostgreSQL BYTEA fields.
+ *
+ * @access private
+ */
+ function _octify($str)
+ {
+ $result = "";
+ for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) {
+ $ch = substr($str, $i, 1);
+ if ($ch == "\\") {
+ $result .= "\\\\\\\\";
+ } else if (ord($ch) == 0) {
+ $result .= "\\\\000";
+ } else {
+ $result .= "\\" . strval(decoct(ord($ch)));
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * "Unoctifies" octal-escaped data from PostgreSQL and returns the
+ * resulting ASCII (possibly binary) string.
+ *
+ * @access private
+ */
+ function _unoctify($str)
+ {
+ $result = "";
+ $i = 0;
+ while ($i < strlen($str)) {
+ $char = $str[$i];
+ if ($char == "\\") {
+ // Look to see if the next char is a backslash and
+ // append it.
+ if ($str[$i + 1] != "\\") {
+ $octal_digits = substr($str, $i + 1, 3);
+ $dec = octdec($octal_digits);
+ $char = chr($dec);
+ $i += 4;
+ } else {
+ $char = "\\";
+ $i += 2;
+ }
+ } else {
+ $i += 1;
+ }
+
+ $result .= $char;
+ }
+
+ return $result;
+ }
+
+ function cleanupNonces()
+ {
+ global $Auth_OpenID_SKEW;
+ $v = time() - $Auth_OpenID_SKEW;
+
+ $this->connection->query($this->sql['clean_nonce'], array($v));
+ $num = $this->connection->affectedRows();
+ $this->connection->commit();
+ return $num;
+ }
+
+ function cleanupAssociations()
+ {
+ $this->connection->query($this->sql['clean_assoc'],
+ array(time()));
+ $num = $this->connection->affectedRows();
+ $this->connection->commit();
+ return $num;
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/SQLiteStore.php b/extlib/Auth/OpenID/SQLiteStore.php
new file mode 100644
index 000000000..ec2bf58e4
--- /dev/null
+++ b/extlib/Auth/OpenID/SQLiteStore.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * An SQLite store.
+ *
+ * @package OpenID
+ */
+
+/**
+ * Require the base class file.
+ */
+require_once "Auth/OpenID/SQLStore.php";
+
+/**
+ * An SQL store that uses SQLite as its backend.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SQLiteStore extends Auth_OpenID_SQLStore {
+ function setSQL()
+ {
+ $this->sql['nonce_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047), timestamp INTEGER, ".
+ "salt CHAR(40), UNIQUE (server_url, timestamp, salt))";
+
+ $this->sql['assoc_table'] =
+ "CREATE TABLE %s (server_url VARCHAR(2047), handle VARCHAR(255), ".
+ "secret BLOB(128), issued INTEGER, lifetime INTEGER, ".
+ "assoc_type VARCHAR(64), PRIMARY KEY (server_url, handle))";
+
+ $this->sql['set_assoc'] =
+ "INSERT OR REPLACE INTO %s VALUES (?, ?, ?, ?, ?, ?)";
+
+ $this->sql['get_assocs'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ?";
+
+ $this->sql['get_assoc'] =
+ "SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
+ "WHERE server_url = ? AND handle = ?";
+
+ $this->sql['remove_assoc'] =
+ "DELETE FROM %s WHERE server_url = ? AND handle = ?";
+
+ $this->sql['add_nonce'] =
+ "INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
+
+ $this->sql['clean_nonce'] =
+ "DELETE FROM %s WHERE timestamp < ?";
+
+ $this->sql['clean_assoc'] =
+ "DELETE FROM %s WHERE issued + lifetime < ?";
+ }
+
+ /**
+ * @access private
+ */
+ function _add_nonce($server_url, $timestamp, $salt)
+ {
+ // PECL SQLite extensions 1.0.3 and older (1.0.3 is the
+ // current release at the time of this writing) have a broken
+ // sqlite_escape_string function that breaks when passed the
+ // empty string. Prefixing all strings with one character
+ // keeps them unique and avoids this bug. The nonce table is
+ // write-only, so we don't have to worry about updating other
+ // functions with this same bad hack.
+ return parent::_add_nonce('x' . $server_url, $timestamp, $salt);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/SReg.php b/extlib/Auth/OpenID/SReg.php
new file mode 100644
index 000000000..63280769f
--- /dev/null
+++ b/extlib/Auth/OpenID/SReg.php
@@ -0,0 +1,521 @@
+<?php
+
+/**
+ * Simple registration request and response parsing and object
+ * representation.
+ *
+ * This module contains objects representing simple registration
+ * requests and responses that can be used with both OpenID relying
+ * parties and OpenID providers.
+ *
+ * 1. The relying party creates a request object and adds it to the
+ * {@link Auth_OpenID_AuthRequest} object before making the
+ * checkid request to the OpenID provider:
+ *
+ * $sreg_req = Auth_OpenID_SRegRequest::build(array('email'));
+ * $auth_request->addExtension($sreg_req);
+ *
+ * 2. The OpenID provider extracts the simple registration request
+ * from the OpenID request using {@link
+ * Auth_OpenID_SRegRequest::fromOpenIDRequest}, gets the user's
+ * approval and data, creates an {@link Auth_OpenID_SRegResponse}
+ * object and adds it to the id_res response:
+ *
+ * $sreg_req = Auth_OpenID_SRegRequest::fromOpenIDRequest(
+ * $checkid_request);
+ * // [ get the user's approval and data, informing the user that
+ * // the fields in sreg_response were requested ]
+ * $sreg_resp = Auth_OpenID_SRegResponse::extractResponse(
+ * $sreg_req, $user_data);
+ * $sreg_resp->toMessage($openid_response->fields);
+ *
+ * 3. The relying party uses {@link
+ * Auth_OpenID_SRegResponse::fromSuccessResponse} to extract the data
+ * from the OpenID response:
+ *
+ * $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse(
+ * $success_response);
+ *
+ * @package OpenID
+ */
+
+/**
+ * Import message and extension internals.
+ */
+require_once 'Auth/OpenID/Message.php';
+require_once 'Auth/OpenID/Extension.php';
+
+// The data fields that are listed in the sreg spec
+global $Auth_OpenID_sreg_data_fields;
+$Auth_OpenID_sreg_data_fields = array(
+ 'fullname' => 'Full Name',
+ 'nickname' => 'Nickname',
+ 'dob' => 'Date of Birth',
+ 'email' => 'E-mail Address',
+ 'gender' => 'Gender',
+ 'postcode' => 'Postal Code',
+ 'country' => 'Country',
+ 'language' => 'Language',
+ 'timezone' => 'Time Zone');
+
+/**
+ * Check to see that the given value is a valid simple registration
+ * data field name. Return true if so, false if not.
+ */
+function Auth_OpenID_checkFieldName($field_name)
+{
+ global $Auth_OpenID_sreg_data_fields;
+
+ if (!in_array($field_name, array_keys($Auth_OpenID_sreg_data_fields))) {
+ return false;
+ }
+ return true;
+}
+
+// URI used in the wild for Yadis documents advertising simple
+// registration support
+define('Auth_OpenID_SREG_NS_URI_1_0', 'http://openid.net/sreg/1.0');
+
+// URI in the draft specification for simple registration 1.1
+// <http://openid.net/specs/openid-simple-registration-extension-1_1-01.html>
+define('Auth_OpenID_SREG_NS_URI_1_1', 'http://openid.net/extensions/sreg/1.1');
+
+// This attribute will always hold the preferred URI to use when
+// adding sreg support to an XRDS file or in an OpenID namespace
+// declaration.
+define('Auth_OpenID_SREG_NS_URI', Auth_OpenID_SREG_NS_URI_1_1);
+
+Auth_OpenID_registerNamespaceAlias(Auth_OpenID_SREG_NS_URI_1_1, 'sreg');
+
+/**
+ * Does the given endpoint advertise support for simple
+ * registration?
+ *
+ * $endpoint: The endpoint object as returned by OpenID discovery.
+ * returns whether an sreg type was advertised by the endpoint
+ */
+function Auth_OpenID_supportsSReg(&$endpoint)
+{
+ return ($endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_1) ||
+ $endpoint->usesExtension(Auth_OpenID_SREG_NS_URI_1_0));
+}
+
+/**
+ * A base class for classes dealing with Simple Registration protocol
+ * messages.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SRegBase extends Auth_OpenID_Extension {
+ /**
+ * Extract the simple registration namespace URI from the given
+ * OpenID message. Handles OpenID 1 and 2, as well as both sreg
+ * namespace URIs found in the wild, as well as missing namespace
+ * definitions (for OpenID 1)
+ *
+ * $message: The OpenID message from which to parse simple
+ * registration fields. This may be a request or response message.
+ *
+ * Returns the sreg namespace URI for the supplied message. The
+ * message may be modified to define a simple registration
+ * namespace.
+ *
+ * @access private
+ */
+ function _getSRegNS(&$message)
+ {
+ $alias = null;
+ $found_ns_uri = null;
+
+ // See if there exists an alias for one of the two defined
+ // simple registration types.
+ foreach (array(Auth_OpenID_SREG_NS_URI_1_1,
+ Auth_OpenID_SREG_NS_URI_1_0) as $sreg_ns_uri) {
+ $alias = $message->namespaces->getAlias($sreg_ns_uri);
+ if ($alias !== null) {
+ $found_ns_uri = $sreg_ns_uri;
+ break;
+ }
+ }
+
+ if ($alias === null) {
+ // There is no alias for either of the types, so try to
+ // add one. We default to using the modern value (1.1)
+ $found_ns_uri = Auth_OpenID_SREG_NS_URI_1_1;
+ if ($message->namespaces->addAlias(Auth_OpenID_SREG_NS_URI_1_1,
+ 'sreg') === null) {
+ // An alias for the string 'sreg' already exists, but
+ // it's defined for something other than simple
+ // registration
+ return null;
+ }
+ }
+
+ return $found_ns_uri;
+ }
+}
+
+/**
+ * An object to hold the state of a simple registration request.
+ *
+ * required: A list of the required fields in this simple registration
+ * request
+ *
+ * optional: A list of the optional fields in this simple registration
+ * request
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SRegRequest extends Auth_OpenID_SRegBase {
+
+ var $ns_alias = 'sreg';
+
+ /**
+ * Initialize an empty simple registration request.
+ */
+ function build($required=null, $optional=null,
+ $policy_url=null,
+ $sreg_ns_uri=Auth_OpenID_SREG_NS_URI,
+ $cls='Auth_OpenID_SRegRequest')
+ {
+ $obj = new $cls();
+
+ $obj->required = array();
+ $obj->optional = array();
+ $obj->policy_url = $policy_url;
+ $obj->ns_uri = $sreg_ns_uri;
+
+ if ($required) {
+ if (!$obj->requestFields($required, true, true)) {
+ return null;
+ }
+ }
+
+ if ($optional) {
+ if (!$obj->requestFields($optional, false, true)) {
+ return null;
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Create a simple registration request that contains the fields
+ * that were requested in the OpenID request with the given
+ * arguments
+ *
+ * $request: The OpenID authentication request from which to
+ * extract an sreg request.
+ *
+ * $cls: name of class to use when creating sreg request object.
+ * Used for testing.
+ *
+ * Returns the newly created simple registration request
+ */
+ function fromOpenIDRequest($request, $cls='Auth_OpenID_SRegRequest')
+ {
+
+ $obj = call_user_func_array(array($cls, 'build'),
+ array(null, null, null, Auth_OpenID_SREG_NS_URI, $cls));
+
+ // Since we're going to mess with namespace URI mapping, don't
+ // mutate the object that was passed in.
+ $m = $request->message;
+
+ $obj->ns_uri = $obj->_getSRegNS($m);
+ $args = $m->getArgs($obj->ns_uri);
+
+ if ($args === null || Auth_OpenID::isFailure($args)) {
+ return null;
+ }
+
+ $obj->parseExtensionArgs($args);
+
+ return $obj;
+ }
+
+ /**
+ * Parse the unqualified simple registration request parameters
+ * and add them to this object.
+ *
+ * This method is essentially the inverse of
+ * getExtensionArgs. This method restores the serialized simple
+ * registration request fields.
+ *
+ * If you are extracting arguments from a standard OpenID
+ * checkid_* request, you probably want to use fromOpenIDRequest,
+ * which will extract the sreg namespace and arguments from the
+ * OpenID request. This method is intended for cases where the
+ * OpenID server needs more control over how the arguments are
+ * parsed than that method provides.
+ *
+ * $args == $message->getArgs($ns_uri);
+ * $request->parseExtensionArgs($args);
+ *
+ * $args: The unqualified simple registration arguments
+ *
+ * strict: Whether requests with fields that are not defined in
+ * the simple registration specification should be tolerated (and
+ * ignored)
+ */
+ function parseExtensionArgs($args, $strict=false)
+ {
+ foreach (array('required', 'optional') as $list_name) {
+ $required = ($list_name == 'required');
+ $items = Auth_OpenID::arrayGet($args, $list_name);
+ if ($items) {
+ foreach (explode(',', $items) as $field_name) {
+ if (!$this->requestField($field_name, $required, $strict)) {
+ if ($strict) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
+ $this->policy_url = Auth_OpenID::arrayGet($args, 'policy_url');
+
+ return true;
+ }
+
+ /**
+ * A list of all of the simple registration fields that were
+ * requested, whether they were required or optional.
+ */
+ function allRequestedFields()
+ {
+ return array_merge($this->required, $this->optional);
+ }
+
+ /**
+ * Have any simple registration fields been requested?
+ */
+ function wereFieldsRequested()
+ {
+ return count($this->allRequestedFields());
+ }
+
+ /**
+ * Was this field in the request?
+ */
+ function contains($field_name)
+ {
+ return (in_array($field_name, $this->required) ||
+ in_array($field_name, $this->optional));
+ }
+
+ /**
+ * Request the specified field from the OpenID user
+ *
+ * $field_name: the unqualified simple registration field name
+ *
+ * required: whether the given field should be presented to the
+ * user as being a required to successfully complete the request
+ *
+ * strict: whether to raise an exception when a field is added to
+ * a request more than once
+ */
+ function requestField($field_name,
+ $required=false, $strict=false)
+ {
+ if (!Auth_OpenID_checkFieldName($field_name)) {
+ return false;
+ }
+
+ if ($strict) {
+ if ($this->contains($field_name)) {
+ return false;
+ }
+ } else {
+ if (in_array($field_name, $this->required)) {
+ return true;
+ }
+
+ if (in_array($field_name, $this->optional)) {
+ if ($required) {
+ unset($this->optional[array_search($field_name,
+ $this->optional)]);
+ } else {
+ return true;
+ }
+ }
+ }
+
+ if ($required) {
+ $this->required[] = $field_name;
+ } else {
+ $this->optional[] = $field_name;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add the given list of fields to the request
+ *
+ * field_names: The simple registration data fields to request
+ *
+ * required: Whether these values should be presented to the user
+ * as required
+ *
+ * strict: whether to raise an exception when a field is added to
+ * a request more than once
+ */
+ function requestFields($field_names, $required=false, $strict=false)
+ {
+ if (!is_array($field_names)) {
+ return false;
+ }
+
+ foreach ($field_names as $field_name) {
+ if (!$this->requestField($field_name, $required, $strict=$strict)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Get a dictionary of unqualified simple registration arguments
+ * representing this request.
+ *
+ * This method is essentially the inverse of
+ * C{L{parseExtensionArgs}}. This method serializes the simple
+ * registration request fields.
+ */
+ function getExtensionArgs()
+ {
+ $args = array();
+
+ if ($this->required) {
+ $args['required'] = implode(',', $this->required);
+ }
+
+ if ($this->optional) {
+ $args['optional'] = implode(',', $this->optional);
+ }
+
+ if ($this->policy_url) {
+ $args['policy_url'] = $this->policy_url;
+ }
+
+ return $args;
+ }
+}
+
+/**
+ * Represents the data returned in a simple registration response
+ * inside of an OpenID C{id_res} response. This object will be created
+ * by the OpenID server, added to the C{id_res} response object, and
+ * then extracted from the C{id_res} message by the Consumer.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SRegResponse extends Auth_OpenID_SRegBase {
+
+ var $ns_alias = 'sreg';
+
+ function Auth_OpenID_SRegResponse($data=null,
+ $sreg_ns_uri=Auth_OpenID_SREG_NS_URI)
+ {
+ if ($data === null) {
+ $this->data = array();
+ } else {
+ $this->data = $data;
+ }
+
+ $this->ns_uri = $sreg_ns_uri;
+ }
+
+ /**
+ * Take a C{L{SRegRequest}} and a dictionary of simple
+ * registration values and create a C{L{SRegResponse}} object
+ * containing that data.
+ *
+ * request: The simple registration request object
+ *
+ * data: The simple registration data for this response, as a
+ * dictionary from unqualified simple registration field name to
+ * string (unicode) value. For instance, the nickname should be
+ * stored under the key 'nickname'.
+ */
+ function extractResponse($request, $data)
+ {
+ $obj = new Auth_OpenID_SRegResponse();
+ $obj->ns_uri = $request->ns_uri;
+
+ foreach ($request->allRequestedFields() as $field) {
+ $value = Auth_OpenID::arrayGet($data, $field);
+ if ($value !== null) {
+ $obj->data[$field] = $value;
+ }
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Create a C{L{SRegResponse}} object from a successful OpenID
+ * library response
+ * (C{L{openid.consumer.consumer.SuccessResponse}}) response
+ * message
+ *
+ * success_response: A SuccessResponse from consumer.complete()
+ *
+ * signed_only: Whether to process only data that was
+ * signed in the id_res message from the server.
+ *
+ * Returns a simple registration response containing the data that
+ * was supplied with the C{id_res} response.
+ */
+ function fromSuccessResponse(&$success_response, $signed_only=true)
+ {
+ global $Auth_OpenID_sreg_data_fields;
+
+ $obj = new Auth_OpenID_SRegResponse();
+ $obj->ns_uri = $obj->_getSRegNS($success_response->message);
+
+ if ($signed_only) {
+ $args = $success_response->getSignedNS($obj->ns_uri);
+ } else {
+ $args = $success_response->message->getArgs($obj->ns_uri);
+ }
+
+ if ($args === null || Auth_OpenID::isFailure($args)) {
+ return null;
+ }
+
+ foreach ($Auth_OpenID_sreg_data_fields as $field_name => $desc) {
+ if (in_array($field_name, array_keys($args))) {
+ $obj->data[$field_name] = $args[$field_name];
+ }
+ }
+
+ return $obj;
+ }
+
+ function getExtensionArgs()
+ {
+ return $this->data;
+ }
+
+ // Read-only dictionary interface
+ function get($field_name, $default=null)
+ {
+ if (!Auth_OpenID_checkFieldName($field_name)) {
+ return null;
+ }
+
+ return Auth_OpenID::arrayGet($this->data, $field_name, $default);
+ }
+
+ function contents()
+ {
+ return $this->data;
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/Server.php b/extlib/Auth/OpenID/Server.php
new file mode 100644
index 000000000..f1db4d872
--- /dev/null
+++ b/extlib/Auth/OpenID/Server.php
@@ -0,0 +1,1760 @@
+<?php
+
+/**
+ * OpenID server protocol and logic.
+ *
+ * Overview
+ *
+ * An OpenID server must perform three tasks:
+ *
+ * 1. Examine the incoming request to determine its nature and validity.
+ * 2. Make a decision about how to respond to this request.
+ * 3. Format the response according to the protocol.
+ *
+ * The first and last of these tasks may performed by the {@link
+ * Auth_OpenID_Server::decodeRequest()} and {@link
+ * Auth_OpenID_Server::encodeResponse} methods. Who gets to do the
+ * intermediate task -- deciding how to respond to the request -- will
+ * depend on what type of request it is.
+ *
+ * If it's a request to authenticate a user (a 'checkid_setup' or
+ * 'checkid_immediate' request), you need to decide if you will assert
+ * that this user may claim the identity in question. Exactly how you
+ * do that is a matter of application policy, but it generally
+ * involves making sure the user has an account with your system and
+ * is logged in, checking to see if that identity is hers to claim,
+ * and verifying with the user that she does consent to releasing that
+ * information to the party making the request.
+ *
+ * Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
+ * object, and if and when you've come to a decision, form a response
+ * by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
+ *
+ * Other types of requests relate to establishing associations between
+ * client and server and verifing the authenticity of previous
+ * communications. {@link Auth_OpenID_Server} contains all the logic
+ * and data necessary to respond to such requests; just pass it to
+ * {@link Auth_OpenID_Server::handleRequest()}.
+ *
+ * OpenID Extensions
+ *
+ * Do you want to provide other information for your users in addition
+ * to authentication? Version 1.2 of the OpenID protocol allows
+ * consumers to add extensions to their requests. For example, with
+ * sites using the Simple Registration
+ * Extension
+ * (http://www.openidenabled.com/openid/simple-registration-extension/),
+ * a user can agree to have their nickname and e-mail address sent to
+ * a site when they sign up.
+ *
+ * Since extensions do not change the way OpenID authentication works,
+ * code to handle extension requests may be completely separate from
+ * the {@link Auth_OpenID_Request} class here. But you'll likely want
+ * data sent back by your extension to be signed. {@link
+ * Auth_OpenID_ServerResponse} provides methods with which you can add
+ * data to it which can be signed with the other data in the OpenID
+ * signature.
+ *
+ * For example:
+ *
+ * <pre> // when request is a checkid_* request
+ * $response = $request->answer(true);
+ * // this will a signed 'openid.sreg.timezone' parameter to the response
+ * response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
+ *
+ * Stores
+ *
+ * The OpenID server needs to maintain state between requests in order
+ * to function. Its mechanism for doing this is called a store. The
+ * store interface is defined in Interface.php. Additionally, several
+ * concrete store implementations are provided, so that most sites
+ * won't need to implement a custom store. For a store backed by flat
+ * files on disk, see {@link Auth_OpenID_FileStore}. For stores based
+ * on MySQL, SQLite, or PostgreSQL, see the {@link
+ * Auth_OpenID_SQLStore} subclasses.
+ *
+ * Upgrading
+ *
+ * The keys by which a server looks up associations in its store have
+ * changed in version 1.2 of this library. If your store has entries
+ * created from version 1.0 code, you should empty it.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Required imports
+ */
+require_once "Auth/OpenID.php";
+require_once "Auth/OpenID/Association.php";
+require_once "Auth/OpenID/CryptUtil.php";
+require_once "Auth/OpenID/BigMath.php";
+require_once "Auth/OpenID/DiffieHellman.php";
+require_once "Auth/OpenID/KVForm.php";
+require_once "Auth/OpenID/TrustRoot.php";
+require_once "Auth/OpenID/ServerRequest.php";
+require_once "Auth/OpenID/Message.php";
+require_once "Auth/OpenID/Nonce.php";
+
+define('AUTH_OPENID_HTTP_OK', 200);
+define('AUTH_OPENID_HTTP_REDIRECT', 302);
+define('AUTH_OPENID_HTTP_ERROR', 400);
+
+/**
+ * @access private
+ */
+global $_Auth_OpenID_Request_Modes;
+$_Auth_OpenID_Request_Modes = array('checkid_setup',
+ 'checkid_immediate');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_URL', 'URL/redirect');
+
+/**
+ * @access private
+ */
+define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');
+
+/**
+ * @access private
+ */
+function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError')
+{
+ return is_a($obj, $cls);
+}
+
+/**
+ * An error class which gets instantiated and returned whenever an
+ * OpenID protocol error occurs. Be prepared to use this in place of
+ * an ordinary server response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerError {
+ /**
+ * @access private
+ */
+ function Auth_OpenID_ServerError($message = null, $text = null,
+ $reference = null, $contact = null)
+ {
+ $this->message = $message;
+ $this->text = $text;
+ $this->contact = $contact;
+ $this->reference = $reference;
+ }
+
+ function getReturnTo()
+ {
+ if ($this->message &&
+ $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
+ return $this->message->getArg(Auth_OpenID_OPENID_NS,
+ 'return_to');
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the return_to URL for the request which caused this
+ * error.
+ */
+ function hasReturnTo()
+ {
+ return $this->getReturnTo() !== null;
+ }
+
+ /**
+ * Encodes this error's response as a URL suitable for
+ * redirection. If the response has no return_to, another
+ * Auth_OpenID_ServerError is returned.
+ */
+ function encodeToURL()
+ {
+ if (!$this->message) {
+ return null;
+ }
+
+ $msg = $this->toMessage();
+ return $msg->toURL($this->getReturnTo());
+ }
+
+ /**
+ * Encodes the response to key-value form. This is a
+ * machine-readable format used to respond to messages which came
+ * directly from the consumer and not through the user-agent. See
+ * the OpenID specification.
+ */
+ function encodeToKVForm()
+ {
+ return Auth_OpenID_KVForm::fromArray(
+ array('mode' => 'error',
+ 'error' => $this->toString()));
+ }
+
+ function toFormMarkup($form_tag_attrs=null)
+ {
+ $msg = $this->toMessage();
+ return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
+ }
+
+ function toHTML($form_tag_attrs=null)
+ {
+ return Auth_OpenID::autoSubmitHTML(
+ $this->toFormMarkup($form_tag_attrs));
+ }
+
+ function toMessage()
+ {
+ // Generate a Message object for sending to the relying party,
+ // after encoding.
+ $namespace = $this->message->getOpenIDNamespace();
+ $reply = new Auth_OpenID_Message($namespace);
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());
+
+ if ($this->contact !== null) {
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
+ }
+
+ if ($this->reference !== null) {
+ $reply->setArg(Auth_OpenID_OPENID_NS, 'reference',
+ $this->reference);
+ }
+
+ return $reply;
+ }
+
+ /**
+ * Returns one of Auth_OpenID_ENCODE_URL,
+ * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
+ * encoding expected for this error's payload.
+ */
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Request_Modes;
+
+ if ($this->hasReturnTo()) {
+ if ($this->message->isOpenID2() &&
+ (strlen($this->encodeToURL()) >
+ Auth_OpenID_OPENID1_URL_LIMIT)) {
+ return Auth_OpenID_ENCODE_HTML_FORM;
+ } else {
+ return Auth_OpenID_ENCODE_URL;
+ }
+ }
+
+ if (!$this->message) {
+ return null;
+ }
+
+ $mode = $this->message->getArg(Auth_OpenID_OPENID_NS,
+ 'mode');
+
+ if ($mode) {
+ if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
+ return Auth_OpenID_ENCODE_KVFORM;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns this error message.
+ */
+ function toString()
+ {
+ if ($this->text) {
+ return $this->text;
+ } else {
+ return get_class($this) . " error";
+ }
+ }
+}
+
+/**
+ * Error returned by the server code when a return_to is absent from a
+ * request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {
+ function Auth_OpenID_NoReturnToError($message = null,
+ $text = "No return_to URL available")
+ {
+ parent::Auth_OpenID_ServerError($message, $text);
+ }
+
+ function toString()
+ {
+ return "No return_to available";
+ }
+}
+
+/**
+ * An error indicating that the return_to URL is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_MalformedReturnURL($message, $return_to)
+ {
+ $this->return_to = $return_to;
+ parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
+ }
+}
+
+/**
+ * This error is returned when the trust_root value is malformed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
+ function Auth_OpenID_MalformedTrustRoot($message = null,
+ $text = "Malformed trust root")
+ {
+ parent::Auth_OpenID_ServerError($message, $text);
+ }
+
+ function toString()
+ {
+ return "Malformed trust root";
+ }
+}
+
+/**
+ * The base class for all server request classes.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Request {
+ var $mode = null;
+}
+
+/**
+ * A request to verify the validity of a previous response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
+ var $mode = "check_authentication";
+ var $invalidate_handle = null;
+
+ function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed,
+ $invalidate_handle = null)
+ {
+ $this->assoc_handle = $assoc_handle;
+ $this->signed = $signed;
+ if ($invalidate_handle !== null) {
+ $this->invalidate_handle = $invalidate_handle;
+ }
+ $this->namespace = Auth_OpenID_OPENID2_NS;
+ $this->message = null;
+ }
+
+ function fromMessage($message, $server=null)
+ {
+ $required_keys = array('assoc_handle', 'sig', 'signed');
+
+ foreach ($required_keys as $k) {
+ if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) {
+ return new Auth_OpenID_ServerError($message,
+ sprintf("%s request missing required parameter %s from \
+ query", "check_authentication", $k));
+ }
+ }
+
+ $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
+ $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig');
+
+ $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
+ $signed_list = explode(",", $signed_list);
+
+ $signed = $message;
+ if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
+ $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
+ }
+
+ $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
+ $result->message = $message;
+ $result->sig = $sig;
+ $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle');
+ return $result;
+ }
+
+ function answer(&$signatory)
+ {
+ $is_valid = $signatory->verify($this->assoc_handle, $this->signed);
+
+ // Now invalidate that assoc_handle so it this checkAuth
+ // message cannot be replayed.
+ $signatory->invalidate($this->assoc_handle, true);
+ $response = new Auth_OpenID_ServerResponse($this);
+
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'is_valid',
+ ($is_valid ? "true" : "false"));
+
+ if ($this->invalidate_handle) {
+ $assoc = $signatory->getAssociation($this->invalidate_handle,
+ false);
+ if (!$assoc) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle',
+ $this->invalidate_handle);
+ }
+ }
+ return $response;
+ }
+}
+
+/**
+ * A class implementing plaintext server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_PlainTextServerSession {
+ /**
+ * An object that knows how to handle association requests with no
+ * session type.
+ */
+ var $session_type = 'no-encryption';
+ var $needs_math = false;
+ var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');
+
+ function fromMessage($unused_request)
+ {
+ return new Auth_OpenID_PlainTextServerSession();
+ }
+
+ function answer($secret)
+ {
+ return array('mac_key' => base64_encode($secret));
+ }
+}
+
+/**
+ * A class implementing DH-SHA1 server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA1ServerSession {
+ /**
+ * An object that knows how to handle association requests with
+ * the Diffie-Hellman session type.
+ */
+
+ var $session_type = 'DH-SHA1';
+ var $needs_math = true;
+ var $allowed_assoc_types = array('HMAC-SHA1');
+ var $hash_func = 'Auth_OpenID_SHA1';
+
+ function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
+ {
+ $this->dh = $dh;
+ $this->consumer_pubkey = $consumer_pubkey;
+ }
+
+ function getDH($message)
+ {
+ $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
+ $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');
+
+ if ((($dh_modulus === null) && ($dh_gen !== null)) ||
+ (($dh_gen === null) && ($dh_modulus !== null))) {
+
+ if ($dh_modulus === null) {
+ $missing = 'modulus';
+ } else {
+ $missing = 'generator';
+ }
+
+ return new Auth_OpenID_ServerError($message,
+ 'If non-default modulus or generator is '.
+ 'supplied, both must be supplied. Missing '.
+ $missing);
+ }
+
+ $lib =& Auth_OpenID_getMathLib();
+
+ if ($dh_modulus || $dh_gen) {
+ $dh_modulus = $lib->base64ToLong($dh_modulus);
+ $dh_gen = $lib->base64ToLong($dh_gen);
+ if ($lib->cmp($dh_modulus, 0) == 0 ||
+ $lib->cmp($dh_gen, 0) == 0) {
+ return new Auth_OpenID_ServerError(
+ $message, "Failed to parse dh_mod or dh_gen");
+ }
+ $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
+ } else {
+ $dh = new Auth_OpenID_DiffieHellman();
+ }
+
+ $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'dh_consumer_public');
+ if ($consumer_pubkey === null) {
+ return new Auth_OpenID_ServerError($message,
+ 'Public key for DH-SHA1 session '.
+ 'not found in query');
+ }
+
+ $consumer_pubkey =
+ $lib->base64ToLong($consumer_pubkey);
+
+ if ($consumer_pubkey === false) {
+ return new Auth_OpenID_ServerError($message,
+ "dh_consumer_public is not base64");
+ }
+
+ return array($dh, $consumer_pubkey);
+ }
+
+ function fromMessage($message)
+ {
+ $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
+
+ if (is_a($result, 'Auth_OpenID_ServerError')) {
+ return $result;
+ } else {
+ list($dh, $consumer_pubkey) = $result;
+ return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
+ $consumer_pubkey);
+ }
+ }
+
+ function answer($secret)
+ {
+ $lib =& Auth_OpenID_getMathLib();
+ $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
+ $this->hash_func);
+ return array(
+ 'dh_server_public' =>
+ $lib->longToBase64($this->dh->public),
+ 'enc_mac_key' => base64_encode($mac_key));
+ }
+}
+
+/**
+ * A class implementing DH-SHA256 server sessions.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_DiffieHellmanSHA256ServerSession
+ extends Auth_OpenID_DiffieHellmanSHA1ServerSession {
+
+ var $session_type = 'DH-SHA256';
+ var $hash_func = 'Auth_OpenID_SHA256';
+ var $allowed_assoc_types = array('HMAC-SHA256');
+
+ function fromMessage($message)
+ {
+ $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);
+
+ if (is_a($result, 'Auth_OpenID_ServerError')) {
+ return $result;
+ } else {
+ list($dh, $consumer_pubkey) = $result;
+ return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh,
+ $consumer_pubkey);
+ }
+ }
+}
+
+/**
+ * A request to associate with the server.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
+ var $mode = "associate";
+
+ function getSessionClasses()
+ {
+ return array(
+ 'no-encryption' => 'Auth_OpenID_PlainTextServerSession',
+ 'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
+ 'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
+ }
+
+ function Auth_OpenID_AssociateRequest(&$session, $assoc_type)
+ {
+ $this->session =& $session;
+ $this->namespace = Auth_OpenID_OPENID2_NS;
+ $this->assoc_type = $assoc_type;
+ }
+
+ function fromMessage($message, $server=null)
+ {
+ if ($message->isOpenID1()) {
+ $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+
+ if ($session_type == 'no-encryption') {
+ // oidutil.log('Received OpenID 1 request with a no-encryption '
+ // 'assocaition session type. Continuing anyway.')
+ } else if (!$session_type) {
+ $session_type = 'no-encryption';
+ }
+ } else {
+ $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'session_type');
+ if ($session_type === null) {
+ return new Auth_OpenID_ServerError($message,
+ "session_type missing from request");
+ }
+ }
+
+ $session_class = Auth_OpenID::arrayGet(
+ Auth_OpenID_AssociateRequest::getSessionClasses(),
+ $session_type);
+
+ if ($session_class === null) {
+ return new Auth_OpenID_ServerError($message,
+ "Unknown session type " .
+ $session_type);
+ }
+
+ $session = call_user_func(array($session_class, 'fromMessage'),
+ $message);
+ if (is_a($session, 'Auth_OpenID_ServerError')) {
+ return $session;
+ }
+
+ $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type', 'HMAC-SHA1');
+
+ if (!in_array($assoc_type, $session->allowed_assoc_types)) {
+ $fmt = "Session type %s does not support association type %s";
+ return new Auth_OpenID_ServerError($message,
+ sprintf($fmt, $session_type, $assoc_type));
+ }
+
+ $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
+ $obj->message = $message;
+ $obj->namespace = $message->getOpenIDNamespace();
+ return $obj;
+ }
+
+ function answer($assoc)
+ {
+ $response = new Auth_OpenID_ServerResponse($this);
+ $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+ array(
+ 'expires_in' => sprintf('%d', $assoc->getExpiresIn()),
+ 'assoc_type' => $this->assoc_type,
+ 'assoc_handle' => $assoc->handle));
+
+ $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+ $this->session->answer($assoc->secret));
+
+ if (! ($this->session->session_type == 'no-encryption'
+ && $this->message->isOpenID1())) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'session_type',
+ $this->session->session_type);
+ }
+
+ return $response;
+ }
+
+ function answerUnsupported($text_message,
+ $preferred_association_type=null,
+ $preferred_session_type=null)
+ {
+ if ($this->message->isOpenID1()) {
+ return new Auth_OpenID_ServerError($this->message);
+ }
+
+ $response = new Auth_OpenID_ServerResponse($this);
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'error_code', 'unsupported-type');
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'error', $text_message);
+
+ if ($preferred_association_type) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'assoc_type',
+ $preferred_association_type);
+ }
+
+ if ($preferred_session_type) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'session_type',
+ $preferred_session_type);
+ }
+
+ return $response;
+ }
+}
+
+/**
+ * A request to confirm the identity of a user.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
+ /**
+ * Return-to verification callback. Default is
+ * Auth_OpenID_verifyReturnTo from TrustRoot.php.
+ */
+ var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';
+
+ /**
+ * The mode of this request.
+ */
+ var $mode = "checkid_setup"; // or "checkid_immediate"
+
+ /**
+ * Whether this request is for immediate mode.
+ */
+ var $immediate = false;
+
+ /**
+ * The trust_root value for this request.
+ */
+ var $trust_root = null;
+
+ /**
+ * The OpenID namespace for this request.
+ * deprecated since version 2.0.2
+ */
+ var $namespace;
+
+ function make(&$message, $identity, $return_to, $trust_root = null,
+ $immediate = false, $assoc_handle = null, $server = null)
+ {
+ if ($server === null) {
+ return new Auth_OpenID_ServerError($message,
+ "server must not be null");
+ }
+
+ if ($return_to &&
+ !Auth_OpenID_TrustRoot::_parse($return_to)) {
+ return new Auth_OpenID_MalformedReturnURL($message, $return_to);
+ }
+
+ $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root, $immediate,
+ $assoc_handle, $server);
+
+ $r->namespace = $message->getOpenIDNamespace();
+ $r->message =& $message;
+
+ if (!$r->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL($message,
+ $return_to,
+ $trust_root);
+ } else {
+ return $r;
+ }
+ }
+
+ function Auth_OpenID_CheckIDRequest($identity, $return_to,
+ $trust_root = null, $immediate = false,
+ $assoc_handle = null, $server = null,
+ $claimed_id = null)
+ {
+ $this->namespace = Auth_OpenID_OPENID2_NS;
+ $this->assoc_handle = $assoc_handle;
+ $this->identity = $identity;
+ if ($claimed_id === null) {
+ $this->claimed_id = $identity;
+ } else {
+ $this->claimed_id = $claimed_id;
+ }
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ $this->server =& $server;
+
+ if ($immediate) {
+ $this->immediate = true;
+ $this->mode = "checkid_immediate";
+ } else {
+ $this->immediate = false;
+ $this->mode = "checkid_setup";
+ }
+ }
+
+ function equals($other)
+ {
+ return (
+ (is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
+ ($this->namespace == $other->namespace) &&
+ ($this->assoc_handle == $other->assoc_handle) &&
+ ($this->identity == $other->identity) &&
+ ($this->claimed_id == $other->claimed_id) &&
+ ($this->return_to == $other->return_to) &&
+ ($this->trust_root == $other->trust_root));
+ }
+
+ /*
+ * Does the relying party publish the return_to URL for this
+ * response under the realm? It is up to the provider to set a
+ * policy for what kinds of realms should be allowed. This
+ * return_to URL verification reduces vulnerability to data-theft
+ * attacks based on open proxies, corss-site-scripting, or open
+ * redirectors.
+ *
+ * This check should only be performed after making sure that the
+ * return_to URL matches the realm.
+ *
+ * @return true if the realm publishes a document with the
+ * return_to URL listed, false if not or if discovery fails
+ */
+ function returnToVerified()
+ {
+ return call_user_func_array($this->verifyReturnTo,
+ array($this->trust_root, $this->return_to));
+ }
+
+ function fromMessage(&$message, $server)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+ $immediate = null;
+
+ if ($mode == "checkid_immediate") {
+ $immediate = true;
+ $mode = "checkid_immediate";
+ } else {
+ $immediate = false;
+ $mode = "checkid_setup";
+ }
+
+ $return_to = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'return_to');
+
+ if (($message->isOpenID1()) &&
+ (!$return_to)) {
+ $fmt = "Missing required field 'return_to' from checkid request";
+ return new Auth_OpenID_ServerError($message, $fmt);
+ }
+
+ $identity = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'identity');
+ $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
+ if ($message->isOpenID1()) {
+ if ($identity === null) {
+ $s = "OpenID 1 message did not contain openid.identity";
+ return new Auth_OpenID_ServerError($message, $s);
+ }
+ } else {
+ if ($identity && !$claimed_id) {
+ $s = "OpenID 2.0 message contained openid.identity but not " .
+ "claimed_id";
+ return new Auth_OpenID_ServerError($message, $s);
+ } else if ($claimed_id && !$identity) {
+ $s = "OpenID 2.0 message contained openid.claimed_id " .
+ "but not identity";
+ return new Auth_OpenID_ServerError($message, $s);
+ }
+ }
+
+ // There's a case for making self.trust_root be a TrustRoot
+ // here. But if TrustRoot isn't currently part of the
+ // "public" API, I'm not sure it's worth doing.
+ if ($message->isOpenID1()) {
+ $trust_root_param = 'trust_root';
+ } else {
+ $trust_root_param = 'realm';
+ }
+ $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
+ $trust_root_param);
+ if (! $trust_root) {
+ $trust_root = $return_to;
+ }
+
+ if (! $message->isOpenID1() &&
+ ($return_to === null) &&
+ ($trust_root === null)) {
+ return new Auth_OpenID_ServerError($message,
+ "openid.realm required when openid.return_to absent");
+ }
+
+ $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
+ 'assoc_handle');
+
+ $obj = Auth_OpenID_CheckIDRequest::make($message,
+ $identity,
+ $return_to,
+ $trust_root,
+ $immediate,
+ $assoc_handle,
+ $server);
+
+ if (is_a($obj, 'Auth_OpenID_ServerError')) {
+ return $obj;
+ }
+
+ $obj->claimed_id = $claimed_id;
+
+ return $obj;
+ }
+
+ function idSelect()
+ {
+ // Is the identifier to be selected by the IDP?
+ // So IDPs don't have to import the constant
+ return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
+ }
+
+ function trustRootValid()
+ {
+ if (!$this->trust_root) {
+ return true;
+ }
+
+ $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
+ if ($tr === false) {
+ return new Auth_OpenID_MalformedTrustRoot($this->message,
+ $this->trust_root);
+ }
+
+ if ($this->return_to !== null) {
+ return Auth_OpenID_TrustRoot::match($this->trust_root,
+ $this->return_to);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Respond to this request. Return either an
+ * {@link Auth_OpenID_ServerResponse} or
+ * {@link Auth_OpenID_ServerError}.
+ *
+ * @param bool $allow Allow this user to claim this identity, and
+ * allow the consumer to have this information?
+ *
+ * @param string $server_url DEPRECATED. Passing $op_endpoint to
+ * the {@link Auth_OpenID_Server} constructor makes this optional.
+ *
+ * When an OpenID 1.x immediate mode request does not succeed, it
+ * gets back a URL where the request may be carried out in a
+ * not-so-immediate fashion. Pass my URL in here (the fully
+ * qualified address of this server's endpoint, i.e.
+ * http://example.com/server), and I will use it as a base for the
+ * URL for a new request.
+ *
+ * Optional for requests where {@link $immediate} is false or
+ * $allow is true.
+ *
+ * @param string $identity The OP-local identifier to answer with.
+ * Only for use when the relying party requested identifier
+ * selection.
+ *
+ * @param string $claimed_id The claimed identifier to answer
+ * with, for use with identifier selection in the case where the
+ * claimed identifier and the OP-local identifier differ,
+ * i.e. when the claimed_id uses delegation.
+ *
+ * If $identity is provided but this is not, $claimed_id will
+ * default to the value of $identity. When answering requests
+ * that did not ask for identifier selection, the response
+ * $claimed_id will default to that of the request.
+ *
+ * This parameter is new in OpenID 2.0.
+ *
+ * @return mixed
+ */
+ function answer($allow, $server_url = null, $identity = null,
+ $claimed_id = null)
+ {
+ if (!$this->return_to) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ if (!$server_url) {
+ if ((!$this->message->isOpenID1()) &&
+ (!$this->server->op_endpoint)) {
+ return new Auth_OpenID_ServerError(null,
+ "server should be constructed with op_endpoint to " .
+ "respond to OpenID 2.0 messages.");
+ }
+
+ $server_url = $this->server->op_endpoint;
+ }
+
+ if ($allow) {
+ $mode = 'id_res';
+ } else if ($this->message->isOpenID1()) {
+ if ($this->immediate) {
+ $mode = 'id_res';
+ } else {
+ $mode = 'cancel';
+ }
+ } else {
+ if ($this->immediate) {
+ $mode = 'setup_needed';
+ } else {
+ $mode = 'cancel';
+ }
+ }
+
+ if (!$this->trustRootValid()) {
+ return new Auth_OpenID_UntrustedReturnURL(null,
+ $this->return_to,
+ $this->trust_root);
+ }
+
+ $response = new Auth_OpenID_ServerResponse($this);
+
+ if ($claimed_id &&
+ ($this->message->isOpenID1())) {
+ return new Auth_OpenID_ServerError(null,
+ "claimed_id is new in OpenID 2.0 and not " .
+ "available for ".$this->namespace);
+ }
+
+ if ($identity && !$claimed_id) {
+ $claimed_id = $identity;
+ }
+
+ if ($allow) {
+
+ if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
+ if (!$identity) {
+ return new Auth_OpenID_ServerError(null,
+ "This request uses IdP-driven identifier selection. " .
+ "You must supply an identifier in the response.");
+ }
+
+ $response_identity = $identity;
+ $response_claimed_id = $claimed_id;
+
+ } else if ($this->identity) {
+ if ($identity &&
+ ($this->identity != $identity)) {
+ $fmt = "Request was for %s, cannot reply with identity %s";
+ return new Auth_OpenID_ServerError(null,
+ sprintf($fmt, $this->identity, $identity));
+ }
+
+ $response_identity = $this->identity;
+ $response_claimed_id = $this->claimed_id;
+ } else {
+ if ($identity) {
+ return new Auth_OpenID_ServerError(null,
+ "This request specified no identity and " .
+ "you supplied ".$identity);
+ }
+
+ $response_identity = null;
+ }
+
+ if (($this->message->isOpenID1()) &&
+ ($response_identity === null)) {
+ return new Auth_OpenID_ServerError(null,
+ "Request was an OpenID 1 request, so response must " .
+ "include an identifier.");
+ }
+
+ $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
+ array('mode' => $mode,
+ 'return_to' => $this->return_to,
+ 'response_nonce' => Auth_OpenID_mkNonce()));
+
+ if (!$this->message->isOpenID1()) {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'op_endpoint', $server_url);
+ }
+
+ if ($response_identity !== null) {
+ $response->fields->setArg(
+ Auth_OpenID_OPENID_NS,
+ 'identity',
+ $response_identity);
+ if ($this->message->isOpenID2()) {
+ $response->fields->setArg(
+ Auth_OpenID_OPENID_NS,
+ 'claimed_id',
+ $response_claimed_id);
+ }
+ }
+
+ } else {
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'mode', $mode);
+
+ if ($this->immediate) {
+ if (($this->message->isOpenID1()) &&
+ (!$server_url)) {
+ return new Auth_OpenID_ServerError(null,
+ 'setup_url is required for $allow=false \
+ in OpenID 1.x immediate mode.');
+ }
+
+ $setup_request =& new Auth_OpenID_CheckIDRequest(
+ $this->identity,
+ $this->return_to,
+ $this->trust_root,
+ false,
+ $this->assoc_handle,
+ $this->server,
+ $this->claimed_id);
+ $setup_request->message = $this->message;
+
+ $setup_url = $setup_request->encodeToURL($server_url);
+
+ if ($setup_url === null) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ $response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'user_setup_url',
+ $setup_url);
+ }
+ }
+
+ return $response;
+ }
+
+ function encodeToURL($server_url)
+ {
+ if (!$this->return_to) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ // Imported from the alternate reality where these classes are
+ // used in both the client and server code, so Requests are
+ // Encodable too. That's right, code imported from alternate
+ // realities all for the love of you, id_res/user_setup_url.
+
+ $q = array('mode' => $this->mode,
+ 'identity' => $this->identity,
+ 'claimed_id' => $this->claimed_id,
+ 'return_to' => $this->return_to);
+
+ if ($this->trust_root) {
+ if ($this->message->isOpenID1()) {
+ $q['trust_root'] = $this->trust_root;
+ } else {
+ $q['realm'] = $this->trust_root;
+ }
+ }
+
+ if ($this->assoc_handle) {
+ $q['assoc_handle'] = $this->assoc_handle;
+ }
+
+ $response = new Auth_OpenID_Message(
+ $this->message->getOpenIDNamespace());
+ $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
+ return $response->toURL($server_url);
+ }
+
+ function getCancelURL()
+ {
+ if (!$this->return_to) {
+ return new Auth_OpenID_NoReturnToError();
+ }
+
+ if ($this->immediate) {
+ return new Auth_OpenID_ServerError(null,
+ "Cancel is not an appropriate \
+ response to immediate mode \
+ requests.");
+ }
+
+ $response = new Auth_OpenID_Message(
+ $this->message->getOpenIDNamespace());
+ $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
+ return $response->toURL($this->return_to);
+ }
+}
+
+/**
+ * This class encapsulates the response to an OpenID server request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServerResponse {
+
+ function Auth_OpenID_ServerResponse(&$request)
+ {
+ $this->request =& $request;
+ $this->fields = new Auth_OpenID_Message($this->request->namespace);
+ }
+
+ function whichEncoding()
+ {
+ global $_Auth_OpenID_Request_Modes;
+
+ if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
+ if ($this->fields->isOpenID2() &&
+ (strlen($this->encodeToURL()) >
+ Auth_OpenID_OPENID1_URL_LIMIT)) {
+ return Auth_OpenID_ENCODE_HTML_FORM;
+ } else {
+ return Auth_OpenID_ENCODE_URL;
+ }
+ } else {
+ return Auth_OpenID_ENCODE_KVFORM;
+ }
+ }
+
+ /*
+ * Returns the form markup for this response.
+ *
+ * @return str
+ */
+ function toFormMarkup($form_tag_attrs=null)
+ {
+ return $this->fields->toFormMarkup($this->request->return_to,
+ $form_tag_attrs);
+ }
+
+ /*
+ * Returns an HTML document containing the form markup for this
+ * response that autosubmits with javascript.
+ */
+ function toHTML()
+ {
+ return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
+ }
+
+ /*
+ * Returns True if this response's encoding is ENCODE_HTML_FORM.
+ * Convenience method for server authors.
+ *
+ * @return bool
+ */
+ function renderAsForm()
+ {
+ return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
+ }
+
+
+ function encodeToURL()
+ {
+ return $this->fields->toURL($this->request->return_to);
+ }
+
+ function addExtension($extension_response)
+ {
+ $extension_response->toMessage($this->fields);
+ }
+
+ function needsSigning()
+ {
+ return $this->fields->getArg(Auth_OpenID_OPENID_NS,
+ 'mode') == 'id_res';
+ }
+
+ function encodeToKVForm()
+ {
+ return $this->fields->toKVForm();
+ }
+}
+
+/**
+ * A web-capable response object which you can use to generate a
+ * user-agent response.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_WebResponse {
+ var $code = AUTH_OPENID_HTTP_OK;
+ var $body = "";
+
+ function Auth_OpenID_WebResponse($code = null, $headers = null,
+ $body = null)
+ {
+ if ($code) {
+ $this->code = $code;
+ }
+
+ if ($headers !== null) {
+ $this->headers = $headers;
+ } else {
+ $this->headers = array();
+ }
+
+ if ($body !== null) {
+ $this->body = $body;
+ }
+ }
+}
+
+/**
+ * Responsible for the signature of query data and the verification of
+ * OpenID signature values.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Signatory {
+
+ // = 14 * 24 * 60 * 60; # 14 days, in seconds
+ var $SECRET_LIFETIME = 1209600;
+
+ // keys have a bogus server URL in them because the filestore
+ // really does expect that key to be a URL. This seems a little
+ // silly for the server store, since I expect there to be only one
+ // server URL.
+ var $normal_key = 'http://localhost/|normal';
+ var $dumb_key = 'http://localhost/|dumb';
+
+ /**
+ * Create a new signatory using a given store.
+ */
+ function Auth_OpenID_Signatory(&$store)
+ {
+ // assert store is not None
+ $this->store =& $store;
+ }
+
+ /**
+ * Verify, using a given association handle, a signature with
+ * signed key-value pairs from an HTTP request.
+ */
+ function verify($assoc_handle, $message)
+ {
+ $assoc = $this->getAssociation($assoc_handle, true);
+ if (!$assoc) {
+ // oidutil.log("failed to get assoc with handle %r to verify sig %r"
+ // % (assoc_handle, sig))
+ return false;
+ }
+
+ return $assoc->checkMessageSignature($message);
+ }
+
+ /**
+ * Given a response, sign the fields in the response's 'signed'
+ * list, and insert the signature into the response.
+ */
+ function sign($response)
+ {
+ $signed_response = $response;
+ $assoc_handle = $response->request->assoc_handle;
+
+ if ($assoc_handle) {
+ // normal mode
+ $assoc = $this->getAssociation($assoc_handle, false, false);
+ if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
+ // fall back to dumb mode
+ $signed_response->fields->setArg(Auth_OpenID_OPENID_NS,
+ 'invalidate_handle', $assoc_handle);
+ $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');
+
+ if ($assoc && ($assoc->getExpiresIn() <= 0)) {
+ $this->invalidate($assoc_handle, false);
+ }
+
+ $assoc = $this->createAssociation(true, $assoc_type);
+ }
+ } else {
+ // dumb mode.
+ $assoc = $this->createAssociation(true);
+ }
+
+ $signed_response->fields = $assoc->signMessage(
+ $signed_response->fields);
+ return $signed_response;
+ }
+
+ /**
+ * Make a new association.
+ */
+ function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
+ {
+ $secret = Auth_OpenID_CryptUtil::getBytes(
+ Auth_OpenID_getSecretSize($assoc_type));
+
+ $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
+ $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);
+
+ $assoc = Auth_OpenID_Association::fromExpiresIn(
+ $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);
+
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+
+ $this->store->storeAssociation($key, $assoc);
+ return $assoc;
+ }
+
+ /**
+ * Given an association handle, get the association from the
+ * store, or return a ServerError or null if something goes wrong.
+ */
+ function getAssociation($assoc_handle, $dumb, $check_expiration=true)
+ {
+ if ($assoc_handle === null) {
+ return new Auth_OpenID_ServerError(null,
+ "assoc_handle must not be null");
+ }
+
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+
+ $assoc = $this->store->getAssociation($key, $assoc_handle);
+
+ if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
+ if ($check_expiration) {
+ $this->store->removeAssociation($key, $assoc_handle);
+ $assoc = null;
+ }
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * Invalidate a given association handle.
+ */
+ function invalidate($assoc_handle, $dumb)
+ {
+ if ($dumb) {
+ $key = $this->dumb_key;
+ } else {
+ $key = $this->normal_key;
+ }
+ $this->store->removeAssociation($key, $assoc_handle);
+ }
+}
+
+/**
+ * Encode an {@link Auth_OpenID_ServerResponse} to an
+ * {@link Auth_OpenID_WebResponse}.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Encoder {
+
+ var $responseFactory = 'Auth_OpenID_WebResponse';
+
+ /**
+ * Encode an {@link Auth_OpenID_ServerResponse} and return an
+ * {@link Auth_OpenID_WebResponse}.
+ */
+ function encode(&$response)
+ {
+ $cls = $this->responseFactory;
+
+ $encode_as = $response->whichEncoding();
+ if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
+ $wr = new $cls(null, null, $response->encodeToKVForm());
+ if (is_a($response, 'Auth_OpenID_ServerError')) {
+ $wr->code = AUTH_OPENID_HTTP_ERROR;
+ }
+ } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
+ $location = $response->encodeToURL();
+ $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
+ array('location' => $location));
+ } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
+ $wr = new $cls(AUTH_OPENID_HTTP_OK, array(),
+ $response->toFormMarkup());
+ } else {
+ return new Auth_OpenID_EncodingError($response);
+ }
+ return $wr;
+ }
+}
+
+/**
+ * An encoder which also takes care of signing fields when required.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {
+
+ function Auth_OpenID_SigningEncoder(&$signatory)
+ {
+ $this->signatory =& $signatory;
+ }
+
+ /**
+ * Sign an {@link Auth_OpenID_ServerResponse} and return an
+ * {@link Auth_OpenID_WebResponse}.
+ */
+ function encode(&$response)
+ {
+ // the isinstance is a bit of a kludge... it means there isn't
+ // really an adapter to make the interfaces quite match.
+ if (!is_a($response, 'Auth_OpenID_ServerError') &&
+ $response->needsSigning()) {
+
+ if (!$this->signatory) {
+ return new Auth_OpenID_ServerError(null,
+ "Must have a store to sign request");
+ }
+
+ if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
+ return new Auth_OpenID_AlreadySigned($response);
+ }
+ $response = $this->signatory->sign($response);
+ }
+
+ return parent::encode($response);
+ }
+}
+
+/**
+ * Decode an incoming query into an Auth_OpenID_Request.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Decoder {
+
+ function Auth_OpenID_Decoder(&$server)
+ {
+ $this->server =& $server;
+
+ $this->handlers = array(
+ 'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
+ 'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
+ 'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
+ 'associate' => 'Auth_OpenID_AssociateRequest'
+ );
+ }
+
+ /**
+ * Given an HTTP query in an array (key-value pairs), decode it
+ * into an Auth_OpenID_Request object.
+ */
+ function decode($query)
+ {
+ if (!$query) {
+ return null;
+ }
+
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+
+ if ($message === null) {
+ /*
+ * It's useful to have a Message attached to a
+ * ProtocolError, so we override the bad ns value to build
+ * a Message out of it. Kinda kludgy, since it's made of
+ * lies, but the parts that aren't lies are more useful
+ * than a 'None'.
+ */
+ $old_ns = $query['openid.ns'];
+
+ $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
+ $message = Auth_OpenID_Message::fromPostArgs($query);
+ return new Auth_OpenID_ServerError(
+ $message,
+ sprintf("Invalid OpenID namespace URI: %s", $old_ns));
+ }
+
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+ if (!$mode) {
+ return new Auth_OpenID_ServerError($message,
+ "No mode value in message");
+ }
+
+ if (Auth_OpenID::isFailure($mode)) {
+ return new Auth_OpenID_ServerError($message,
+ $mode->message);
+ }
+
+ $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
+ $this->defaultDecoder($message));
+
+ if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
+ return call_user_func_array(array($handlerCls, 'fromMessage'),
+ array($message, $this->server));
+ } else {
+ return $handlerCls;
+ }
+ }
+
+ function defaultDecoder($message)
+ {
+ $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
+
+ if (Auth_OpenID::isFailure($mode)) {
+ return new Auth_OpenID_ServerError($message,
+ $mode->message);
+ }
+
+ return new Auth_OpenID_ServerError($message,
+ sprintf("Unrecognized OpenID mode %s", $mode));
+ }
+}
+
+/**
+ * An error that indicates an encoding problem occurred.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_EncodingError {
+ function Auth_OpenID_EncodingError(&$response)
+ {
+ $this->response =& $response;
+ }
+}
+
+/**
+ * An error that indicates that a response was already signed.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
+ // This response is already signed.
+}
+
+/**
+ * An error that indicates that the given return_to is not under the
+ * given trust_root.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
+ function Auth_OpenID_UntrustedReturnURL($message, $return_to,
+ $trust_root)
+ {
+ parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
+ $this->return_to = $return_to;
+ $this->trust_root = $trust_root;
+ }
+
+ function toString()
+ {
+ return sprintf("return_to %s not under trust_root %s",
+ $this->return_to, $this->trust_root);
+ }
+}
+
+/**
+ * I handle requests for an OpenID server.
+ *
+ * Some types of requests (those which are not checkid requests) may
+ * be handed to my {@link handleRequest} method, and I will take care
+ * of it and return a response.
+ *
+ * For your convenience, I also provide an interface to {@link
+ * Auth_OpenID_Decoder::decode()} and {@link
+ * Auth_OpenID_SigningEncoder::encode()} through my methods {@link
+ * decodeRequest} and {@link encodeResponse}.
+ *
+ * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
+ *
+ * Example:
+ *
+ * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
+ * "http://example.com/op");
+ * $request = $oserver->decodeRequest();
+ * if (in_array($request->mode, array('checkid_immediate',
+ * 'checkid_setup'))) {
+ * if ($app->isAuthorized($request->identity, $request->trust_root)) {
+ * $response = $request->answer(true);
+ * } else if ($request->immediate) {
+ * $response = $request->answer(false);
+ * } else {
+ * $app->showDecidePage($request);
+ * return;
+ * }
+ * } else {
+ * $response = $oserver->handleRequest($request);
+ * }
+ *
+ * $webresponse = $oserver->encode($response);</pre>
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_Server {
+ function Auth_OpenID_Server(&$store, $op_endpoint=null)
+ {
+ $this->store =& $store;
+ $this->signatory =& new Auth_OpenID_Signatory($this->store);
+ $this->encoder =& new Auth_OpenID_SigningEncoder($this->signatory);
+ $this->decoder =& new Auth_OpenID_Decoder($this);
+ $this->op_endpoint = $op_endpoint;
+ $this->negotiator =& Auth_OpenID_getDefaultNegotiator();
+ }
+
+ /**
+ * Handle a request. Given an {@link Auth_OpenID_Request} object,
+ * call the appropriate {@link Auth_OpenID_Server} method to
+ * process the request and generate a response.
+ *
+ * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
+ * returned by {@link Auth_OpenID_Server::decodeRequest()}.
+ *
+ * @return Auth_OpenID_ServerResponse $response A response object
+ * capable of generating a user-agent reply.
+ */
+ function handleRequest($request)
+ {
+ if (method_exists($this, "openid_" . $request->mode)) {
+ $handler = array($this, "openid_" . $request->mode);
+ return call_user_func($handler, $request);
+ }
+ return null;
+ }
+
+ /**
+ * The callback for 'check_authentication' messages.
+ */
+ function openid_check_authentication(&$request)
+ {
+ return $request->answer($this->signatory);
+ }
+
+ /**
+ * The callback for 'associate' messages.
+ */
+ function openid_associate(&$request)
+ {
+ $assoc_type = $request->assoc_type;
+ $session_type = $request->session->session_type;
+ if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
+ $assoc = $this->signatory->createAssociation(false,
+ $assoc_type);
+ return $request->answer($assoc);
+ } else {
+ $message = sprintf('Association type %s is not supported with '.
+ 'session type %s', $assoc_type, $session_type);
+ list($preferred_assoc_type, $preferred_session_type) =
+ $this->negotiator->getAllowedType();
+ return $request->answerUnsupported($message,
+ $preferred_assoc_type,
+ $preferred_session_type);
+ }
+ }
+
+ /**
+ * Encodes as response in the appropriate format suitable for
+ * sending to the user agent.
+ */
+ function encodeResponse(&$response)
+ {
+ return $this->encoder->encode($response);
+ }
+
+ /**
+ * Decodes a query args array into the appropriate
+ * {@link Auth_OpenID_Request} object.
+ */
+ function decodeRequest($query=null)
+ {
+ if ($query === null) {
+ $query = Auth_OpenID::getQuery();
+ }
+
+ return $this->decoder->decode($query);
+ }
+}
+
+?>
diff --git a/extlib/Auth/OpenID/ServerRequest.php b/extlib/Auth/OpenID/ServerRequest.php
new file mode 100644
index 000000000..33a8556ce
--- /dev/null
+++ b/extlib/Auth/OpenID/ServerRequest.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * OpenID Server Request
+ *
+ * @see Auth_OpenID_Server
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Imports
+ */
+require_once "Auth/OpenID.php";
+
+/**
+ * Object that holds the state of a request to the OpenID server
+ *
+ * With accessor functions to get at the internal request data.
+ *
+ * @see Auth_OpenID_Server
+ * @package OpenID
+ */
+class Auth_OpenID_ServerRequest {
+ function Auth_OpenID_ServerRequest()
+ {
+ $this->mode = null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/TrustRoot.php b/extlib/Auth/OpenID/TrustRoot.php
new file mode 100644
index 000000000..4919a6065
--- /dev/null
+++ b/extlib/Auth/OpenID/TrustRoot.php
@@ -0,0 +1,462 @@
+<?php
+/**
+ * Functions for dealing with OpenID trust roots
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/OpenID/Discover.php';
+
+/**
+ * A regular expression that matches a domain ending in a top-level domains.
+ * Used in checking trust roots for sanity.
+ *
+ * @access private
+ */
+define('Auth_OpenID___TLDs',
+ '/\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|asia' .
+ '|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br' .
+ '|bs|bt|bv|bw|by|bz|ca|cat|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co' .
+ '|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg' .
+ '|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl' .
+ '|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie' .
+ '|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jobs|jp|ke|kg|kh' .
+ '|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly' .
+ '|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mo|mobi|mp|mq|mr|ms|mt' .
+ '|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no' .
+ '|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt' .
+ '|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl' .
+ '|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm' .
+ '|tn|to|tp|tr|travel|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve' .
+ '|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g' .
+ '|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d' .
+ '|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp' .
+ '|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)\.?$/');
+
+define('Auth_OpenID___HostSegmentRe',
+ "/^(?:[-a-zA-Z0-9!$&'\\(\\)\\*+,;=._~]|%[a-zA-Z0-9]{2})*$/");
+
+/**
+ * A wrapper for trust-root related functions
+ */
+class Auth_OpenID_TrustRoot {
+ /*
+ * Return a discovery URL for this realm.
+ *
+ * Return null if the realm could not be parsed or was not valid.
+ *
+ * @param return_to The relying party return URL of the OpenID
+ * authentication request
+ *
+ * @return The URL upon which relying party discovery should be
+ * run in order to verify the return_to URL
+ */
+ function buildDiscoveryURL($realm)
+ {
+ $parsed = Auth_OpenID_TrustRoot::_parse($realm);
+
+ if ($parsed === false) {
+ return false;
+ }
+
+ if ($parsed['wildcard']) {
+ // Use "www." in place of the star
+ if ($parsed['host'][0] != '.') {
+ return false;
+ }
+
+ $www_domain = 'www' . $parsed['host'];
+
+ return sprintf('%s://%s%s', $parsed['scheme'],
+ $www_domain, $parsed['path']);
+ } else {
+ return $parsed['unparsed'];
+ }
+ }
+
+ /**
+ * Parse a URL into its trust_root parts.
+ *
+ * @static
+ *
+ * @access private
+ *
+ * @param string $trust_root The url to parse
+ *
+ * @return mixed $parsed Either an associative array of trust root
+ * parts or false if parsing failed.
+ */
+ function _parse($trust_root)
+ {
+ $trust_root = Auth_OpenID_urinorm($trust_root);
+ if ($trust_root === null) {
+ return false;
+ }
+
+ if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
+ return false;
+ }
+
+ $parts = @parse_url($trust_root);
+ if ($parts === false) {
+ return false;
+ }
+
+ $required_parts = array('scheme', 'host');
+ $forbidden_parts = array('user', 'pass', 'fragment');
+ $keys = array_keys($parts);
+ if (array_intersect($keys, $required_parts) != $required_parts) {
+ return false;
+ }
+
+ if (array_intersect($keys, $forbidden_parts) != array()) {
+ return false;
+ }
+
+ if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
+ return false;
+ }
+
+ $scheme = strtolower($parts['scheme']);
+ $allowed_schemes = array('http', 'https');
+ if (!in_array($scheme, $allowed_schemes)) {
+ return false;
+ }
+ $parts['scheme'] = $scheme;
+
+ $host = strtolower($parts['host']);
+ $hostparts = explode('*', $host);
+ switch (count($hostparts)) {
+ case 1:
+ $parts['wildcard'] = false;
+ break;
+ case 2:
+ if ($hostparts[0] ||
+ ($hostparts[1] && substr($hostparts[1], 0, 1) != '.')) {
+ return false;
+ }
+ $host = $hostparts[1];
+ $parts['wildcard'] = true;
+ break;
+ default:
+ return false;
+ }
+ if (strpos($host, ':') !== false) {
+ return false;
+ }
+
+ $parts['host'] = $host;
+
+ if (isset($parts['path'])) {
+ $path = strtolower($parts['path']);
+ if (substr($path, 0, 1) != '/') {
+ return false;
+ }
+ } else {
+ $path = '/';
+ }
+
+ $parts['path'] = $path;
+ if (!isset($parts['port'])) {
+ $parts['port'] = false;
+ }
+
+
+ $parts['unparsed'] = $trust_root;
+
+ return $parts;
+ }
+
+ /**
+ * Is this trust root sane?
+ *
+ * A trust root is sane if it is syntactically valid and it has a
+ * reasonable domain name. Specifically, the domain name must be
+ * more than one level below a standard TLD or more than two
+ * levels below a two-letter tld.
+ *
+ * For example, '*.com' is not a sane trust root, but '*.foo.com'
+ * is. '*.co.uk' is not sane, but '*.bbc.co.uk' is.
+ *
+ * This check is not always correct, but it attempts to err on the
+ * side of marking sane trust roots insane instead of marking
+ * insane trust roots sane. For example, 'kink.fm' is marked as
+ * insane even though it "should" (for some meaning of should) be
+ * marked sane.
+ *
+ * This function should be used when creating OpenID servers to
+ * alert the users of the server when a consumer attempts to get
+ * the user to accept a suspicious trust root.
+ *
+ * @static
+ * @param string $trust_root The trust root to check
+ * @return bool $sanity Whether the trust root looks OK
+ */
+ function isSane($trust_root)
+ {
+ $parts = Auth_OpenID_TrustRoot::_parse($trust_root);
+ if ($parts === false) {
+ return false;
+ }
+
+ // Localhost is a special case
+ if ($parts['host'] == 'localhost') {
+ return true;
+ }
+
+ $host_parts = explode('.', $parts['host']);
+ if ($parts['wildcard']) {
+ // Remove the empty string from the beginning of the array
+ array_shift($host_parts);
+ }
+
+ if ($host_parts && !$host_parts[count($host_parts) - 1]) {
+ array_pop($host_parts);
+ }
+
+ if (!$host_parts) {
+ return false;
+ }
+
+ // Don't allow adjacent dots
+ if (in_array('', $host_parts, true)) {
+ return false;
+ }
+
+ // Get the top-level domain of the host. If it is not a valid TLD,
+ // it's not sane.
+ preg_match(Auth_OpenID___TLDs, $parts['host'], $matches);
+ if (!$matches) {
+ return false;
+ }
+ $tld = $matches[1];
+
+ if (count($host_parts) == 1) {
+ return false;
+ }
+
+ if ($parts['wildcard']) {
+ // It's a 2-letter tld with a short second to last segment
+ // so there needs to be more than two segments specified
+ // (e.g. *.co.uk is insane)
+ $second_level = $host_parts[count($host_parts) - 2];
+ if (strlen($tld) == 2 && strlen($second_level) <= 3) {
+ return count($host_parts) > 2;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Does this URL match the given trust root?
+ *
+ * Return whether the URL falls under the given trust root. This
+ * does not check whether the trust root is sane. If the URL or
+ * trust root do not parse, this function will return false.
+ *
+ * @param string $trust_root The trust root to match against
+ *
+ * @param string $url The URL to check
+ *
+ * @return bool $matches Whether the URL matches against the
+ * trust root
+ */
+ function match($trust_root, $url)
+ {
+ $trust_root_parsed = Auth_OpenID_TrustRoot::_parse($trust_root);
+ $url_parsed = Auth_OpenID_TrustRoot::_parse($url);
+ if (!$trust_root_parsed || !$url_parsed) {
+ return false;
+ }
+
+ // Check hosts matching
+ if ($url_parsed['wildcard']) {
+ return false;
+ }
+ if ($trust_root_parsed['wildcard']) {
+ $host_tail = $trust_root_parsed['host'];
+ $host = $url_parsed['host'];
+ if ($host_tail &&
+ substr($host, -(strlen($host_tail))) != $host_tail &&
+ substr($host_tail, 1) != $host) {
+ return false;
+ }
+ } else {
+ if ($trust_root_parsed['host'] != $url_parsed['host']) {
+ return false;
+ }
+ }
+
+ // Check path and query matching
+ $base_path = $trust_root_parsed['path'];
+ $path = $url_parsed['path'];
+ if (!isset($trust_root_parsed['query'])) {
+ if ($base_path != $path) {
+ if (substr($path, 0, strlen($base_path)) != $base_path) {
+ return false;
+ }
+ if (substr($base_path, strlen($base_path) - 1, 1) != '/' &&
+ substr($path, strlen($base_path), 1) != '/') {
+ return false;
+ }
+ }
+ } else {
+ $base_query = $trust_root_parsed['query'];
+ $query = @$url_parsed['query'];
+ $qplus = substr($query, 0, strlen($base_query) + 1);
+ $bqplus = $base_query . '&';
+ if ($base_path != $path ||
+ ($base_query != $query && $qplus != $bqplus)) {
+ return false;
+ }
+ }
+
+ // The port and scheme need to match exactly
+ return ($trust_root_parsed['scheme'] == $url_parsed['scheme'] &&
+ $url_parsed['port'] === $trust_root_parsed['port']);
+ }
+}
+
+/*
+ * If the endpoint is a relying party OpenID return_to endpoint,
+ * return the endpoint URL. Otherwise, return None.
+ *
+ * This function is intended to be used as a filter for the Yadis
+ * filtering interface.
+ *
+ * @see: C{L{openid.yadis.services}}
+ * @see: C{L{openid.yadis.filters}}
+ *
+ * @param endpoint: An XRDS BasicServiceEndpoint, as returned by
+ * performing Yadis dicovery.
+ *
+ * @returns: The endpoint URL or None if the endpoint is not a
+ * relying party endpoint.
+ */
+function filter_extractReturnURL(&$endpoint)
+{
+ if ($endpoint->matchTypes(array(Auth_OpenID_RP_RETURN_TO_URL_TYPE))) {
+ return $endpoint;
+ } else {
+ return null;
+ }
+}
+
+function &Auth_OpenID_extractReturnURL(&$endpoint_list)
+{
+ $result = array();
+
+ foreach ($endpoint_list as $endpoint) {
+ if (filter_extractReturnURL($endpoint)) {
+ $result[] = $endpoint;
+ }
+ }
+
+ return $result;
+}
+
+/*
+ * Is the return_to URL under one of the supplied allowed return_to
+ * URLs?
+ */
+function Auth_OpenID_returnToMatches($allowed_return_to_urls, $return_to)
+{
+ foreach ($allowed_return_to_urls as $allowed_return_to) {
+ // A return_to pattern works the same as a realm, except that
+ // it's not allowed to use a wildcard. We'll model this by
+ // parsing it as a realm, and not trying to match it if it has
+ // a wildcard.
+
+ $return_realm = Auth_OpenID_TrustRoot::_parse($allowed_return_to);
+ if (// Parses as a trust root
+ ($return_realm !== false) &&
+ // Does not have a wildcard
+ (!$return_realm['wildcard']) &&
+ // Matches the return_to that we passed in with it
+ (Auth_OpenID_TrustRoot::match($allowed_return_to, $return_to))) {
+ return true;
+ }
+ }
+
+ // No URL in the list matched
+ return false;
+}
+
+/*
+ * Given a relying party discovery URL return a list of return_to
+ * URLs.
+ */
+function Auth_OpenID_getAllowedReturnURLs($relying_party_url, &$fetcher,
+ $discover_function=null)
+{
+ if ($discover_function === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $xrds_parse_cb = array('Auth_OpenID_ServiceEndpoint', 'fromXRDS');
+
+ list($rp_url_after_redirects, $endpoints) =
+ Auth_Yadis_getServiceEndpoints($relying_party_url, $xrds_parse_cb,
+ $discover_function, $fetcher);
+
+ if ($rp_url_after_redirects != $relying_party_url) {
+ // Verification caused a redirect
+ return false;
+ }
+
+ call_user_func_array($discover_function,
+ array($relying_party_url, $fetcher));
+
+ $return_to_urls = array();
+ $matching_endpoints = Auth_OpenID_extractReturnURL($endpoints);
+
+ foreach ($matching_endpoints as $e) {
+ $return_to_urls[] = $e->server_url;
+ }
+
+ return $return_to_urls;
+}
+
+/*
+ * Verify that a return_to URL is valid for the given realm.
+ *
+ * This function builds a discovery URL, performs Yadis discovery on
+ * it, makes sure that the URL does not redirect, parses out the
+ * return_to URLs, and finally checks to see if the current return_to
+ * URL matches the return_to.
+ *
+ * @return true if the return_to URL is valid for the realm
+ */
+function Auth_OpenID_verifyReturnTo($realm_str, $return_to, &$fetcher,
+ $_vrfy='Auth_OpenID_getAllowedReturnURLs')
+{
+ $disco_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm_str);
+
+ if ($disco_url === false) {
+ return false;
+ }
+
+ $allowable_urls = call_user_func_array($_vrfy,
+ array($disco_url, &$fetcher));
+
+ // The realm_str could not be parsed.
+ if ($allowable_urls === false) {
+ return false;
+ }
+
+ if (Auth_OpenID_returnToMatches($allowable_urls, $return_to)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/OpenID/URINorm.php b/extlib/Auth/OpenID/URINorm.php
new file mode 100644
index 000000000..f821d836a
--- /dev/null
+++ b/extlib/Auth/OpenID/URINorm.php
@@ -0,0 +1,249 @@
+<?php
+
+/**
+ * URI normalization routines.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/Yadis/Misc.php';
+
+// from appendix B of rfc 3986 (http://www.ietf.org/rfc/rfc3986.txt)
+function Auth_OpenID_getURIPattern()
+{
+ return '&^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?&';
+}
+
+function Auth_OpenID_getAuthorityPattern()
+{
+ return '/^([^@]*@)?([^:]*)(:.*)?/';
+}
+
+function Auth_OpenID_getEncodedPattern()
+{
+ return '/%([0-9A-Fa-f]{2})/';
+}
+
+# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+#
+# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+# / "*" / "+" / "," / ";" / "="
+#
+# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+function Auth_OpenID_getURLIllegalCharRE()
+{
+ return "/([^-A-Za-z0-9:\/\?#\[\]@\!\$&'\(\)\*\+,;=\._~\%])/";
+}
+
+function Auth_OpenID_getUnreserved()
+{
+ $_unreserved = array();
+ for ($i = 0; $i < 256; $i++) {
+ $_unreserved[$i] = false;
+ }
+
+ for ($i = ord('A'); $i <= ord('Z'); $i++) {
+ $_unreserved[$i] = true;
+ }
+
+ for ($i = ord('0'); $i <= ord('9'); $i++) {
+ $_unreserved[$i] = true;
+ }
+
+ for ($i = ord('a'); $i <= ord('z'); $i++) {
+ $_unreserved[$i] = true;
+ }
+
+ $_unreserved[ord('-')] = true;
+ $_unreserved[ord('.')] = true;
+ $_unreserved[ord('_')] = true;
+ $_unreserved[ord('~')] = true;
+
+ return $_unreserved;
+}
+
+function Auth_OpenID_getEscapeRE()
+{
+ $parts = array();
+ foreach (array_merge(Auth_Yadis_getUCSChars(),
+ Auth_Yadis_getIPrivateChars()) as $pair) {
+ list($m, $n) = $pair;
+ $parts[] = sprintf("%s-%s", chr($m), chr($n));
+ }
+
+ return sprintf('[%s]', implode('', $parts));
+}
+
+function Auth_OpenID_pct_encoded_replace_unreserved($mo)
+{
+ $_unreserved = Auth_OpenID_getUnreserved();
+
+ $i = intval($mo[1], 16);
+ if ($_unreserved[$i]) {
+ return chr($i);
+ } else {
+ return strtoupper($mo[0]);
+ }
+
+ return $mo[0];
+}
+
+function Auth_OpenID_pct_encoded_replace($mo)
+{
+ return chr(intval($mo[1], 16));
+}
+
+function Auth_OpenID_remove_dot_segments($path)
+{
+ $result_segments = array();
+
+ while ($path) {
+ if (Auth_Yadis_startswith($path, '../')) {
+ $path = substr($path, 3);
+ } else if (Auth_Yadis_startswith($path, './')) {
+ $path = substr($path, 2);
+ } else if (Auth_Yadis_startswith($path, '/./')) {
+ $path = substr($path, 2);
+ } else if ($path == '/.') {
+ $path = '/';
+ } else if (Auth_Yadis_startswith($path, '/../')) {
+ $path = substr($path, 3);
+ if ($result_segments) {
+ array_pop($result_segments);
+ }
+ } else if ($path == '/..') {
+ $path = '/';
+ if ($result_segments) {
+ array_pop($result_segments);
+ }
+ } else if (($path == '..') ||
+ ($path == '.')) {
+ $path = '';
+ } else {
+ $i = 0;
+ if ($path[0] == '/') {
+ $i = 1;
+ }
+ $i = strpos($path, '/', $i);
+ if ($i === false) {
+ $i = strlen($path);
+ }
+ $result_segments[] = substr($path, 0, $i);
+ $path = substr($path, $i);
+ }
+ }
+
+ return implode('', $result_segments);
+}
+
+function Auth_OpenID_urinorm($uri)
+{
+ $uri_matches = array();
+ preg_match(Auth_OpenID_getURIPattern(), $uri, $uri_matches);
+
+ if (count($uri_matches) < 9) {
+ for ($i = count($uri_matches); $i <= 9; $i++) {
+ $uri_matches[] = '';
+ }
+ }
+
+ $illegal_matches = array();
+ preg_match(Auth_OpenID_getURLIllegalCharRE(),
+ $uri, $illegal_matches);
+ if ($illegal_matches) {
+ return null;
+ }
+
+ $scheme = $uri_matches[2];
+ if ($scheme) {
+ $scheme = strtolower($scheme);
+ }
+
+ $scheme = $uri_matches[2];
+ if ($scheme === '') {
+ // No scheme specified
+ return null;
+ }
+
+ $scheme = strtolower($scheme);
+ if (!in_array($scheme, array('http', 'https'))) {
+ // Not an absolute HTTP or HTTPS URI
+ return null;
+ }
+
+ $authority = $uri_matches[4];
+ if ($authority === '') {
+ // Not an absolute URI
+ return null;
+ }
+
+ $authority_matches = array();
+ preg_match(Auth_OpenID_getAuthorityPattern(),
+ $authority, $authority_matches);
+ if (count($authority_matches) === 0) {
+ // URI does not have a valid authority
+ return null;
+ }
+
+ if (count($authority_matches) < 4) {
+ for ($i = count($authority_matches); $i <= 4; $i++) {
+ $authority_matches[] = '';
+ }
+ }
+
+ list($_whole, $userinfo, $host, $port) = $authority_matches;
+
+ if ($userinfo === null) {
+ $userinfo = '';
+ }
+
+ if (strpos($host, '%') !== -1) {
+ $host = strtolower($host);
+ $host = preg_replace_callback(
+ Auth_OpenID_getEncodedPattern(),
+ 'Auth_OpenID_pct_encoded_replace', $host);
+ // NO IDNA.
+ // $host = unicode($host, 'utf-8').encode('idna');
+ } else {
+ $host = strtolower($host);
+ }
+
+ if ($port) {
+ if (($port == ':') ||
+ ($scheme == 'http' && $port == ':80') ||
+ ($scheme == 'https' && $port == ':443')) {
+ $port = '';
+ }
+ } else {
+ $port = '';
+ }
+
+ $authority = $userinfo . $host . $port;
+
+ $path = $uri_matches[5];
+ $path = preg_replace_callback(
+ Auth_OpenID_getEncodedPattern(),
+ 'Auth_OpenID_pct_encoded_replace_unreserved', $path);
+
+ $path = Auth_OpenID_remove_dot_segments($path);
+ if (!$path) {
+ $path = '/';
+ }
+
+ $query = $uri_matches[6];
+ if ($query === null) {
+ $query = '';
+ }
+
+ $fragment = $uri_matches[8];
+ if ($fragment === null) {
+ $fragment = '';
+ }
+
+ return $scheme . '://' . $authority . $path . $query . $fragment;
+}
+
+?>
diff --git a/extlib/Auth/Yadis/HTTPFetcher.php b/extlib/Auth/Yadis/HTTPFetcher.php
new file mode 100644
index 000000000..a1825403d
--- /dev/null
+++ b/extlib/Auth/Yadis/HTTPFetcher.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * This module contains the HTTP fetcher interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require logging functionality
+ */
+require_once "Auth/OpenID.php";
+
+define('Auth_OpenID_FETCHER_MAX_RESPONSE_KB', 1024);
+define('Auth_OpenID_USER_AGENT',
+ 'php-openid/'.Auth_OpenID_VERSION.' (php/'.phpversion().')');
+
+class Auth_Yadis_HTTPResponse {
+ function Auth_Yadis_HTTPResponse($final_url = null, $status = null,
+ $headers = null, $body = null)
+ {
+ $this->final_url = $final_url;
+ $this->status = $status;
+ $this->headers = $headers;
+ $this->body = $body;
+ }
+}
+
+/**
+ * This class is the interface for HTTP fetchers the Yadis library
+ * uses. This interface is only important if you need to write a new
+ * fetcher for some reason.
+ *
+ * @access private
+ * @package OpenID
+ */
+class Auth_Yadis_HTTPFetcher {
+
+ var $timeout = 20; // timeout in seconds.
+
+ /**
+ * Return whether a URL can be fetched. Returns false if the URL
+ * scheme is not allowed or is not supported by this fetcher
+ * implementation; returns true otherwise.
+ *
+ * @return bool
+ */
+ function canFetchURL($url)
+ {
+ if ($this->isHTTPS($url) && !$this->supportsSSL()) {
+ Auth_OpenID::log("HTTPS URL unsupported fetching %s",
+ $url);
+ return false;
+ }
+
+ if (!$this->allowedURL($url)) {
+ Auth_OpenID::log("URL fetching not allowed for '%s'",
+ $url);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return whether a URL should be allowed. Override this method to
+ * conform to your local policy.
+ *
+ * By default, will attempt to fetch any http or https URL.
+ */
+ function allowedURL($url)
+ {
+ return $this->URLHasAllowedScheme($url);
+ }
+
+ /**
+ * Does this fetcher implementation (and runtime) support fetching
+ * HTTPS URLs? May inspect the runtime environment.
+ *
+ * @return bool $support True if this fetcher supports HTTPS
+ * fetching; false if not.
+ */
+ function supportsSSL()
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+
+ /**
+ * Is this an https URL?
+ *
+ * @access private
+ */
+ function isHTTPS($url)
+ {
+ return (bool)preg_match('/^https:\/\//i', $url);
+ }
+
+ /**
+ * Is this an http or https URL?
+ *
+ * @access private
+ */
+ function URLHasAllowedScheme($url)
+ {
+ return (bool)preg_match('/^https?:\/\//i', $url);
+ }
+
+ /**
+ * @access private
+ */
+ function _findRedirect($headers)
+ {
+ foreach ($headers as $line) {
+ if (strpos(strtolower($line), "location: ") === 0) {
+ $parts = explode(" ", $line, 2);
+ return $parts[1];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Fetches the specified URL using optional extra headers and
+ * returns the server's response.
+ *
+ * @param string $url The URL to be fetched.
+ * @param array $extra_headers An array of header strings
+ * (e.g. "Accept: text/html").
+ * @return mixed $result An array of ($code, $url, $headers,
+ * $body) if the URL could be fetched; null if the URL does not
+ * pass the URLHasAllowedScheme check or if the server's response
+ * is malformed.
+ */
+ function get($url, $headers)
+ {
+ trigger_error("not implemented", E_USER_ERROR);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/Manager.php b/extlib/Auth/Yadis/Manager.php
new file mode 100644
index 000000000..d50cf7ad6
--- /dev/null
+++ b/extlib/Auth/Yadis/Manager.php
@@ -0,0 +1,529 @@
+<?php
+
+/**
+ * Yadis service manager to be used during yadis-driven authentication
+ * attempts.
+ *
+ * @package OpenID
+ */
+
+/**
+ * The base session class used by the Auth_Yadis_Manager. This
+ * class wraps the default PHP session machinery and should be
+ * subclassed if your application doesn't use PHP sessioning.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_PHPSession {
+ /**
+ * Set a session key/value pair.
+ *
+ * @param string $name The name of the session key to add.
+ * @param string $value The value to add to the session.
+ */
+ function set($name, $value)
+ {
+ $_SESSION[$name] = $value;
+ }
+
+ /**
+ * Get a key's value from the session.
+ *
+ * @param string $name The name of the key to retrieve.
+ * @param string $default The optional value to return if the key
+ * is not found in the session.
+ * @return string $result The key's value in the session or
+ * $default if it isn't found.
+ */
+ function get($name, $default=null)
+ {
+ if (array_key_exists($name, $_SESSION)) {
+ return $_SESSION[$name];
+ } else {
+ return $default;
+ }
+ }
+
+ /**
+ * Remove a key/value pair from the session.
+ *
+ * @param string $name The name of the key to remove.
+ */
+ function del($name)
+ {
+ unset($_SESSION[$name]);
+ }
+
+ /**
+ * Return the contents of the session in array form.
+ */
+ function contents()
+ {
+ return $_SESSION;
+ }
+}
+
+/**
+ * A session helper class designed to translate between arrays and
+ * objects. Note that the class used must have a constructor that
+ * takes no parameters. This is not a general solution, but it works
+ * for dumb objects that just need to have attributes set. The idea
+ * is that you'll subclass this and override $this->check($data) ->
+ * bool to implement your own session data validation.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_SessionLoader {
+ /**
+ * Override this.
+ *
+ * @access private
+ */
+ function check($data)
+ {
+ return true;
+ }
+
+ /**
+ * Given a session data value (an array), this creates an object
+ * (returned by $this->newObject()) whose attributes and values
+ * are those in $data. Returns null if $data lacks keys found in
+ * $this->requiredKeys(). Returns null if $this->check($data)
+ * evaluates to false. Returns null if $this->newObject()
+ * evaluates to false.
+ *
+ * @access private
+ */
+ function fromSession($data)
+ {
+ if (!$data) {
+ return null;
+ }
+
+ $required = $this->requiredKeys();
+
+ foreach ($required as $k) {
+ if (!array_key_exists($k, $data)) {
+ return null;
+ }
+ }
+
+ if (!$this->check($data)) {
+ return null;
+ }
+
+ $data = array_merge($data, $this->prepareForLoad($data));
+ $obj = $this->newObject($data);
+
+ if (!$obj) {
+ return null;
+ }
+
+ foreach ($required as $k) {
+ $obj->$k = $data[$k];
+ }
+
+ return $obj;
+ }
+
+ /**
+ * Prepares the data array by making any necessary changes.
+ * Returns an array whose keys and values will be used to update
+ * the original data array before calling $this->newObject($data).
+ *
+ * @access private
+ */
+ function prepareForLoad($data)
+ {
+ return array();
+ }
+
+ /**
+ * Returns a new instance of this loader's class, using the
+ * session data to construct it if necessary. The object need
+ * only be created; $this->fromSession() will take care of setting
+ * the object's attributes.
+ *
+ * @access private
+ */
+ function newObject($data)
+ {
+ return null;
+ }
+
+ /**
+ * Returns an array of keys and values built from the attributes
+ * of $obj. If $this->prepareForSave($obj) returns an array, its keys
+ * and values are used to update the $data array of attributes
+ * from $obj.
+ *
+ * @access private
+ */
+ function toSession($obj)
+ {
+ $data = array();
+ foreach ($obj as $k => $v) {
+ $data[$k] = $v;
+ }
+
+ $extra = $this->prepareForSave($obj);
+
+ if ($extra && is_array($extra)) {
+ foreach ($extra as $k => $v) {
+ $data[$k] = $v;
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Override this.
+ *
+ * @access private
+ */
+ function prepareForSave($obj)
+ {
+ return array();
+ }
+}
+
+/**
+ * A concrete loader implementation for Auth_OpenID_ServiceEndpoints.
+ *
+ * @package OpenID
+ */
+class Auth_OpenID_ServiceEndpointLoader extends Auth_Yadis_SessionLoader {
+ function newObject($data)
+ {
+ return new Auth_OpenID_ServiceEndpoint();
+ }
+
+ function requiredKeys()
+ {
+ $obj = new Auth_OpenID_ServiceEndpoint();
+ $data = array();
+ foreach ($obj as $k => $v) {
+ $data[] = $k;
+ }
+ return $data;
+ }
+
+ function check($data)
+ {
+ return is_array($data['type_uris']);
+ }
+}
+
+/**
+ * A concrete loader implementation for Auth_Yadis_Managers.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ManagerLoader extends Auth_Yadis_SessionLoader {
+ function requiredKeys()
+ {
+ return array('starting_url',
+ 'yadis_url',
+ 'services',
+ 'session_key',
+ '_current',
+ 'stale');
+ }
+
+ function newObject($data)
+ {
+ return new Auth_Yadis_Manager($data['starting_url'],
+ $data['yadis_url'],
+ $data['services'],
+ $data['session_key']);
+ }
+
+ function check($data)
+ {
+ return is_array($data['services']);
+ }
+
+ function prepareForLoad($data)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $services = array();
+ foreach ($data['services'] as $s) {
+ $services[] = $loader->fromSession($s);
+ }
+ return array('services' => $services);
+ }
+
+ function prepareForSave($obj)
+ {
+ $loader = new Auth_OpenID_ServiceEndpointLoader();
+ $services = array();
+ foreach ($obj->services as $s) {
+ $services[] = $loader->toSession($s);
+ }
+ return array('services' => $services);
+ }
+}
+
+/**
+ * The Yadis service manager which stores state in a session and
+ * iterates over <Service> elements in a Yadis XRDS document and lets
+ * a caller attempt to use each one. This is used by the Yadis
+ * library internally.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Manager {
+
+ /**
+ * Intialize a new yadis service manager.
+ *
+ * @access private
+ */
+ function Auth_Yadis_Manager($starting_url, $yadis_url,
+ $services, $session_key)
+ {
+ // The URL that was used to initiate the Yadis protocol
+ $this->starting_url = $starting_url;
+
+ // The URL after following redirects (the identifier)
+ $this->yadis_url = $yadis_url;
+
+ // List of service elements
+ $this->services = $services;
+
+ $this->session_key = $session_key;
+
+ // Reference to the current service object
+ $this->_current = null;
+
+ // Stale flag for cleanup if PHP lib has trouble.
+ $this->stale = false;
+ }
+
+ /**
+ * @access private
+ */
+ function length()
+ {
+ // How many untried services remain?
+ return count($this->services);
+ }
+
+ /**
+ * Return the next service
+ *
+ * $this->current() will continue to return that service until the
+ * next call to this method.
+ */
+ function nextService()
+ {
+
+ if ($this->services) {
+ $this->_current = array_shift($this->services);
+ } else {
+ $this->_current = null;
+ }
+
+ return $this->_current;
+ }
+
+ /**
+ * @access private
+ */
+ function current()
+ {
+ // Return the current service.
+ // Returns None if there are no services left.
+ return $this->_current;
+ }
+
+ /**
+ * @access private
+ */
+ function forURL($url)
+ {
+ return in_array($url, array($this->starting_url, $this->yadis_url));
+ }
+
+ /**
+ * @access private
+ */
+ function started()
+ {
+ // Has the first service been returned?
+ return $this->_current !== null;
+ }
+}
+
+/**
+ * State management for discovery.
+ *
+ * High-level usage pattern is to call .getNextService(discover) in
+ * order to find the next available service for this user for this
+ * session. Once a request completes, call .cleanup() to clean up the
+ * session state.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Discovery {
+
+ /**
+ * @access private
+ */
+ var $DEFAULT_SUFFIX = 'auth';
+
+ /**
+ * @access private
+ */
+ var $PREFIX = '_yadis_services_';
+
+ /**
+ * Initialize a discovery object.
+ *
+ * @param Auth_Yadis_PHPSession $session An object which
+ * implements the Auth_Yadis_PHPSession API.
+ * @param string $url The URL on which to attempt discovery.
+ * @param string $session_key_suffix The optional session key
+ * suffix override.
+ */
+ function Auth_Yadis_Discovery(&$session, $url,
+ $session_key_suffix = null)
+ {
+ /// Initialize a discovery object
+ $this->session =& $session;
+ $this->url = $url;
+ if ($session_key_suffix === null) {
+ $session_key_suffix = $this->DEFAULT_SUFFIX;
+ }
+
+ $this->session_key_suffix = $session_key_suffix;
+ $this->session_key = $this->PREFIX . $this->session_key_suffix;
+ }
+
+ /**
+ * Return the next authentication service for the pair of
+ * user_input and session. This function handles fallback.
+ */
+ function getNextService($discover_cb, &$fetcher)
+ {
+ $manager = $this->getManager();
+ if (!$manager || (!$manager->services)) {
+ $this->destroyManager();
+
+ list($yadis_url, $services) = call_user_func($discover_cb,
+ $this->url,
+ $fetcher);
+
+ $manager = $this->createManager($services, $yadis_url);
+ }
+
+ if ($manager) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $service = $manager->nextService();
+ $this->session->set($this->session_key,
+ serialize($loader->toSession($manager)));
+ } else {
+ $service = null;
+ }
+
+ return $service;
+ }
+
+ /**
+ * Clean up Yadis-related services in the session and return the
+ * most-recently-attempted service from the manager, if one
+ * exists.
+ *
+ * @param $force True if the manager should be deleted regardless
+ * of whether it's a manager for $this->url.
+ */
+ function cleanup($force=false)
+ {
+ $manager = $this->getManager($force);
+ if ($manager) {
+ $service = $manager->current();
+ $this->destroyManager($force);
+ } else {
+ $service = null;
+ }
+
+ return $service;
+ }
+
+ /**
+ * @access private
+ */
+ function getSessionKey()
+ {
+ // Get the session key for this starting URL and suffix
+ return $this->PREFIX . $this->session_key_suffix;
+ }
+
+ /**
+ * @access private
+ *
+ * @param $force True if the manager should be returned regardless
+ * of whether it's a manager for $this->url.
+ */
+ function &getManager($force=false)
+ {
+ // Extract the YadisServiceManager for this object's URL and
+ // suffix from the session.
+
+ $manager_str = $this->session->get($this->getSessionKey());
+ $manager = null;
+
+ if ($manager_str !== null) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $manager = $loader->fromSession(unserialize($manager_str));
+ }
+
+ if ($manager && ($manager->forURL($this->url) || $force)) {
+ return $manager;
+ } else {
+ $unused = null;
+ return $unused;
+ }
+ }
+
+ /**
+ * @access private
+ */
+ function &createManager($services, $yadis_url = null)
+ {
+ $key = $this->getSessionKey();
+ if ($this->getManager()) {
+ return $this->getManager();
+ }
+
+ if ($services) {
+ $loader = new Auth_Yadis_ManagerLoader();
+ $manager = new Auth_Yadis_Manager($this->url, $yadis_url,
+ $services, $key);
+ $this->session->set($this->session_key,
+ serialize($loader->toSession($manager)));
+ return $manager;
+ } else {
+ // Oh, PHP.
+ $unused = null;
+ return $unused;
+ }
+ }
+
+ /**
+ * @access private
+ *
+ * @param $force True if the manager should be deleted regardless
+ * of whether it's a manager for $this->url.
+ */
+ function destroyManager($force=false)
+ {
+ if ($this->getManager($force) !== null) {
+ $key = $this->getSessionKey();
+ $this->session->del($key);
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/Misc.php b/extlib/Auth/Yadis/Misc.php
new file mode 100644
index 000000000..1134a4ff4
--- /dev/null
+++ b/extlib/Auth/Yadis/Misc.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Miscellaneous utility values and functions for OpenID and Yadis.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+function Auth_Yadis_getUCSChars()
+{
+ return array(
+ array(0xA0, 0xD7FF),
+ array(0xF900, 0xFDCF),
+ array(0xFDF0, 0xFFEF),
+ array(0x10000, 0x1FFFD),
+ array(0x20000, 0x2FFFD),
+ array(0x30000, 0x3FFFD),
+ array(0x40000, 0x4FFFD),
+ array(0x50000, 0x5FFFD),
+ array(0x60000, 0x6FFFD),
+ array(0x70000, 0x7FFFD),
+ array(0x80000, 0x8FFFD),
+ array(0x90000, 0x9FFFD),
+ array(0xA0000, 0xAFFFD),
+ array(0xB0000, 0xBFFFD),
+ array(0xC0000, 0xCFFFD),
+ array(0xD0000, 0xDFFFD),
+ array(0xE1000, 0xEFFFD)
+ );
+}
+
+function Auth_Yadis_getIPrivateChars()
+{
+ return array(
+ array(0xE000, 0xF8FF),
+ array(0xF0000, 0xFFFFD),
+ array(0x100000, 0x10FFFD)
+ );
+}
+
+function Auth_Yadis_pct_escape_unicode($char_match)
+{
+ $c = $char_match[0];
+ $result = "";
+ for ($i = 0; $i < strlen($c); $i++) {
+ $result .= "%".sprintf("%X", ord($c[$i]));
+ }
+ return $result;
+}
+
+function Auth_Yadis_startswith($s, $stuff)
+{
+ return strpos($s, $stuff) === 0;
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/ParanoidHTTPFetcher.php b/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
new file mode 100644
index 000000000..8975d7f89
--- /dev/null
+++ b/extlib/Auth/Yadis/ParanoidHTTPFetcher.php
@@ -0,0 +1,228 @@
+<?php
+
+/**
+ * This module contains the CURL-based HTTP fetcher implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Interface import
+ */
+require_once "Auth/Yadis/HTTPFetcher.php";
+
+require_once "Auth/OpenID.php";
+
+/**
+ * A paranoid {@link Auth_Yadis_HTTPFetcher} class which uses CURL
+ * for fetching.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ParanoidHTTPFetcher extends Auth_Yadis_HTTPFetcher {
+ function Auth_Yadis_ParanoidHTTPFetcher()
+ {
+ $this->reset();
+ }
+
+ function reset()
+ {
+ $this->headers = array();
+ $this->data = "";
+ }
+
+ /**
+ * @access private
+ */
+ function _writeHeader($ch, $header)
+ {
+ array_push($this->headers, rtrim($header));
+ return strlen($header);
+ }
+
+ /**
+ * @access private
+ */
+ function _writeData($ch, $data)
+ {
+ if (strlen($this->data) > 1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB) {
+ return 0;
+ } else {
+ $this->data .= $data;
+ return strlen($data);
+ }
+ }
+
+ /**
+ * Does this fetcher support SSL URLs?
+ */
+ function supportsSSL()
+ {
+ $v = curl_version();
+ if(is_array($v)) {
+ return in_array('https', $v['protocols']);
+ } elseif (is_string($v)) {
+ return preg_match('/OpenSSL/i', $v);
+ } else {
+ return 0;
+ }
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ $redir = true;
+
+ while ($redir && ($off > 0)) {
+ $this->reset();
+
+ $c = curl_init();
+
+ if ($c === false) {
+ Auth_OpenID::log(
+ "curl_init returned false; could not " .
+ "initialize for URL '%s'", $url);
+ return null;
+ }
+
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ if (!$this->allowedURL($url)) {
+ Auth_OpenID::log("Fetching URL not allowed: %s",
+ $url);
+ return null;
+ }
+
+ curl_setopt($c, CURLOPT_WRITEFUNCTION,
+ array(&$this, "_writeData"));
+ curl_setopt($c, CURLOPT_HEADERFUNCTION,
+ array(&$this, "_writeHeader"));
+
+ if ($extra_headers) {
+ curl_setopt($c, CURLOPT_HTTPHEADER, $extra_headers);
+ }
+
+ $cv = curl_version();
+ if(is_array($cv)) {
+ $curl_user_agent = 'curl/'.$cv['version'];
+ } else {
+ $curl_user_agent = $cv;
+ }
+ curl_setopt($c, CURLOPT_USERAGENT,
+ Auth_OpenID_USER_AGENT.' '.$curl_user_agent);
+ curl_setopt($c, CURLOPT_TIMEOUT, $off);
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_RANGE,
+ "0-".(1024 * Auth_OpenID_FETCHER_MAX_RESPONSE_KB));
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+ $body = $this->data;
+ $headers = $this->headers;
+
+ if (!$code) {
+ Auth_OpenID::log("Got no response code when fetching %s", $url);
+ Auth_OpenID::log("CURL error (%s): %s",
+ curl_errno($c), curl_error($c));
+ return null;
+ }
+
+ if (in_array($code, array(301, 302, 303, 307))) {
+ $url = $this->_findRedirect($headers);
+ $redir = true;
+ } else {
+ $redir = false;
+ curl_close($c);
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (strpos($header, ': ')) {
+ list($name, $value) = explode(': ', $header, 2);
+ $new_headers[$name] = $value;
+ }
+ }
+
+ Auth_OpenID::log(
+ "Successfully fetched '%s': GET response code %s",
+ $url, $code);
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+
+ $off = $stop - time();
+ }
+
+ return null;
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $this->reset();
+
+ $c = curl_init();
+
+ if (defined('CURLOPT_NOSIGNAL')) {
+ curl_setopt($c, CURLOPT_NOSIGNAL, true);
+ }
+
+ curl_setopt($c, CURLOPT_POST, true);
+ curl_setopt($c, CURLOPT_POSTFIELDS, $body);
+ curl_setopt($c, CURLOPT_TIMEOUT, $this->timeout);
+ curl_setopt($c, CURLOPT_URL, $url);
+ curl_setopt($c, CURLOPT_WRITEFUNCTION,
+ array(&$this, "_writeData"));
+
+ curl_exec($c);
+
+ $code = curl_getinfo($c, CURLINFO_HTTP_CODE);
+
+ if (!$code) {
+ Auth_OpenID::log("Got no response code when fetching %s", $url);
+ return null;
+ }
+
+ $body = $this->data;
+
+ curl_close($c);
+
+ $new_headers = $extra_headers;
+
+ foreach ($this->headers as $header) {
+ if (strpos($header, ': ')) {
+ list($name, $value) = explode(': ', $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ Auth_OpenID::log("Successfully fetched '%s': POST response code %s",
+ $url, $code);
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/ParseHTML.php b/extlib/Auth/Yadis/ParseHTML.php
new file mode 100644
index 000000000..297ccbd2c
--- /dev/null
+++ b/extlib/Auth/Yadis/ParseHTML.php
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * This is the HTML pseudo-parser for the Yadis library.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * This class is responsible for scanning an HTML string to find META
+ * tags and their attributes. This is used by the Yadis discovery
+ * process. This class must be instantiated to be used.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_ParseHTML {
+
+ /**
+ * @access private
+ */
+ var $_re_flags = "si";
+
+ /**
+ * @access private
+ */
+ var $_removed_re =
+ "<!--.*?-->|<!\[CDATA\[.*?\]\]>|<script\b(?!:)[^>]*>.*?<\/script>";
+
+ /**
+ * @access private
+ */
+ var $_tag_expr = "<%s%s(?:\s.*?)?%s>";
+
+ /**
+ * @access private
+ */
+ var $_attr_find = '\b([-\w]+)=(".*?"|\'.*?\'|.+?)[\/\s>]';
+
+ function Auth_Yadis_ParseHTML()
+ {
+ $this->_attr_find = sprintf("/%s/%s",
+ $this->_attr_find,
+ $this->_re_flags);
+
+ $this->_removed_re = sprintf("/%s/%s",
+ $this->_removed_re,
+ $this->_re_flags);
+
+ $this->_entity_replacements = array(
+ 'amp' => '&',
+ 'lt' => '<',
+ 'gt' => '>',
+ 'quot' => '"'
+ );
+
+ $this->_ent_replace =
+ sprintf("&(%s);", implode("|",
+ $this->_entity_replacements));
+ }
+
+ /**
+ * Replace HTML entities (amp, lt, gt, and quot) as well as
+ * numeric entities (e.g. #x9f;) with their actual values and
+ * return the new string.
+ *
+ * @access private
+ * @param string $str The string in which to look for entities
+ * @return string $new_str The new string entities decoded
+ */
+ function replaceEntities($str)
+ {
+ foreach ($this->_entity_replacements as $old => $new) {
+ $str = preg_replace(sprintf("/&%s;/", $old), $new, $str);
+ }
+
+ // Replace numeric entities because html_entity_decode doesn't
+ // do it for us.
+ $str = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $str);
+ $str = preg_replace('~&#([0-9]+);~e', 'chr(\\1)', $str);
+
+ return $str;
+ }
+
+ /**
+ * Strip single and double quotes off of a string, if they are
+ * present.
+ *
+ * @access private
+ * @param string $str The original string
+ * @return string $new_str The new string with leading and
+ * trailing quotes removed
+ */
+ function removeQuotes($str)
+ {
+ $matches = array();
+ $double = '/^"(.*)"$/';
+ $single = "/^\'(.*)\'$/";
+
+ if (preg_match($double, $str, $matches)) {
+ return $matches[1];
+ } else if (preg_match($single, $str, $matches)) {
+ return $matches[1];
+ } else {
+ return $str;
+ }
+ }
+
+ /**
+ * Create a regular expression that will match an opening
+ * or closing tag from a set of names.
+ *
+ * @access private
+ * @param mixed $tag_names Tag names to match
+ * @param mixed $close false/0 = no, true/1 = yes, other = maybe
+ * @param mixed $self_close false/0 = no, true/1 = yes, other = maybe
+ * @return string $regex A regular expression string to be used
+ * in, say, preg_match.
+ */
+ function tagPattern($tag_names, $close, $self_close)
+ {
+ if (is_array($tag_names)) {
+ $tag_names = '(?:'.implode('|',$tag_names).')';
+ }
+ if ($close) {
+ $close = '\/' . (($close == 1)? '' : '?');
+ } else {
+ $close = '';
+ }
+ if ($self_close) {
+ $self_close = '(?:\/\s*)' . (($self_close == 1)? '' : '?');
+ } else {
+ $self_close = '';
+ }
+ $expr = sprintf($this->_tag_expr, $close, $tag_names, $self_close);
+
+ return sprintf("/%s/%s", $expr, $this->_re_flags);
+ }
+
+ /**
+ * Given an HTML document string, this finds all the META tags in
+ * the document, provided they are found in the
+ * <HTML><HEAD>...</HEAD> section of the document. The <HTML> tag
+ * may be missing.
+ *
+ * @access private
+ * @param string $html_string An HTMl document string
+ * @return array $tag_list Array of tags; each tag is an array of
+ * attribute -> value.
+ */
+ function getMetaTags($html_string)
+ {
+ $html_string = preg_replace($this->_removed_re,
+ "",
+ $html_string);
+
+ $key_tags = array($this->tagPattern('html', false, false),
+ $this->tagPattern('head', false, false),
+ $this->tagPattern('head', true, false),
+ $this->tagPattern('html', true, false),
+ $this->tagPattern(array(
+ 'body', 'frameset', 'frame', 'p', 'div',
+ 'table','span','a'), 'maybe', 'maybe'));
+ $key_tags_pos = array();
+ foreach ($key_tags as $pat) {
+ $matches = array();
+ preg_match($pat, $html_string, $matches, PREG_OFFSET_CAPTURE);
+ if($matches) {
+ $key_tags_pos[] = $matches[0][1];
+ } else {
+ $key_tags_pos[] = null;
+ }
+ }
+ // no opening head tag
+ if (is_null($key_tags_pos[1])) {
+ return array();
+ }
+ // the effective </head> is the min of the following
+ if (is_null($key_tags_pos[2])) {
+ $key_tags_pos[2] = strlen($html_string);
+ }
+ foreach (array($key_tags_pos[3], $key_tags_pos[4]) as $pos) {
+ if (!is_null($pos) && $pos < $key_tags_pos[2]) {
+ $key_tags_pos[2] = $pos;
+ }
+ }
+ // closing head tag comes before opening head tag
+ if ($key_tags_pos[1] > $key_tags_pos[2]) {
+ return array();
+ }
+ // if there is an opening html tag, make sure the opening head tag
+ // comes after it
+ if (!is_null($key_tags_pos[0]) && $key_tags_pos[1] < $key_tags_pos[0]) {
+ return array();
+ }
+ $html_string = substr($html_string, $key_tags_pos[1],
+ ($key_tags_pos[2]-$key_tags_pos[1]));
+
+ $link_data = array();
+ $link_matches = array();
+
+ if (!preg_match_all($this->tagPattern('meta', false, 'maybe'),
+ $html_string, $link_matches)) {
+ return array();
+ }
+
+ foreach ($link_matches[0] as $link) {
+ $attr_matches = array();
+ preg_match_all($this->_attr_find, $link, $attr_matches);
+ $link_attrs = array();
+ foreach ($attr_matches[0] as $index => $full_match) {
+ $name = $attr_matches[1][$index];
+ $value = $this->replaceEntities(
+ $this->removeQuotes($attr_matches[2][$index]));
+
+ $link_attrs[strtolower($name)] = $value;
+ }
+ $link_data[] = $link_attrs;
+ }
+
+ return $link_data;
+ }
+
+ /**
+ * Looks for a META tag with an "http-equiv" attribute whose value
+ * is one of ("x-xrds-location", "x-yadis-location"), ignoring
+ * case. If such a META tag is found, its "content" attribute
+ * value is returned.
+ *
+ * @param string $html_string An HTML document in string format
+ * @return mixed $content The "content" attribute value of the
+ * META tag, if found, or null if no such tag was found.
+ */
+ function getHTTPEquiv($html_string)
+ {
+ $meta_tags = $this->getMetaTags($html_string);
+
+ if ($meta_tags) {
+ foreach ($meta_tags as $tag) {
+ if (array_key_exists('http-equiv', $tag) &&
+ (in_array(strtolower($tag['http-equiv']),
+ array('x-xrds-location', 'x-yadis-location'))) &&
+ array_key_exists('content', $tag)) {
+ return $tag['content'];
+ }
+ }
+ }
+
+ return null;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/PlainHTTPFetcher.php b/extlib/Auth/Yadis/PlainHTTPFetcher.php
new file mode 100644
index 000000000..8882e3cef
--- /dev/null
+++ b/extlib/Auth/Yadis/PlainHTTPFetcher.php
@@ -0,0 +1,251 @@
+<?php
+
+/**
+ * This module contains the plain non-curl HTTP fetcher
+ * implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Interface import
+ */
+require_once "Auth/Yadis/HTTPFetcher.php";
+
+/**
+ * This class implements a plain, hand-built socket-based fetcher
+ * which will be used in the event that CURL is unavailable.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_PlainHTTPFetcher extends Auth_Yadis_HTTPFetcher {
+ /**
+ * Does this fetcher support SSL URLs?
+ */
+ function supportsSSL()
+ {
+ return function_exists('openssl_open');
+ }
+
+ function get($url, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $redir = true;
+
+ $stop = time() + $this->timeout;
+ $off = $this->timeout;
+
+ while ($redir && ($off > 0)) {
+
+ $parts = parse_url($url);
+
+ $specify_port = true;
+
+ // Set a default port.
+ if (!array_key_exists('port', $parts)) {
+ $specify_port = false;
+ if ($parts['scheme'] == 'http') {
+ $parts['port'] = 80;
+ } elseif ($parts['scheme'] == 'https') {
+ $parts['port'] = 443;
+ } else {
+ return null;
+ }
+ }
+
+ if (!array_key_exists('path', $parts)) {
+ $parts['path'] = '/';
+ }
+
+ $host = $parts['host'];
+
+ if ($parts['scheme'] == 'https') {
+ $host = 'ssl://' . $host;
+ }
+
+ $user_agent = Auth_OpenID_USER_AGENT;
+
+ $headers = array(
+ "GET ".$parts['path'].
+ (array_key_exists('query', $parts) ?
+ "?".$parts['query'] : "").
+ " HTTP/1.0",
+ "User-Agent: $user_agent",
+ "Host: ".$parts['host'].
+ ($specify_port ? ":".$parts['port'] : ""),
+ "Range: 0-".
+ (1024*Auth_OpenID_FETCHER_MAX_RESPONSE_KB),
+ "Port: ".$parts['port']);
+
+ $errno = 0;
+ $errstr = '';
+
+ if ($extra_headers) {
+ foreach ($extra_headers as $h) {
+ $headers[] = $h;
+ }
+ }
+
+ @$sock = fsockopen($host, $parts['port'], $errno, $errstr,
+ $this->timeout);
+ if ($sock === false) {
+ return false;
+ }
+
+ stream_set_timeout($sock, $this->timeout);
+
+ fputs($sock, implode("\r\n", $headers) . "\r\n\r\n");
+
+ $data = "";
+ $kilobytes = 0;
+ while (!feof($sock) &&
+ $kilobytes < Auth_OpenID_FETCHER_MAX_RESPONSE_KB ) {
+ $data .= fgets($sock, 1024);
+ $kilobytes += 1;
+ }
+
+ fclose($sock);
+
+ // Split response into header and body sections
+ list($headers, $body) = explode("\r\n\r\n", $data, 2);
+ $headers = explode("\r\n", $headers);
+
+ $http_code = explode(" ", $headers[0]);
+ $code = $http_code[1];
+
+ if (in_array($code, array('301', '302'))) {
+ $url = $this->_findRedirect($headers);
+ $redir = true;
+ } else {
+ $redir = false;
+ }
+
+ $off = $stop - time();
+ }
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (preg_match("/:/", $header)) {
+ $parts = explode(": ", $header, 2);
+
+ if (count($parts) == 2) {
+ list($name, $value) = $parts;
+ $new_headers[$name] = $value;
+ }
+ }
+
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $code, $new_headers, $body);
+ }
+
+ function post($url, $body, $extra_headers = null)
+ {
+ if (!$this->canFetchURL($url)) {
+ return null;
+ }
+
+ $parts = parse_url($url);
+
+ $headers = array();
+
+ $post_path = $parts['path'];
+ if (isset($parts['query'])) {
+ $post_path .= '?' . $parts['query'];
+ }
+
+ $headers[] = "POST ".$post_path." HTTP/1.0";
+ $headers[] = "Host: " . $parts['host'];
+ $headers[] = "Content-type: application/x-www-form-urlencoded";
+ $headers[] = "Content-length: " . strval(strlen($body));
+
+ if ($extra_headers &&
+ is_array($extra_headers)) {
+ $headers = array_merge($headers, $extra_headers);
+ }
+
+ // Join all headers together.
+ $all_headers = implode("\r\n", $headers);
+
+ // Add headers, two newlines, and request body.
+ $request = $all_headers . "\r\n\r\n" . $body;
+
+ // Set a default port.
+ if (!array_key_exists('port', $parts)) {
+ if ($parts['scheme'] == 'http') {
+ $parts['port'] = 80;
+ } elseif ($parts['scheme'] == 'https') {
+ $parts['port'] = 443;
+ } else {
+ return null;
+ }
+ }
+
+ if ($parts['scheme'] == 'https') {
+ $parts['host'] = sprintf("ssl://%s", $parts['host']);
+ }
+
+ // Connect to the remote server.
+ $errno = 0;
+ $errstr = '';
+
+ $sock = fsockopen($parts['host'], $parts['port'], $errno, $errstr,
+ $this->timeout);
+
+ if ($sock === false) {
+ return null;
+ }
+
+ stream_set_timeout($sock, $this->timeout);
+
+ // Write the POST request.
+ fputs($sock, $request);
+
+ // Get the response from the server.
+ $response = "";
+ while (!feof($sock)) {
+ if ($data = fgets($sock, 128)) {
+ $response .= $data;
+ } else {
+ break;
+ }
+ }
+
+ // Split the request into headers and body.
+ list($headers, $response_body) = explode("\r\n\r\n", $response, 2);
+
+ $headers = explode("\r\n", $headers);
+
+ // Expect the first line of the headers data to be something
+ // like HTTP/1.1 200 OK. Split the line on spaces and take
+ // the second token, which should be the return code.
+ $http_code = explode(" ", $headers[0]);
+ $code = $http_code[1];
+
+ $new_headers = array();
+
+ foreach ($headers as $header) {
+ if (preg_match("/:/", $header)) {
+ list($name, $value) = explode(": ", $header, 2);
+ $new_headers[$name] = $value;
+ }
+
+ }
+
+ return new Auth_Yadis_HTTPResponse($url, $code,
+ $new_headers, $response_body);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/XML.php b/extlib/Auth/Yadis/XML.php
new file mode 100644
index 000000000..4854f12bb
--- /dev/null
+++ b/extlib/Auth/Yadis/XML.php
@@ -0,0 +1,374 @@
+<?php
+
+/**
+ * XML-parsing classes to wrap the domxml and DOM extensions for PHP 4
+ * and 5, respectively.
+ *
+ * @package OpenID
+ */
+
+/**
+ * The base class for wrappers for available PHP XML-parsing
+ * extensions. To work with this Yadis library, subclasses of this
+ * class MUST implement the API as defined in the remarks for this
+ * class. Subclasses of Auth_Yadis_XMLParser are used to wrap
+ * particular PHP XML extensions such as 'domxml'. These are used
+ * internally by the library depending on the availability of
+ * supported PHP XML extensions.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_XMLParser {
+ /**
+ * Initialize an instance of Auth_Yadis_XMLParser with some
+ * XML and namespaces. This SHOULD NOT be overridden by
+ * subclasses.
+ *
+ * @param string $xml_string A string of XML to be parsed.
+ * @param array $namespace_map An array of ($ns_name => $ns_uri)
+ * to be registered with the XML parser. May be empty.
+ * @return boolean $result True if the initialization and
+ * namespace registration(s) succeeded; false otherwise.
+ */
+ function init($xml_string, $namespace_map)
+ {
+ if (!$this->setXML($xml_string)) {
+ return false;
+ }
+
+ foreach ($namespace_map as $prefix => $uri) {
+ if (!$this->registerNamespace($prefix, $uri)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Register a namespace with the XML parser. This should be
+ * overridden by subclasses.
+ *
+ * @param string $prefix The namespace prefix to appear in XML tag
+ * names.
+ *
+ * @param string $uri The namespace URI to be used to identify the
+ * namespace in the XML.
+ *
+ * @return boolean $result True if the registration succeeded;
+ * false otherwise.
+ */
+ function registerNamespace($prefix, $uri)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Set this parser object's XML payload. This should be
+ * overridden by subclasses.
+ *
+ * @param string $xml_string The XML string to pass to this
+ * object's XML parser.
+ *
+ * @return boolean $result True if the initialization succeeded;
+ * false otherwise.
+ */
+ function setXML($xml_string)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Evaluate an XPath expression and return the resulting node
+ * list. This should be overridden by subclasses.
+ *
+ * @param string $xpath The XPath expression to be evaluated.
+ *
+ * @param mixed $node A node object resulting from a previous
+ * evalXPath call. This node, if specified, provides the context
+ * for the evaluation of this xpath expression.
+ *
+ * @return array $node_list An array of matching opaque node
+ * objects to be used with other methods of this parser class.
+ */
+ function evalXPath($xpath, $node = null)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Return the textual content of a specified node.
+ *
+ * @param mixed $node A node object from a previous call to
+ * $this->evalXPath().
+ *
+ * @return string $content The content of this node.
+ */
+ function content($node)
+ {
+ // Not implemented.
+ }
+
+ /**
+ * Return the attributes of a specified node.
+ *
+ * @param mixed $node A node object from a previous call to
+ * $this->evalXPath().
+ *
+ * @return array $attrs An array mapping attribute names to
+ * values.
+ */
+ function attributes($node)
+ {
+ // Not implemented.
+ }
+}
+
+/**
+ * This concrete implementation of Auth_Yadis_XMLParser implements
+ * the appropriate API for the 'domxml' extension which is typically
+ * packaged with PHP 4. This class will be used whenever the 'domxml'
+ * extension is detected. See the Auth_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_domxml extends Auth_Yadis_XMLParser {
+ function Auth_Yadis_domxml()
+ {
+ $this->xml = null;
+ $this->doc = null;
+ $this->xpath = null;
+ $this->errors = array();
+ }
+
+ function setXML($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->doc = @domxml_open_mem($xml_string, DOMXML_LOAD_PARSING,
+ $this->errors);
+
+ if (!$this->doc) {
+ return false;
+ }
+
+ $this->xpath = $this->doc->xpath_new_context();
+
+ return true;
+ }
+
+ function registerNamespace($prefix, $uri)
+ {
+ return xpath_register_ns($this->xpath, $prefix, $uri);
+ }
+
+ function &evalXPath($xpath, $node = null)
+ {
+ if ($node) {
+ $result = @$this->xpath->xpath_eval($xpath, $node);
+ } else {
+ $result = @$this->xpath->xpath_eval($xpath);
+ }
+
+ if (!$result) {
+ $n = array();
+ return $n;
+ }
+
+ if (!$result->nodeset) {
+ $n = array();
+ return $n;
+ }
+
+ return $result->nodeset;
+ }
+
+ function content($node)
+ {
+ if ($node) {
+ return $node->get_content();
+ }
+ }
+
+ function attributes($node)
+ {
+ if ($node) {
+ $arr = $node->attributes();
+ $result = array();
+
+ if ($arr) {
+ foreach ($arr as $attrnode) {
+ $result[$attrnode->name] = $attrnode->value;
+ }
+ }
+
+ return $result;
+ }
+ }
+}
+
+/**
+ * This concrete implementation of Auth_Yadis_XMLParser implements
+ * the appropriate API for the 'dom' extension which is typically
+ * packaged with PHP 5. This class will be used whenever the 'dom'
+ * extension is detected. See the Auth_Yadis_XMLParser class for
+ * details on this class's methods.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_dom extends Auth_Yadis_XMLParser {
+ function Auth_Yadis_dom()
+ {
+ $this->xml = null;
+ $this->doc = null;
+ $this->xpath = null;
+ $this->errors = array();
+ }
+
+ function setXML($xml_string)
+ {
+ $this->xml = $xml_string;
+ $this->doc = new DOMDocument;
+
+ if (!$this->doc) {
+ return false;
+ }
+
+ if (!@$this->doc->loadXML($xml_string)) {
+ return false;
+ }
+
+ $this->xpath = new DOMXPath($this->doc);
+
+ if ($this->xpath) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function registerNamespace($prefix, $uri)
+ {
+ return $this->xpath->registerNamespace($prefix, $uri);
+ }
+
+ function &evalXPath($xpath, $node = null)
+ {
+ if ($node) {
+ $result = @$this->xpath->query($xpath, $node);
+ } else {
+ $result = @$this->xpath->query($xpath);
+ }
+
+ $n = array();
+
+ if (!$result) {
+ return $n;
+ }
+
+ for ($i = 0; $i < $result->length; $i++) {
+ $n[] = $result->item($i);
+ }
+
+ return $n;
+ }
+
+ function content($node)
+ {
+ if ($node) {
+ return $node->textContent;
+ }
+ }
+
+ function attributes($node)
+ {
+ if ($node) {
+ $arr = $node->attributes;
+ $result = array();
+
+ if ($arr) {
+ for ($i = 0; $i < $arr->length; $i++) {
+ $node = $arr->item($i);
+ $result[$node->nodeName] = $node->nodeValue;
+ }
+ }
+
+ return $result;
+ }
+ }
+}
+
+global $__Auth_Yadis_defaultParser;
+$__Auth_Yadis_defaultParser = null;
+
+/**
+ * Set a default parser to override the extension-driven selection of
+ * available parser classes. This is helpful in a test environment or
+ * one in which multiple parsers can be used but one is more
+ * desirable.
+ *
+ * @param Auth_Yadis_XMLParser $parser An instance of a
+ * Auth_Yadis_XMLParser subclass.
+ */
+function Auth_Yadis_setDefaultParser(&$parser)
+{
+ global $__Auth_Yadis_defaultParser;
+ $__Auth_Yadis_defaultParser =& $parser;
+}
+
+function Auth_Yadis_getSupportedExtensions()
+{
+ return array(
+ 'dom' => array('classname' => 'Auth_Yadis_dom',
+ 'libname' => array('dom.so', 'dom.dll')),
+ 'domxml' => array('classname' => 'Auth_Yadis_domxml',
+ 'libname' => array('domxml.so', 'php_domxml.dll')),
+ );
+}
+
+/**
+ * Returns an instance of a Auth_Yadis_XMLParser subclass based on
+ * the availability of PHP extensions for XML parsing. If
+ * Auth_Yadis_setDefaultParser has been called, the parser used in
+ * that call will be returned instead.
+ */
+function &Auth_Yadis_getXMLParser()
+{
+ global $__Auth_Yadis_defaultParser;
+
+ if (isset($__Auth_Yadis_defaultParser)) {
+ return $__Auth_Yadis_defaultParser;
+ }
+
+ $p = null;
+ $classname = null;
+
+ $extensions = Auth_Yadis_getSupportedExtensions();
+
+ // Return a wrapper for the resident implementation, if any.
+ foreach ($extensions as $name => $params) {
+ if (!extension_loaded($name)) {
+ foreach ($params['libname'] as $libname) {
+ if (@dl($libname)) {
+ $classname = $params['classname'];
+ }
+ }
+ } else {
+ $classname = $params['classname'];
+ }
+ if (isset($classname)) {
+ $p = new $classname();
+ return $p;
+ }
+ }
+
+ if (!isset($p)) {
+ trigger_error('No XML parser was found', E_USER_ERROR);
+ } else {
+ Auth_Yadis_setDefaultParser($p);
+ }
+
+ return $p;
+}
+
+?>
diff --git a/extlib/Auth/Yadis/XRDS.php b/extlib/Auth/Yadis/XRDS.php
new file mode 100644
index 000000000..f14a7948e
--- /dev/null
+++ b/extlib/Auth/Yadis/XRDS.php
@@ -0,0 +1,478 @@
+<?php
+
+/**
+ * This module contains the XRDS parsing code.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Require the XPath implementation.
+ */
+require_once 'Auth/Yadis/XML.php';
+
+/**
+ * This match mode means a given service must match ALL filters passed
+ * to the Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ALL', 101);
+
+/**
+ * This match mode means a given service must match ANY filters (at
+ * least one) passed to the Auth_Yadis_XRDS::services() call.
+ */
+define('SERVICES_YADIS_MATCH_ANY', 102);
+
+/**
+ * The priority value used for service elements with no priority
+ * specified.
+ */
+define('SERVICES_YADIS_MAX_PRIORITY', pow(2, 30));
+
+/**
+ * XRD XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRD_2_0', 'xri://$xrd*($v*2.0)');
+
+/**
+ * XRDS XML namespace
+ */
+define('Auth_Yadis_XMLNS_XRDS', 'xri://$xrds');
+
+function Auth_Yadis_getNSMap()
+{
+ return array('xrds' => Auth_Yadis_XMLNS_XRDS,
+ 'xrd' => Auth_Yadis_XMLNS_XRD_2_0);
+}
+
+/**
+ * @access private
+ */
+function Auth_Yadis_array_scramble($arr)
+{
+ $result = array();
+
+ while (count($arr)) {
+ $index = array_rand($arr, 1);
+ $result[] = $arr[$index];
+ unset($arr[$index]);
+ }
+
+ return $result;
+}
+
+/**
+ * This class represents a <Service> element in an XRDS document.
+ * Objects of this type are returned by
+ * Auth_Yadis_XRDS::services() and
+ * Auth_Yadis_Yadis::services(). Each object corresponds directly
+ * to a <Service> element in the XRDS and supplies a
+ * getElements($name) method which you should use to inspect the
+ * element's contents. See {@link Auth_Yadis_Yadis} for more
+ * information on the role this class plays in Yadis discovery.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Service {
+
+ /**
+ * Creates an empty service object.
+ */
+ function Auth_Yadis_Service()
+ {
+ $this->element = null;
+ $this->parser = null;
+ }
+
+ /**
+ * Return the URIs in the "Type" elements, if any, of this Service
+ * element.
+ *
+ * @return array $type_uris An array of Type URI strings.
+ */
+ function getTypes()
+ {
+ $t = array();
+ foreach ($this->getElements('xrd:Type') as $elem) {
+ $c = $this->parser->content($elem);
+ if ($c) {
+ $t[] = $c;
+ }
+ }
+ return $t;
+ }
+
+ function matchTypes($type_uris)
+ {
+ $result = array();
+
+ foreach ($this->getTypes() as $typ) {
+ if (in_array($typ, $type_uris)) {
+ $result[] = $typ;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return the URIs in the "URI" elements, if any, of this Service
+ * element. The URIs are returned sorted in priority order.
+ *
+ * @return array $uris An array of URI strings.
+ */
+ function getURIs()
+ {
+ $uris = array();
+ $last = array();
+
+ foreach ($this->getElements('xrd:URI') as $elem) {
+ $uri_string = $this->parser->content($elem);
+ $attrs = $this->parser->attributes($elem);
+ if ($attrs &&
+ array_key_exists('priority', $attrs)) {
+ $priority = intval($attrs['priority']);
+ if (!array_key_exists($priority, $uris)) {
+ $uris[$priority] = array();
+ }
+
+ $uris[$priority][] = $uri_string;
+ } else {
+ $last[] = $uri_string;
+ }
+ }
+
+ $keys = array_keys($uris);
+ sort($keys);
+
+ // Rebuild array of URIs.
+ $result = array();
+ foreach ($keys as $k) {
+ $new_uris = Auth_Yadis_array_scramble($uris[$k]);
+ $result = array_merge($result, $new_uris);
+ }
+
+ $result = array_merge($result,
+ Auth_Yadis_array_scramble($last));
+
+ return $result;
+ }
+
+ /**
+ * Returns the "priority" attribute value of this <Service>
+ * element, if the attribute is present. Returns null if not.
+ *
+ * @return mixed $result Null or integer, depending on whether
+ * this Service element has a 'priority' attribute.
+ */
+ function getPriority()
+ {
+ $attributes = $this->parser->attributes($this->element);
+
+ if (array_key_exists('priority', $attributes)) {
+ return intval($attributes['priority']);
+ }
+
+ return null;
+ }
+
+ /**
+ * Used to get XML elements from this object's <Service> element.
+ *
+ * This is what you should use to get all custom information out
+ * of this element. This is used by service filter functions to
+ * determine whether a service element contains specific tags,
+ * etc. NOTE: this only considers elements which are direct
+ * children of the <Service> element for this object.
+ *
+ * @param string $name The name of the element to look for
+ * @return array $list An array of elements with the specified
+ * name which are direct children of the <Service> element. The
+ * nodes returned by this function can be passed to $this->parser
+ * methods (see {@link Auth_Yadis_XMLParser}).
+ */
+ function getElements($name)
+ {
+ return $this->parser->evalXPath($name, $this->element);
+ }
+}
+
+/*
+ * Return the expiration date of this XRD element, or None if no
+ * expiration was specified.
+ *
+ * @param $default The value to use as the expiration if no expiration
+ * was specified in the XRD.
+ */
+function Auth_Yadis_getXRDExpiration($xrd_element, $default=null)
+{
+ $expires_element = $xrd_element->$parser->evalXPath('/xrd:Expires');
+ if ($expires_element === null) {
+ return $default;
+ } else {
+ $expires_string = $expires_element->text;
+
+ // Will raise ValueError if the string is not the expected
+ // format
+ $t = strptime($expires_string, "%Y-%m-%dT%H:%M:%SZ");
+
+ if ($t === false) {
+ return false;
+ }
+
+ // [int $hour [, int $minute [, int $second [,
+ // int $month [, int $day [, int $year ]]]]]]
+ return mktime($t['tm_hour'], $t['tm_min'], $t['tm_sec'],
+ $t['tm_mon'], $t['tm_day'], $t['tm_year']);
+ }
+}
+
+/**
+ * This class performs parsing of XRDS documents.
+ *
+ * You should not instantiate this class directly; rather, call
+ * parseXRDS statically:
+ *
+ * <pre> $xrds = Auth_Yadis_XRDS::parseXRDS($xml_string);</pre>
+ *
+ * If the XRDS can be parsed and is valid, an instance of
+ * Auth_Yadis_XRDS will be returned. Otherwise, null will be
+ * returned. This class is used by the Auth_Yadis_Yadis::discover
+ * method.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_XRDS {
+
+ /**
+ * Instantiate a Auth_Yadis_XRDS object. Requires an XPath
+ * instance which has been used to parse a valid XRDS document.
+ */
+ function Auth_Yadis_XRDS(&$xmlParser, &$xrdNodes)
+ {
+ $this->parser =& $xmlParser;
+ $this->xrdNode = $xrdNodes[count($xrdNodes) - 1];
+ $this->allXrdNodes =& $xrdNodes;
+ $this->serviceList = array();
+ $this->_parse();
+ }
+
+ /**
+ * Parse an XML string (XRDS document) and return either a
+ * Auth_Yadis_XRDS object or null, depending on whether the
+ * XRDS XML is valid.
+ *
+ * @param string $xml_string An XRDS XML string.
+ * @return mixed $xrds An instance of Auth_Yadis_XRDS or null,
+ * depending on the validity of $xml_string
+ */
+ function &parseXRDS($xml_string, $extra_ns_map = null)
+ {
+ $_null = null;
+
+ if (!$xml_string) {
+ return $_null;
+ }
+
+ $parser = Auth_Yadis_getXMLParser();
+
+ $ns_map = Auth_Yadis_getNSMap();
+
+ if ($extra_ns_map && is_array($extra_ns_map)) {
+ $ns_map = array_merge($ns_map, $extra_ns_map);
+ }
+
+ if (!($parser && $parser->init($xml_string, $ns_map))) {
+ return $_null;
+ }
+
+ // Try to get root element.
+ $root = $parser->evalXPath('/xrds:XRDS[1]');
+ if (!$root) {
+ return $_null;
+ }
+
+ if (is_array($root)) {
+ $root = $root[0];
+ }
+
+ $attrs = $parser->attributes($root);
+
+ if (array_key_exists('xmlns:xrd', $attrs) &&
+ $attrs['xmlns:xrd'] != Auth_Yadis_XMLNS_XRDS) {
+ return $_null;
+ } else if (array_key_exists('xmlns', $attrs) &&
+ preg_match('/xri/', $attrs['xmlns']) &&
+ $attrs['xmlns'] != Auth_Yadis_XMLNS_XRD_2_0) {
+ return $_null;
+ }
+
+ // Get the last XRD node.
+ $xrd_nodes = $parser->evalXPath('/xrds:XRDS[1]/xrd:XRD');
+
+ if (!$xrd_nodes) {
+ return $_null;
+ }
+
+ $xrds = new Auth_Yadis_XRDS($parser, $xrd_nodes);
+ return $xrds;
+ }
+
+ /**
+ * @access private
+ */
+ function _addService($priority, $service)
+ {
+ $priority = intval($priority);
+
+ if (!array_key_exists($priority, $this->serviceList)) {
+ $this->serviceList[$priority] = array();
+ }
+
+ $this->serviceList[$priority][] = $service;
+ }
+
+ /**
+ * Creates the service list using nodes from the XRDS XML
+ * document.
+ *
+ * @access private
+ */
+ function _parse()
+ {
+ $this->serviceList = array();
+
+ $services = $this->parser->evalXPath('xrd:Service', $this->xrdNode);
+
+ foreach ($services as $node) {
+ $s =& new Auth_Yadis_Service();
+ $s->element = $node;
+ $s->parser =& $this->parser;
+
+ $priority = $s->getPriority();
+
+ if ($priority === null) {
+ $priority = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ $this->_addService($priority, $s);
+ }
+ }
+
+ /**
+ * Returns a list of service objects which correspond to <Service>
+ * elements in the XRDS XML document for this object.
+ *
+ * Optionally, an array of filter callbacks may be given to limit
+ * the list of returned service objects. Furthermore, the default
+ * mode is to return all service objects which match ANY of the
+ * specified filters, but $filter_mode may be
+ * SERVICES_YADIS_MATCH_ALL if you want to be sure that the
+ * returned services match all the given filters. See {@link
+ * Auth_Yadis_Yadis} for detailed usage information on filter
+ * functions.
+ *
+ * @param mixed $filters An array of callbacks to filter the
+ * returned services, or null if all services are to be returned.
+ * @param integer $filter_mode SERVICES_YADIS_MATCH_ALL or
+ * SERVICES_YADIS_MATCH_ANY, depending on whether the returned
+ * services should match ALL or ANY of the specified filters,
+ * respectively.
+ * @return mixed $services An array of {@link
+ * Auth_Yadis_Service} objects if $filter_mode is a valid
+ * mode; null if $filter_mode is an invalid mode (i.e., not
+ * SERVICES_YADIS_MATCH_ANY or SERVICES_YADIS_MATCH_ALL).
+ */
+ function services($filters = null,
+ $filter_mode = SERVICES_YADIS_MATCH_ANY)
+ {
+
+ $pri_keys = array_keys($this->serviceList);
+ sort($pri_keys, SORT_NUMERIC);
+
+ // If no filters are specified, return the entire service
+ // list, ordered by priority.
+ if (!$filters ||
+ (!is_array($filters))) {
+
+ $result = array();
+ foreach ($pri_keys as $pri) {
+ $result = array_merge($result, $this->serviceList[$pri]);
+ }
+
+ return $result;
+ }
+
+ // If a bad filter mode is specified, return null.
+ if (!in_array($filter_mode, array(SERVICES_YADIS_MATCH_ANY,
+ SERVICES_YADIS_MATCH_ALL))) {
+ return null;
+ }
+
+ // Otherwise, use the callbacks in the filter list to
+ // determine which services are returned.
+ $filtered = array();
+
+ foreach ($pri_keys as $priority_value) {
+ $service_obj_list = $this->serviceList[$priority_value];
+
+ foreach ($service_obj_list as $service) {
+
+ $matches = 0;
+
+ foreach ($filters as $filter) {
+ if (call_user_func_array($filter, array($service))) {
+ $matches++;
+
+ if ($filter_mode == SERVICES_YADIS_MATCH_ANY) {
+ $pri = $service->getPriority();
+ if ($pri === null) {
+ $pri = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ if (!array_key_exists($pri, $filtered)) {
+ $filtered[$pri] = array();
+ }
+
+ $filtered[$pri][] = $service;
+ break;
+ }
+ }
+ }
+
+ if (($filter_mode == SERVICES_YADIS_MATCH_ALL) &&
+ ($matches == count($filters))) {
+
+ $pri = $service->getPriority();
+ if ($pri === null) {
+ $pri = SERVICES_YADIS_MAX_PRIORITY;
+ }
+
+ if (!array_key_exists($pri, $filtered)) {
+ $filtered[$pri] = array();
+ }
+ $filtered[$pri][] = $service;
+ }
+ }
+ }
+
+ $pri_keys = array_keys($filtered);
+ sort($pri_keys, SORT_NUMERIC);
+
+ $result = array();
+ foreach ($pri_keys as $pri) {
+ $result = array_merge($result, $filtered[$pri]);
+ }
+
+ return $result;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/extlib/Auth/Yadis/XRI.php b/extlib/Auth/Yadis/XRI.php
new file mode 100644
index 000000000..4e3462317
--- /dev/null
+++ b/extlib/Auth/Yadis/XRI.php
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * Routines for XRI resolution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+require_once 'Auth/Yadis/Misc.php';
+require_once 'Auth/Yadis/Yadis.php';
+require_once 'Auth/OpenID.php';
+
+function Auth_Yadis_getDefaultProxy()
+{
+ return 'http://xri.net/';
+}
+
+function Auth_Yadis_getXRIAuthorities()
+{
+ return array('!', '=', '@', '+', '$', '(');
+}
+
+function Auth_Yadis_getEscapeRE()
+{
+ $parts = array();
+ foreach (array_merge(Auth_Yadis_getUCSChars(),
+ Auth_Yadis_getIPrivateChars()) as $pair) {
+ list($m, $n) = $pair;
+ $parts[] = sprintf("%s-%s", chr($m), chr($n));
+ }
+
+ return sprintf('/[%s]/', implode('', $parts));
+}
+
+function Auth_Yadis_getXrefRE()
+{
+ return '/\((.*?)\)/';
+}
+
+function Auth_Yadis_identifierScheme($identifier)
+{
+ if (Auth_Yadis_startswith($identifier, 'xri://') ||
+ ($identifier &&
+ in_array($identifier[0], Auth_Yadis_getXRIAuthorities()))) {
+ return "XRI";
+ } else {
+ return "URI";
+ }
+}
+
+function Auth_Yadis_toIRINormal($xri)
+{
+ if (!Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+
+ return Auth_Yadis_escapeForIRI($xri);
+}
+
+function _escape_xref($xref_match)
+{
+ $xref = $xref_match[0];
+ $xref = str_replace('/', '%2F', $xref);
+ $xref = str_replace('?', '%3F', $xref);
+ $xref = str_replace('#', '%23', $xref);
+ return $xref;
+}
+
+function Auth_Yadis_escapeForIRI($xri)
+{
+ $xri = str_replace('%', '%25', $xri);
+ $xri = preg_replace_callback(Auth_Yadis_getXrefRE(),
+ '_escape_xref', $xri);
+ return $xri;
+}
+
+function Auth_Yadis_toURINormal($xri)
+{
+ return Auth_Yadis_iriToURI(Auth_Yadis_toIRINormal($xri));
+}
+
+function Auth_Yadis_iriToURI($iri)
+{
+ if (1) {
+ return $iri;
+ } else {
+ // According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
+ return preg_replace_callback(Auth_Yadis_getEscapeRE(),
+ 'Auth_Yadis_pct_escape_unicode', $iri);
+ }
+}
+
+
+function Auth_Yadis_XRIAppendArgs($url, $args)
+{
+ // Append some arguments to an HTTP query. Yes, this is just like
+ // OpenID's appendArgs, but with special seasoning for XRI
+ // queries.
+
+ if (count($args) == 0) {
+ return $url;
+ }
+
+ // Non-empty array; if it is an array of arrays, use multisort;
+ // otherwise use sort.
+ if (array_key_exists(0, $args) &&
+ is_array($args[0])) {
+ // Do nothing here.
+ } else {
+ $keys = array_keys($args);
+ sort($keys);
+ $new_args = array();
+ foreach ($keys as $key) {
+ $new_args[] = array($key, $args[$key]);
+ }
+ $args = $new_args;
+ }
+
+ // According to XRI Resolution section "QXRI query parameters":
+ //
+ // "If the original QXRI had a null query component (only a
+ // leading question mark), or a query component consisting of
+ // only question marks, one additional leading question mark MUST
+ // be added when adding any XRI resolution parameters."
+ if (strpos(rtrim($url, '?'), '?') !== false) {
+ $sep = '&';
+ } else {
+ $sep = '?';
+ }
+
+ return $url . $sep . Auth_OpenID::httpBuildQuery($args);
+}
+
+function Auth_Yadis_providerIsAuthoritative($providerID, $canonicalID)
+{
+ $lastbang = strrpos($canonicalID, '!');
+ $p = substr($canonicalID, 0, $lastbang);
+ return $p == $providerID;
+}
+
+function Auth_Yadis_rootAuthority($xri)
+{
+ // Return the root authority for an XRI.
+
+ $root = null;
+
+ if (Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = substr($xri, 6);
+ }
+
+ $authority = explode('/', $xri, 2);
+ $authority = $authority[0];
+ if ($authority[0] == '(') {
+ // Cross-reference.
+ // XXX: This is incorrect if someone nests cross-references so
+ // there is another close-paren in there. Hopefully nobody
+ // does that before we have a real xriparse function.
+ // Hopefully nobody does that *ever*.
+ $root = substr($authority, 0, strpos($authority, ')') + 1);
+ } else if (in_array($authority[0], Auth_Yadis_getXRIAuthorities())) {
+ // Other XRI reference.
+ $root = $authority[0];
+ } else {
+ // IRI reference.
+ $_segments = explode("!", $authority);
+ $segments = array();
+ foreach ($_segments as $s) {
+ $segments = array_merge($segments, explode("*", $s));
+ }
+ $root = $segments[0];
+ }
+
+ return Auth_Yadis_XRI($root);
+}
+
+function Auth_Yadis_XRI($xri)
+{
+ if (!Auth_Yadis_startswith($xri, 'xri://')) {
+ $xri = 'xri://' . $xri;
+ }
+ return $xri;
+}
+
+function Auth_Yadis_getCanonicalID($iname, $xrds)
+{
+ // Returns false or a canonical ID value.
+
+ // Now nodes are in reverse order.
+ $xrd_list = array_reverse($xrds->allXrdNodes);
+ $parser =& $xrds->parser;
+ $node = $xrd_list[0];
+
+ $canonicalID_nodes = $parser->evalXPath('xrd:CanonicalID', $node);
+
+ if (!$canonicalID_nodes) {
+ return false;
+ }
+
+ $canonicalID = $canonicalID_nodes[0];
+ $canonicalID = Auth_Yadis_XRI($parser->content($canonicalID));
+
+ $childID = $canonicalID;
+
+ for ($i = 1; $i < count($xrd_list); $i++) {
+ $xrd = $xrd_list[$i];
+
+ $parent_sought = substr($childID, 0, strrpos($childID, '!'));
+ $parentCID = $parser->evalXPath('xrd:CanonicalID', $xrd);
+ if (!$parentCID) {
+ return false;
+ }
+ $parentCID = Auth_Yadis_XRI($parser->content($parentCID[0]));
+
+ if (strcasecmp($parent_sought, $parentCID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ $childID = $parent_sought;
+ }
+
+ $root = Auth_Yadis_rootAuthority($iname);
+ if (!Auth_Yadis_providerIsAuthoritative($root, $childID)) {
+ // raise XRDSFraud.
+ return false;
+ }
+
+ return $canonicalID;
+}
+
+?>
diff --git a/extlib/Auth/Yadis/XRIRes.php b/extlib/Auth/Yadis/XRIRes.php
new file mode 100644
index 000000000..4e8e8d037
--- /dev/null
+++ b/extlib/Auth/Yadis/XRIRes.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Code for using a proxy XRI resolver.
+ */
+
+require_once 'Auth/Yadis/XRDS.php';
+require_once 'Auth/Yadis/XRI.php';
+
+class Auth_Yadis_ProxyResolver {
+ function Auth_Yadis_ProxyResolver(&$fetcher, $proxy_url = null)
+ {
+ $this->fetcher =& $fetcher;
+ $this->proxy_url = $proxy_url;
+ if (!$this->proxy_url) {
+ $this->proxy_url = Auth_Yadis_getDefaultProxy();
+ }
+ }
+
+ function queryURL($xri, $service_type = null)
+ {
+ // trim off the xri:// prefix
+ $qxri = substr(Auth_Yadis_toURINormal($xri), 6);
+ $hxri = $this->proxy_url . $qxri;
+ $args = array(
+ '_xrd_r' => 'application/xrds+xml'
+ );
+
+ if ($service_type) {
+ $args['_xrd_t'] = $service_type;
+ } else {
+ // Don't perform service endpoint selection.
+ $args['_xrd_r'] .= ';sep=false';
+ }
+
+ $query = Auth_Yadis_XRIAppendArgs($hxri, $args);
+ return $query;
+ }
+
+ function query($xri, $service_types, $filters = array())
+ {
+ $services = array();
+ $canonicalID = null;
+ foreach ($service_types as $service_type) {
+ $url = $this->queryURL($xri, $service_type);
+ $response = $this->fetcher->get($url);
+ if ($response->status != 200 and $response->status != 206) {
+ continue;
+ }
+ $xrds = Auth_Yadis_XRDS::parseXRDS($response->body);
+ if (!$xrds) {
+ continue;
+ }
+ $canonicalID = Auth_Yadis_getCanonicalID($xri,
+ $xrds);
+
+ if ($canonicalID === false) {
+ return null;
+ }
+
+ $some_services = $xrds->services($filters);
+ $services = array_merge($services, $some_services);
+ // TODO:
+ // * If we do get hits for multiple service_types, we're
+ // almost certainly going to have duplicated service
+ // entries and broken priority ordering.
+ }
+ return array($canonicalID, $services);
+ }
+}
+
+?>
diff --git a/extlib/Auth/Yadis/Yadis.php b/extlib/Auth/Yadis/Yadis.php
new file mode 100644
index 000000000..d89f77c6d
--- /dev/null
+++ b/extlib/Auth/Yadis/Yadis.php
@@ -0,0 +1,382 @@
+<?php
+
+/**
+ * The core PHP Yadis implementation.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: See the COPYING file included in this distribution.
+ *
+ * @package OpenID
+ * @author JanRain, Inc. <openid@janrain.com>
+ * @copyright 2005-2008 Janrain, Inc.
+ * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
+ */
+
+/**
+ * Need both fetcher types so we can use the right one based on the
+ * presence or absence of CURL.
+ */
+require_once "Auth/Yadis/PlainHTTPFetcher.php";
+require_once "Auth/Yadis/ParanoidHTTPFetcher.php";
+
+/**
+ * Need this for parsing HTML (looking for META tags).
+ */
+require_once "Auth/Yadis/ParseHTML.php";
+
+/**
+ * Need this to parse the XRDS document during Yadis discovery.
+ */
+require_once "Auth/Yadis/XRDS.php";
+
+/**
+ * XRDS (yadis) content type
+ */
+define('Auth_Yadis_CONTENT_TYPE', 'application/xrds+xml');
+
+/**
+ * Yadis header
+ */
+define('Auth_Yadis_HEADER_NAME', 'X-XRDS-Location');
+
+/**
+ * Contains the result of performing Yadis discovery on a URI.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_DiscoveryResult {
+
+ // The URI that was passed to the fetcher
+ var $request_uri = null;
+
+ // The result of following redirects from the request_uri
+ var $normalized_uri = null;
+
+ // The URI from which the response text was returned (set to
+ // None if there was no XRDS document found)
+ var $xrds_uri = null;
+
+ var $xrds = null;
+
+ // The content-type returned with the response_text
+ var $content_type = null;
+
+ // The document returned from the xrds_uri
+ var $response_text = null;
+
+ // Did the discovery fail miserably?
+ var $failed = false;
+
+ function Auth_Yadis_DiscoveryResult($request_uri)
+ {
+ // Initialize the state of the object
+ // sets all attributes to None except the request_uri
+ $this->request_uri = $request_uri;
+ }
+
+ function fail()
+ {
+ $this->failed = true;
+ }
+
+ function isFailure()
+ {
+ return $this->failed;
+ }
+
+ /**
+ * Returns the list of service objects as described by the XRDS
+ * document, if this yadis object represents a successful Yadis
+ * discovery.
+ *
+ * @return array $services An array of {@link Auth_Yadis_Service}
+ * objects
+ */
+ function services()
+ {
+ if ($this->xrds) {
+ return $this->xrds->services();
+ }
+
+ return null;
+ }
+
+ function usedYadisLocation()
+ {
+ // Was the Yadis protocol's indirection used?
+ return $this->normalized_uri != $this->xrds_uri;
+ }
+
+ function isXRDS()
+ {
+ // Is the response text supposed to be an XRDS document?
+ return ($this->usedYadisLocation() ||
+ $this->content_type == Auth_Yadis_CONTENT_TYPE);
+ }
+}
+
+/**
+ *
+ * Perform the Yadis protocol on the input URL and return an iterable
+ * of resulting endpoint objects.
+ *
+ * input_url: The URL on which to perform the Yadis protocol
+ *
+ * @return: The normalized identity URL and an iterable of endpoint
+ * objects generated by the filter function.
+ *
+ * xrds_parse_func: a callback which will take (uri, xrds_text) and
+ * return an array of service endpoint objects or null. Usually
+ * array('Auth_OpenID_ServiceEndpoint', 'fromXRDS').
+ *
+ * discover_func: if not null, a callback which should take (uri) and
+ * return an Auth_Yadis_Yadis object or null.
+ */
+function Auth_Yadis_getServiceEndpoints($input_url, $xrds_parse_func,
+ $discover_func=null, $fetcher=null)
+{
+ if ($discover_func === null) {
+ $discover_function = array('Auth_Yadis_Yadis', 'discover');
+ }
+
+ $yadis_result = call_user_func_array($discover_func,
+ array($input_url, $fetcher));
+
+ if ($yadis_result === null) {
+ return array($input_url, array());
+ }
+
+ $endpoints = call_user_func_array($xrds_parse_func,
+ array($yadis_result->normalized_uri,
+ $yadis_result->response_text));
+
+ if ($endpoints === null) {
+ $endpoints = array();
+ }
+
+ return array($yadis_result->normalized_uri, $endpoints);
+}
+
+/**
+ * This is the core of the PHP Yadis library. This is the only class
+ * a user needs to use to perform Yadis discovery. This class
+ * performs the discovery AND stores the result of the discovery.
+ *
+ * First, require this library into your program source:
+ *
+ * <pre> require_once "Auth/Yadis/Yadis.php";</pre>
+ *
+ * To perform Yadis discovery, first call the "discover" method
+ * statically with a URI parameter:
+ *
+ * <pre> $http_response = array();
+ * $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+ * $yadis_object = Auth_Yadis_Yadis::discover($uri,
+ * $http_response, $fetcher);</pre>
+ *
+ * If the discovery succeeds, $yadis_object will be an instance of
+ * {@link Auth_Yadis_Yadis}. If not, it will be null. The XRDS
+ * document found during discovery should have service descriptions,
+ * which can be accessed by calling
+ *
+ * <pre> $service_list = $yadis_object->services();</pre>
+ *
+ * which returns an array of objects which describe each service.
+ * These objects are instances of Auth_Yadis_Service. Each object
+ * describes exactly one whole Service element, complete with all of
+ * its Types and URIs (no expansion is performed). The common use
+ * case for using the service objects returned by services() is to
+ * write one or more filter functions and pass those to services():
+ *
+ * <pre> $service_list = $yadis_object->services(
+ * array("filterByURI",
+ * "filterByExtension"));</pre>
+ *
+ * The filter functions (whose names appear in the array passed to
+ * services()) take the following form:
+ *
+ * <pre> function myFilter(&$service) {
+ * // Query $service object here. Return true if the service
+ * // matches your query; false if not.
+ * }</pre>
+ *
+ * This is an example of a filter which uses a regular expression to
+ * match the content of URI tags (note that the Auth_Yadis_Service
+ * class provides a getURIs() method which you should use instead of
+ * this contrived example):
+ *
+ * <pre>
+ * function URIMatcher(&$service) {
+ * foreach ($service->getElements('xrd:URI') as $uri) {
+ * if (preg_match("/some_pattern/",
+ * $service->parser->content($uri))) {
+ * return true;
+ * }
+ * }
+ * return false;
+ * }</pre>
+ *
+ * The filter functions you pass will be called for each service
+ * object to determine which ones match the criteria your filters
+ * specify. The default behavior is that if a given service object
+ * matches ANY of the filters specified in the services() call, it
+ * will be returned. You can specify that a given service object will
+ * be returned ONLY if it matches ALL specified filters by changing
+ * the match mode of services():
+ *
+ * <pre> $yadis_object->services(array("filter1", "filter2"),
+ * SERVICES_YADIS_MATCH_ALL);</pre>
+ *
+ * See {@link SERVICES_YADIS_MATCH_ALL} and {@link
+ * SERVICES_YADIS_MATCH_ANY}.
+ *
+ * Services described in an XRDS should have a library which you'll
+ * probably be using. Those libraries are responsible for defining
+ * filters that can be used with the "services()" call. If you need
+ * to write your own filter, see the documentation for {@link
+ * Auth_Yadis_Service}.
+ *
+ * @package OpenID
+ */
+class Auth_Yadis_Yadis {
+
+ /**
+ * Returns an HTTP fetcher object. If the CURL extension is
+ * present, an instance of {@link Auth_Yadis_ParanoidHTTPFetcher}
+ * is returned. If not, an instance of
+ * {@link Auth_Yadis_PlainHTTPFetcher} is returned.
+ *
+ * If Auth_Yadis_CURL_OVERRIDE is defined, this method will always
+ * return a {@link Auth_Yadis_PlainHTTPFetcher}.
+ */
+ function getHTTPFetcher($timeout = 20)
+ {
+ if (Auth_Yadis_Yadis::curlPresent() &&
+ (!defined('Auth_Yadis_CURL_OVERRIDE'))) {
+ $fetcher = new Auth_Yadis_ParanoidHTTPFetcher($timeout);
+ } else {
+ $fetcher = new Auth_Yadis_PlainHTTPFetcher($timeout);
+ }
+ return $fetcher;
+ }
+
+ function curlPresent()
+ {
+ return function_exists('curl_init');
+ }
+
+ /**
+ * @access private
+ */
+ function _getHeader($header_list, $names)
+ {
+ foreach ($header_list as $name => $value) {
+ foreach ($names as $n) {
+ if (strtolower($name) == strtolower($n)) {
+ return $value;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @access private
+ */
+ function _getContentType($content_type_header)
+ {
+ if ($content_type_header) {
+ $parts = explode(";", $content_type_header);
+ return strtolower($parts[0]);
+ }
+ }
+
+ /**
+ * This should be called statically and will build a Yadis
+ * instance if the discovery process succeeds. This implements
+ * Yadis discovery as specified in the Yadis specification.
+ *
+ * @param string $uri The URI on which to perform Yadis discovery.
+ *
+ * @param array $http_response An array reference where the HTTP
+ * response object will be stored (see {@link
+ * Auth_Yadis_HTTPResponse}.
+ *
+ * @param Auth_Yadis_HTTPFetcher $fetcher An instance of a
+ * Auth_Yadis_HTTPFetcher subclass.
+ *
+ * @param array $extra_ns_map An array which maps namespace names
+ * to namespace URIs to be used when parsing the Yadis XRDS
+ * document.
+ *
+ * @param integer $timeout An optional fetcher timeout, in seconds.
+ *
+ * @return mixed $obj Either null or an instance of
+ * Auth_Yadis_Yadis, depending on whether the discovery
+ * succeeded.
+ */
+ function discover($uri, &$fetcher,
+ $extra_ns_map = null, $timeout = 20)
+ {
+ $result = new Auth_Yadis_DiscoveryResult($uri);
+
+ $request_uri = $uri;
+ $headers = array("Accept: " . Auth_Yadis_CONTENT_TYPE .
+ ', text/html; q=0.3, application/xhtml+xml; q=0.5');
+
+ if ($fetcher === null) {
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher($timeout);
+ }
+
+ $response = $fetcher->get($uri, $headers);
+
+ if (!$response || ($response->status != 200 and
+ $response->status != 206)) {
+ $result->fail();
+ return $result;
+ }
+
+ $result->normalized_uri = $response->final_url;
+ $result->content_type = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+
+ if ($result->content_type &&
+ (Auth_Yadis_Yadis::_getContentType($result->content_type) ==
+ Auth_Yadis_CONTENT_TYPE)) {
+ $result->xrds_uri = $result->normalized_uri;
+ } else {
+ $yadis_location = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array(Auth_Yadis_HEADER_NAME));
+
+ if (!$yadis_location) {
+ $parser = new Auth_Yadis_ParseHTML();
+ $yadis_location = $parser->getHTTPEquiv($response->body);
+ }
+
+ if ($yadis_location) {
+ $result->xrds_uri = $yadis_location;
+
+ $response = $fetcher->get($yadis_location);
+
+ if ((!$response) || ($response->status != 200 and
+ $response->status != 206)) {
+ $result->fail();
+ return $result;
+ }
+
+ $result->content_type = Auth_Yadis_Yadis::_getHeader(
+ $response->headers,
+ array('content-type'));
+ }
+ }
+
+ $result->response_text = $response->body;
+ return $result;
+ }
+}
+
+?>
diff --git a/extlib/DB.php b/extlib/DB.php
new file mode 100644
index 000000000..a511979e6
--- /dev/null
+++ b/extlib/DB.php
@@ -0,0 +1,1489 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Database independent query interface
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: DB.php,v 1.88 2007/08/12 05:27:25 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+
+// {{{ constants
+// {{{ error codes
+
+/**#@+
+ * One of PEAR DB's portable error codes.
+ * @see DB_common::errorCode(), DB::errorMessage()
+ *
+ * {@internal If you add an error code here, make sure you also add a textual
+ * version of it in DB::errorMessage().}}
+ */
+
+/**
+ * The code returned by many methods upon success
+ */
+define('DB_OK', 1);
+
+/**
+ * Unkown error
+ */
+define('DB_ERROR', -1);
+
+/**
+ * Syntax error
+ */
+define('DB_ERROR_SYNTAX', -2);
+
+/**
+ * Tried to insert a duplicate value into a primary or unique index
+ */
+define('DB_ERROR_CONSTRAINT', -3);
+
+/**
+ * An identifier in the query refers to a non-existant object
+ */
+define('DB_ERROR_NOT_FOUND', -4);
+
+/**
+ * Tried to create a duplicate object
+ */
+define('DB_ERROR_ALREADY_EXISTS', -5);
+
+/**
+ * The current driver does not support the action you attempted
+ */
+define('DB_ERROR_UNSUPPORTED', -6);
+
+/**
+ * The number of parameters does not match the number of placeholders
+ */
+define('DB_ERROR_MISMATCH', -7);
+
+/**
+ * A literal submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID', -8);
+
+/**
+ * The current DBMS does not support the action you attempted
+ */
+define('DB_ERROR_NOT_CAPABLE', -9);
+
+/**
+ * A literal submitted was too long so the end of it was removed
+ */
+define('DB_ERROR_TRUNCATED', -10);
+
+/**
+ * A literal number submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_NUMBER', -11);
+
+/**
+ * A literal date submitted did not match the data type expected
+ */
+define('DB_ERROR_INVALID_DATE', -12);
+
+/**
+ * Attempt to divide something by zero
+ */
+define('DB_ERROR_DIVZERO', -13);
+
+/**
+ * A database needs to be selected
+ */
+define('DB_ERROR_NODBSELECTED', -14);
+
+/**
+ * Could not create the object requested
+ */
+define('DB_ERROR_CANNOT_CREATE', -15);
+
+/**
+ * Could not drop the database requested because it does not exist
+ */
+define('DB_ERROR_CANNOT_DROP', -17);
+
+/**
+ * An identifier in the query refers to a non-existant table
+ */
+define('DB_ERROR_NOSUCHTABLE', -18);
+
+/**
+ * An identifier in the query refers to a non-existant column
+ */
+define('DB_ERROR_NOSUCHFIELD', -19);
+
+/**
+ * The data submitted to the method was inappropriate
+ */
+define('DB_ERROR_NEED_MORE_DATA', -20);
+
+/**
+ * The attempt to lock the table failed
+ */
+define('DB_ERROR_NOT_LOCKED', -21);
+
+/**
+ * The number of columns doesn't match the number of values
+ */
+define('DB_ERROR_VALUE_COUNT_ON_ROW', -22);
+
+/**
+ * The DSN submitted has problems
+ */
+define('DB_ERROR_INVALID_DSN', -23);
+
+/**
+ * Could not connect to the database
+ */
+define('DB_ERROR_CONNECT_FAILED', -24);
+
+/**
+ * The PHP extension needed for this DBMS could not be found
+ */
+define('DB_ERROR_EXTENSION_NOT_FOUND',-25);
+
+/**
+ * The present user has inadequate permissions to perform the task requestd
+ */
+define('DB_ERROR_ACCESS_VIOLATION', -26);
+
+/**
+ * The database requested does not exist
+ */
+define('DB_ERROR_NOSUCHDB', -27);
+
+/**
+ * Tried to insert a null value into a column that doesn't allow nulls
+ */
+define('DB_ERROR_CONSTRAINT_NOT_NULL',-29);
+/**#@-*/
+
+
+// }}}
+// {{{ prepared statement-related
+
+
+/**#@+
+ * Identifiers for the placeholders used in prepared statements.
+ * @see DB_common::prepare()
+ */
+
+/**
+ * Indicates a scalar (<kbd>?</kbd>) placeholder was used
+ *
+ * Quote and escape the value as necessary.
+ */
+define('DB_PARAM_SCALAR', 1);
+
+/**
+ * Indicates an opaque (<kbd>&</kbd>) placeholder was used
+ *
+ * The value presented is a file name. Extract the contents of that file
+ * and place them in this column.
+ */
+define('DB_PARAM_OPAQUE', 2);
+
+/**
+ * Indicates a misc (<kbd>!</kbd>) placeholder was used
+ *
+ * The value should not be quoted or escaped.
+ */
+define('DB_PARAM_MISC', 3);
+/**#@-*/
+
+
+// }}}
+// {{{ binary data-related
+
+
+/**#@+
+ * The different ways of returning binary data from queries.
+ */
+
+/**
+ * Sends the fetched data straight through to output
+ */
+define('DB_BINMODE_PASSTHRU', 1);
+
+/**
+ * Lets you return data as usual
+ */
+define('DB_BINMODE_RETURN', 2);
+
+/**
+ * Converts the data to hex format before returning it
+ *
+ * For example the string "123" would become "313233".
+ */
+define('DB_BINMODE_CONVERT', 3);
+/**#@-*/
+
+
+// }}}
+// {{{ fetch modes
+
+
+/**#@+
+ * Fetch Modes.
+ * @see DB_common::setFetchMode()
+ */
+
+/**
+ * Indicates the current default fetch mode should be used
+ * @see DB_common::$fetchmode
+ */
+define('DB_FETCHMODE_DEFAULT', 0);
+
+/**
+ * Column data indexed by numbers, ordered from 0 and up
+ */
+define('DB_FETCHMODE_ORDERED', 1);
+
+/**
+ * Column data indexed by column names
+ */
+define('DB_FETCHMODE_ASSOC', 2);
+
+/**
+ * Column data as object properties
+ */
+define('DB_FETCHMODE_OBJECT', 3);
+
+/**
+ * For multi-dimensional results, make the column name the first level
+ * of the array and put the row number in the second level of the array
+ *
+ * This is flipped from the normal behavior, which puts the row numbers
+ * in the first level of the array and the column names in the second level.
+ */
+define('DB_FETCHMODE_FLIPPED', 4);
+/**#@-*/
+
+/**#@+
+ * Old fetch modes. Left here for compatibility.
+ */
+define('DB_GETMODE_ORDERED', DB_FETCHMODE_ORDERED);
+define('DB_GETMODE_ASSOC', DB_FETCHMODE_ASSOC);
+define('DB_GETMODE_FLIPPED', DB_FETCHMODE_FLIPPED);
+/**#@-*/
+
+
+// }}}
+// {{{ tableInfo() && autoPrepare()-related
+
+
+/**#@+
+ * The type of information to return from the tableInfo() method.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::tableInfo()
+ *
+ * {@internal Since the TABLEINFO constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_TABLEINFO_FULL accordingly.}}
+ */
+define('DB_TABLEINFO_ORDER', 1);
+define('DB_TABLEINFO_ORDERTABLE', 2);
+define('DB_TABLEINFO_FULL', 3);
+/**#@-*/
+
+
+/**#@+
+ * The type of query to create with the automatic query building methods.
+ * @see DB_common::autoPrepare(), DB_common::autoExecute()
+ */
+define('DB_AUTOQUERY_INSERT', 1);
+define('DB_AUTOQUERY_UPDATE', 2);
+/**#@-*/
+
+
+// }}}
+// {{{ portability modes
+
+
+/**#@+
+ * Portability Modes.
+ *
+ * Bitwised constants, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>.
+ *
+ * @see DB_common::setOption()
+ *
+ * {@internal Since the PORTABILITY constants are bitwised, if more of them are
+ * added in the future, make sure to adjust DB_PORTABILITY_ALL accordingly.}}
+ */
+
+/**
+ * Turn off all portability features
+ */
+define('DB_PORTABILITY_NONE', 0);
+
+/**
+ * Convert names of tables and fields to lower case
+ * when using the get*(), fetch*() and tableInfo() methods
+ */
+define('DB_PORTABILITY_LOWERCASE', 1);
+
+/**
+ * Right trim the data output by get*() and fetch*()
+ */
+define('DB_PORTABILITY_RTRIM', 2);
+
+/**
+ * Force reporting the number of rows deleted
+ */
+define('DB_PORTABILITY_DELETE_COUNT', 4);
+
+/**
+ * Enable hack that makes numRows() work in Oracle
+ */
+define('DB_PORTABILITY_NUMROWS', 8);
+
+/**
+ * Makes certain error messages in certain drivers compatible
+ * with those from other DBMS's
+ *
+ * + mysql, mysqli: change unique/primary key constraints
+ * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+ *
+ * + odbc(access): MS's ODBC driver reports 'no such field' as code
+ * 07001, which means 'too few parameters.' When this option is on
+ * that code gets mapped to DB_ERROR_NOSUCHFIELD.
+ */
+define('DB_PORTABILITY_ERRORS', 16);
+
+/**
+ * Convert null values to empty strings in data output by
+ * get*() and fetch*()
+ */
+define('DB_PORTABILITY_NULL_TO_EMPTY', 32);
+
+/**
+ * Turn on all portability features
+ */
+define('DB_PORTABILITY_ALL', 63);
+/**#@-*/
+
+// }}}
+
+
+// }}}
+// {{{ class DB
+
+/**
+ * Database independent query interface
+ *
+ * The main "DB" class is simply a container class with some static
+ * methods for creating DB objects as well as some utility functions
+ * common to all parts of DB.
+ *
+ * The object model of DB is as follows (indentation means inheritance):
+ * <pre>
+ * DB The main DB class. This is simply a utility class
+ * with some "static" methods for creating DB objects as
+ * well as common utility functions for other DB classes.
+ *
+ * DB_common The base for each DB implementation. Provides default
+ * | implementations (in OO lingo virtual methods) for
+ * | the actual DB implementations as well as a bunch of
+ * | query utility functions.
+ * |
+ * +-DB_mysql The DB implementation for MySQL. Inherits DB_common.
+ * When calling DB::factory or DB::connect for MySQL
+ * connections, the object returned is an instance of this
+ * class.
+ * </pre>
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.13
+ * @link http://pear.php.net/package/DB
+ */
+class DB
+{
+ // {{{ &factory()
+
+ /**
+ * Create a new DB object for the specified database type but don't
+ * connect to the database
+ *
+ * @param string $type the database type (eg "mysql")
+ * @param array $options an associative array of option names and values
+ *
+ * @return object a new DB object. A DB_Error object on failure.
+ *
+ * @see DB_common::setOption()
+ */
+ function &factory($type, $options = false)
+ {
+ if (!is_array($options)) {
+ $options = array('persistent' => $options);
+ }
+
+ if (isset($options['debug']) && $options['debug'] >= 2) {
+ // expose php errors with sufficient debug level
+ include_once "DB/{$type}.php";
+ } else {
+ @include_once "DB/{$type}.php";
+ }
+
+ $classname = "DB_${type}";
+
+ if (!class_exists($classname)) {
+ $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+ "Unable to include the DB/{$type}.php"
+ . " file for '$dsn'",
+ 'DB_Error', true);
+ return $tmp;
+ }
+
+ @$obj = new $classname;
+
+ foreach ($options as $option => $value) {
+ $test = $obj->setOption($option, $value);
+ if (DB::isError($test)) {
+ return $test;
+ }
+ }
+
+ return $obj;
+ }
+
+ // }}}
+ // {{{ &connect()
+
+ /**
+ * Create a new DB object including a connection to the specified database
+ *
+ * Example 1.
+ * <code>
+ * require_once 'DB.php';
+ *
+ * $dsn = 'pgsql://user:password@host/database';
+ * $options = array(
+ * 'debug' => 2,
+ * 'portability' => DB_PORTABILITY_ALL,
+ * );
+ *
+ * $db =& DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param mixed $dsn the string "data source name" or array in the
+ * format returned by DB::parseDSN()
+ * @param array $options an associative array of option names and values
+ *
+ * @return object a new DB object. A DB_Error object on failure.
+ *
+ * @uses DB_dbase::connect(), DB_fbsql::connect(), DB_ibase::connect(),
+ * DB_ifx::connect(), DB_msql::connect(), DB_mssql::connect(),
+ * DB_mysql::connect(), DB_mysqli::connect(), DB_oci8::connect(),
+ * DB_odbc::connect(), DB_pgsql::connect(), DB_sqlite::connect(),
+ * DB_sybase::connect()
+ *
+ * @uses DB::parseDSN(), DB_common::setOption(), PEAR::isError()
+ */
+ function &connect($dsn, $options = array())
+ {
+ $dsninfo = DB::parseDSN($dsn);
+ $type = $dsninfo['phptype'];
+
+ if (!is_array($options)) {
+ /*
+ * For backwards compatibility. $options used to be boolean,
+ * indicating whether the connection should be persistent.
+ */
+ $options = array('persistent' => $options);
+ }
+
+ if (isset($options['debug']) && $options['debug'] >= 2) {
+ // expose php errors with sufficient debug level
+ include_once "DB/${type}.php";
+ } else {
+ @include_once "DB/${type}.php";
+ }
+
+ $classname = "DB_${type}";
+ if (!class_exists($classname)) {
+ $tmp = PEAR::raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+ "Unable to include the DB/{$type}.php"
+ . " file for '$dsn'",
+ 'DB_Error', true);
+ return $tmp;
+ }
+
+ @$obj = new $classname;
+
+ foreach ($options as $option => $value) {
+ $test = $obj->setOption($option, $value);
+ if (DB::isError($test)) {
+ return $test;
+ }
+ }
+
+ $err = $obj->connect($dsninfo, $obj->getOption('persistent'));
+ if (DB::isError($err)) {
+ if (is_array($dsn)) {
+ $err->addUserInfo(DB::getDSNString($dsn, true));
+ } else {
+ $err->addUserInfo($dsn);
+ }
+ return $err;
+ }
+
+ return $obj;
+ }
+
+ // }}}
+ // {{{ apiVersion()
+
+ /**
+ * Return the DB API version
+ *
+ * @return string the DB API version number
+ */
+ function apiVersion()
+ {
+ return '1.7.13';
+ }
+
+ // }}}
+ // {{{ isError()
+
+ /**
+ * Determines if a variable is a DB_Error object
+ *
+ * @param mixed $value the variable to check
+ *
+ * @return bool whether $value is DB_Error object
+ */
+ function isError($value)
+ {
+ return is_a($value, 'DB_Error');
+ }
+
+ // }}}
+ // {{{ isConnection()
+
+ /**
+ * Determines if a value is a DB_<driver> object
+ *
+ * @param mixed $value the value to test
+ *
+ * @return bool whether $value is a DB_<driver> object
+ */
+ function isConnection($value)
+ {
+ return (is_object($value) &&
+ is_subclass_of($value, 'db_common') &&
+ method_exists($value, 'simpleQuery'));
+ }
+
+ // }}}
+ // {{{ isManip()
+
+ /**
+ * Tell whether a query is a data manipulation or data definition query
+ *
+ * Examples of data manipulation queries are INSERT, UPDATE and DELETE.
+ * Examples of data definition queries are CREATE, DROP, ALTER, GRANT,
+ * REVOKE.
+ *
+ * @param string $query the query
+ *
+ * @return boolean whether $query is a data manipulation query
+ */
+ function isManip($query)
+ {
+ $manips = 'INSERT|UPDATE|DELETE|REPLACE|'
+ . 'CREATE|DROP|'
+ . 'LOAD DATA|SELECT .* INTO .* FROM|COPY|'
+ . 'ALTER|GRANT|REVOKE|'
+ . 'LOCK|UNLOCK';
+ if (preg_match('/^\s*"?(' . $manips . ')\s+/i', $query)) {
+ return true;
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ errorMessage()
+
+ /**
+ * Return a textual error message for a DB error code
+ *
+ * @param integer $value the DB error code
+ *
+ * @return string the error message or false if the error code was
+ * not recognized
+ */
+ function errorMessage($value)
+ {
+ static $errorMessages;
+ if (!isset($errorMessages)) {
+ $errorMessages = array(
+ DB_ERROR => 'unknown error',
+ DB_ERROR_ACCESS_VIOLATION => 'insufficient permissions',
+ DB_ERROR_ALREADY_EXISTS => 'already exists',
+ DB_ERROR_CANNOT_CREATE => 'can not create',
+ DB_ERROR_CANNOT_DROP => 'can not drop',
+ DB_ERROR_CONNECT_FAILED => 'connect failed',
+ DB_ERROR_CONSTRAINT => 'constraint violation',
+ DB_ERROR_CONSTRAINT_NOT_NULL=> 'null value violates not-null constraint',
+ DB_ERROR_DIVZERO => 'division by zero',
+ DB_ERROR_EXTENSION_NOT_FOUND=> 'extension not found',
+ DB_ERROR_INVALID => 'invalid',
+ DB_ERROR_INVALID_DATE => 'invalid date or time',
+ DB_ERROR_INVALID_DSN => 'invalid DSN',
+ DB_ERROR_INVALID_NUMBER => 'invalid number',
+ DB_ERROR_MISMATCH => 'mismatch',
+ DB_ERROR_NEED_MORE_DATA => 'insufficient data supplied',
+ DB_ERROR_NODBSELECTED => 'no database selected',
+ DB_ERROR_NOSUCHDB => 'no such database',
+ DB_ERROR_NOSUCHFIELD => 'no such field',
+ DB_ERROR_NOSUCHTABLE => 'no such table',
+ DB_ERROR_NOT_CAPABLE => 'DB backend not capable',
+ DB_ERROR_NOT_FOUND => 'not found',
+ DB_ERROR_NOT_LOCKED => 'not locked',
+ DB_ERROR_SYNTAX => 'syntax error',
+ DB_ERROR_UNSUPPORTED => 'not supported',
+ DB_ERROR_TRUNCATED => 'truncated',
+ DB_ERROR_VALUE_COUNT_ON_ROW => 'value count on row',
+ DB_OK => 'no error',
+ );
+ }
+
+ if (DB::isError($value)) {
+ $value = $value->getCode();
+ }
+
+ return isset($errorMessages[$value]) ? $errorMessages[$value]
+ : $errorMessages[DB_ERROR];
+ }
+
+ // }}}
+ // {{{ parseDSN()
+
+ /**
+ * Parse a data source name
+ *
+ * Additional keys can be added by appending a URI query string to the
+ * end of the DSN.
+ *
+ * The format of the supplied DSN is in its fullest form:
+ * <code>
+ * phptype(dbsyntax)://username:password@protocol+hostspec/database?option=8&another=true
+ * </code>
+ *
+ * Most variations are allowed:
+ * <code>
+ * phptype://username:password@protocol+hostspec:110//usr/db_file.db?mode=0644
+ * phptype://username:password@hostspec/database_name
+ * phptype://username:password@hostspec
+ * phptype://username@hostspec
+ * phptype://hostspec/database
+ * phptype://hostspec
+ * phptype(dbsyntax)
+ * phptype
+ * </code>
+ *
+ * @param string $dsn Data Source Name to be parsed
+ *
+ * @return array an associative array with the following keys:
+ * + phptype: Database backend used in PHP (mysql, odbc etc.)
+ * + dbsyntax: Database used with regards to SQL syntax etc.
+ * + protocol: Communication protocol to use (tcp, unix etc.)
+ * + hostspec: Host specification (hostname[:port])
+ * + database: Database to use on the DBMS server
+ * + username: User name for login
+ * + password: Password for login
+ */
+ function parseDSN($dsn)
+ {
+ $parsed = array(
+ 'phptype' => false,
+ 'dbsyntax' => false,
+ 'username' => false,
+ 'password' => false,
+ 'protocol' => false,
+ 'hostspec' => false,
+ 'port' => false,
+ 'socket' => false,
+ 'database' => false,
+ );
+
+ if (is_array($dsn)) {
+ $dsn = array_merge($parsed, $dsn);
+ if (!$dsn['dbsyntax']) {
+ $dsn['dbsyntax'] = $dsn['phptype'];
+ }
+ return $dsn;
+ }
+
+ // Find phptype and dbsyntax
+ if (($pos = strpos($dsn, '://')) !== false) {
+ $str = substr($dsn, 0, $pos);
+ $dsn = substr($dsn, $pos + 3);
+ } else {
+ $str = $dsn;
+ $dsn = null;
+ }
+
+ // Get phptype and dbsyntax
+ // $str => phptype(dbsyntax)
+ if (preg_match('|^(.+?)\((.*?)\)$|', $str, $arr)) {
+ $parsed['phptype'] = $arr[1];
+ $parsed['dbsyntax'] = !$arr[2] ? $arr[1] : $arr[2];
+ } else {
+ $parsed['phptype'] = $str;
+ $parsed['dbsyntax'] = $str;
+ }
+
+ if (!count($dsn)) {
+ return $parsed;
+ }
+
+ // Get (if found): username and password
+ // $dsn => username:password@protocol+hostspec/database
+ if (($at = strrpos($dsn,'@')) !== false) {
+ $str = substr($dsn, 0, $at);
+ $dsn = substr($dsn, $at + 1);
+ if (($pos = strpos($str, ':')) !== false) {
+ $parsed['username'] = rawurldecode(substr($str, 0, $pos));
+ $parsed['password'] = rawurldecode(substr($str, $pos + 1));
+ } else {
+ $parsed['username'] = rawurldecode($str);
+ }
+ }
+
+ // Find protocol and hostspec
+
+ if (preg_match('|^([^(]+)\((.*?)\)/?(.*?)$|', $dsn, $match)) {
+ // $dsn => proto(proto_opts)/database
+ $proto = $match[1];
+ $proto_opts = $match[2] ? $match[2] : false;
+ $dsn = $match[3];
+
+ } else {
+ // $dsn => protocol+hostspec/database (old format)
+ if (strpos($dsn, '+') !== false) {
+ list($proto, $dsn) = explode('+', $dsn, 2);
+ }
+ if (strpos($dsn, '/') !== false) {
+ list($proto_opts, $dsn) = explode('/', $dsn, 2);
+ } else {
+ $proto_opts = $dsn;
+ $dsn = null;
+ }
+ }
+
+ // process the different protocol options
+ $parsed['protocol'] = (!empty($proto)) ? $proto : 'tcp';
+ $proto_opts = rawurldecode($proto_opts);
+ if (strpos($proto_opts, ':') !== false) {
+ list($proto_opts, $parsed['port']) = explode(':', $proto_opts);
+ }
+ if ($parsed['protocol'] == 'tcp') {
+ $parsed['hostspec'] = $proto_opts;
+ } elseif ($parsed['protocol'] == 'unix') {
+ $parsed['socket'] = $proto_opts;
+ }
+
+ // Get dabase if any
+ // $dsn => database
+ if ($dsn) {
+ if (($pos = strpos($dsn, '?')) === false) {
+ // /database
+ $parsed['database'] = rawurldecode($dsn);
+ } else {
+ // /database?param1=value1&param2=value2
+ $parsed['database'] = rawurldecode(substr($dsn, 0, $pos));
+ $dsn = substr($dsn, $pos + 1);
+ if (strpos($dsn, '&') !== false) {
+ $opts = explode('&', $dsn);
+ } else { // database?param1=value1
+ $opts = array($dsn);
+ }
+ foreach ($opts as $opt) {
+ list($key, $value) = explode('=', $opt);
+ if (!isset($parsed[$key])) {
+ // don't allow params overwrite
+ $parsed[$key] = rawurldecode($value);
+ }
+ }
+ }
+ }
+
+ return $parsed;
+ }
+
+ // }}}
+ // {{{ getDSNString()
+
+ /**
+ * Returns the given DSN in a string format suitable for output.
+ *
+ * @param array|string the DSN to parse and format
+ * @param boolean true to hide the password, false to include it
+ * @return string
+ */
+ function getDSNString($dsn, $hidePassword) {
+ /* Calling parseDSN will ensure that we have all the array elements
+ * defined, and means that we deal with strings and array in the same
+ * manner. */
+ $dsnArray = DB::parseDSN($dsn);
+
+ if ($hidePassword) {
+ $dsnArray['password'] = 'PASSWORD';
+ }
+
+ /* Protocol is special-cased, as using the default "tcp" along with an
+ * Oracle TNS connection string fails. */
+ if (is_string($dsn) && strpos($dsn, 'tcp') === false && $dsnArray['protocol'] == 'tcp') {
+ $dsnArray['protocol'] = false;
+ }
+
+ // Now we just have to construct the actual string. This is ugly.
+ $dsnString = $dsnArray['phptype'];
+ if ($dsnArray['dbsyntax']) {
+ $dsnString .= '('.$dsnArray['dbsyntax'].')';
+ }
+ $dsnString .= '://'
+ .$dsnArray['username']
+ .':'
+ .$dsnArray['password']
+ .'@'
+ .$dsnArray['protocol'];
+ if ($dsnArray['socket']) {
+ $dsnString .= '('.$dsnArray['socket'].')';
+ }
+ if ($dsnArray['protocol'] && $dsnArray['hostspec']) {
+ $dsnString .= '+';
+ }
+ $dsnString .= $dsnArray['hostspec'];
+ if ($dsnArray['port']) {
+ $dsnString .= ':'.$dsnArray['port'];
+ }
+ $dsnString .= '/'.$dsnArray['database'];
+
+ /* Option handling. Unfortunately, parseDSN simply places options into
+ * the top-level array, so we'll first get rid of the fields defined by
+ * DB and see what's left. */
+ unset($dsnArray['phptype'],
+ $dsnArray['dbsyntax'],
+ $dsnArray['username'],
+ $dsnArray['password'],
+ $dsnArray['protocol'],
+ $dsnArray['socket'],
+ $dsnArray['hostspec'],
+ $dsnArray['port'],
+ $dsnArray['database']
+ );
+ if (count($dsnArray) > 0) {
+ $dsnString .= '?';
+ $i = 0;
+ foreach ($dsnArray as $key => $value) {
+ if (++$i > 1) {
+ $dsnString .= '&';
+ }
+ $dsnString .= $key.'='.$value;
+ }
+ }
+
+ return $dsnString;
+ }
+
+ // }}}
+}
+
+// }}}
+// {{{ class DB_Error
+
+/**
+ * DB_Error implements a class for reporting portable database error
+ * messages
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.13
+ * @link http://pear.php.net/package/DB
+ */
+class DB_Error extends PEAR_Error
+{
+ // {{{ constructor
+
+ /**
+ * DB_Error constructor
+ *
+ * @param mixed $code DB error code, or string with error message
+ * @param int $mode what "error mode" to operate in
+ * @param int $level what error level to use for $mode &
+ * PEAR_ERROR_TRIGGER
+ * @param mixed $debuginfo additional debug info, such as the last query
+ *
+ * @see PEAR_Error
+ */
+ function DB_Error($code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
+ $level = E_USER_NOTICE, $debuginfo = null)
+ {
+ if (is_int($code)) {
+ $this->PEAR_Error('DB Error: ' . DB::errorMessage($code), $code,
+ $mode, $level, $debuginfo);
+ } else {
+ $this->PEAR_Error("DB Error: $code", DB_ERROR,
+ $mode, $level, $debuginfo);
+ }
+ }
+
+ // }}}
+}
+
+// }}}
+// {{{ class DB_result
+
+/**
+ * This class implements a wrapper for a DB result set
+ *
+ * A new instance of this class will be returned by the DB implementation
+ * after processing a query that returns data.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.13
+ * @link http://pear.php.net/package/DB
+ */
+class DB_result
+{
+ // {{{ properties
+
+ /**
+ * Should results be freed automatically when there are no more rows?
+ * @var boolean
+ * @see DB_common::$options
+ */
+ var $autofree;
+
+ /**
+ * A reference to the DB_<driver> object
+ * @var object
+ */
+ var $dbh;
+
+ /**
+ * The current default fetch mode
+ * @var integer
+ * @see DB_common::$fetchmode
+ */
+ var $fetchmode;
+
+ /**
+ * The name of the class into which results should be fetched when
+ * DB_FETCHMODE_OBJECT is in effect
+ *
+ * @var string
+ * @see DB_common::$fetchmode_object_class
+ */
+ var $fetchmode_object_class;
+
+ /**
+ * The number of rows to fetch from a limit query
+ * @var integer
+ */
+ var $limit_count = null;
+
+ /**
+ * The row to start fetching from in limit queries
+ * @var integer
+ */
+ var $limit_from = null;
+
+ /**
+ * The execute parameters that created this result
+ * @var array
+ * @since Property available since Release 1.7.0
+ */
+ var $parameters;
+
+ /**
+ * The query string that created this result
+ *
+ * Copied here incase it changes in $dbh, which is referenced
+ *
+ * @var string
+ * @since Property available since Release 1.7.0
+ */
+ var $query;
+
+ /**
+ * The query result resource id created by PHP
+ * @var resource
+ */
+ var $result;
+
+ /**
+ * The present row being dealt with
+ * @var integer
+ */
+ var $row_counter = null;
+
+ /**
+ * The prepared statement resource id created by PHP in $dbh
+ *
+ * This resource is only available when the result set was created using
+ * a driver's native execute() method, not PEAR DB's emulated one.
+ *
+ * Copied here incase it changes in $dbh, which is referenced
+ *
+ * {@internal Mainly here because the InterBase/Firebird API is only
+ * able to retrieve data from result sets if the statemnt handle is
+ * still in scope.}}
+ *
+ * @var resource
+ * @since Property available since Release 1.7.0
+ */
+ var $statement;
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor sets the object's properties
+ *
+ * @param object &$dbh the DB object reference
+ * @param resource $result the result resource id
+ * @param array $options an associative array with result options
+ *
+ * @return void
+ */
+ function DB_result(&$dbh, $result, $options = array())
+ {
+ $this->autofree = $dbh->options['autofree'];
+ $this->dbh = &$dbh;
+ $this->fetchmode = $dbh->fetchmode;
+ $this->fetchmode_object_class = $dbh->fetchmode_object_class;
+ $this->parameters = $dbh->last_parameters;
+ $this->query = $dbh->last_query;
+ $this->result = $result;
+ $this->statement = empty($dbh->last_stmt) ? null : $dbh->last_stmt;
+ foreach ($options as $key => $value) {
+ $this->setOption($key, $value);
+ }
+ }
+
+ /**
+ * Set options for the DB_result object
+ *
+ * @param string $key the option to set
+ * @param mixed $value the value to set the option to
+ *
+ * @return void
+ */
+ function setOption($key, $value = null)
+ {
+ switch ($key) {
+ case 'limit_from':
+ $this->limit_from = $value;
+ break;
+ case 'limit_count':
+ $this->limit_count = $value;
+ }
+ }
+
+ // }}}
+ // {{{ fetchRow()
+
+ /**
+ * Fetch a row of data and return it by reference into an array
+ *
+ * The type of array returned can be controlled either by setting this
+ * method's <var>$fetchmode</var> parameter or by changing the default
+ * fetch mode setFetchMode() before calling this method.
+ *
+ * There are two options for standardizing the information returned
+ * from databases, ensuring their values are consistent when changing
+ * DBMS's. These portability options can be turned on when creating a
+ * new DB object or by using setOption().
+ *
+ * + <var>DB_PORTABILITY_LOWERCASE</var>
+ * convert names of fields to lower case
+ *
+ * + <var>DB_PORTABILITY_RTRIM</var>
+ * right trim the data
+ *
+ * @param int $fetchmode the constant indicating how to format the data
+ * @param int $rownum the row number to fetch (index starts at 0)
+ *
+ * @return mixed an array or object containing the row's data,
+ * NULL when the end of the result set is reached
+ * or a DB_Error object on failure.
+ *
+ * @see DB_common::setOption(), DB_common::setFetchMode()
+ */
+ function &fetchRow($fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+ {
+ if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+ $fetchmode = $this->fetchmode;
+ }
+ if ($fetchmode === DB_FETCHMODE_OBJECT) {
+ $fetchmode = DB_FETCHMODE_ASSOC;
+ $object_class = $this->fetchmode_object_class;
+ }
+ if (is_null($rownum) && $this->limit_from !== null) {
+ if ($this->row_counter === null) {
+ $this->row_counter = $this->limit_from;
+ // Skip rows
+ if ($this->dbh->features['limit'] === false) {
+ $i = 0;
+ while ($i++ < $this->limit_from) {
+ $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+ }
+ }
+ }
+ if ($this->row_counter >= ($this->limit_from + $this->limit_count))
+ {
+ if ($this->autofree) {
+ $this->free();
+ }
+ $tmp = null;
+ return $tmp;
+ }
+ if ($this->dbh->features['limit'] === 'emulate') {
+ $rownum = $this->row_counter;
+ }
+ $this->row_counter++;
+ }
+ $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+ if ($res === DB_OK) {
+ if (isset($object_class)) {
+ // The default mode is specified in the
+ // DB_common::fetchmode_object_class property
+ if ($object_class == 'stdClass') {
+ $arr = (object) $arr;
+ } else {
+ $arr = new $object_class($arr);
+ }
+ }
+ return $arr;
+ }
+ if ($res == null && $this->autofree) {
+ $this->free();
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Fetch a row of data into an array which is passed by reference
+ *
+ * The type of array returned can be controlled either by setting this
+ * method's <var>$fetchmode</var> parameter or by changing the default
+ * fetch mode setFetchMode() before calling this method.
+ *
+ * There are two options for standardizing the information returned
+ * from databases, ensuring their values are consistent when changing
+ * DBMS's. These portability options can be turned on when creating a
+ * new DB object or by using setOption().
+ *
+ * + <var>DB_PORTABILITY_LOWERCASE</var>
+ * convert names of fields to lower case
+ *
+ * + <var>DB_PORTABILITY_RTRIM</var>
+ * right trim the data
+ *
+ * @param array &$arr the variable where the data should be placed
+ * @param int $fetchmode the constant indicating how to format the data
+ * @param int $rownum the row number to fetch (index starts at 0)
+ *
+ * @return mixed DB_OK if a row is processed, NULL when the end of the
+ * result set is reached or a DB_Error object on failure
+ *
+ * @see DB_common::setOption(), DB_common::setFetchMode()
+ */
+ function fetchInto(&$arr, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
+ {
+ if ($fetchmode === DB_FETCHMODE_DEFAULT) {
+ $fetchmode = $this->fetchmode;
+ }
+ if ($fetchmode === DB_FETCHMODE_OBJECT) {
+ $fetchmode = DB_FETCHMODE_ASSOC;
+ $object_class = $this->fetchmode_object_class;
+ }
+ if (is_null($rownum) && $this->limit_from !== null) {
+ if ($this->row_counter === null) {
+ $this->row_counter = $this->limit_from;
+ // Skip rows
+ if ($this->dbh->features['limit'] === false) {
+ $i = 0;
+ while ($i++ < $this->limit_from) {
+ $this->dbh->fetchInto($this->result, $arr, $fetchmode);
+ }
+ }
+ }
+ if ($this->row_counter >= (
+ $this->limit_from + $this->limit_count))
+ {
+ if ($this->autofree) {
+ $this->free();
+ }
+ return null;
+ }
+ if ($this->dbh->features['limit'] === 'emulate') {
+ $rownum = $this->row_counter;
+ }
+
+ $this->row_counter++;
+ }
+ $res = $this->dbh->fetchInto($this->result, $arr, $fetchmode, $rownum);
+ if ($res === DB_OK) {
+ if (isset($object_class)) {
+ // default mode specified in the
+ // DB_common::fetchmode_object_class property
+ if ($object_class == 'stdClass') {
+ $arr = (object) $arr;
+ } else {
+ $arr = new $object_class($arr);
+ }
+ }
+ return DB_OK;
+ }
+ if ($res == null && $this->autofree) {
+ $this->free();
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Get the the number of columns in a result set
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ */
+ function numCols()
+ {
+ return $this->dbh->numCols($this->result);
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Get the number of rows in a result set
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function numRows()
+ {
+ if ($this->dbh->features['numrows'] === 'emulate'
+ && $this->dbh->options['portability'] & DB_PORTABILITY_NUMROWS)
+ {
+ if ($this->dbh->features['prepare']) {
+ $res = $this->dbh->query($this->query, $this->parameters);
+ } else {
+ $res = $this->dbh->query($this->query);
+ }
+ if (DB::isError($res)) {
+ return $res;
+ }
+ $i = 0;
+ while ($res->fetchInto($tmp, DB_FETCHMODE_ORDERED)) {
+ $i++;
+ }
+ $count = $i;
+ } else {
+ $count = $this->dbh->numRows($this->result);
+ }
+
+ /* fbsql is checked for here because limit queries are implemented
+ * using a TOP() function, which results in fbsql_num_rows still
+ * returning the total number of rows that would have been returned,
+ * rather than the real number. As a result, we'll just do the limit
+ * calculations for fbsql in the same way as a database with emulated
+ * limits. Unfortunately, we can't just do this in DB_fbsql::numRows()
+ * because that only gets the result resource, rather than the full
+ * DB_Result object. */
+ if (($this->dbh->features['limit'] === 'emulate'
+ && $this->limit_from !== null)
+ || $this->dbh->phptype == 'fbsql') {
+ $limit_count = is_null($this->limit_count) ? $count : $this->limit_count;
+ if ($count < $this->limit_from) {
+ $count = 0;
+ } elseif ($count < ($this->limit_from + $limit_count)) {
+ $count -= $this->limit_from;
+ } else {
+ $count = $limit_count;
+ }
+ }
+
+ return $count;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Get the next result if a batch of queries was executed
+ *
+ * @return bool true if a new result is available or false if not
+ */
+ function nextResult()
+ {
+ return $this->dbh->nextResult($this->result);
+ }
+
+ // }}}
+ // {{{ free()
+
+ /**
+ * Frees the resources allocated for this result set
+ *
+ * @return bool true on success. A DB_Error object on failure.
+ */
+ function free()
+ {
+ $err = $this->dbh->freeResult($this->result);
+ if (DB::isError($err)) {
+ return $err;
+ }
+ $this->result = false;
+ $this->statement = false;
+ return true;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * @see DB_common::tableInfo()
+ * @deprecated Method deprecated some time before Release 1.2
+ */
+ function tableInfo($mode = null)
+ {
+ if (is_string($mode)) {
+ return $this->dbh->raiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+ return $this->dbh->tableInfo($this, $mode);
+ }
+
+ // }}}
+ // {{{ getQuery()
+
+ /**
+ * Determine the query string that created this result
+ *
+ * @return string the query string
+ *
+ * @since Method available since Release 1.7.0
+ */
+ function getQuery()
+ {
+ return $this->query;
+ }
+
+ // }}}
+ // {{{ getRowCounter()
+
+ /**
+ * Tells which row number is currently being processed
+ *
+ * @return integer the current row being looked at. Starts at 1.
+ */
+ function getRowCounter()
+ {
+ return $this->row_counter;
+ }
+
+ // }}}
+}
+
+// }}}
+// {{{ class DB_row
+
+/**
+ * PEAR DB Row Object
+ *
+ * The object contains a row of data from a result set. Each column's data
+ * is placed in a property named for the column.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.13
+ * @link http://pear.php.net/package/DB
+ * @see DB_common::setFetchMode()
+ */
+class DB_row
+{
+ // {{{ constructor
+
+ /**
+ * The constructor places a row's data into properties of this object
+ *
+ * @param array the array containing the row's data
+ *
+ * @return void
+ */
+ function DB_row(&$arr)
+ {
+ foreach ($arr as $key => $value) {
+ $this->$key = &$arr[$key];
+ }
+ }
+
+ // }}}
+}
+
+// }}}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/DataObject.php b/extlib/DB/DataObject.php
new file mode 100644
index 000000000..b1a1a4e21
--- /dev/null
+++ b/extlib/DB/DataObject.php
@@ -0,0 +1,4152 @@
+<?php
+/**
+ * Object Based Database Query Builder and data store
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB_DataObject
+ * @author Alan Knowles <alan@akbkhome.com>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: DataObject.php,v 1.439 2008/01/30 02:14:06 alan_k Exp $
+ * @link http://pear.php.net/package/DB_DataObject
+ */
+
+
+/* ===========================================================================
+ *
+ * !!!!!!!!!!!!! W A R N I N G !!!!!!!!!!!
+ *
+ * THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it,
+ * just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include
+ * this file. reducing the optimization level may also solve the segfault.
+ * ===========================================================================
+ */
+
+/**
+ * The main "DB_DataObject" class is really a base class for your own tables classes
+ *
+ * // Set up the class by creating an ini file (refer to the manual for more details
+ * [DB_DataObject]
+ * database = mysql:/username:password@host/database
+ * schema_location = /home/myapplication/database
+ * class_location = /home/myapplication/DBTables/
+ * clase_prefix = DBTables_
+ *
+ *
+ * //Start and initialize...................... - dont forget the &
+ * $config = parse_ini_file('example.ini',true);
+ * $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ * $options = $config['DB_DataObject'];
+ *
+ * // example of a class (that does not use the 'auto generated tables data')
+ * class mytable extends DB_DataObject {
+ * // mandatory - set the table
+ * var $_database_dsn = "mysql://username:password@localhost/database";
+ * var $__table = "mytable";
+ * function table() {
+ * return array(
+ * 'id' => 1, // integer or number
+ * 'name' => 2, // string
+ * );
+ * }
+ * function keys() {
+ * return array('id');
+ * }
+ * }
+ *
+ * // use in the application
+ *
+ *
+ * Simple get one row
+ *
+ * $instance = new mytable;
+ * $instance->get("id",12);
+ * echo $instance->somedata;
+ *
+ *
+ * Get multiple rows
+ *
+ * $instance = new mytable;
+ * $instance->whereAdd("ID > 12");
+ * $instance->whereAdd("ID < 14");
+ * $instance->find();
+ * while ($instance->fetch()) {
+ * echo $instance->somedata;
+ * }
+
+
+/**
+ * Needed classes
+ * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM)
+ */
+
+require_once 'PEAR.php';
+
+/**
+ * We are duping fetchmode constants to be compatible with
+ * both DB and MDB2
+ */
+define('DB_DATAOBJECT_FETCHMODE_ORDERED',1);
+define('DB_DATAOBJECT_FETCHMODE_ASSOC',2);
+
+
+
+
+
+/**
+ * these are constants for the get_table array
+ * user to determine what type of escaping is required around the object vars.
+ */
+define('DB_DATAOBJECT_INT', 1); // does not require ''
+define('DB_DATAOBJECT_STR', 2); // requires ''
+
+define('DB_DATAOBJECT_DATE', 4); // is date #TODO
+define('DB_DATAOBJECT_TIME', 8); // is time #TODO
+define('DB_DATAOBJECT_BOOL', 16); // is boolean #TODO
+define('DB_DATAOBJECT_TXT', 32); // is long text #TODO
+define('DB_DATAOBJECT_BLOB', 64); // is blob type
+
+
+define('DB_DATAOBJECT_NOTNULL', 128); // not null col.
+define('DB_DATAOBJECT_MYSQLTIMESTAMP' , 256); // mysql timestamps (ignored by update/insert)
+/*
+ * Define this before you include DataObjects.php to disable overload - if it segfaults due to Zend optimizer..
+ */
+//define('DB_DATAOBJECT_NO_OVERLOAD',true)
+
+
+/**
+ * Theses are the standard error codes, most methods will fail silently - and return false
+ * to access the error message either use $table->_lastError
+ * or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
+ * the code is $last_error->code, and the message is $last_error->message (a standard PEAR error)
+ */
+
+define('DB_DATAOBJECT_ERROR_INVALIDARGS', -1); // wrong args to function
+define('DB_DATAOBJECT_ERROR_NODATA', -2); // no data available
+define('DB_DATAOBJECT_ERROR_INVALIDCONFIG', -3); // something wrong with the config
+define('DB_DATAOBJECT_ERROR_NOCLASS', -4); // no class exists
+define('DB_DATAOBJECT_ERROR_INVALID_CALL' ,-7); // overlad getter/setter failure
+
+/**
+ * Used in methods like delete() and count() to specify that the method should
+ * build the condition only out of the whereAdd's and not the object parameters.
+ */
+define('DB_DATAOBJECT_WHEREADD_ONLY', true);
+
+/**
+ *
+ * storage for connection and result objects,
+ * it is done this way so that print_r()'ing the is smaller, and
+ * it reduces the memory size of the object.
+ * -- future versions may use $this->_connection = & PEAR object..
+ * although will need speed tests to see how this affects it.
+ * - includes sub arrays
+ * - connections = md5 sum mapp to pear db object
+ * - results = [id] => map to pear db object
+ * - resultseq = sequence id for results & results field
+ * - resultfields = [id] => list of fields return from query (for use with toArray())
+ * - ini = mapping of database to ini file results
+ * - links = mapping of database to links file
+ * - lasterror = pear error objects for last error event.
+ * - config = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance.
+ * - array of loaded classes by autoload method - to stop it doing file access request over and over again!
+ */
+$GLOBALS['_DB_DATAOBJECT']['RESULTS'] = array();
+$GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1;
+$GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array();
+$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array();
+$GLOBALS['_DB_DATAOBJECT']['INI'] = array();
+$GLOBALS['_DB_DATAOBJECT']['LINKS'] = array();
+$GLOBALS['_DB_DATAOBJECT']['SEQUENCE'] = array();
+$GLOBALS['_DB_DATAOBJECT']['LASTERROR'] = null;
+$GLOBALS['_DB_DATAOBJECT']['CONFIG'] = array();
+$GLOBALS['_DB_DATAOBJECT']['CACHE'] = array();
+$GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false;
+$GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0;
+
+
+
+// this will be horrifically slow!!!!
+// NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..)
+// these two are BC/FC handlers for call in PHP4/5
+
+if ( substr(phpversion(),0,1) == 5) {
+ class DB_DataObject_Overload
+ {
+ function __call($method,$args)
+ {
+ $return = null;
+ $this->_call($method,$args,$return);
+ return $return;
+ }
+ function __sleep()
+ {
+ return array_keys(get_object_vars($this)) ;
+ }
+ }
+} else {
+ if (version_compare(phpversion(),'4.3.10','eq') && !defined('DB_DATAOBJECT_NO_OVERLOAD')) {
+ trigger_error(
+ "overload does not work with PHP4.3.10, either upgrade
+ (snaps.php.net) or more recent version
+ or define DB_DATAOBJECT_NO_OVERLOAD as per the manual.
+ ",E_USER_ERROR);
+ }
+
+ if (!function_exists('clone')) {
+ // emulate clone - as per php_compact, slow but really the correct behaviour..
+ eval('function clone($t) { $r = $t; if (method_exists($r,"__clone")) { $r->__clone(); } return $r; }');
+ }
+ eval('
+ class DB_DataObject_Overload {
+ function __call($method,$args,&$return) {
+ return $this->_call($method,$args,$return);
+ }
+ }
+ ');
+}
+
+
+
+
+
+
+ /*
+ *
+ * @package DB_DataObject
+ * @author Alan Knowles <alan@akbkhome.com>
+ * @since PHP 4.0
+ */
+
+class DB_DataObject extends DB_DataObject_Overload
+{
+ /**
+ * The Version - use this to check feature changes
+ *
+ * @access private
+ * @var string
+ */
+ var $_DB_DataObject_version = "1.8.8";
+
+ /**
+ * The Database table (used by table extends)
+ *
+ * @access private
+ * @var string
+ */
+ var $__table = ''; // database table
+
+ /**
+ * The Number of rows returned from a query
+ *
+ * @access public
+ * @var int
+ */
+ var $N = 0; // Number of rows returned from a query
+
+ /* ============================================================= */
+ /* Major Public Methods */
+ /* (designed to be optionally then called with parent::method()) */
+ /* ============================================================= */
+
+
+ /**
+ * Get a result using key, value.
+ *
+ * for example
+ * $object->get("ID",1234);
+ * Returns Number of rows located (usually 1) for success,
+ * and puts all the table columns into this classes variables
+ *
+ * see the fetch example on how to extend this.
+ *
+ * if no value is entered, it is assumed that $key is a value
+ * and get will then use the first key in keys()
+ * to obtain the key.
+ *
+ * @param string $k column
+ * @param string $v value
+ * @access public
+ * @return int No. of rows
+ */
+ function get($k = null, $v = null)
+ {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+ $keys = array();
+
+ if ($v === null) {
+ $v = $k;
+ $keys = $this->keys();
+ if (!$keys) {
+ $this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ $k = $keys[0];
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("$k $v " .print_r($keys,true), "GET");
+ }
+
+ if ($v === null) {
+ $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ $this->$k = $v;
+ return $this->find(1);
+ }
+
+ /**
+ * An autoloading, caching static get method using key, value (based on get)
+ *
+ * Usage:
+ * $object = DB_DataObject::staticGet("DbTable_mytable",12);
+ * or
+ * $object = DB_DataObject::staticGet("DbTable_mytable","name","fred");
+ *
+ * or write it into your extended class:
+ * function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v); }
+ *
+ * @param string $class class name
+ * @param string $k column (or value if using keys)
+ * @param string $v value (optional)
+ * @access public
+ * @return object
+ */
+ function &staticGet($class, $k, $v = null)
+ {
+ $lclass = strtolower($class);
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+
+
+
+ $key = "$k:$v";
+ if ($v === null) {
+ $key = $k;
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
+ }
+ if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
+ return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
+ }
+
+ $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
+ if (PEAR::isError($obj)) {
+ DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
+ $r = false;
+ return $r;
+ }
+
+ if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
+ $_DB_DATAOBJECT['CACHE'][$lclass] = array();
+ }
+ if (!$obj->get($k,$v)) {
+ DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
+
+ $r = false;
+ return $r;
+ }
+ $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
+ return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
+ }
+
+ /**
+ * find results, either normal or crosstable
+ *
+ * for example
+ *
+ * $object = new mytable();
+ * $object->ID = 1;
+ * $object->find();
+ *
+ *
+ * will set $object->N to number of rows, and expects next command to fetch rows
+ * will return $object->N
+ *
+ * @param boolean $n Fetch first result
+ * @access public
+ * @return mixed (number of rows returned, or true if numRows fetching is not supported)
+ */
+ function find($n = false)
+ {
+ global $_DB_DATAOBJECT;
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug($n, "find",1);
+ }
+ if (!$this->__table) {
+ // xdebug can backtrace this!
+ trigger_error("NO \$__table SPECIFIED in class definition",E_USER_ERROR);
+ }
+ $this->N = 0;
+ $query_before = $this->_query;
+ $this->_build_condition($this->table()) ;
+
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ /* We are checking for method modifyLimitQuery as it is PEAR DB specific */
+ $sql = 'SELECT ' .
+ $this->_query['data_select'] . " \n" .
+ ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " \n" .
+ $this->_join . " \n" .
+ $this->_query['condition'] . " \n" .
+ $this->_query['group_by'] . " \n" .
+ $this->_query['having'] . " \n" .
+ $this->_query['order_by'] . " \n";
+
+ if ((!isset($_DB_DATAOBJECT['CONFIG']['db_driver'])) ||
+ ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
+ /* PEAR DB specific */
+
+ if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
+ $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
+ }
+ } else {
+ /* theoretically MDB2! */
+ if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
+ $DB->setLimit($this->_query['limit_count'],$this->_query['limit_start']);
+ }
+ }
+
+
+ $this->_query($sql);
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("CHECK autofetchd $n", "find", 1);
+ }
+
+ // find(true)
+
+ $ret = $this->N;
+ if (!$ret && !empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
+ // clear up memory if nothing found!?
+ unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
+ }
+
+ if ($n && $this->N > 0 ) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("ABOUT TO AUTOFETCH", "find", 1);
+ }
+ $fs = $this->fetch();
+ // if fetch returns false (eg. failed), then the backend doesnt support numRows (eg. ret=true)
+ // - hence find() also returns false..
+ $ret = ($ret === true) ? $fs : $ret;
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("DONE", "find", 1);
+ }
+ $this->_query = $query_before;
+ return $ret;
+ }
+
+ /**
+ * fetches next row into this objects var's
+ *
+ * returns 1 on success 0 on failure
+ *
+ *
+ *
+ * Example
+ * $object = new mytable();
+ * $object->name = "fred";
+ * $object->find();
+ * $store = array();
+ * while ($object->fetch()) {
+ * echo $this->ID;
+ * $store[] = $object; // builds an array of object lines.
+ * }
+ *
+ * to add features to a fetch
+ * function fetch () {
+ * $ret = parent::fetch();
+ * $this->date_formated = date('dmY',$this->date);
+ * return $ret;
+ * }
+ *
+ * @access public
+ * @return boolean on success
+ */
+ function fetch()
+ {
+
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+ if (empty($this->N)) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3);
+ }
+ return false;
+ }
+
+ if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) ||
+ !is_object($result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]))
+ {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug('fetched on object after fetch completed (no results found)');
+ }
+ return false;
+ }
+
+
+ $array = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ASSOC);
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug(serialize($array),"FETCH");
+ }
+
+ // fetched after last row..
+ if ($array === null) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $t= explode(' ',microtime());
+
+ $this->debug("Last Data Fetch'ed after " .
+ ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME'] ) .
+ " seconds",
+ "FETCH", 1);
+ }
+ // reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
+ unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
+
+ // we need to keep a copy of resultfields locally so toArray() still works
+ // however we dont want to keep it in the global cache..
+
+ if (!empty($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
+ $this->_resultFields = $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid];
+ unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
+ }
+ // this is probably end of data!!
+ //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+ // make sure resultFields is always empty..
+ $this->_resultFields = false;
+
+ if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
+ // note: we dont declare this to keep the print_r size down.
+ $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
+ }
+
+ foreach($array as $k=>$v) {
+ $kk = str_replace(".", "_", $k);
+ $kk = str_replace(" ", "_", $kk);
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
+ }
+ $this->$kk = $array[$k];
+ }
+
+ // set link flag
+ $this->_link_loaded=false;
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("{$this->__table} DONE", "fetchrow",2);
+ }
+ if (($this->_query !== false) && empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
+ $this->_query = false;
+ }
+ return true;
+ }
+
+ /**
+ * Adds a condition to the WHERE statement, defaults to AND
+ *
+ * $object->whereAdd(); //reset or cleaer ewhwer
+ * $object->whereAdd("ID > 20");
+ * $object->whereAdd("age > 20","OR");
+ *
+ * @param string $cond condition
+ * @param string $logic optional logic "OR" (defaults to "AND")
+ * @access public
+ * @return string|PEAR::Error - previous condition or Error when invalid args found
+ */
+ function whereAdd($cond = false, $logic = 'AND')
+ {
+ // for PHP5.2.3 - there is a bug with setting array properties of an object.
+ $_query = $this->_query;
+
+ if (!isset($this->_query) || ($_query === false)) {
+ return $this->raiseError(
+ "You cannot do two queries on the same object (clone it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+
+ if ($cond === false) {
+ $r = $this->_query['condition'];
+ $_query['condition'] = '';
+ $this->_query = $_query;
+ return preg_replace('/^\s+WHERE\s+/','',$r);
+ }
+ // check input...= 0 or ' ' == error!
+ if (!trim($cond)) {
+ return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+ $r = $_query['condition'];
+ if ($_query['condition']) {
+ $_query['condition'] .= " {$logic} ( {$cond} )";
+ $this->_query = $_query;
+ return $r;
+ }
+ $_query['condition'] = " WHERE ( {$cond} ) ";
+ $this->_query = $_query;
+ return $r;
+ }
+
+ /**
+ * Adds a order by condition
+ *
+ * $object->orderBy(); //clears order by
+ * $object->orderBy("ID");
+ * $object->orderBy("ID,age");
+ *
+ * @param string $order Order
+ * @access public
+ * @return none|PEAR::Error - invalid args only
+ */
+ function orderBy($order = false)
+ {
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ if ($order === false) {
+ $this->_query['order_by'] = '';
+ return;
+ }
+ // check input...= 0 or ' ' == error!
+ if (!trim($order)) {
+ return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+
+ if (!$this->_query['order_by']) {
+ $this->_query['order_by'] = " ORDER BY {$order} ";
+ return;
+ }
+ $this->_query['order_by'] .= " , {$order}";
+ }
+
+ /**
+ * Adds a group by condition
+ *
+ * $object->groupBy(); //reset the grouping
+ * $object->groupBy("ID DESC");
+ * $object->groupBy("ID,age");
+ *
+ * @param string $group Grouping
+ * @access public
+ * @return none|PEAR::Error - invalid args only
+ */
+ function groupBy($group = false)
+ {
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ if ($group === false) {
+ $this->_query['group_by'] = '';
+ return;
+ }
+ // check input...= 0 or ' ' == error!
+ if (!trim($group)) {
+ return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+
+
+ if (!$this->_query['group_by']) {
+ $this->_query['group_by'] = " GROUP BY {$group} ";
+ return;
+ }
+ $this->_query['group_by'] .= " , {$group}";
+ }
+
+ /**
+ * Adds a having clause
+ *
+ * $object->having(); //reset the grouping
+ * $object->having("sum(value) > 0 ");
+ *
+ * @param string $having condition
+ * @access public
+ * @return none|PEAR::Error - invalid args only
+ */
+ function having($having = false)
+ {
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ if ($having === false) {
+ $this->_query['having'] = '';
+ return;
+ }
+ // check input...= 0 or ' ' == error!
+ if (!trim($having)) {
+ return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+
+
+ if (!$this->_query['having']) {
+ $this->_query['having'] = " HAVING {$having} ";
+ return;
+ }
+ $this->_query['having'] .= " AND {$having}";
+ }
+
+ /**
+ * Sets the Limit
+ *
+ * $boject->limit(); // clear limit
+ * $object->limit(12);
+ * $object->limit(12,10);
+ *
+ * Note this will emit an error on databases other than mysql/postgress
+ * as there is no 'clean way' to implement it. - you should consider refering to
+ * your database manual to decide how you want to implement it.
+ *
+ * @param string $a limit start (or number), or blank to reset
+ * @param string $b number
+ * @access public
+ * @return none|PEAR::Error - invalid args only
+ */
+ function limit($a = null, $b = null)
+ {
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+
+ if ($a === null) {
+ $this->_query['limit_start'] = '';
+ $this->_query['limit_count'] = '';
+ return;
+ }
+ // check input...= 0 or ' ' == error!
+ if ((!is_int($a) && ((string)((int)$a) !== (string)$a))
+ || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) {
+ return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+ global $_DB_DATAOBJECT;
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ $this->_query['limit_start'] = ($b == null) ? 0 : (int)$a;
+ $this->_query['limit_count'] = ($b == null) ? (int)$a : (int)$b;
+
+ }
+
+ /**
+ * Adds a select columns
+ *
+ * $object->selectAdd(); // resets select to nothing!
+ * $object->selectAdd("*"); // default select
+ * $object->selectAdd("unixtime(DATE) as udate");
+ * $object->selectAdd("DATE");
+ *
+ * to prepend distict:
+ * $object->selectAdd('distinct ' . $object->selectAdd());
+ *
+ * @param string $k
+ * @access public
+ * @return mixed null or old string if you reset it.
+ */
+ function selectAdd($k = null)
+ {
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ if ($k === null) {
+ $old = $this->_query['data_select'];
+ $this->_query['data_select'] = '';
+ return $old;
+ }
+
+ // check input...= 0 or ' ' == error!
+ if (!trim($k)) {
+ return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+
+ if ($this->_query['data_select']) {
+ $this->_query['data_select'] .= ', ';
+ }
+ $this->_query['data_select'] .= " $k ";
+ }
+ /**
+ * Adds multiple Columns or objects to select with formating.
+ *
+ * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
+ * // note with null it will also clear the '*' default select
+ * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
+ * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
+ * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
+ * objectTableName.colnameA as prefix_colnameA
+ *
+ * @param array|object|null the array or object to take column names from.
+ * @param string format in sprintf format (use %s for the colname)
+ * @param string table name eg. if you have joinAdd'd or send $from as an array.
+ * @access public
+ * @return void
+ */
+ function selectAs($from = null,$format = '%s',$tableName=false)
+ {
+ global $_DB_DATAOBJECT;
+
+ if ($this->_query === false) {
+ $this->raiseError(
+ "You cannot do two queries on the same object (copy it before finding)",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+
+ if ($from === null) {
+ // blank the '*'
+ $this->selectAdd();
+ $from = $this;
+ }
+
+
+ $table = $this->__table;
+ if (is_object($from)) {
+ $table = $from->__table;
+ $from = array_keys($from->table());
+ }
+
+ if ($tableName !== false) {
+ $table = $tableName;
+ }
+ $s = '%s';
+ if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+ $s = $DB->quoteIdentifier($s);
+ $format = $DB->quoteIdentifier($format);
+ }
+ foreach ($from as $k) {
+ $this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k));
+ }
+ $this->_query['data_select'] .= "\n";
+ }
+ /**
+ * Insert the current objects variables into the database
+ *
+ * Returns the ID of the inserted element (if auto increment or sequences are used.)
+ *
+ * for example
+ *
+ * Designed to be extended
+ *
+ * $object = new mytable();
+ * $object->name = "fred";
+ * echo $object->insert();
+ *
+ * @access public
+ * @return mixed false on failure, int when auto increment or sequence used, otherwise true on success
+ */
+ function insert()
+ {
+ global $_DB_DATAOBJECT;
+
+ // we need to write to the connection (For nextid) - so us the real
+ // one not, a copyied on (as ret-by-ref fails with overload!)
+
+ if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ $this->_connect();
+ }
+
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ $items = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
+ $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
+
+ if (!$items) {
+ $this->raiseError("insert:No table definition for {$this->__table}",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ $options = &$_DB_DATAOBJECT['CONFIG'];
+
+
+ $datasaved = 1;
+ $leftq = '';
+ $rightq = '';
+
+ $seqKeys = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
+ $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] :
+ $this->sequenceKey();
+
+ $key = isset($seqKeys[0]) ? $seqKeys[0] : false;
+ $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
+ $seq = isset($seqKeys[2]) ? $seqKeys[2] : false;
+
+ $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
+
+
+ // nativeSequences or Sequences..
+
+ // big check for using sequences
+
+ if (($key !== false) && !$useNative) {
+
+ if (!$seq) {
+ $keyvalue = $DB->nextId($this->__table);
+ } else {
+ $f = $DB->getOption('seqname_format');
+ $DB->setOption('seqname_format','%s');
+ $keyvalue = $DB->nextId($seq);
+ $DB->setOption('seqname_format',$f);
+ }
+ if (PEAR::isError($keyvalue)) {
+ $this->raiseError($keyvalue->toString(), DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ $this->$key = $keyvalue;
+ }
+
+
+
+ foreach($items as $k => $v) {
+
+ // if we are using autoincrement - skip the column...
+ if ($key && ($k == $key) && $useNative) {
+ continue;
+ }
+
+
+ if (!isset($this->$k)) {
+ continue;
+ }
+ // dont insert data into mysql timestamps
+ // use query() if you really want to do this!!!!
+ if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
+ continue;
+ }
+
+ if ($leftq) {
+ $leftq .= ', ';
+ $rightq .= ', ';
+ }
+
+ $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ') : "$k ");
+
+ if (is_a($this->$k,'DB_DataObject_Cast')) {
+ $value = $this->$k->toString($v,$DB);
+ if (PEAR::isError($value)) {
+ $this->raiseError($value->toString() ,DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ $rightq .= $value;
+ continue;
+ }
+
+
+
+ if (!isset($options['disable_null_strings']) && is_string($this->$k) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
+ $rightq .= " NULL ";
+ continue;
+ }
+ // DATE is empty... on a col. that can be null..
+ // note: this may be usefull for time as well..
+ if (!$this->$k &&
+ (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
+ !($v & DB_DATAOBJECT_NOTNULL)) {
+
+ $rightq .= " NULL ";
+ continue;
+ }
+
+
+ if ($v & DB_DATAOBJECT_STR) {
+ $rightq .= $this->_quote((string) (
+ ($v & DB_DATAOBJECT_BOOL) ?
+ // this is thanks to the braindead idea of postgres to
+ // use t/f for boolean.
+ (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :
+ $this->$k
+ )) . " ";
+ continue;
+ }
+ if (is_numeric($this->$k)) {
+ $rightq .=" {$this->$k} ";
+ continue;
+ }
+ /* flag up string values - only at debug level... !!!??? */
+ if (is_object($this->$k) || is_array($this->$k)) {
+ $this->debug('ODD DATA: ' .$k . ' ' . print_r($this->$k,true),'ERROR');
+ }
+
+ // at present we only cast to integers
+ // - V2 may store additional data about float/int
+ $rightq .= ' ' . intval($this->$k) . ' ';
+
+ }
+
+ // not sure why we let empty insert here.. - I guess to generate a blank row..
+
+
+ if ($leftq || $useNative) {
+ $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+
+ $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) ");
+
+
+
+ if (PEAR::isError($r)) {
+ $this->raiseError($r);
+ return false;
+ }
+
+ if ($r < 1) {
+ return 0;
+ }
+
+
+ // now do we have an integer key!
+
+ if ($key && $useNative) {
+ switch ($dbtype) {
+ case 'mysql':
+ case 'mysqli':
+ $method = "{$dbtype}_insert_id";
+ $this->$key = $method(
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
+ );
+ break;
+
+ case 'mssql':
+ // note this is not really thread safe - you should wrapp it with
+ // transactions = eg.
+ // $db->query('BEGIN');
+ // $db->insert();
+ // $db->query('COMMIT');
+ $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
+ $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne';
+ $mssql_key = $DB->$method("SELECT @@IDENTITY");
+ if (PEAR::isError($mssql_key)) {
+ $this->raiseError($mssql_key);
+ return false;
+ }
+ $this->$key = $mssql_key;
+ break;
+
+ case 'pgsql':
+ if (!$seq) {
+ $seq = $DB->getSequenceName(strtolower($this->__table));
+ }
+ $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
+ $method = ($db_driver == 'DB') ? 'getOne' : 'queryOne';
+ $pgsql_key = $DB->$method("SELECT currval('".$seq . "')");
+
+
+ if (PEAR::isError($pgsql_key)) {
+ $this->raiseError($pgsql_key);
+ return false;
+ }
+ $this->$key = $pgsql_key;
+ break;
+
+ case 'ifx':
+ $this->$key = array_shift (
+ ifx_fetch_row (
+ ifx_query(
+ "select DBINFO('sqlca.sqlerrd1') FROM systables where tabid=1",
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection,
+ IFX_SCROLL
+ ),
+ "FIRST"
+ )
+ );
+ break;
+
+ }
+
+ }
+
+ if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
+ $this->_clear_cache();
+ }
+ if ($key) {
+ return $this->$key;
+ }
+ return true;
+ }
+ $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+
+ /**
+ * Updates current objects variables into the database
+ * uses the keys() to decide how to update
+ * Returns the true on success
+ *
+ * for example
+ *
+ * $object = DB_DataObject::factory('mytable');
+ * $object->get("ID",234);
+ * $object->email="testing@test.com";
+ * if(!$object->update())
+ * echo "UPDATE FAILED";
+ *
+ * to only update changed items :
+ * $dataobject->get(132);
+ * $original = $dataobject; // clone/copy it..
+ * $dataobject->setFrom($_POST);
+ * if ($dataobject->validate()) {
+ * $dataobject->update($original);
+ * } // otherwise an error...
+ *
+ * performing global updates:
+ * $object = DB_DataObject::factory('mytable');
+ * $object->status = "dead";
+ * $object->whereAdd('age > 150');
+ * $object->update(DB_DATAOBJECT_WHEREADD_ONLY);
+ *
+ * @param object dataobject (optional) | DB_DATAOBJECT_WHEREADD_ONLY - used to only update changed items.
+ * @access public
+ * @return int rows affected or false on failure
+ */
+ function update($dataObject = false)
+ {
+ global $_DB_DATAOBJECT;
+ // connect will load the config!
+ $this->_connect();
+
+
+ $original_query = $this->_query;
+
+ $items = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
+ $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
+
+ // only apply update against sequence key if it is set?????
+
+ $seq = $this->sequenceKey();
+ if ($seq[0] !== false) {
+ $keys = array($seq[0]);
+ if (empty($this->{$keys[0]}) && $dataObject !== true) {
+ $this->raiseError("update: trying to perform an update without
+ the key set, and argument to update is not
+ DB_DATAOBJECT_WHEREADD_ONLY
+ ", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ } else {
+ $keys = $this->keys();
+ }
+
+
+ if (!$items) {
+ $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ $datasaved = 1;
+ $settings = '';
+ $this->_connect();
+
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+ $dbtype = $DB->dsn["phptype"];
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+ $options = $_DB_DATAOBJECT['CONFIG'];
+
+
+ foreach($items as $k => $v) {
+ if (!isset($this->$k)) {
+ continue;
+ }
+ // ignore stuff thats
+
+ // dont write things that havent changed..
+ if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k === $this->$k)) {
+ continue;
+ }
+
+ // - dont write keys to left.!!!
+ if (in_array($k,$keys)) {
+ continue;
+ }
+
+ // dont insert data into mysql timestamps
+ // use query() if you really want to do this!!!!
+ if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
+ continue;
+ }
+
+
+ if ($settings) {
+ $settings .= ', ';
+ }
+
+ $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
+
+ if (is_a($this->$k,'DB_DataObject_Cast')) {
+ $value = $this->$k->toString($v,$DB);
+ if (PEAR::isError($value)) {
+ $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
+ return false;
+ }
+ $settings .= "$kSql = $value ";
+ continue;
+ }
+
+ // special values ... at least null is handled...
+ if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
+ $settings .= "$kSql = NULL ";
+ continue;
+ }
+ // DATE is empty... on a col. that can be null..
+ // note: this may be usefull for time as well..
+ if (!$this->$k &&
+ (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) &&
+ !($v & DB_DATAOBJECT_NOTNULL)) {
+
+ $settings .= "$kSql = NULL ";
+ continue;
+ }
+
+
+ if ($v & DB_DATAOBJECT_STR) {
+ $settings .= "$kSql = ". $this->_quote((string) (
+ ($v & DB_DATAOBJECT_BOOL) ?
+ // this is thanks to the braindead idea of postgres to
+ // use t/f for boolean.
+ (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :
+ $this->$k
+ )) . ' ';
+ continue;
+ }
+ if (is_numeric($this->$k)) {
+ $settings .= "$kSql = {$this->$k} ";
+ continue;
+ }
+ // at present we only cast to integers
+ // - V2 may store additional data about float/int
+ $settings .= "$kSql = " . intval($this->$k) . ' ';
+ }
+
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("got keys as ".serialize($keys),3);
+ }
+ if ($dataObject !== true) {
+ $this->_build_condition($items,$keys);
+ } else {
+ // prevent wiping out of data!
+ if (empty($this->_query['condition'])) {
+ $this->raiseError("update: global table update not available
+ do \$do->whereAdd('1=1'); if you really want to do that.
+ ", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ }
+
+
+
+ // echo " $settings, $this->condition ";
+ if ($settings && isset($this->_query) && $this->_query['condition']) {
+
+ $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+
+ $r = $this->_query("UPDATE {$table} SET {$settings} {$this->_query['condition']} ");
+
+ // restore original query conditions.
+ $this->_query = $original_query;
+
+ if (PEAR::isError($r)) {
+ $this->raiseError($r);
+ return false;
+ }
+ if ($r < 1) {
+ return 0;
+ }
+
+ $this->_clear_cache();
+ return $r;
+ }
+ // restore original query conditions.
+ $this->_query = $original_query;
+
+ // if you manually specified a dataobject, and there where no changes - then it's ok..
+ if ($dataObject !== false) {
+ return true;
+ }
+
+ $this->raiseError(
+ "update: No Data specifed for query $settings , {$this->_query['condition']}",
+ DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+
+ /**
+ * Deletes items from table which match current objects variables
+ *
+ * Returns the true on success
+ *
+ * for example
+ *
+ * Designed to be extended
+ *
+ * $object = new mytable();
+ * $object->ID=123;
+ * echo $object->delete(); // builds a conditon
+ *
+ * $object = new mytable();
+ * $object->whereAdd('age > 12');
+ * $object->limit(1);
+ * $object->orderBy('age DESC');
+ * $object->delete(true); // dont use object vars, use the conditions, limit and order.
+ *
+ * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
+ * we will build the condition only using the whereAdd's. Default is to
+ * build the condition only using the object parameters.
+ *
+ * @access public
+ * @return mixed True on success, false on failure, 0 on no data affected
+ */
+ function delete($useWhere = false)
+ {
+ global $_DB_DATAOBJECT;
+ // connect will load the config!
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+
+ $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : '');
+
+ if (!$useWhere) {
+
+ $keys = $this->keys();
+ $this->_query = array(); // as it's probably unset!
+ $this->_query['condition'] = ''; // default behaviour not to use where condition
+ $this->_build_condition($this->table(),$keys);
+ // if primary keys are not set then use data from rest of object.
+ if (!$this->_query['condition']) {
+ $this->_build_condition($this->table(),array(),$keys);
+ }
+ $extra_cond = '';
+ }
+
+
+ // don't delete without a condition
+ if (($this->_query !== false) && $this->_query['condition']) {
+
+ $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+ $sql = "DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}";
+
+ // add limit..
+
+ if (isset($this->_query['limit_start']) && strlen($this->_query['limit_start'] . $this->_query['limit_count'])) {
+
+ if (!isset($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
+ ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB')) {
+ // pear DB
+ $sql = $DB->modifyLimitQuery($sql,$this->_query['limit_start'], $this->_query['limit_count']);
+
+ } else {
+ // MDB2
+ $DB->setLimit( $this->_query['limit_count'],$this->_query['limit_start']);
+ }
+
+ }
+
+
+ $r = $this->_query($sql);
+
+
+ if (PEAR::isError($r)) {
+ $this->raiseError($r);
+ return false;
+ }
+ if ($r < 1) {
+ return 0;
+ }
+ $this->_clear_cache();
+ return $r;
+ } else {
+ $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+ }
+
+ /**
+ * fetches a specific row into this object variables
+ *
+ * Not recommended - better to use fetch()
+ *
+ * Returens true on success
+ *
+ * @param int $row row
+ * @access public
+ * @return boolean true on success
+ */
+ function fetchRow($row = null)
+ {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ $this->_loadConfig();
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
+ }
+ if (!$this->__table) {
+ $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ if ($row === null) {
+ $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ if (!$this->N) {
+ $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
+ }
+
+
+ $result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
+ $array = $result->fetchrow(DB_DATAOBJECT_FETCHMODE_ASSOC,$row);
+ if (!is_array($array)) {
+ $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+
+ foreach($array as $k => $v) {
+ $kk = str_replace(".", "_", $k);
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
+ }
+ $this->$kk = $array[$k];
+ }
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("{$this->__table} DONE", "fetchrow", 3);
+ }
+ return true;
+ }
+
+ /**
+ * Find the number of results from a simple query
+ *
+ * for example
+ *
+ * $object = new mytable();
+ * $object->name = "fred";
+ * echo $object->count();
+ * echo $object->count(true); // dont use object vars.
+ * echo $object->count('distinct mycol'); count distinct mycol.
+ * echo $object->count('distinct mycol',true); // dont use object vars.
+ * echo $object->count('distinct'); // count distinct id (eg. the primary key)
+ *
+ *
+ * @param bool|string (optional)
+ * (true|false => see below not on whereAddonly)
+ * (string)
+ * "DISTINCT" => does a distinct count on the tables 'key' column
+ * otherwise => normally it counts primary keys - you can use
+ * this to do things like $do->count('distinct mycol');
+ *
+ * @param bool $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
+ * we will build the condition only using the whereAdd's. Default is to
+ * build the condition using the object parameters as well.
+ *
+ * @access public
+ * @return int
+ */
+ function count($countWhat = false,$whereAddOnly = false)
+ {
+ global $_DB_DATAOBJECT;
+
+ if (is_bool($countWhat)) {
+ $whereAddOnly = $countWhat;
+ }
+
+ $t = clone($this);
+ $items = $t->table();
+
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+
+
+ if (!isset($t->_query)) {
+ $this->raiseError(
+ "You cannot do run count after you have run fetch()",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+
+ if (!$whereAddOnly && $items) {
+ $t->_build_condition($items);
+ }
+ $keys = $this->keys();
+
+ if (!$keys[0] && !is_string($countWhat)) {
+ $this->raiseError(
+ "You cannot do run count without keys - use \$do->keys('id');",
+ DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
+ return false;
+
+ }
+ $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
+ $key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]);
+ $as = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
+
+ // support distinct on default keys.
+ $countWhat = (strtoupper($countWhat) == 'DISTINCT') ?
+ "DISTINCT {$table}.{$key_col}" : $countWhat;
+
+ $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}";
+
+ $r = $t->_query(
+ "SELECT count({$countWhat}) as $as
+ FROM $table {$t->_join} {$t->_query['condition']}");
+ if (PEAR::isError($r)) {
+ return false;
+ }
+
+ $result = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
+ $l = $result->fetchRow(DB_DATAOBJECT_FETCHMODE_ORDERED);
+ // free the results - essential on oracle.
+ $t->free();
+
+ return (int) $l[0];
+ }
+
+ /**
+ * sends raw query to database
+ *
+ * Since _query has to be a private 'non overwriteable method', this is a relay
+ *
+ * @param string $string SQL Query
+ * @access public
+ * @return void or DB_Error
+ */
+ function query($string)
+ {
+ return $this->_query($string);
+ }
+
+
+ /**
+ * an escape wrapper around DB->escapeSimple()
+ * can be used when adding manual queries or clauses
+ * eg.
+ * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'");
+ *
+ * @param string $string value to be escaped
+ * @param bool $likeEscape escapes % and _ as well. - so like queries can be protected.
+ * @access public
+ * @return string
+ */
+ function escape($string, $likeEscape=false)
+ {
+ global $_DB_DATAOBJECT;
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+ // mdb2 uses escape...
+ $dd = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
+ $ret = ($dd == 'DB') ? $DB->escapeSimple($string) : $DB->escape($string);
+ if ($likeEscape) {
+ $ret = str_replace(array('_','%'), array('\_','\%'), $ret);
+ }
+ return $ret;
+
+ }
+
+ /* ==================================================== */
+ /* Major Private Vars */
+ /* ==================================================== */
+
+ /**
+ * The Database connection dsn (as described in the PEAR DB)
+ * only used really if you are writing a very simple application/test..
+ * try not to use this - it is better stored in configuration files..
+ *
+ * @access private
+ * @var string
+ */
+ var $_database_dsn = '';
+
+ /**
+ * The Database connection id (md5 sum of databasedsn)
+ *
+ * @access private
+ * @var string
+ */
+ var $_database_dsn_md5 = '';
+
+ /**
+ * The Database name
+ * created in __connection
+ *
+ * @access private
+ * @var string
+ */
+ var $_database = '';
+
+
+
+ /**
+ * The QUERY rules
+ * This replaces alot of the private variables
+ * used to build a query, it is unset after find() is run.
+ *
+ *
+ *
+ * @access private
+ * @var array
+ */
+ var $_query = array(
+ 'condition' => '', // the WHERE condition
+ 'group_by' => '', // the GROUP BY condition
+ 'order_by' => '', // the ORDER BY condition
+ 'having' => '', // the HAVING condition
+ 'limit_start' => '', // the LIMIT condition
+ 'limit_count' => '', // the LIMIT condition
+ 'data_select' => '*', // the columns to be SELECTed
+ );
+
+
+
+
+ /**
+ * Database result id (references global $_DB_DataObject[results]
+ *
+ * @access private
+ * @var integer
+ */
+ var $_DB_resultid;
+
+ /**
+ * ResultFields - on the last call to fetch(), resultfields is sent here,
+ * so we can clean up the memory.
+ *
+ * @access public
+ * @var array
+ */
+ var $_resultFields = false;
+
+
+ /* ============================================================== */
+ /* Table definition layer (started of very private but 'came out'*/
+ /* ============================================================== */
+
+ /**
+ * Autoload or manually load the table definitions
+ *
+ *
+ * usage :
+ * DB_DataObject::databaseStructure( 'databasename',
+ * parse_ini_file('mydb.ini',true),
+ * parse_ini_file('mydb.link.ini',true));
+ *
+ * obviously you dont have to use ini files.. (just return array similar to ini files..)
+ *
+ * It should append to the table structure array
+ *
+ *
+ * @param optional string name of database to assign / read
+ * @param optional array structure of database, and keys
+ * @param optional array table links
+ *
+ * @access public
+ * @return true or PEAR:error on wrong paramenters.. or false if no file exists..
+ * or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
+ */
+ function databaseStructure()
+ {
+
+ global $_DB_DATAOBJECT;
+
+ // Assignment code
+
+ if ($args = func_get_args()) {
+
+ if (count($args) == 1) {
+
+ // this returns all the tables and their structure..
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Loading Generator as databaseStructure called with args",1);
+ }
+
+ $x = new DB_DataObject;
+ $x->_database = $args[0];
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ $tables = $DB->getListOf('tables');
+ class_exists('DB_DataObject_Generator') ? '' :
+ require_once 'DB/DataObject/Generator.php';
+
+ foreach($tables as $table) {
+ $y = new DB_DataObject_Generator;
+ $y->fillTableSchema($x->_database,$table);
+ }
+ return $_DB_DATAOBJECT['INI'][$x->_database];
+ } else {
+
+ $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ?
+ $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1];
+
+ if (isset($args[1])) {
+ $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ?
+ $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2];
+ }
+ return true;
+ }
+
+ }
+
+
+
+ if (!$this->_database) {
+ $this->_connect();
+ }
+
+ // loaded already?
+ if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
+
+ // database loaded - but this is table is not available..
+ if (
+ empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])
+ && !empty($_DB_DATAOBJECT['CONFIG']['proxy'])
+ ) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Loading Generator to fetch Schema",1);
+ }
+ class_exists('DB_DataObject_Generator') ? '' :
+ require_once 'DB/DataObject/Generator.php';
+
+
+ $x = new DB_DataObject_Generator;
+ $x->fillTableSchema($this->_database,$this->__table);
+ }
+ return true;
+ }
+
+
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+
+ // if you supply this with arguments, then it will take those
+ // as the database and links array...
+
+ $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
+ array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
+ array() ;
+
+ if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
+ $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
+ $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
+ explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
+ }
+
+
+
+ foreach ($schemas as $ini) {
+ if (file_exists($ini) && is_file($ini)) {
+ $_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true);
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ if (!is_readable ($ini)) {
+ $this->debug("ini file is not readable: $ini","databaseStructure",1);
+ } else {
+ $this->debug("Loaded ini file: $ini","databaseStructure",1);
+ }
+ }
+ } else {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Missing ini file: $ini","databaseStructure",1);
+ }
+ }
+
+ }
+ // now have we loaded the structure..
+
+ if (!empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
+ return true;
+ }
+ // - if not try building it..
+ if (!empty($_DB_DATAOBJECT['CONFIG']['proxy'])) {
+ class_exists('DB_DataObject_Generator') ? '' :
+ require_once 'DB/DataObject/Generator.php';
+
+ $x = new DB_DataObject_Generator;
+ $x->fillTableSchema($this->_database,$this->__table);
+ // should this fail!!!???
+ return true;
+ }
+ $this->debug("Cant find database schema: {$this->_database}/{$this->__table} \n".
+ "in links file data: " . print_r($_DB_DATAOBJECT['INI'],true),"databaseStructure",5);
+ // we have to die here!! - it causes chaos if we dont (including looping forever!)
+ $this->raiseError( "Unable to load schema for database and table (turn debugging up to 5 for full error message)", DB_DATAOBJECT_ERROR_INVALIDARGS, PEAR_ERROR_DIE);
+ return false;
+
+
+ }
+
+
+
+
+ /**
+ * Return or assign the name of the current table
+ *
+ *
+ * @param string optinal table name to set
+ * @access public
+ * @return string The name of the current table
+ */
+ function tableName()
+ {
+ $args = func_get_args();
+ if (count($args)) {
+ $this->__table = $args[0];
+ }
+ return $this->__table;
+ }
+
+ /**
+ * Return or assign the name of the current database
+ *
+ * @param string optional database name to set
+ * @access public
+ * @return string The name of the current database
+ */
+ function database()
+ {
+ $args = func_get_args();
+ if (count($args)) {
+ $this->_database = $args[0];
+ }
+ return $this->_database;
+ }
+
+ /**
+ * get/set an associative array of table columns
+ *
+ * @access public
+ * @param array key=>type array
+ * @return array (associative)
+ */
+ function table()
+ {
+
+ // for temporary storage of database fields..
+ // note this is not declared as we dont want to bloat the print_r output
+ $args = func_get_args();
+ if (count($args)) {
+ $this->_database_fields = $args[0];
+ }
+ if (isset($this->_database_fields)) {
+ return $this->_database_fields;
+ }
+
+
+ global $_DB_DATAOBJECT;
+ if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ $this->_connect();
+ }
+
+ if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
+ return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
+ }
+
+ $this->databaseStructure();
+
+
+ $ret = array();
+ if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
+ $ret = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
+ }
+
+ return $ret;
+ }
+
+ /**
+ * get/set an array of table primary keys
+ *
+ * set usage: $do->keys('id','code');
+ *
+ * This is defined in the table definition if it gets it wrong,
+ * or you do not want to use ini tables, you can override this.
+ * @param string optional set the key
+ * @param * optional set more keys
+ * @access private
+ * @return array
+ */
+ function keys()
+ {
+ // for temporary storage of database fields..
+ // note this is not declared as we dont want to bloat the print_r output
+ $args = func_get_args();
+ if (count($args)) {
+ $this->_database_keys = $args;
+ }
+ if (isset($this->_database_keys)) {
+ return $this->_database_keys;
+ }
+
+ global $_DB_DATAOBJECT;
+ if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ $this->_connect();
+ }
+ if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
+ return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
+ }
+ $this->databaseStructure();
+
+ if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
+ return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
+ }
+ return array();
+ }
+ /**
+ * get/set an sequence key
+ *
+ * by default it returns the first key from keys()
+ * set usage: $do->sequenceKey('id',true);
+ *
+ * override this to return array(false,false) if table has no real sequence key.
+ *
+ * @param string optional the key sequence/autoinc. key
+ * @param boolean optional use native increment. default false
+ * @param false|string optional native sequence name
+ * @access private
+ * @return array (column,use_native,sequence_name)
+ */
+ function sequenceKey()
+ {
+ global $_DB_DATAOBJECT;
+
+ // call setting
+ if (!$this->_database) {
+ $this->_connect();
+ }
+
+ if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) {
+ $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array();
+ }
+
+
+ $args = func_get_args();
+ if (count($args)) {
+ $args[1] = isset($args[1]) ? $args[1] : false;
+ $args[2] = isset($args[2]) ? $args[2] : false;
+ $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = $args;
+ }
+ if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table];
+ }
+ // end call setting (eg. $do->sequenceKeys(a,b,c); )
+
+
+
+
+ $keys = $this->keys();
+ if (!$keys) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]
+ = array(false,false,false);
+ }
+
+
+ $table = isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?
+ $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
+
+ $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
+
+ $usekey = $keys[0];
+
+
+
+ $seqname = false;
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
+ $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
+ if (strpos($usekey,':') !== false) {
+ list($usekey,$seqname) = explode(':',$usekey);
+ }
+ }
+
+
+ // if the key is not an integer - then it's not a sequence or native
+ if (empty($table[$usekey]) || !($table[$usekey] & DB_DATAOBJECT_INT)) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,false);
+ }
+
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
+ $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
+ if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
+ }
+ if (is_string($ignore)) {
+ $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore);
+ }
+ if (in_array($this->__table,$ignore)) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
+ }
+ }
+
+
+ $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
+
+ // if you are using an old ini file - go back to old behaviour...
+ if (is_numeric($realkeys[$usekey])) {
+ $realkeys[$usekey] = 'N';
+ }
+
+ // multiple unique primary keys without a native sequence...
+ if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
+ }
+ // use native sequence keys...
+ // technically postgres native here...
+ // we need to get the new improved tabledata sorted out first.
+
+ if ( in_array($dbtype , array('psql', 'mysql', 'mysqli', 'mssql', 'ifx')) &&
+ ($table[$usekey] & DB_DATAOBJECT_INT) &&
+ isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
+ ) {
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,true,$seqname);
+ }
+ // if not a native autoinc, and we have not assumed all primary keys are sequence
+ if (($realkeys[$usekey] != 'N') &&
+ !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
+ return array(false,false,false);
+ }
+ // I assume it's going to try and be a nextval DB sequence.. (not native)
+ return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,false,$seqname);
+ }
+
+
+
+ /* =========================================================== */
+ /* Major Private Methods - the core part! */
+ /* =========================================================== */
+
+
+
+ /**
+ * clear the cache values for this class - normally done on insert/update etc.
+ *
+ * @access private
+ * @return void
+ */
+ function _clear_cache()
+ {
+ global $_DB_DATAOBJECT;
+
+ $class = strtolower(get_class($this));
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Clearing Cache for ".$class,1);
+ }
+
+ if (!empty($_DB_DATAOBJECT['CACHE'][$class])) {
+ unset($_DB_DATAOBJECT['CACHE'][$class]);
+ }
+ }
+
+
+ /**
+ * backend wrapper for quoting, as MDB2 and DB do it differently...
+ *
+ * @access private
+ * @return string quoted
+ */
+
+ function _quote($str)
+ {
+ global $_DB_DATAOBJECT;
+ return (empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ||
+ ($_DB_DATAOBJECT['CONFIG']['db_driver'] == 'DB'))
+ ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quoteSmart($str)
+ : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->quote($str);
+ }
+
+
+ /**
+ * connects to the database
+ *
+ *
+ * TODO: tidy this up - This has grown to support a number of connection options like
+ * a) dynamic changing of ini file to change which database to connect to
+ * b) multi data via the table_{$table} = dsn ini option
+ * c) session based storage.
+ *
+ * @access private
+ * @return true | PEAR::error
+ */
+ function _connect()
+ {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ $this->_loadConfig();
+ }
+ // Set database driver for reference
+ $db_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ? 'DB' : $_DB_DATAOBJECT['CONFIG']['db_driver'];
+ // is it already connected ?
+
+ if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ return $this->raiseError(
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
+ );
+
+ }
+
+ if (!$this->_database) {
+ $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
+ $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
+
+ $this->_database = ($db_driver != 'DB' && $hasGetDatabase)
+ ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase()
+ : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
+
+
+
+ if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite')
+ && is_file($this->_database)) {
+ $this->_database = basename($this->_database);
+ }
+ if ($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'ibase') {
+ $this->_database = substr(basename($this->_database), 0, -4);
+ }
+
+ }
+ // theoretically we have a md5, it's listed in connections and it's not an error.
+ // so everything is ok!
+ return true;
+
+ }
+
+ // it's not currently connected!
+ // try and work out what to use for the dsn !
+
+ $options= &$_DB_DATAOBJECT['CONFIG'];
+ $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
+
+ if (!$dsn) {
+ if (!$this->_database) {
+ $this->_database = isset($options["table_{$this->__table}"]) ? $options["table_{$this->__table}"] : null;
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Checking for database database_{$this->_database} in options","CONNECT");
+ }
+
+ if ($this->_database && !empty($options["database_{$this->_database}"])) {
+
+ $dsn = $options["database_{$this->_database}"];
+ } else if (!empty($options['database'])) {
+ $dsn = $options['database'];
+ }
+ }
+
+ // if still no database...
+ if (!$dsn) {
+ return $this->raiseError(
+ "No database name / dsn found anywhere",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG, PEAR_ERROR_DIE
+ );
+
+ }
+
+
+ if (is_string($dsn)) {
+ $this->_database_dsn_md5 = md5($dsn);
+ } else {
+ /// support array based dsn's
+ $this->_database_dsn_md5 = md5(serialize($dsn));
+ }
+
+ if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("USING CACHED CONNECTION", "CONNECT",3);
+ }
+ if (!$this->_database) {
+
+ $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
+ $this->_database = ($db_driver != 'DB' && $hasGetDatabase)
+ ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase()
+ : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
+
+ if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite')
+ && is_file($this->_database))
+ {
+ $this->_database = basename($this->_database);
+ }
+ }
+ return true;
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("NEW CONNECTION", "CONNECT",3);
+ /* actualy make a connection */
+ $this->debug(print_r($dsn,true) ." {$this->_database_dsn_md5}", "CONNECT",3);
+ }
+
+ // Note this is verbose deliberatly!
+
+ if ($db_driver == 'DB') {
+
+ /* PEAR DB connect */
+
+ // this allows the setings of compatibility on DB
+ $db_options = PEAR::getStaticProperty('DB','options');
+ require_once 'DB.php';
+ if ($db_options) {
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &DB::connect($dsn,$db_options);
+ } else {
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &DB::connect($dsn);
+ }
+
+ } else {
+ /* assumption is MDB2 */
+ require_once 'MDB2.php';
+ // this allows the setings of compatibility on MDB2
+ $db_options = PEAR::getStaticProperty('MDB2','options');
+ $db_options = is_array($db_options) ? $db_options : array();
+ $db_options['portability'] = isset($db_options['portability'] )
+ ? $db_options['portability'] : MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE;
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = &MDB2::connect($dsn,$db_options);
+
+ }
+
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5);
+ }
+ if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ $this->debug($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->toString(), "CONNECT FAILED",5);
+ return $this->raiseError(
+ "Connect failed, turn on debugging to 5 see why",
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
+ );
+
+ }
+
+ if (!$this->_database) {
+ $hasGetDatabase = method_exists($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5], 'getDatabase');
+
+ $this->_database = ($db_driver != 'DB' && $hasGetDatabase)
+ ? $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->getDatabase()
+ : $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
+
+
+ if (($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'] == 'sqlite')
+ && is_file($this->_database))
+ {
+ $this->_database = basename($this->_database);
+ }
+ }
+
+ // Oracle need to optimize for portibility - not sure exactly what this does though :)
+ $c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ return true;
+ }
+
+ /**
+ * sends query to database - this is the private one that must work
+ * - internal functions use this rather than $this->query()
+ *
+ * @param string $string
+ * @access private
+ * @return mixed none or PEAR_Error
+ */
+ function _query($string)
+ {
+ global $_DB_DATAOBJECT;
+ $this->_connect();
+
+
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ $options = &$_DB_DATAOBJECT['CONFIG'];
+
+ $_DB_driver = empty($_DB_DATAOBJECT['CONFIG']['db_driver']) ?
+ 'DB': $_DB_DATAOBJECT['CONFIG']['db_driver'];
+
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug($string,$log="QUERY");
+
+ }
+
+ if (strtoupper($string) == 'BEGIN') {
+ if ($_DB_driver == 'DB') {
+ $DB->autoCommit(false);
+ } else {
+ $DB->beginTransaction();
+ }
+ // db backend adds begin anyway from now on..
+ return true;
+ }
+ if (strtoupper($string) == 'COMMIT') {
+ $res = $DB->commit();
+ if ($_DB_driver == 'DB') {
+ $DB->autoCommit(true);
+ }
+ return $res;
+ }
+
+ if (strtoupper($string) == 'ROLLBACK') {
+ $DB->rollback();
+ if ($_DB_driver == 'DB') {
+ $DB->autoCommit(true);
+ }
+ return true;
+ }
+
+
+ if (!empty($options['debug_ignore_updates']) &&
+ (strtolower(substr(trim($string), 0, 6)) != 'select') &&
+ (strtolower(substr(trim($string), 0, 4)) != 'show') &&
+ (strtolower(substr(trim($string), 0, 8)) != 'describe')) {
+
+ $this->debug('Disabling Update as you are in debug mode');
+ return $this->raiseError("Disabling Update as you are in debug mode", null) ;
+
+ }
+ //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) {
+ // this will only work when PEAR:DB supports it.
+ //$this->debug($DB->getAll('explain ' .$string,DB_DATAOBJECT_FETCHMODE_ASSOC), $log="sql",2);
+ //}
+
+ // some sim
+ $t= explode(' ',microtime());
+ $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1];
+
+
+ if ($_DB_driver == 'DB') {
+ $result = $DB->query($string);
+ } else {
+ switch (strtolower(substr(trim($string),0,6))) {
+
+ case 'insert':
+ case 'update':
+ case 'delete':
+ $result = $DB->exec($string);
+ break;
+
+ default:
+ $result = $DB->query($string);
+ break;
+ }
+ }
+
+
+
+ if (is_a($result,'PEAR_Error')) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug($result->toString(), "Query Error",1 );
+ }
+ return $this->raiseError($result);
+ }
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $t= explode(' ',microtime());
+ $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1];
+ $this->debug('QUERY DONE IN '.($t[0]+$t[1]-$time)." seconds", 'query',1);
+ }
+ switch (strtolower(substr(trim($string),0,6))) {
+ case 'insert':
+ case 'update':
+ case 'delete':
+ if ($_DB_driver == 'DB') {
+ // pear DB specific
+ return $DB->affectedRows();
+ }
+ return $result;
+ }
+ if (is_object($result)) {
+ // lets hope that copying the result object is OK!
+
+ $_DB_resultid = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++;
+ $_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result;
+ $this->_DB_resultid = $_DB_resultid;
+ }
+ $this->N = 0;
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug(serialize($result), 'RESULT',5);
+ }
+ if (method_exists($result, 'numrows')) {
+ if ($_DB_driver == 'DB') {
+ $DB->expectError(DB_ERROR_UNSUPPORTED);
+ } else {
+ $DB->expectError(MDB2_ERROR_UNSUPPORTED);
+ }
+ $this->N = $result->numrows();
+ if (is_a($this->N,'PEAR_Error')) {
+ $this->N = true;
+ }
+ $DB->popExpect();
+ }
+ }
+
+ /**
+ * Builds the WHERE based on the values of of this object
+ *
+ * @param mixed $keys
+ * @param array $filter (used by update to only uses keys in this filter list).
+ * @param array $negative_filter (used by delete to prevent deleting using the keys mentioned..)
+ * @access private
+ * @return string
+ */
+ function _build_condition($keys, $filter = array(),$negative_filter=array())
+ {
+ global $_DB_DATAOBJECT;
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+ $options = $_DB_DATAOBJECT['CONFIG'];
+
+ // if we dont have query vars.. - reset them.
+ if ($this->_query === false) {
+ $x = new DB_DataObject;
+ $this->_query= $x->_query;
+ }
+
+ foreach($keys as $k => $v) {
+ // index keys is an indexed array
+ /* these filter checks are a bit suspicious..
+ - need to check that update really wants to work this way */
+
+ if ($filter) {
+ if (!in_array($k, $filter)) {
+ continue;
+ }
+ }
+ if ($negative_filter) {
+ if (in_array($k, $negative_filter)) {
+ continue;
+ }
+ }
+ if (!isset($this->$k)) {
+ continue;
+ }
+
+ $kSql = $quoteIdentifiers
+ ? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) )
+ : "{$this->__table}.{$k}";
+
+
+
+ if (is_a($this->$k,'DB_DataObject_Cast')) {
+ $dbtype = $DB->dsn["phptype"];
+ $value = $this->$k->toString($v,$DB);
+ if (PEAR::isError($value)) {
+ $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
+ return false;
+ }
+ if ((strtolower($value) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
+ $this->whereAdd(" $kSql IS NULL");
+ continue;
+ }
+ $this->whereAdd(" $kSql = $value");
+ continue;
+ }
+
+ if (!isset($options['disable_null_strings']) && (strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
+ $this->whereAdd(" $kSql IS NULL");
+ continue;
+ }
+
+
+ if ($v & DB_DATAOBJECT_STR) {
+ $this->whereAdd(" $kSql = " . $this->_quote((string) (
+ ($v & DB_DATAOBJECT_BOOL) ?
+ // this is thanks to the braindead idea of postgres to
+ // use t/f for boolean.
+ (($this->$k === 'f') ? 0 : (int)(bool) $this->$k) :
+ $this->$k
+ )) );
+ continue;
+ }
+ if (is_numeric($this->$k)) {
+ $this->whereAdd(" $kSql = {$this->$k}");
+ continue;
+ }
+ /* this is probably an error condition! */
+ $this->whereAdd(" $kSql = ".intval($this->$k));
+ }
+ }
+
+ /**
+ * autoload Class relating to a table
+ * (depreciated - use ::factory)
+ *
+ * @param string $table table
+ * @access private
+ * @return string classname on Success
+ */
+ function staticAutoloadTable($table)
+ {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+ $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
+ $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
+ $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
+
+ $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
+ $class = $ce ? $class : DB_DataObject::_autoloadClass($class);
+ return $class;
+ }
+
+
+ /**
+ * classic factory method for loading a table class
+ * usage: $do = DB_DataObject::factory('person')
+ * WARNING - this may emit a include error if the file does not exist..
+ * use @ to silence it (if you are sure it is acceptable)
+ * eg. $do = @DB_DataObject::factory('person')
+ *
+ * table name will eventually be databasename/table
+ * - and allow modular dataobjects to be written..
+ * (this also helps proxy creation)
+ *
+ *
+ * @param string $table tablename (use blank to create a new instance of the same class.)
+ * @access private
+ * @return DataObject|PEAR_Error
+ */
+
+
+
+ function factory($table = '') {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+
+ if ($table === '') {
+ if (is_a($this,'DB_DataObject') && strlen($this->__table)) {
+ $table = $this->__table;
+ } else {
+ return DB_DataObject::raiseError(
+ "factory did not recieve a table name",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ }
+ }
+
+
+ $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
+ $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
+ $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
+
+ $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
+ $class = $ce ? $class : DB_DataObject::_autoloadClass($class);
+
+ // proxy = full|light
+ if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) {
+ $proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy'];
+ class_exists('DB_DataObject_Generator') ? '' :
+ require_once 'DB/DataObject/Generator.php';
+
+ $d = new DB_DataObject;
+
+ $d->__table = $table;
+ if (is_a($ret = $d->_connect(), 'PEAR_Error')) {
+ return $ret;
+ }
+
+ $x = new DB_DataObject_Generator;
+ return $x->$proxyMethod( $d->_database, $table);
+ }
+
+ if (!$class) {
+ return DB_DataObject::raiseError(
+ "factory could not find class $class from $table",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ }
+
+ return new $class;
+ }
+ /**
+ * autoload Class
+ *
+ * @param string $class Class
+ * @access private
+ * @return string classname on Success
+ */
+ function _autoloadClass($class)
+ {
+ global $_DB_DATAOBJECT;
+
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+ $class_prefix = empty($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
+ '' : $_DB_DATAOBJECT['CONFIG']['class_prefix'];
+
+ $table = substr($class,strlen($class_prefix));
+
+ // only include the file if it exists - and barf badly if it has parse errors :)
+ if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) || empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
+ return false;
+ }
+
+
+ if (strpos($_DB_DATAOBJECT['CONFIG']['class_location'],'%s') !== false) {
+ $file = sprintf($_DB_DATAOBJECT['CONFIG']['class_location'], preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)));
+ } else {
+ $file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
+ }
+
+ if (!file_exists($file)) {
+ $found = false;
+ foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) {
+ if (file_exists("$p/$file")) {
+ $file = "$p/$file";
+ $found = true;
+ break;
+ }
+ }
+ if (!$found) {
+ DB_DataObject::raiseError(
+ "autoload:Could not find class {$class} using class_location value",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ }
+
+ include_once $file;
+
+
+ $ce = substr(phpversion(),0,1) > 4 ? class_exists($class,false) : class_exists($class);
+
+ if (!$ce) {
+ DB_DataObject::raiseError(
+ "autoload:Could not autoload {$class}",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ return $class;
+ }
+
+
+
+ /**
+ * Have the links been loaded?
+ * if they have it contains a array of those variables.
+ *
+ * @access private
+ * @var boolean | array
+ */
+ var $_link_loaded = false;
+
+ /**
+ * Get the links associate array as defined by the links.ini file.
+ *
+ *
+ * Experimental... -
+ * Should look a bit like
+ * [local_col_name] => "related_tablename:related_col_name"
+ *
+ *
+ * @return array|null
+ * array = if there are links defined for this table.
+ * empty array - if there is a links.ini file, but no links on this table
+ * null - if no links.ini exists for this database (hence try auto_links).
+ * @access public
+ * @see DB_DataObject::getLinks(), DB_DataObject::getLink()
+ */
+
+ function links()
+ {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ $this->_loadConfig();
+ }
+ // have to connect.. -> otherwise things break later.
+ $this->_connect();
+
+ if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
+ return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
+ }
+
+
+
+
+
+ // attempt to load links file here..
+
+ if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
+ $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
+ array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
+ array() ;
+
+ if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
+ $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
+ $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
+ explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
+ }
+
+
+
+ foreach ($schemas as $ini) {
+
+ $links =
+ isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ?
+ $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
+ str_replace('.ini','.links.ini',$ini);
+
+ if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links) && is_file($links)) {
+ /* not sure why $links = ... here - TODO check if that works */
+ $_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true);
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Loaded links.ini file: $links","links",1);
+ }
+ } else {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
+ $this->debug("Missing links.ini file: $links","links",1);
+ }
+ }
+ }
+ }
+
+
+ // if there is no link data at all on the file!
+ // we return null.
+ if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
+ return null;
+ }
+
+ if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
+ return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
+ }
+
+ return array();
+ }
+ /**
+ * load related objects
+ *
+ * There are two ways to use this, one is to set up a <dbname>.links.ini file
+ * into a static property named <dbname>.links and specifies the table joins,
+ * the other highly dependent on naming columns 'correctly' :)
+ * using colname = xxxxx_yyyyyy
+ * xxxxxx = related table; (yyyyy = user defined..)
+ * looks up table xxxxx, for value id=$this->xxxxx
+ * stores it in $this->_xxxxx_yyyyy
+ * you can change what object vars the links are stored in by
+ * changeing the format parameter
+ *
+ *
+ * @param string format (default _%s) where %s is the table name.
+ * @author Tim White <tim@cyface.com>
+ * @access public
+ * @return boolean , true on success
+ */
+ function getLinks($format = '_%s')
+ {
+
+ // get table will load the options.
+ if ($this->_link_loaded) {
+ return true;
+ }
+ $this->_link_loaded = false;
+ $cols = $this->table();
+ $links = $this->links();
+
+ $loaded = array();
+
+ if ($links) {
+ foreach($links as $key => $match) {
+ list($table,$link) = explode(':', $match);
+ $k = sprintf($format, str_replace('.', '_', $key));
+ // makes sure that '.' is the end of the key;
+ if ($p = strpos($key,'.')) {
+ $key = substr($key, 0, $p);
+ }
+
+ $this->$k = $this->getLink($key, $table, $link);
+
+ if (is_object($this->$k)) {
+ $loaded[] = $k;
+ }
+ }
+ $this->_link_loaded = $loaded;
+ return true;
+ }
+ // this is the autonaming stuff..
+ // it sends the column name down to getLink and lets that sort it out..
+ // if there is a links file then it is not used!
+ // IT IS DEPRECIATED!!!! - USE
+ if (!is_null($links)) {
+ return false;
+ }
+
+
+ foreach (array_keys($cols) as $key) {
+ if (!($p = strpos($key, '_'))) {
+ continue;
+ }
+ // does the table exist.
+ $k =sprintf($format, $key);
+ $this->$k = $this->getLink($key);
+ if (is_object($this->$k)) {
+ $loaded[] = $k;
+ }
+ }
+ $this->_link_loaded = $loaded;
+ return true;
+ }
+
+ /**
+ * return name from related object
+ *
+ * There are two ways to use this, one is to set up a <dbname>.links.ini file
+ * into a static property named <dbname>.links and specifies the table joins,
+ * the other is highly dependant on naming columns 'correctly' :)
+ *
+ * NOTE: the naming convention is depreciated!!! - use links.ini
+ *
+ * using colname = xxxxx_yyyyyy
+ * xxxxxx = related table; (yyyyy = user defined..)
+ * looks up table xxxxx, for value id=$this->xxxxx
+ * stores it in $this->_xxxxx_yyyyy
+ *
+ * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
+ *
+ *
+ * @param string $row either row or row.xxxxx
+ * @param string $table name of table to look up value in
+ * @param string $link name of column in other table to match
+ * @author Tim White <tim@cyface.com>
+ * @access public
+ * @return mixed object on success
+ */
+ function getLink($row, $table = null, $link = false)
+ {
+
+
+ // GUESS THE LINKED TABLE.. (if found - recursevly call self)
+
+ if ($table === null) {
+ $links = $this->links();
+
+ if (is_array($links)) {
+
+ if ($links[$row]) {
+ list($table,$link) = explode(':', $links[$row]);
+ if ($p = strpos($row,".")) {
+ $row = substr($row,0,$p);
+ }
+ return $this->getLink($row,$table,$link);
+
+ }
+
+ $this->raiseError(
+ "getLink: $row is not defined as a link (normally this is ok)",
+ DB_DATAOBJECT_ERROR_NODATA);
+
+ $r = false;
+ return $r;// technically a possible error condition?
+
+ }
+ // use the old _ method - this shouldnt happen if called via getLinks()
+ if (!($p = strpos($row, '_'))) {
+ $r = null;
+ return $r;
+ }
+ $table = substr($row, 0, $p);
+ return $this->getLink($row, $table);
+
+
+ }
+
+
+
+ if (!isset($this->$row)) {
+ $this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+
+ // check to see if we know anything about this table..
+
+ $obj = $this->factory($table);
+
+ if (!is_a($obj,'DB_DataObject')) {
+ $this->raiseError(
+ "getLink:Could not find class for row $row, table $table",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+ if ($link) {
+ if ($obj->get($link, $this->$row)) {
+ $obj->free();
+ return $obj;
+ }
+ return false;
+ }
+
+ if ($obj->get($this->$row)) {
+ $obj->free();
+ return $obj;
+ }
+ return false;
+
+ }
+
+ /**
+ * IS THIS SUPPORTED/USED ANYMORE????
+ *return a list of options for a linked table
+ *
+ * This is highly dependant on naming columns 'correctly' :)
+ * using colname = xxxxx_yyyyyy
+ * xxxxxx = related table; (yyyyy = user defined..)
+ * looks up table xxxxx, for value id=$this->xxxxx
+ * stores it in $this->_xxxxx_yyyyy
+ *
+ * @access public
+ * @return array of results (empty array on failure)
+ */
+ function &getLinkArray($row, $table = null)
+ {
+
+ $ret = array();
+ if (!$table) {
+ $links = $this->links();
+
+ if (is_array($links)) {
+ if (!isset($links[$row])) {
+ // failed..
+ return $ret;
+ }
+ list($table,$link) = explode(':',$links[$row]);
+ } else {
+ if (!($p = strpos($row,'_'))) {
+ return $ret;
+ }
+ $table = substr($row,0,$p);
+ }
+ }
+
+ $c = $this->factory($table);
+
+ if (!is_a($c,'DB_DataObject')) {
+ $this->raiseError(
+ "getLinkArray:Could not find class for row $row, table $table",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG
+ );
+ return $ret;
+ }
+
+ // if the user defined method list exists - use it...
+ if (method_exists($c, 'listFind')) {
+ $c->listFind($this->id);
+ } else {
+ $c->find();
+ }
+ while ($c->fetch()) {
+ $ret[] = $c;
+ }
+ return $ret;
+ }
+
+ /**
+ * The JOIN condition
+ *
+ * @access private
+ * @var string
+ */
+ var $_join = '';
+
+ /**
+ * joinAdd - adds another dataobject to this, building a joined query.
+ *
+ * example (requires links.ini to be set up correctly)
+ * // get all the images for product 24
+ * $i = new DataObject_Image();
+ * $pi = new DataObjects_Product_image();
+ * $pi->product_id = 24; // set the product id to 24
+ * $i->joinAdd($pi); // add the product_image connectoin
+ * $i->find();
+ * while ($i->fetch()) {
+ * // do stuff
+ * }
+ * // an example with 2 joins
+ * // get all the images linked with products or productgroups
+ * $i = new DataObject_Image();
+ * $pi = new DataObject_Product_image();
+ * $pgi = new DataObject_Productgroup_image();
+ * $i->joinAdd($pi);
+ * $i->joinAdd($pgi);
+ * $i->find();
+ * while ($i->fetch()) {
+ * // do stuff
+ * }
+ *
+ *
+ * @param optional $obj object |array the joining object (no value resets the join)
+ * If you use an array here it should be in the format:
+ * array('local_column','remotetable:remote_column');
+ * if remotetable does not have a definition, you should
+ * use @ to hide the include error message..
+ *
+ *
+ * @param optional $joinType string | array
+ * 'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates
+ * just select ... from a,b,c with no join and
+ * links are added as where items.
+ *
+ * If second Argument is array, it is assumed to be an associative
+ * array with arguments matching below = eg.
+ * 'joinType' => 'INNER',
+ * 'joinAs' => '...'
+ * 'joinCol' => ....
+ * 'useWhereAsOn' => false,
+ *
+ * @param optional $joinAs string if you want to select the table as anther name
+ * useful when you want to select multiple columsn
+ * from a secondary table.
+
+ * @param optional $joinCol string The column on This objects table to match (needed
+ * if this table links to the child object in
+ * multiple places eg.
+ * user->friend (is a link to another user)
+ * user->mother (is a link to another user..)
+ *
+ * optional 'useWhereAsOn' bool default false;
+ * convert the where argments from the object being added
+ * into ON arguments.
+ *
+ *
+ * @return none
+ * @access public
+ * @author Stijn de Reede <sjr@gmx.co.uk>
+ */
+ function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false)
+ {
+ global $_DB_DATAOBJECT;
+ if ($obj === false) {
+ $this->_join = '';
+ return;
+ }
+
+
+ $useWhereAsOn = false;
+ // support for 2nd argument as an array of options
+ if (is_array($joinType)) {
+ // new options can now go in here... (dont forget to document them)
+ $useWhereAsOn = !empty($joinType['useWhereAsOn']);
+ $joinCol = isset($joinType['joinCol']) ? $joinType['joinCol'] : $joinCol;
+ $joinAs = isset($joinType['joinAs']) ? $joinType['joinAs'] : $joinAs;
+ $joinType = isset($joinType['joinType']) ? $joinType['joinType'] : 'INNER';
+ }
+ // support for array as first argument
+ // this assumes that you dont have a links.ini for the specified table.
+ // and it doesnt exist as am extended dataobject!! - experimental.
+
+ $ofield = false; // object field
+ $tfield = false; // this field
+ $toTable = false;
+ if (is_array($obj)) {
+ $tfield = $obj[0];
+ list($toTable,$ofield) = explode(':',$obj[1]);
+ $obj = DB_DataObject::factory($toTable);
+
+ if (!$obj || is_a($obj,'PEAR_Error')) {
+ $obj = new DB_DataObject;
+ $obj->__table = $toTable;
+ }
+ $obj->_connect();
+ // set the table items to nothing.. - eg. do not try and match
+ // things in the child table...???
+ $items = array();
+ }
+
+ if (!is_object($obj) || !is_a($obj,'DB_DataObject')) {
+ return $this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
+ }
+ /* make sure $this->_database is set. */
+ $this->_connect();
+ $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+
+
+
+
+ /* look up the links for obj table */
+ //print_r($obj->links());
+ if (!$ofield && ($olinks = $obj->links())) {
+
+ foreach ($olinks as $k => $v) {
+ /* link contains {this column} = {linked table}:{linked column} */
+ $ar = explode(':', $v);
+
+ // Feature Request #4266 - Allow joins with multiple keys
+
+ $links_key_array = strpos($k,',');
+ if ($links_key_array !== false) {
+ $k = explode(',', $k);
+ }
+
+ $ar_array = strpos($ar[1],',');
+ if ($ar_array !== false) {
+ $ar[1] = explode(',', $ar[1]);
+ }
+
+ if ($ar[0] == $this->__table) {
+
+ // you have explictly specified the column
+ // and the col is listed here..
+ // not sure if 1:1 table could cause probs here..
+
+ if ($joinCol !== false) {
+ $this->raiseError(
+ "joinAdd: You cannot target a join column in the " .
+ "'link from' table ({$obj->__table}). " .
+ "Either remove the fourth argument to joinAdd() ".
+ "({$joinCol}), or alter your links.ini file.",
+ DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+
+ $ofield = $k;
+ $tfield = $ar[1];
+ break;
+ }
+ }
+ }
+
+ /* otherwise see if there are any links from this table to the obj. */
+ //print_r($this->links());
+ if (($ofield === false) && ($links = $this->links())) {
+ foreach ($links as $k => $v) {
+ /* link contains {this column} = {linked table}:{linked column} */
+ $ar = explode(':', $v);
+ // Feature Request #4266 - Allow joins with multiple keys
+ if (strpos($k, ',') !== false) {
+ $k = explode(',', $k);
+ }
+ if (strpos($ar[1], ',') !== false) {
+ $ar[1] = explode(',', $ar[1]);
+ }
+
+ if ($ar[0] == $obj->__table) {
+ if ($joinCol !== false) {
+ if ($k == $joinCol) {
+ $tfield = $k;
+ $ofield = $ar[1];
+ break;
+ } else {
+ continue;
+ }
+ } else {
+ $tfield = $k;
+ $ofield = $ar[1];
+ break;
+ }
+ }
+ }
+ }
+ // finally if these two table have column names that match do a join by default on them
+
+ if (($ofield === false) && $joinCol) {
+ $ofield = $joinCol;
+ $tfield = $joinCol;
+
+ }
+ /* did I find a conneciton between them? */
+
+ if ($ofield === false) {
+ $this->raiseError(
+ "joinAdd: {$obj->__table} has no link with {$this->__table}",
+ DB_DATAOBJECT_ERROR_NODATA);
+ return false;
+ }
+ $joinType = strtoupper($joinType);
+
+ // we default to joining as the same name (this is remvoed later..)
+
+ if ($joinAs === false) {
+ $joinAs = $obj->__table;
+ }
+
+ $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
+ $options = $_DB_DATAOBJECT['CONFIG'];
+
+ // not sure how portable adding database prefixes is..
+ $objTable = $quoteIdentifiers ?
+ $DB->quoteIdentifier($obj->__table) :
+ $obj->__table ;
+
+ $dbPrefix = '';
+ if (strlen($obj->_database) && in_array($DB->dsn['phptype'],array('mysql','mysqli'))) {
+ $dbPrefix = ($quoteIdentifiers
+ ? $DB->quoteIdentifier($obj->_database)
+ : $obj->_database) . '.';
+ }
+
+ // if they are the same, then dont add a prefix...
+ if ($obj->_database == $this->_database) {
+ $dbPrefix = '';
+ }
+ // as far as we know only mysql supports database prefixes..
+ // prefixing the database name is now the default behaviour,
+ // as it enables joining mutiple columns from multiple databases...
+
+ // prefix database (quoted if neccessary..)
+ $objTable = $dbPrefix . $objTable;
+
+ $cond = '';
+
+ // if obj only a dataobject - eg. no extended class has been defined..
+ // it obvioulsy cant work out what child elements might exist...
+ // until we get on the fly querying of tables..
+ // note: we have already checked that it is_a(db_dataobject earlier)
+ if ( strtolower(get_class($obj)) != 'db_dataobject') {
+
+ // now add where conditions for anything that is set in the object
+
+
+
+ $items = $obj->table();
+ // will return an array if no items..
+
+ // only fail if we where expecting it to work (eg. not joined on a array)
+
+ if (!$items) {
+ $this->raiseError(
+ "joinAdd: No table definition for {$obj->__table}",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return false;
+ }
+
+ foreach($items as $k => $v) {
+ if (!isset($obj->$k)) {
+ continue;
+ }
+
+ $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
+
+
+ if ($v & DB_DATAOBJECT_STR) {
+ $obj->whereAdd("{$joinAs}.{$kSql} = " . $this->_quote((string) (
+ ($v & DB_DATAOBJECT_BOOL) ?
+ // this is thanks to the braindead idea of postgres to
+ // use t/f for boolean.
+ (($obj->$k === 'f') ? 0 : (int)(bool) $obj->$k) :
+ $obj->$k
+ )));
+ continue;
+ }
+ if (is_numeric($obj->$k)) {
+ $obj->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
+ continue;
+ }
+
+ if (is_a($obj->$k,'DB_DataObject_Cast')) {
+ $value = $obj->$k->toString($v,$DB);
+ if (PEAR::isError($value)) {
+ $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
+ return false;
+ }
+ if (!isset($options['disable_null_strings']) && strtolower($value) === 'null') {
+ $obj->whereAdd("{$joinAs}.{$kSql} IS NULL");
+ continue;
+ } else {
+ $obj->whereAdd("{$joinAs}.{$kSql} = $value");
+ continue;
+ }
+ }
+
+
+ /* this is probably an error condition! */
+ $obj->whereAdd("{$joinAs}.{$kSql} = 0");
+ }
+ if ($this->_query === false) {
+ $this->raiseError(
+ "joinAdd can not be run from a object that has had a query run on it,
+ clone the object or create a new one and use setFrom()",
+ DB_DATAOBJECT_ERROR_INVALIDARGS);
+ return false;
+ }
+ }
+
+ // and finally merge the whereAdd from the child..
+ if ($obj->_query['condition']) {
+ $cond = preg_replace('/^\sWHERE/i','',$obj->_query['condition']);
+
+ if (!$useWhereAsOn) {
+ $this->whereAdd($cond);
+ }
+ }
+
+
+
+
+ // nested (join of joined objects..)
+ $appendJoin = '';
+ if ($obj->_join) {
+ // postgres allows nested queries, with ()'s
+ // not sure what the results are with other databases..
+ // may be unpredictable..
+ if (in_array($DB->dsn["phptype"],array('pgsql'))) {
+ $objTable = "($objTable {$obj->_join})";
+ } else {
+ $appendJoin = $obj->_join;
+ }
+ }
+
+
+ // fix for #2216
+ // add the joinee object's conditions to the ON clause instead of the WHERE clause
+ if ($useWhereAsOn && strlen($cond)) {
+ $appendJoin = ' AND ' . $cond . ' ' . $appendJoin;
+ }
+
+
+
+ $table = $this->__table;
+
+ if ($quoteIdentifiers) {
+ $joinAs = $DB->quoteIdentifier($joinAs);
+ $table = $DB->quoteIdentifier($table);
+ $ofield = (is_array($ofield)) ? array_map(array($DB, 'quoteIdentifier'), $ofield) : $DB->quoteIdentifier($ofield);
+ $tfield = (is_array($tfield)) ? array_map(array($DB, 'quoteIdentifier'), $tfield) : $DB->quoteIdentifier($tfield);
+ }
+ // add database prefix if they are different databases
+
+
+ $fullJoinAs = '';
+ $addJoinAs = ($quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table) != $joinAs;
+ if ($addJoinAs) {
+ // join table a AS b - is only supported by a few databases and is probably not needed
+ // , however since it makes the whole Statement alot clearer we are leaving it in
+ // for those databases.
+ $fullJoinAs = in_array($DB->dsn["phptype"],array('mysql','mysqli','pgsql')) ? "AS {$joinAs}" : $joinAs;
+ } else {
+ // if
+ $joinAs = $dbPrefix . $joinAs;
+ }
+
+
+ switch ($joinType) {
+ case 'INNER':
+ case 'LEFT':
+ case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
+
+ // Feature Request #4266 - Allow joins with multiple keys
+ $this->_join .= "\n {$joinType} JOIN {$objTable} {$fullJoinAs}";
+ if (is_array($ofield)) {
+ $key_count = count($ofield);
+ for($i = 0; $i < $key_count; $i++) {
+ if ($i == 0) {
+ $this->_join .= " ON ({$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]}) ";
+ }
+ else {
+ $this->_join .= " AND {$joinAs}.{$ofield[$i]}={$table}.{$tfield[$i]} ";
+ }
+ }
+ $this->_join .= ' ' . $appendJoin . ' ';
+ } else {
+ $this->_join .= " ON ({$joinAs}.{$ofield}={$table}.{$tfield}) {$appendJoin} ";
+ }
+
+ break;
+
+ case '': // this is just a standard multitable select..
+ $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
+ $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
+ }
+
+
+ return true;
+
+ }
+
+ /**
+ * Copies items that are in the table definitions from an
+ * array or object into the current object
+ * will not override key values.
+ *
+ *
+ * @param array | object $from
+ * @param string $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
+ * @param boolean $skipEmpty (dont assign empty values if a column is empty (eg. '' / 0 etc...)
+ * @access public
+ * @return true on success or array of key=>setValue error message
+ */
+ function setFrom($from, $format = '%s', $skipEmpty=false)
+ {
+ global $_DB_DATAOBJECT;
+ $keys = $this->keys();
+ $items = $this->table();
+ if (!$items) {
+ $this->raiseError(
+ "setFrom:Could not find table definition for {$this->__table}",
+ DB_DATAOBJECT_ERROR_INVALIDCONFIG);
+ return;
+ }
+ $overload_return = array();
+ foreach (array_keys($items) as $k) {
+ if (in_array($k,$keys)) {
+ continue; // dont overwrite keys
+ }
+ if (!$k) {
+ continue; // ignore empty keys!!! what
+ }
+ if (is_object($from) && isset($from->{sprintf($format,$k)})) {
+ $kk = (strtolower($k) == 'from') ? '_from' : $k;
+ if (method_exists($this,'set'.$kk)) {
+ $ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});
+ if (is_string($ret)) {
+ $overload_return[$k] = $ret;
+ }
+ continue;
+ }
+ $this->$k = $from->{sprintf($format,$k)};
+ continue;
+ }
+
+ if (is_object($from)) {
+ continue;
+ }
+
+ if (empty($from[$k]) && $skipEmpty) {
+ continue;
+ }
+
+ if (!isset($from[sprintf($format,$k)])) {
+ continue;
+ }
+
+ $kk = (strtolower($k) == 'from') ? '_from' : $k;
+ if (method_exists($this,'set'. $kk)) {
+ $ret = $this->{'set'.$kk}($from[sprintf($format,$k)]);
+ if (is_string($ret)) {
+ $overload_return[$k] = $ret;
+ }
+ continue;
+ }
+ if (is_object($from[sprintf($format,$k)])) {
+ continue;
+ }
+ if (is_array($from[sprintf($format,$k)])) {
+ continue;
+ }
+ $ret = $this->fromValue($k,$from[sprintf($format,$k)]);
+ if ($ret !== true) {
+ $overload_return[$k] = 'Not A Valid Value';
+ }
+ //$this->$k = $from[sprintf($format,$k)];
+ }
+ if ($overload_return) {
+ return $overload_return;
+ }
+ return true;
+ }
+
+ /**
+ * Returns an associative array from the current data
+ * (kind of oblivates the idea behind DataObjects, but
+ * is usefull if you use it with things like QuickForms.
+ *
+ * you can use the format to return things like user[key]
+ * by sending it $object->toArray('user[%s]')
+ *
+ * will also return links converted to arrays.
+ *
+ * @param string sprintf format for array
+ * @param bool empty only return elemnts that have a value set.
+ *
+ * @access public
+ * @return array of key => value for row
+ */
+
+ function toArray($format = '%s', $hideEmpty = false)
+ {
+ global $_DB_DATAOBJECT;
+ $ret = array();
+ $rf = ($this->_resultFields !== false) ? $this->_resultFields :
+ (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ? $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid] : false);
+ $ar = ($rf !== false) ?
+ array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
+ $this->table();
+
+ foreach($ar as $k=>$v) {
+
+ if (!isset($this->$k)) {
+ if (!$hideEmpty) {
+ $ret[sprintf($format,$k)] = '';
+ }
+ continue;
+ }
+ // call the overloaded getXXXX() method. - except getLink and getLinks
+ if (method_exists($this,'get'.$k) && !in_array(strtolower($k),array('links','link'))) {
+ $ret[sprintf($format,$k)] = $this->{'get'.$k}();
+ continue;
+ }
+ // should this call toValue() ???
+ $ret[sprintf($format,$k)] = $this->$k;
+ }
+ if (!$this->_link_loaded) {
+ return $ret;
+ }
+ foreach($this->_link_loaded as $k) {
+ $ret[sprintf($format,$k)] = $this->$k->toArray();
+
+ }
+
+ return $ret;
+ }
+
+ /**
+ * validate the values of the object (usually prior to inserting/updating..)
+ *
+ * Note: This was always intended as a simple validation routine.
+ * It lacks understanding of field length, whether you are inserting or updating (and hence null key values)
+ *
+ * This should be moved to another class: DB_DataObject_Validate
+ * FEEL FREE TO SEND ME YOUR VERSION FOR CONSIDERATION!!!
+ *
+ * Usage:
+ * if (is_array($ret = $obj->validate())) { ... there are problems with the data ... }
+ *
+ * Logic:
+ * - defaults to only testing strings/numbers if numbers or strings are the correct type and null values are correct
+ * - validate Column methods : "validate{ROWNAME}()" are called if they are defined.
+ * These methods should return
+ * true = everything ok
+ * false|object = something is wrong!
+ *
+ * - This method loads and uses the PEAR Validate Class.
+ *
+ *
+ * @access public
+ * @return array of validation results (where key=>value, value=false|object if it failed) or true (if they all succeeded)
+ */
+ function validate()
+ {
+ global $_DB_DATAOBJECT;
+ require_once 'Validate.php';
+ $table = $this->table();
+ $ret = array();
+ $seq = $this->sequenceKey();
+ $options = $_DB_DATAOBJECT['CONFIG'];
+ foreach($table as $key => $val) {
+
+
+ // call user defined validation always...
+ $method = "Validate" . ucfirst($key);
+ if (method_exists($this, $method)) {
+ $ret[$key] = $this->$method();
+ continue;
+ }
+
+ // if not null - and it's not set.......
+
+ if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
+ // dont check empty sequence key values..
+ if (($key == $seq[0]) && ($seq[1] == true)) {
+ continue;
+ }
+ $ret[$key] = false;
+ continue;
+ }
+
+
+ if (!isset($options['disable_null_strings']) && is_string($this->$key) && (strtolower($this->$key) == 'null')) {
+ if ($val & DB_DATAOBJECT_NOTNULL) {
+ $this->debug("'null' field used for '$key', but it is defined as NOT NULL", 'VALIDATION', 4);
+ $ret[$key] = false;
+ continue;
+ }
+ continue;
+ }
+
+ // ignore things that are not set. ?
+
+ if (!isset($this->$key)) {
+ continue;
+ }
+
+ // if the string is empty.. assume it is ok..
+ if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
+ continue;
+ }
+
+ // dont try and validate cast objects - assume they are problably ok..
+ if (is_object($this->$key) && is_a($this->$key,'DB_DataObject_Cast')) {
+ continue;
+ }
+ // at this point if you have set something to an object, and it's not expected
+ // the Validate will probably break!!... - rightly so! (your design is broken,
+ // so issuing a runtime error like PEAR_Error is probably not appropriate..
+
+ switch (true) {
+ // todo: date time.....
+ case ($val & DB_DATAOBJECT_STR):
+ $ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
+ continue;
+ case ($val & DB_DATAOBJECT_INT):
+ $ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
+ continue;
+ }
+ }
+ // if any of the results are false or an object (eg. PEAR_Error).. then return the array..
+ foreach ($ret as $key => $val) {
+ if ($val !== true) {
+ return $ret;
+ }
+ }
+ return true; // everything is OK.
+ }
+
+ /**
+ * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
+ *
+ * @access public
+ * @return object The DB connection
+ */
+ function &getDatabaseConnection()
+ {
+ global $_DB_DATAOBJECT;
+
+ if (($e = $this->_connect()) !== true) {
+ return $e;
+ }
+ if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ $r = false;
+ return $r;
+ }
+ return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+ }
+
+
+ /**
+ * Gets the DB result object related to the objects active query
+ * - so you can use funky pear stuff with it - like pager for example.. :)
+ *
+ * @access public
+ * @return object The DB result object
+ */
+
+ function &getDatabaseResult()
+ {
+ global $_DB_DATAOBJECT;
+ $this->_connect();
+ if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
+ $r = false;
+ return $r;
+ }
+ return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
+ }
+
+ /**
+ * Overload Extension support
+ * - enables setCOLNAME/getCOLNAME
+ * if you define a set/get method for the item it will be called.
+ * otherwise it will just return/set the value.
+ * NOTE this currently means that a few Names are NO-NO's
+ * eg. links,link,linksarray, from, Databaseconnection,databaseresult
+ *
+ * note
+ * - set is automatically called by setFrom.
+ * - get is automatically called by toArray()
+ *
+ * setters return true on success. = strings on failure
+ * getters return the value!
+ *
+ * this fires off trigger_error - if any problems.. pear_error,
+ * has problems with 4.3.2RC2 here
+ *
+ * @access public
+ * @return true?
+ * @see overload
+ */
+
+
+ function _call($method,$params,&$return) {
+
+ //$this->debug("ATTEMPTING OVERLOAD? $method");
+ // ignore constructors : - mm
+ if (strtolower($method) == strtolower(get_class($this))) {
+ return true;
+ }
+ $type = strtolower(substr($method,0,3));
+ $class = get_class($this);
+ if (($type != 'set') && ($type != 'get')) {
+ return false;
+ }
+
+
+
+ // deal with naming conflick of setFrom = this is messy ATM!
+
+ if (strtolower($method) == 'set_from') {
+ $return = $this->toValue('from',isset($params[0]) ? $params[0] : null);
+ return true;
+ }
+
+ $element = substr($method,3);
+
+ // dont you just love php's case insensitivity!!!!
+
+ $array = array_keys(get_class_vars($class));
+ /* php5 version which segfaults on 5.0.3 */
+ if (class_exists('ReflectionClass')) {
+ $reflection = new ReflectionClass($class);
+ $array = array_keys($reflection->getdefaultProperties());
+ }
+
+ if (!in_array($element,$array)) {
+ // munge case
+ foreach($array as $k) {
+ $case[strtolower($k)] = $k;
+ }
+ if ((substr(phpversion(),0,1) == 5) && isset($case[strtolower($element)])) {
+ trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
+ $element = strtolower($element);
+ }
+
+ // does it really exist?
+ if (!isset($case[$element])) {
+ return false;
+ }
+ // use the mundged case
+ $element = $case[$element]; // real case !
+ }
+
+
+ if ($type == 'get') {
+ $return = $this->toValue($element,isset($params[0]) ? $params[0] : null);
+ return true;
+ }
+
+
+ $return = $this->fromValue($element, $params[0]);
+
+ return true;
+
+
+ }
+
+
+ /**
+ * standard set* implementation.
+ *
+ * takes data and uses it to set dates/strings etc.
+ * normally called from __call..
+ *
+ * Current supports
+ * date = using (standard time format, or unixtimestamp).... so you could create a method :
+ * function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
+ *
+ * time = using strtotime
+ * datetime = using same as date - accepts iso standard or unixtimestamp.
+ * string = typecast only..
+ *
+ * TODO: add formater:: eg. d/m/Y for date! ???
+ *
+ * @param string column of database
+ * @param mixed value to assign
+ *
+ * @return true| false (False on error)
+ * @access public
+ * @see DB_DataObject::_call
+ */
+
+
+ function fromValue($col,$value)
+ {
+ global $_DB_DATAOBJECT;
+ $options = $_DB_DATAOBJECT['CONFIG'];
+ $cols = $this->table();
+ // dont know anything about this col..
+ if (!isset($cols[$col])) {
+ $this->$col = $value;
+ return true;
+ }
+ //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
+ switch (true) {
+ // set to null and column is can be null...
+ case (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
+ case (is_object($value) && is_a($value,'DB_DataObject_Cast')):
+ $this->$col = $value;
+ return true;
+
+ // fail on setting null on a not null field..
+ case (!isset($options['disable_null_strings']) && (strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
+ return false;
+
+ case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
+ // empty values get set to '' (which is inserted/updated as NULl
+ if (!$value) {
+ $this->$col = '';
+ }
+
+ if (is_numeric($value)) {
+ $this->$col = date('Y-m-d H:i:s', $value);
+ return true;
+ }
+
+ // eak... - no way to validate date time otherwise...
+ $this->$col = (string) $value;
+ return true;
+
+ case ($cols[$col] & DB_DATAOBJECT_DATE):
+ // empty values get set to '' (which is inserted/updated as NULl
+
+ if (!$value) {
+ $this->$col = '';
+ return true;
+ }
+
+ if (is_numeric($value)) {
+ $this->$col = date('Y-m-d',$value);
+ return true;
+ }
+
+ // try date!!!!
+ require_once 'Date.php';
+ $x = new Date($value);
+ $this->$col = $x->format("%Y-%m-%d");
+ return true;
+
+ case ($cols[$col] & DB_DATAOBJECT_TIME):
+ // empty values get set to '' (which is inserted/updated as NULl
+ if (!$value) {
+ $this->$col = '';
+ }
+
+ $guess = strtotime($value);
+ if ($guess != -1) {
+ $this->$col = date('H:i:s', $guess);
+ return $return = true;
+ }
+ // otherwise an error in type...
+ return false;
+
+ case ($cols[$col] & DB_DATAOBJECT_STR):
+
+ $this->$col = (string) $value;
+ return true;
+
+ // todo : floats numerics and ints...
+ default:
+ $this->$col = $value;
+ return true;
+ }
+
+
+
+ }
+ /**
+ * standard get* implementation.
+ *
+ * with formaters..
+ * supported formaters:
+ * date/time : %d/%m/%Y (eg. php strftime) or pear::Date
+ * numbers : %02d (eg. sprintf)
+ * NOTE you will get unexpected results with times like 0000-00-00 !!!
+ *
+ *
+ *
+ * @param string column of database
+ * @param format foramt
+ *
+ * @return true Description
+ * @access public
+ * @see DB_DataObject::_call(),strftime(),Date::format()
+ */
+ function toValue($col,$format = null)
+ {
+ if (is_null($format)) {
+ return $this->$col;
+ }
+ $cols = $this->table();
+ switch (true) {
+ case (($cols[$col] & DB_DATAOBJECT_DATE) && ($cols[$col] & DB_DATAOBJECT_TIME)):
+ if (!$this->$col) {
+ return '';
+ }
+ $guess = strtotime($this->$col);
+ if ($guess != -1) {
+ return strftime($format, $guess);
+ }
+ // eak... - no way to validate date time otherwise...
+ return $this->$col;
+ case ($cols[$col] & DB_DATAOBJECT_DATE):
+ if (!$this->$col) {
+ return '';
+ }
+ $guess = strtotime($this->$col);
+ if ($guess != -1) {
+ return strftime($format,$guess);
+ }
+ // try date!!!!
+ require_once 'Date.php';
+ $x = new Date($this->$col);
+ return $x->format($format);
+
+ case ($cols[$col] & DB_DATAOBJECT_TIME):
+ if (!$this->$col) {
+ return '';
+ }
+ $guess = strtotime($this->$col);
+ if ($guess > -1) {
+ return strftime($format, $guess);
+ }
+ // otherwise an error in type...
+ return $this->$col;
+
+ case ($cols[$col] & DB_DATAOBJECT_MYSQLTIMESTAMP):
+ if (!$this->$col) {
+ return '';
+ }
+ require_once 'Date.php';
+
+ $x = new Date($this->$col);
+
+ return $x->format($format);
+
+
+ case ($cols[$col] & DB_DATAOBJECT_BOOL):
+
+ if ($cols[$col] & DB_DATAOBJECT_STR) {
+ // it's a 't'/'f' !
+ return ($this->$col === 't');
+ }
+ return (bool) $this->$col;
+
+
+ default:
+ return sprintf($format,$this->col);
+ }
+
+
+ }
+
+
+ /* ----------------------- Debugger ------------------ */
+
+ /**
+ * Debugger. - use this in your extended classes to output debugging information.
+ *
+ * Uses DB_DataObject::DebugLevel(x) to turn it on
+ *
+ * @param string $message - message to output
+ * @param string $logtype - bold at start
+ * @param string $level - output level
+ * @access public
+ * @return none
+ */
+ function debug($message, $logtype = 0, $level = 1)
+ {
+ global $_DB_DATAOBJECT;
+
+ if (empty($_DB_DATAOBJECT['CONFIG']['debug']) ||
+ (is_numeric($_DB_DATAOBJECT['CONFIG']['debug']) && $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
+ return;
+ }
+ // this is a bit flaky due to php's wonderfull class passing around crap..
+ // but it's about as good as it gets..
+ $class = (isset($this) && is_a($this,'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
+
+ if (!is_string($message)) {
+ $message = print_r($message,true);
+ }
+ if (!is_numeric( $_DB_DATAOBJECT['CONFIG']['debug']) && is_callable( $_DB_DATAOBJECT['CONFIG']['debug'])) {
+ return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
+ }
+
+ if (!ini_get('html_errors')) {
+ echo "$class : $logtype : $message\n";
+ flush();
+ return;
+ }
+ if (!is_string($message)) {
+ $message = print_r($message,true);
+ }
+ $colorize = ($logtype == 'ERROR') ? '<font color="red">' : '<font>';
+ echo "<code>{$colorize}<B>$class: $logtype:</B> ". nl2br(htmlspecialchars($message)) . "</font></code><BR>\n";
+ }
+
+ /**
+ * sets and returns debug level
+ * eg. DB_DataObject::debugLevel(4);
+ *
+ * @param int $v level
+ * @access public
+ * @return none
+ */
+ function debugLevel($v = null)
+ {
+ global $_DB_DATAOBJECT;
+ if (empty($_DB_DATAOBJECT['CONFIG'])) {
+ DB_DataObject::_loadConfig();
+ }
+ if ($v !== null) {
+ $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
+ $_DB_DATAOBJECT['CONFIG']['debug'] = $v;
+ return $r;
+ }
+ return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
+ }
+
+ /**
+ * Last Error that has occured
+ * - use $this->_lastError or
+ * $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ *
+ * @access public
+ * @var object PEAR_Error (or false)
+ */
+ var $_lastError = false;
+
+ /**
+ * Default error handling is to create a pear error, but never return it.
+ * if you need to handle errors you should look at setting the PEAR_Error callback
+ * this is due to the fact it would wreck havoc on the internal methods!
+ *
+ * @param int $message message
+ * @param int $type type
+ * @param int $behaviour behaviour (die or continue!);
+ * @access public
+ * @return error object
+ */
+ function raiseError($message, $type = null, $behaviour = null)
+ {
+ global $_DB_DATAOBJECT;
+
+ if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
+ $behaviour = null;
+ }
+ $error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+
+ // this will never work totally with PHP's object model.
+ // as this is passed on static calls (like staticGet in our case)
+
+ if (isset($this) && is_object($this) && is_subclass_of($this,'db_dataobject')) {
+ $this->_lastError = $error;
+ }
+
+ $_DB_DATAOBJECT['LASTERROR'] = $error;
+
+ // no checks for production here?....... - we log errors before we throw them.
+ DB_DataObject::debug($message,'ERROR',1);
+
+
+ if (PEAR::isError($message)) {
+ $error = $message;
+ } else {
+ require_once 'DB/DataObject/Error.php';
+ $error = PEAR::raiseError($message, $type, $behaviour,
+ $opts=null, $userinfo=null, 'DB_DataObject_Error'
+ );
+ }
+
+ return $error;
+ }
+
+ /**
+ * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to PEAR::getStaticProperty('DB_DataObject','options');
+ *
+ * After Profiling DB_DataObject, I discoved that the debug calls where taking
+ * considerable time (well 0.1 ms), so this should stop those calls happening. as
+ * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton
+ * THIS STILL NEEDS FURTHER INVESTIGATION
+ *
+ * @access public
+ * @return object an error object
+ */
+ function _loadConfig()
+ {
+ global $_DB_DATAOBJECT;
+
+ $_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject','options');
+
+
+ }
+ /**
+ * Free global arrays associated with this object.
+ *
+ *
+ * @access public
+ * @return none
+ */
+ function free()
+ {
+ global $_DB_DATAOBJECT;
+
+ if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
+ unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
+ }
+ if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
+ unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
+ }
+ // clear the staticGet cache as well.
+ $this->_clear_cache();
+ // this is a huge bug in DB!
+ if (isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
+ $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
+ }
+
+ }
+
+
+ /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
+
+ function _get_table() { return $this->table(); }
+ function _get_keys() { return $this->keys(); }
+
+
+
+
+}
+// technially 4.3.2RC1 was broken!!
+// looks like 4.3.3 may have problems too....
+if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
+
+ if ((phpversion() != '4.3.2-RC1') && (version_compare( phpversion(), "4.3.1") > 0)) {
+ if (version_compare( phpversion(), "5") < 0) {
+ overload('DB_DataObject');
+ }
+ $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = true;
+ }
+}
+
diff --git a/extlib/DB/DataObject/Cast.php b/extlib/DB/DataObject/Cast.php
new file mode 100644
index 000000000..616abb55e
--- /dev/null
+++ b/extlib/DB/DataObject/Cast.php
@@ -0,0 +1,546 @@
+<?php
+/**
+ * Prototype Castable Object.. for DataObject queries
+ *
+ * Storage for Data that may be cast into a variety of formats.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB_DataObject
+ * @author Alan Knowles <alan@akbkhome.com>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Cast.php,v 1.15 2005/07/07 05:30:53 alan_k Exp $
+ * @link http://pear.php.net/package/DB_DataObject
+ */
+
+/**
+*
+* Common usages:
+* // blobs
+* $data = DB_DataObject_Cast::blob($somefile);
+* $data = DB_DataObject_Cast::string($somefile);
+* $dataObject->someblobfield = $data
+*
+* // dates?
+* $d1 = new DB_DataObject_Cast::date('12/12/2000');
+* $d2 = new DB_DataObject_Cast::date(2000,12,30);
+* $d3 = new DB_DataObject_Cast::date($d1->year, $d1->month+30, $d1->day+30);
+*
+* // time, datetime.. ?????????
+*
+* // raw sql????
+* $data = DB_DataObject_Cast::sql('cast("123123",datetime)');
+* $data = DB_DataObject_Cast::sql('NULL');
+*
+* // int's/string etc. are proably pretty pointless..!!!!
+*
+*
+* inside DB_DataObject,
+* if (is_a($v,'db_dataobject_class')) {
+* $value .= $v->toString(DB_DATAOBJECT_INT,'mysql');
+* }
+*
+*
+*
+*
+
+*/
+class DB_DataObject_Cast {
+
+ /**
+ * Type of data Stored in the object..
+ *
+ * @var string (date|blob|.....?)
+ * @access public
+ */
+ var $type;
+
+ /**
+ * Data For date representation
+ *
+ * @var int day/month/year
+ * @access public
+ */
+ var $day;
+ var $month;
+ var $year;
+
+
+ /**
+ * Generic Data..
+ *
+ * @var string
+ * @access public
+ */
+
+ var $value;
+
+
+
+ /**
+ * Blob consructor
+ *
+ * create a Cast object from some raw data.. (binary)
+ *
+ *
+ * @param string (with binary data!)
+ *
+ * @return object DB_DataObject_Cast
+ * @access public
+ */
+
+ function blob($value) {
+ $r = new DB_DataObject_Cast;
+ $r->type = 'blob';
+ $r->value = $value;
+ return $r;
+ }
+
+
+ /**
+ * String consructor (actually use if for ints and everything else!!!
+ *
+ * create a Cast object from some string (not binary)
+ *
+ *
+ * @param string (with binary data!)
+ *
+ * @return object DB_DataObject_Cast
+ * @access public
+ */
+
+ function string($value) {
+ $r = new DB_DataObject_Cast;
+ $r->type = 'string';
+ $r->value = $value;
+ return $r;
+ }
+
+ /**
+ * SQL constructor (for raw SQL insert)
+ *
+ * create a Cast object from some sql
+ *
+ * @param string (with binary data!)
+ *
+ * @return object DB_DataObject_Cast
+ * @access public
+ */
+
+ function sql($value)
+ {
+ $r = new DB_DataObject_Cast;
+ $r->type = 'sql';
+ $r->value = $value;
+ return $r;
+ }
+
+
+ /**
+ * Date Constructor
+ *
+ * create a Cast object from some string (not binary)
+ * NO VALIDATION DONE, although some crappy re-calcing done!
+ *
+ * @param vargs... accepts
+ * dd/mm
+ * dd/mm/yyyy
+ * yyyy-mm
+ * yyyy-mm-dd
+ * array(yyyy,dd)
+ * array(yyyy,dd,mm)
+ *
+ *
+ *
+ * @return object DB_DataObject_Cast
+ * @access public
+ */
+
+ function date()
+ {
+ $args = func_get_args();
+ switch(count($args)) {
+ case 0: // no args = today!
+ $bits = explode('-',date('Y-m-d'));
+ break;
+ case 1: // one arg = a string
+
+ if (strpos($args[0],'/') !== false) {
+ $bits = array_reverse(explode('/',$args[0]));
+ } else {
+ $bits = explode('-',$args[0]);
+ }
+ break;
+ default: // 2 or more..
+ $bits = $args;
+ }
+ if (count($bits) == 1) { // if YYYY set day = 1st..
+ $bits[] = 1;
+ }
+
+ if (count($bits) == 2) { // if YYYY-DD set day = 1st..
+ $bits[] = 1;
+ }
+
+ // if year < 1970 we cant use system tools to check it...
+ // so we make a few best gueses....
+ // basically do date calculations for the year 2000!!!
+ // fix me if anyone has more time...
+ if (($bits[0] < 1975) || ($bits[0] > 2030)) {
+ $oldyear = $bits[0];
+ $bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],2000)));
+ $bits[0] = ($bits[0] - 2000) + $oldyear;
+ } else {
+ // now mktime
+ $bits = explode('-',date('Y-m-d',mktime(1,1,1,$bits[1],$bits[2],$bits[0])));
+ }
+ $r = new DB_DataObject_Cast;
+ $r->type = 'date';
+ list($r->year,$r->month,$r->day) = $bits;
+ return $r;
+ }
+
+
+
+ /**
+ * Data For time representation ** does not handle timezones!!
+ *
+ * @var int hour/minute/second
+ * @access public
+ */
+ var $hour;
+ var $minute;
+ var $second;
+
+
+ /**
+ * DateTime Constructor
+ *
+ * create a Cast object from a Date/Time
+ * Maybe should accept a Date object.!
+ * NO VALIDATION DONE, although some crappy re-calcing done!
+ *
+ * @param vargs... accepts
+ * noargs (now)
+ * yyyy-mm-dd HH:MM:SS (Iso)
+ * array(yyyy,mm,dd,HH,MM,SS)
+ *
+ *
+ * @return object DB_DataObject_Cast
+ * @access public
+ * @author therion 5 at hotmail
+ */
+
+ function dateTime()
+ {
+ $args = func_get_args();
+ switch(count($args)) {
+ case 0: // no args = now!
+ $datetime = date('Y-m-d G:i:s', mktime());
+
+ case 1:
+ // continue on from 0 args.
+ if (!isset($datetime)) {
+ $datetime = $args[0];
+ }
+
+ $parts = explode(' ', $datetime);
+ $bits = explode('-', $parts[0]);
+ $bits = array_merge($bits, explode(':', $parts[1]));
+ break;
+
+ default: // 2 or more..
+ $bits = $args;
+
+ }
+
+ if (count($bits) != 6) {
+ // PEAR ERROR?
+ return false;
+ }
+
+ $r = DB_DataObject_Cast::date($bits[0], $bits[1], $bits[2]);
+ if (!$r) {
+ return $r; // pass thru error (False) - doesnt happen at present!
+ }
+ // change the type!
+ $r->type = 'datetime';
+
+ // should we mathematically sort this out..
+ // (or just assume that no-one's dumb enough to enter 26:90:90 as a time!
+ $r->hour = $bits[3];
+ $r->minute = $bits[4];
+ $r->second = $bits[5];
+ return $r;
+
+ }
+
+
+
+ /**
+ * time Constructor
+ *
+ * create a Cast object from a Date/Time
+ * Maybe should accept a Date object.!
+ * NO VALIDATION DONE, and no-recalcing done!
+ *
+ * @param vargs... accepts
+ * noargs (now)
+ * HH:MM:SS (Iso)
+ * array(HH,MM,SS)
+ *
+ *
+ * @return object DB_DataObject_Cast
+ * @access public
+ * @author therion 5 at hotmail
+ */
+ function time()
+ {
+ $args = func_get_args();
+ switch (count($args)) {
+ case 0: // no args = now!
+ $time = date('G:i:s', mktime());
+
+ case 1:
+ // continue on from 0 args.
+ if (!isset($time)) {
+ $time = $args[0];
+ }
+ $bits = explode(':', $time);
+ break;
+
+ default: // 2 or more..
+ $bits = $args;
+
+ }
+
+ if (count($bits) != 3) {
+ return false;
+ }
+
+ // now take data from bits into object fields
+ $r = new DB_DataObject_Cast;
+ $r->type = 'time';
+ $r->hour = $bits[0];
+ $r->minute = $bits[1];
+ $r->second = $bits[2];
+ return $r;
+
+ }
+
+
+
+ /**
+ * get the string to use in the SQL statement for this...
+ *
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ */
+
+ function toString($to=false,$db)
+ {
+ // if $this->type is not set, we are in serious trouble!!!!
+ // values for to:
+ $method = 'toStringFrom'.$this->type;
+ return $this->$method($to,$db);
+ }
+
+ /**
+ * get the string to use in the SQL statement from a blob of binary data
+ * ** Suppots only blob->postgres::bytea
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ */
+ function toStringFromBlob($to,$db)
+ {
+ // first weed out invalid casts..
+ // in blobs can only be cast to blobs.!
+
+ // perhaps we should support TEXT fields???
+
+ if (!($to & DB_DATAOBJECT_BLOB)) {
+ return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::blob to something other than a blob!');
+ }
+
+ switch ($db->dsn["phptype"]) {
+ case 'pgsql':
+ return "'".pg_escape_bytea($this->value)."'::bytea";
+
+ case 'mysql':
+ return "'".mysql_real_escape_string($this->value,$db->connection)."'";
+
+ case 'mysqli':
+ // this is funny - the parameter order is reversed ;)
+ return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
+
+
+
+ default:
+ return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
+ }
+
+ }
+
+ /**
+ * get the string to use in the SQL statement for a blob from a string!
+ * ** Suppots only string->postgres::bytea
+ *
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ */
+ function toStringFromString($to,$db)
+ {
+ // first weed out invalid casts..
+ // in blobs can only be cast to blobs.!
+
+ // perhaps we should support TEXT fields???
+ //
+
+ if (!($to & DB_DATAOBJECT_BLOB)) {
+ return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a blob!'.
+ ' (why not just use native features)');
+ }
+
+ switch ($db->dsn['phptype']) {
+ case 'pgsql':
+ return "'".pg_escape_string($this->value)."'::bytea";
+
+ case 'mysql':
+ return "'".mysql_real_escape_string($this->value,$db->connection)."'";
+
+
+ case 'mysqli':
+ return "'".mysqli_real_escape_string($db->connection, $this->value)."'";
+
+
+ default:
+ return PEAR::raiseError("DB_DataObject_Cast cant handle blobs for Database:{$db->dsn['phptype']} Yet");
+ }
+
+ }
+
+
+ /**
+ * get the string to use in the SQL statement for a date
+ *
+ *
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ */
+ function toStringFromDate($to,$db)
+ {
+ // first weed out invalid casts..
+ // in blobs can only be cast to blobs.!
+ // perhaps we should support TEXT fields???
+ //
+
+ if (($to !== false) && !($to & DB_DATAOBJECT_DATE)) {
+ return PEAR::raiseError('Invalid Cast from a DB_DataObject_Cast::string to something other than a date!'.
+ ' (why not just use native features)');
+ }
+ return "'{$this->year}-{$this->month}-{$this->day}'";
+ }
+
+ /**
+ * get the string to use in the SQL statement for a datetime
+ *
+ *
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ * @author therion 5 at hotmail
+ */
+
+ function toStringFromDateTime($to,$db)
+ {
+ // first weed out invalid casts..
+ // in blobs can only be cast to blobs.!
+ // perhaps we should support TEXT fields???
+ if (($to !== false) &&
+ !($to & (DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME))) {
+ return PEAR::raiseError('Invalid Cast from a ' .
+ ' DB_DataObject_Cast::dateTime to something other than a datetime!' .
+ ' (try using native features)');
+ }
+ return "'{$this->year}-{$this->month}-{$this->day} {$this->hour}:{$this->minute}:{$this->second}'";
+ }
+
+ /**
+ * get the string to use in the SQL statement for a time
+ *
+ *
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ * @author therion 5 at hotmail
+ */
+
+ function toStringFromTime($to,$db)
+ {
+ // first weed out invalid casts..
+ // in blobs can only be cast to blobs.!
+ // perhaps we should support TEXT fields???
+ if (($to !== false) && !($to & DB_DATAOBJECT_TIME)) {
+ return PEAR::raiseError('Invalid Cast from a' .
+ ' DB_DataObject_Cast::time to something other than a time!'.
+ ' (try using native features)');
+ }
+ return "'{$this->hour}:{$this->minute}:{$this->second}'";
+ }
+
+ /**
+ * get the string to use in the SQL statement for a raw sql statement.
+ *
+ * @param int $to Type (DB_DATAOBJECT_*
+ * @param object $db DB Connection Object
+ *
+ *
+ * @return string
+ * @access public
+ */
+ function toStringFromSql($to,$db)
+ {
+ return $this->value;
+ }
+
+
+
+
+}
+
diff --git a/extlib/DB/DataObject/Error.php b/extlib/DB/DataObject/Error.php
new file mode 100644
index 000000000..05a741408
--- /dev/null
+++ b/extlib/DB/DataObject/Error.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * DataObjects error handler, loaded on demand...
+ *
+ * DB_DataObject_Error is a quick wrapper around pear error, so you can distinguish the
+ * error code source.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB_DataObject
+ * @author Alan Knowles <alan@akbkhome.com>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Error.php,v 1.3 2005/03/23 02:35:35 alan_k Exp $
+ * @link http://pear.php.net/package/DB_DataObject
+ */
+
+
+class DB_DataObject_Error extends PEAR_Error
+{
+
+ /**
+ * DB_DataObject_Error constructor.
+ *
+ * @param mixed $code DB error code, or string with error message.
+ * @param integer $mode what "error mode" to operate in
+ * @param integer $level what error level to use for $mode & PEAR_ERROR_TRIGGER
+ * @param mixed $debuginfo additional debug info, such as the last query
+ *
+ * @access public
+ *
+ * @see PEAR_Error
+ */
+ function DB_DataObject_Error($message = '', $code = DB_ERROR, $mode = PEAR_ERROR_RETURN,
+ $level = E_USER_NOTICE)
+ {
+ $this->PEAR_Error('DB_DataObject Error: ' . $message, $code, $mode, $level);
+
+ }
+
+
+ // todo : - support code -> message handling, and translated error messages...
+
+
+
+}
diff --git a/extlib/DB/DataObject/Generator.php b/extlib/DB/DataObject/Generator.php
new file mode 100644
index 000000000..de16af692
--- /dev/null
+++ b/extlib/DB/DataObject/Generator.php
@@ -0,0 +1,1553 @@
+<?php
+/**
+ * Generation tools for DB_DataObject
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB_DataObject
+ * @author Alan Knowles <alan@akbkhome.com>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: Generator.php,v 1.141 2008/01/30 02:29:39 alan_k Exp $
+ * @link http://pear.php.net/package/DB_DataObject
+ */
+
+ /*
+ * Security Notes:
+ * This class uses eval to create classes on the fly.
+ * The table name and database name are used to check the database before writing the
+ * class definitions, we now check for quotes and semi-colon's in both variables
+ * so I cant see how it would be possible to generate code even if
+ * for some crazy reason you took the classname and table name from User Input.
+ *
+ * If you consider that wrong, or can prove it.. let me know!
+ */
+
+ /**
+ *
+ * Config _$ptions
+ * [DB_DataObject_Generator]
+ * ; optional default = DB/DataObject.php
+ * extends_location =
+ * ; optional default = DB_DataObject
+ * extends =
+ * ; alter the extends field when updating a class (defaults to only replacing DB_DataObject)
+ * generator_class_rewrite = ANY|specific_name // default is DB_DataObject
+ *
+ */
+
+/**
+ * Needed classes
+ * We lazy load here, due to problems with the tests not setting up include path correctly.
+ * FIXME!
+ */
+class_exists('DB_DataObject') ? '' : require_once 'DB/DataObject.php';
+//require_once('Config.php');
+
+/**
+ * Generator class
+ *
+ * @package DB_DataObject
+ */
+class DB_DataObject_Generator extends DB_DataObject
+{
+ /* =========================================================== */
+ /* Utility functions - for building db config files */
+ /* =========================================================== */
+
+ /**
+ * Array of table names
+ *
+ * @var array
+ * @access private
+ */
+ var $tables;
+
+ /**
+ * associative array table -> array of table row objects
+ *
+ * @var array
+ * @access private
+ */
+ var $_definitions;
+
+ /**
+ * active table being output
+ *
+ * @var string
+ * @access private
+ */
+ var $table; // active tablename
+
+
+ /**
+ * The 'starter' = call this to start the process
+ *
+ * @access public
+ * @return none
+ */
+ function start()
+ {
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
+
+ $databases = array();
+ foreach($options as $k=>$v) {
+ if (substr($k,0,9) == 'database_') {
+ $databases[substr($k,9)] = $v;
+ }
+ }
+
+ if (isset($options['database'])) {
+ if ($db_driver == 'DB') {
+ require_once 'DB.php';
+ $dsn = DB::parseDSN($options['database']);
+ } else {
+ require_once 'MDB2.php';
+ $dsn = MDB2::parseDSN($options['database']);
+ }
+
+ if (!isset($database[$dsn['database']])) {
+ $databases[$dsn['database']] = $options['database'];
+ }
+ }
+
+ foreach($databases as $databasename => $database) {
+ if (!$database) {
+ continue;
+ }
+ $this->debug("CREATING FOR $databasename\n");
+ $class = get_class($this);
+ $t = new $class;
+ $t->_database_dsn = $database;
+
+
+ $t->_database = $databasename;
+ if ($db_driver == 'DB') {
+ require_once 'DB.php';
+ $dsn = DB::parseDSN($database);
+ } else {
+ require_once 'MDB2.php';
+ $dsn = MDB2::parseDSN($database);
+ }
+
+ if (($dsn['phptype'] == 'sqlite') && is_file($databasename)) {
+ $t->_database = basename($t->_database);
+ }
+ $t->_createTableList();
+
+ foreach(get_class_methods($class) as $method) {
+ if (substr($method,0,8 ) != 'generate') {
+ continue;
+ }
+ $this->debug("calling $method");
+ $t->$method();
+ }
+ }
+ $this->debug("DONE\n\n");
+ }
+
+ /**
+ * Output File was config object, now just string
+ * Used to generate the Tables
+ *
+ * @var string outputbuffer for table definitions
+ * @access private
+ */
+ var $_newConfig;
+
+ /**
+ * Build a list of tables;
+ * and store it in $this->tables and $this->_definitions[tablename];
+ *
+ * @access private
+ * @return none
+ */
+ function _createTableList()
+ {
+ $this->_connect();
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+
+ $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
+
+ $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
+ $is_MDB2 = ($db_driver != 'DB') ? true : false;
+
+ if (is_a($__DB , 'PEAR_Error')) {
+ return PEAR::raiseError($__DB->toString(), null, PEAR_ERROR_DIE);
+ }
+
+ if (!$is_MDB2) {
+ // try getting a list of schema tables first. (postgres)
+ $__DB->expectError(DB_ERROR_UNSUPPORTED);
+ $this->tables = $__DB->getListOf('schema.tables');
+ $__DB->popExpect();
+ } else {
+ /**
+ * set portability and some modules to fetch the informations
+ */
+ $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
+ $__DB->loadModule('Manager');
+ $__DB->loadModule('Reverse');
+ }
+
+ if ((empty($this->tables) || is_a($this->tables , 'PEAR_Error'))) {
+ //if that fails fall back to clasic tables list.
+ if (!$is_MDB2) {
+ // try getting a list of schema tables first. (postgres)
+ $__DB->expectError(DB_ERROR_UNSUPPORTED);
+ $this->tables = $__DB->getListOf('tables');
+ $__DB->popExpect();
+ } else {
+ $this->tables = $__DB->manager->listTables();
+ $sequences = $__DB->manager->listSequences();
+ foreach ($sequences as $k => $v) {
+ $this->tables[] = $__DB->getSequenceName($v);
+ }
+ }
+ }
+
+ if (is_a($this->tables , 'PEAR_Error')) {
+ return PEAR::raiseError($this->tables->toString(), null, PEAR_ERROR_DIE);
+ }
+
+ // build views as well if asked to.
+ if (!empty($options['build_views'])) {
+ if (!$is_MDB2) {
+ $views = $__DB->getListOf('views');
+ } else {
+ $views = $__DB->manager->listViews();
+ }
+ if (is_a($views,'PEAR_Error')) {
+ return PEAR::raiseError(
+ 'Error getting Views (check the PEAR bug database for the fix to DB), ' .
+ $views->toString(),
+ null,
+ PEAR_ERROR_DIE
+ );
+ }
+ $this->tables = array_merge ($this->tables, $views);
+ }
+
+ // declare a temporary table to be filled with matching tables names
+ $tmp_table = array();
+
+
+ foreach($this->tables as $table) {
+ if (isset($options['generator_include_regex']) &&
+ !preg_match($options['generator_include_regex'],$table)) {
+ continue;
+ } else if (isset($options['generator_exclude_regex']) &&
+ preg_match($options['generator_exclude_regex'],$table)) {
+ continue;
+ }
+ // postgres strip the schema bit from the
+ if (!empty($options['generator_strip_schema'])) {
+ $bits = explode('.', $table,2);
+ $table = $bits[0];
+ if (count($bits) > 1) {
+ $table = $bits[1];
+ }
+ }
+ $quotedTable = !empty($options['quote_identifiers_tableinfo']) ?
+ $__DB->quoteIdentifier($table) : $table;
+
+ if (!$is_MDB2) {
+
+ $defs = $__DB->tableInfo($quotedTable);
+ } else {
+ $defs = $__DB->reverse->tableInfo($quotedTable);
+ // rename the length value, so it matches db's return.
+ foreach ($defs as $k => $v) {
+ if (!isset($defs[$k]['length'])) {
+ continue;
+ }
+ $defs[$k]['len'] = $defs[$k]['length'];
+ }
+ }
+
+ if (is_a($defs,'PEAR_Error')) {
+ // running in debug mode should pick this up as a big warning..
+ $this->raiseError('Error reading tableInfo, '. $defs->toString());
+ continue;
+ }
+ // cast all definitions to objects - as we deal with that better.
+
+
+
+ foreach($defs as $def) {
+ if (!is_array($def)) {
+ continue;
+ }
+
+ $this->_definitions[$table][] = (object) $def;
+
+ }
+ // we find a matching table, just store it into a temporary array
+ $tmp_table[] = $table;
+
+
+ }
+ // the temporary table array is now the right one (tables names matching
+ // with regex expressions have been removed)
+ $this->tables = $tmp_table;
+ //print_r($this->_definitions);
+ }
+
+ /**
+ * Auto generation of table data.
+ *
+ * it will output to db_oo_{database} the table definitions
+ *
+ * @access private
+ * @return none
+ */
+ function generateDefinitions()
+ {
+ $this->debug("Generating Definitions file: ");
+ if (!$this->tables) {
+ $this->debug("-- NO TABLES -- \n");
+ return;
+ }
+
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+
+
+ //$this->_newConfig = new Config('IniFile');
+ $this->_newConfig = '';
+ foreach($this->tables as $this->table) {
+ $this->_generateDefinitionsTable();
+ }
+ $this->_connect();
+ // dont generate a schema if location is not set
+ // it's created on the fly!
+ if (empty($options['schema_location']) && empty($options["ini_{$this->_database}"]) ) {
+ return;
+ }
+ if (!empty($options['generator_no_ini'])) { // built in ini files..
+ return;
+ }
+ $base = @$options['schema_location'];
+ if (isset($options["ini_{$this->_database}"])) {
+ $file = $options["ini_{$this->_database}"];
+ } else {
+ $file = "{$base}/{$this->_database}.ini";
+ }
+
+ if (!file_exists(dirname($file))) {
+ require_once 'System.php';
+ System::mkdir(array('-p','-m',0755,dirname($file)));
+ }
+ $this->debug("Writing ini as {$file}\n");
+ //touch($file);
+ $tmpname = tempnam(session_save_path(),'DataObject_');
+ //print_r($this->_newConfig);
+ $fh = fopen($tmpname,'w');
+ fwrite($fh,$this->_newConfig);
+ fclose($fh);
+ $perms = file_exists($file) ? fileperms($file) : 0755;
+ // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
+
+ if (!@rename($tmpname, $file)) {
+ unlink($file);
+ rename($tmpname, $file);
+ }
+ chmod($file,$perms);
+ //$ret = $this->_newConfig->writeInput($file,false);
+
+ //if (PEAR::isError($ret) ) {
+ // return PEAR::raiseError($ret->message,null,PEAR_ERROR_DIE);
+ // }
+ }
+
+ /**
+ * generate Foreign Keys (for links.ini)
+ * Currenly only works with mysql / mysqli
+ * to use, you must set option: generate_links=true
+ *
+ * @author Pascal Schöni
+ */
+ function generateForeignKeys()
+ {
+ $options = PEAR::getStaticProperty('DB_DataObject','options');
+ if (empty($options['generate_links'])) {
+ return false;
+ }
+ $__DB = &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
+ if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
+ echo "WARNING: cant handle non-mysql introspection for defaults.";
+ return; // cant handle non-mysql introspection for defaults.
+ }
+
+ $DB = $this->getDatabaseConnection();
+
+ $fk = array();
+
+ foreach($this->tables as $this->table) {
+ $res =& $DB->query('SHOW CREATE TABLE ' . $this->table);
+ if (PEAR::isError($res)) {
+ die($res->getMessage());
+ }
+
+ $text = $res->fetchRow(DB_FETCHMODE_DEFAULT, 0);
+ $treffer = array();
+ // Extract FOREIGN KEYS
+ preg_match_all(
+ "/FOREIGN KEY \(`(\w*)`\) REFERENCES `(\w*)` \(`(\w*)`\)/i",
+ $text[1],
+ $treffer,
+ PREG_SET_ORDER);
+
+ if (count($treffer) < 1) {
+ continue;
+ }
+ for ($i = 0; $i < count($treffer); $i++) {
+ $fk[$this->table][$treffer[$i][1]] = $treffer[$i][2] . ":" . $treffer[$i][3];
+ }
+
+ }
+
+ $links_ini = "";
+
+ foreach($fk as $table => $details) {
+ $links_ini .= "[$table]\n";
+ foreach ($details as $col => $ref) {
+ $links_ini .= "$col = $ref\n";
+ }
+ $links_ini .= "\n";
+ }
+
+ // dont generate a schema if location is not set
+ // it's created on the fly!
+ $options = PEAR::getStaticProperty('DB_DataObject','options');
+
+ if (empty($options['schema_location'])) {
+ return;
+ }
+
+
+ $file = "{$options['schema_location']}/{$this->_database}.links.ini";
+
+ if (!file_exists(dirname($file))) {
+ require_once 'System.php';
+ System::mkdir(array('-p','-m',0755,dirname($file)));
+ }
+
+ $this->debug("Writing ini as {$file}\n");
+
+ //touch($file); // not sure why this is needed?
+ $tmpname = tempnam(session_save_path(),'DataObject_');
+
+ $fh = fopen($tmpname,'w');
+ fwrite($fh,$links_ini);
+ fclose($fh);
+ $perms = file_exists($file) ? fileperms($file) : 0755;
+ // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
+ if (!@rename($tmpname, $file)) {
+ unlink($file);
+ rename($tmpname, $file);
+ }
+ chmod($file, $perms);
+ }
+
+
+ /**
+ * The table geneation part
+ *
+ * @access private
+ * @return tabledef and keys array.
+ */
+ function _generateDefinitionsTable()
+ {
+ global $_DB_DATAOBJECT;
+
+ $defs = $this->_definitions[$this->table];
+ $this->_newConfig .= "\n[{$this->table}]\n";
+ $keys_out = "\n[{$this->table}__keys]\n";
+ $keys_out_primary = '';
+ $keys_out_secondary = '';
+ if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
+ echo "TABLE STRUCTURE FOR {$this->table}\n";
+ print_r($defs);
+ }
+ $DB = $this->getDatabaseConnection();
+ $dbtype = $DB->phptype;
+
+ $ret = array(
+ 'table' => array(),
+ 'keys' => array(),
+ );
+
+ $ret_keys_primary = array();
+ $ret_keys_secondary = array();
+
+
+
+ foreach($defs as $t) {
+
+ $n=0;
+ $write_ini = true;
+
+
+ switch (strtoupper($t->type)) {
+
+ case 'INT':
+ case 'INT2': // postgres
+ case 'INT4': // postgres
+ case 'INT8': // postgres
+ case 'SERIAL4': // postgres
+ case 'SERIAL8': // postgres
+ case 'INTEGER':
+ case 'TINYINT':
+ case 'SMALLINT':
+ case 'MEDIUMINT':
+ case 'BIGINT':
+ $type = DB_DATAOBJECT_INT;
+ if ($t->len == 1) {
+ $type += DB_DATAOBJECT_BOOL;
+ }
+ break;
+
+ case 'REAL':
+ case 'DOUBLE':
+ case 'DOUBLE PRECISION': // double precision (firebird)
+ case 'FLOAT':
+ case 'FLOAT4': // real (postgres)
+ case 'FLOAT8': // double precision (postgres)
+ case 'DECIMAL':
+ case 'MONEY': // mssql and maybe others
+ case 'NUMERIC':
+ case 'NUMBER': // oci8
+ $type = DB_DATAOBJECT_INT; // should really by FLOAT!!! / MONEY...
+ break;
+
+ case 'YEAR':
+ $type = DB_DATAOBJECT_INT;
+ break;
+
+ case 'BIT':
+ case 'BOOL':
+ case 'BOOLEAN':
+
+ $type = DB_DATAOBJECT_BOOL;
+ // postgres needs to quote '0'
+ if ($dbtype == 'pgsql') {
+ $type += DB_DATAOBJECT_STR;
+ }
+ break;
+
+ case 'STRING':
+ case 'CHAR':
+ case 'VARCHAR':
+ case 'VARCHAR2':
+ case 'TINYTEXT':
+
+ case 'ENUM':
+ case 'SET': // not really but oh well
+ case 'TIMESTAMPTZ': // postgres
+ case 'BPCHAR': // postgres
+ case 'INTERVAL': // postgres (eg. '12 days')
+
+ case 'CIDR': // postgres IP net spec
+ case 'INET': // postgres IP
+ case 'MACADDR': // postgress network Mac address.
+
+ case 'INTEGER[]': // postgres type
+ case 'BOOLEAN[]': // postgres type
+
+ $type = DB_DATAOBJECT_STR;
+ break;
+
+ case 'TEXT':
+ case 'MEDIUMTEXT':
+ case 'LONGTEXT':
+
+ $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TXT;
+ break;
+
+
+ case 'DATE':
+ $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE;
+ break;
+
+ case 'TIME':
+ $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_TIME;
+ break;
+
+
+ case 'DATETIME':
+
+ $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
+ break;
+
+ case 'TIMESTAMP': // do other databases use this???
+
+ $type = ($dbtype == 'mysql') ?
+ DB_DATAOBJECT_MYSQLTIMESTAMP :
+ DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME;
+ break;
+
+
+ case 'TINYBLOB':
+ case 'BLOB': /// these should really be ignored!!!???
+ case 'MEDIUMBLOB':
+ case 'LONGBLOB':
+ case 'BYTEA': // postgres blob support..
+ $type = DB_DATAOBJECT_STR + DB_DATAOBJECT_BLOB;
+ break;
+ default:
+ echo "*****************************************************************\n".
+ "** WARNING UNKNOWN TYPE **\n".
+ "** Found column '{$t->name}', of type '{$t->type}' **\n".
+ "** Please submit a bug, describe what type you expect this **\n".
+ "** column to be **\n".
+ "** ---------POSSIBLE FIX / WORKAROUND -------------------------**\n".
+ "** Try using MDB2 as the backend - eg set the config option **\n".
+ "** db_driver = MDB2 **\n".
+ "*****************************************************************\n";
+ $write_ini = false;
+ break;
+ }
+
+ if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
+ echo "*****************************************************************\n".
+ "** WARNING COLUMN NAME UNUSABLE **\n".
+ "** Found column '{$t->name}', of type '{$t->type}' **\n".
+ "** Since this column name can't be converted to a php variable **\n".
+ "** name, and the whole idea of mapping would result in a mess **\n".
+ "** This column has been ignored... **\n".
+ "*****************************************************************\n";
+ continue;
+ }
+
+ if (!strlen(trim($t->name))) {
+ continue; // is this a bug?
+ }
+
+ if (preg_match('/not[ _]null/i',$t->flags)) {
+ $type += DB_DATAOBJECT_NOTNULL;
+ }
+
+
+ if (in_array($t->name,array('null','yes','no','true','false'))) {
+ echo "*****************************************************************\n".
+ "** WARNING **\n".
+ "** Found column '{$t->name}', which is invalid in an .ini file **\n".
+ "** This line will not be writen to the file - you will have **\n".
+ "** define the keys()/method manually. **\n".
+ "*****************************************************************\n";
+ $write_ini = false;
+ } else {
+ $this->_newConfig .= "{$t->name} = $type\n";
+ }
+
+ $ret['table'][$t->name] = $type;
+ // i've no idea if this will work well on other databases?
+ // only use primary key or nextval(), cause the setFrom blocks you setting all key items...
+ // if no keys exist fall back to using unique
+ //echo "\n{$t->name} => {$t->flags}\n";
+ if (preg_match("/(auto_increment|nextval\()/i",rawurldecode($t->flags))
+ || (isset($t->autoincrement) && ($t->autoincrement === true))) {
+
+ // native sequences = 2
+ if ($write_ini) {
+ $keys_out_primary .= "{$t->name} = N\n";
+ }
+ $ret_keys_primary[$t->name] = 'N';
+
+ } else if (preg_match("/(primary|unique)/i",$t->flags)) {
+ // keys.. = 1
+ $key_type = 'K';
+ if (!preg_match("/(primary)/i",$t->flags)) {
+ $key_type = 'U';
+ }
+
+ if ($write_ini) {
+ $keys_out_secondary .= "{$t->name} = {$key_type}\n";
+ }
+ $ret_keys_secondary[$t->name] = $key_type;
+ }
+
+
+ }
+
+ $this->_newConfig .= $keys_out . (empty($keys_out_primary) ? $keys_out_secondary : $keys_out_primary);
+ $ret['keys'] = empty($keys_out_primary) ? $ret_keys_secondary : $ret_keys_primary;
+
+ if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
+ print_r(array("dump for {$this->table}", $ret));
+ }
+
+ return $ret;
+
+
+ }
+
+ /**
+ * Convert a table name into a class name -> override this if you want a different mapping
+ *
+ * @access public
+ * @return string class name;
+ */
+
+
+ function getClassNameFromTableName($table)
+ {
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
+ return $class_prefix.preg_replace('/[^A-Z0-9]/i','_',ucfirst(trim($this->table)));
+ }
+
+
+ /**
+ * Convert a table name into a file name -> override this if you want a different mapping
+ *
+ * @access public
+ * @return string file name;
+ */
+
+
+ function getFileNameFromTableName($table)
+ {
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ $base = $options['class_location'];
+ if (strpos($base,'%s') !== false) {
+ $base = dirname($base);
+ }
+ if (!file_exists($base)) {
+ require_once 'System.php';
+ System::mkdir(array('-p',$base));
+ }
+ if (strpos($options['class_location'],'%s') !== false) {
+ $outfilename = sprintf($options['class_location'],
+ preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)));
+ } else {
+ $outfilename = "{$base}/".preg_replace('/[^A-Z0-9]/i','_',ucfirst($this->table)).".php";
+ }
+ return $outfilename;
+
+ }
+
+
+ /**
+ * Convert a column name into a method name (usually prefixed by get/set/validateXXXXX)
+ *
+ * @access public
+ * @return string method name;
+ */
+
+
+ function getMethodNameFromColumnName($col)
+ {
+ return ucfirst($col);
+ }
+
+
+
+
+ /*
+ * building the class files
+ * for each of the tables output a file!
+ */
+ function generateClasses()
+ {
+ //echo "Generating Class files: \n";
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+
+
+ if ($extends = @$options['extends']) {
+ $this->_extends = $extends;
+ $this->_extendsFile = $options['extends_location'];
+ }
+
+ foreach($this->tables as $this->table) {
+ $this->table = trim($this->table);
+ $this->classname = $this->getClassNameFromTableName($this->table);
+ $i = '';
+ $outfilename = $this->getFileNameFromTableName($this->table);
+
+ $oldcontents = '';
+ if (file_exists($outfilename)) {
+ // file_get_contents???
+ $oldcontents = implode('',file($outfilename));
+ }
+
+ $out = $this->_generateClassTable($oldcontents);
+ $this->debug( "writing $this->classname\n");
+ $tmpname = tempnam(session_save_path(),'DataObject_');
+
+ $fh = fopen($tmpname, "w");
+ fputs($fh,$out);
+ fclose($fh);
+ $perms = file_exists($outfilename) ? fileperms($outfilename) : 0755;
+
+ // windows can fail doing this. - not a perfect solution but otherwise it's getting really kludgy..
+ if (!@rename($tmpname, $outfilename)) {
+ unlink($outfilename);
+ rename($tmpname, $outfilename);
+ }
+
+ chmod($outfilename, $perms);
+ }
+ //echo $out;
+ }
+
+ /**
+ * class being extended (can be overridden by [DB_DataObject_Generator] extends=xxxx
+ *
+ * @var string
+ * @access private
+ */
+ var $_extends = 'DB_DataObject';
+
+ /**
+ * line to use for require('DB/DataObject.php');
+ *
+ * @var string
+ * @access private
+ */
+ var $_extendsFile = "DB/DataObject.php";
+
+ /**
+ * class being generated
+ *
+ * @var string
+ * @access private
+ */
+ var $_className;
+
+ /**
+ * The table class geneation part - single file.
+ *
+ * @access private
+ * @return none
+ */
+ function _generateClassTable($input = '')
+ {
+ // title = expand me!
+ $foot = "";
+ $head = "<?php\n/**\n * Table Definition for {$this->table}\n";
+ $head .= $this->derivedHookPageLevelDocBlock();
+ $head .= " */\n";
+ $head .= $this->derivedHookExtendsDocBlock();
+
+
+ // requires
+ $head .= "require_once '{$this->_extendsFile}';\n\n";
+ // add dummy class header in...
+ // class
+ $head .= $this->derivedHookClassDocBlock();
+ $head .= "class {$this->classname} extends {$this->_extends} \n{";
+
+ $body = "\n ###START_AUTOCODE\n";
+ $body .= " /* the code below is auto generated do not remove the above tag */\n\n";
+ // table
+ $padding = (30 - strlen($this->table));
+ $padding = ($padding < 2) ? 2 : $padding;
+
+ $p = str_repeat(' ',$padding) ;
+
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+
+
+ $var = (substr(phpversion(),0,1) > 4) ? 'public' : 'var';
+ $var = !empty($options['generator_var_keyword']) ? $options['generator_var_keyword'] : $var;
+
+
+ $body .= " {$var} \$__table = '{$this->table}'; {$p}// table name\n";
+
+
+ // if we are using the option database_{databasename} = dsn
+ // then we should add var $_database = here
+ // as database names may not always match..
+
+
+
+
+ if (isset($options["database_{$this->_database}"])) {
+ $body .= " {$var} \$_database = '{$this->_database}'; {$p}// database name (used with database_{*} config)\n";
+ }
+
+
+ if (!empty($options['generator_novars'])) {
+ $var = '//'.$var;
+ }
+
+ $defs = $this->_definitions[$this->table];
+
+ // show nice information!
+ $connections = array();
+ $sets = array();
+ foreach($defs as $t) {
+ if (!strlen(trim($t->name))) {
+ continue;
+ }
+ if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $t->name)) {
+ echo "*****************************************************************\n".
+ "** WARNING COLUMN NAME UNUSABLE **\n".
+ "** Found column '{$t->name}', of type '{$t->type}' **\n".
+ "** Since this column name can't be converted to a php variable **\n".
+ "** name, and the whole idea of mapping would result in a mess **\n".
+ "** This column has been ignored... **\n".
+ "*****************************************************************\n";
+ continue;
+ }
+
+
+ $padding = (30 - strlen($t->name));
+ if ($padding < 2) $padding =2;
+ $p = str_repeat(' ',$padding) ;
+
+ $body .=" {$var} \${$t->name}; {$p}// {$t->type}({$t->len}) {$t->flags}\n";
+
+ // can not do set as PEAR::DB table info doesnt support it.
+ //if (substr($t->Type,0,3) == "set")
+ // $sets[$t->Field] = "array".substr($t->Type,3);
+ $body .= $this->derivedHookVar($t,$padding);
+ }
+
+ // THIS IS TOTALLY BORKED old FC creation
+ // IT WILL BE REMOVED!!!!! in DataObjects 1.6
+ // grep -r __clone * to find all it's uses
+ // and replace them with $x = clone($y);
+ // due to the change in the PHP5 clone design.
+
+ if ( substr(phpversion(),0,1) < 5) {
+ $body .= "\n";
+ $body .= " /* ZE2 compatibility trick*/\n";
+ $body .= " function __clone() { return \$this;}\n";
+ }
+
+ // simple creation tools ! (static stuff!)
+ $body .= "\n";
+ $body .= " /* Static get */\n";
+ $body .= " function staticGet(\$k,\$v=NULL) { return DB_DataObject::staticGet('{$this->classname}',\$k,\$v); }\n";
+
+ // generate getter and setter methods
+ $body .= $this->_generateGetters($input);
+ $body .= $this->_generateSetters($input);
+
+ /*
+ theoretically there is scope here to introduce 'list' methods
+ based up 'xxxx_up' column!!! for heiracitcal trees..
+ */
+
+ // set methods
+ //foreach ($sets as $k=>$v) {
+ // $kk = strtoupper($k);
+ // $body .=" function getSets{$k}() { return {$v}; }\n";
+ //}
+
+ if (!empty($options['generator_no_ini'])) {
+ $def = $this->_generateDefinitionsTable(); // simplify this!?
+ $body .= $this->_generateTableFunction($def['table']);
+ $body .= $this->_generateKeysFunction($def['keys']);
+ $body .= $this->_generateSequenceKeyFunction($def);
+ $body .= $this->_generateDefaultsFunction($this->table, $def['table']);
+ } else if (!empty($options['generator_add_defaults'])) {
+ // I dont really like doing it this way (adding another option)
+ // but it helps on older projects.
+ $def = $this->_generateDefinitionsTable(); // simplify this!?
+ $body .= $this->_generateDefaultsFunction($this->table,$def['table']);
+
+ }
+ $body .= $this->derivedHookFunctions($input);
+
+ $body .= "\n /* the code above is auto generated do not remove the tag below */";
+ $body .= "\n ###END_AUTOCODE\n";
+
+
+ // stubs..
+
+ if (!empty($options['generator_add_validate_stubs'])) {
+ foreach($defs as $t) {
+ if (!strlen(trim($t->name))) {
+ continue;
+ }
+ $validate_fname = 'validate' . $this->getMethodNameFromColumnName($t->name);
+ // dont re-add it..
+ if (preg_match('/\s+function\s+' . $validate_fname . '\s*\(/i', $input)) {
+ continue;
+ }
+ $body .= "\n function {$validate_fname}()\n {\n return false;\n }\n";
+ }
+ }
+
+
+
+
+ $foot .= "}\n";
+ $full = $head . $body . $foot;
+
+ if (!$input) {
+ return $full;
+ }
+ if (!preg_match('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n)/s',$input)) {
+ return $full;
+ }
+ if (!preg_match('/(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',$input)) {
+ return $full;
+ }
+
+
+ /* this will only replace extends DB_DataObject by default,
+ unless use set generator_class_rewrite to ANY or a name*/
+
+ $class_rewrite = 'DB_DataObject';
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ if (empty($options['generator_class_rewrite']) || !($class_rewrite = $options['generator_class_rewrite'])) {
+ $class_rewrite = 'DB_DataObject';
+ }
+ if ($class_rewrite == 'ANY') {
+ $class_rewrite = '[a-z_]+';
+ }
+
+ $input = preg_replace(
+ '/(\n|\r\n)class\s*[a-z0-9_]+\s*extends\s*' .$class_rewrite . '\s*(\n|\r\n)\{(\n|\r\n)/si',
+ "\nclass {$this->classname} extends {$this->_extends} \n{\n",
+ $input);
+
+ $ret = preg_replace(
+ '/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s',
+ $body,$input);
+
+ if (!strlen($ret)) {
+ return PEAR::raiseError(
+ "PREG_REPLACE failed to replace body, - you probably need to set these in your php.ini\n".
+ "pcre.backtrack_limit=1000000\n".
+ "pcre.recursion_limit=1000000\n"
+ ,null, PEAR_ERROR_DIE);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * hook to add extra methods to all classes
+ *
+ * called once for each class, use with $this->table and
+ * $this->_definitions[$this->table], to get data out of the current table,
+ * use it to add extra methods to the default classes.
+ *
+ * @access public
+ * @return string added to class eg. functions.
+ */
+ function derivedHookFunctions($input = "")
+ {
+ // This is so derived generator classes can generate functions
+ // It MUST NOT be changed here!!!
+ return "";
+ }
+
+ /**
+ * hook for var lines
+ * called each time a var line is generated, override to add extra var
+ * lines
+ *
+ * @param object t containing type,len,flags etc. from tableInfo call
+ * @param int padding number of spaces
+ * @access public
+ * @return string added to class eg. functions.
+ */
+ function derivedHookVar(&$t,$padding)
+ {
+ // This is so derived generator classes can generate variabels
+ // It MUST NOT be changed here!!!
+ return "";
+ }
+
+ /**
+ * hook to add extra page-level (in terms of phpDocumentor) DocBlock
+ *
+ * called once for each class, use it add extra page-level docs
+ * @access public
+ * @return string added to class eg. functions.
+ */
+ function derivedHookPageLevelDocBlock() {
+ return '';
+ }
+
+ /**
+ * hook to add extra doc block (in terms of phpDocumentor) to extend string
+ *
+ * called once for each class, use it add extra comments to extends
+ * string (require_once...)
+ * @access public
+ * @return string added to class eg. functions.
+ */
+ function derivedHookExtendsDocBlock() {
+ return '';
+ }
+
+ /**
+ * hook to add extra class level DocBlock (in terms of phpDocumentor)
+ *
+ * called once for each class, use it add extra comments to class
+ * string (require_once...)
+ * @access public
+ * @return string added to class eg. functions.
+ */
+ function derivedHookClassDocBlock() {
+ return '';
+ }
+
+ /**
+
+ /**
+ * getProxyFull - create a class definition on the fly and instantate it..
+ *
+ * similar to generated files - but also evals the class definitoin code.
+ *
+ *
+ * @param string database name
+ * @param string table name of table to create proxy for.
+ *
+ *
+ * @return object Instance of class. or PEAR Error
+ * @access public
+ */
+ function getProxyFull($database,$table)
+ {
+
+ if ($err = $this->fillTableSchema($database,$table)) {
+ return $err;
+ }
+
+
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ $class_prefix = empty($options['class_prefix']) ? '' : $options['class_prefix'];
+
+ if ($extends = @$options['extends']) {
+ $this->_extends = $extends;
+ $this->_extendsFile = $options['extends_location'];
+ }
+ $classname = $this->classname = $this->getClassNameFromTableName($this->table);
+
+ $out = $this->_generateClassTable();
+ //echo $out;
+ eval('?>'.$out);
+ return new $classname;
+
+ }
+
+ /**
+ * fillTableSchema - set the database schema on the fly
+ *
+ *
+ *
+ * @param string database name
+ * @param string table name of table to create schema info for
+ *
+ * @return none | PEAR::error()
+ * @access public
+ */
+ function fillTableSchema($database,$table)
+ {
+ global $_DB_DATAOBJECT;
+ // a little bit of sanity testing.
+ if ((false !== strpos($database,"'")) || (false !== strpos($database,";"))) {
+ return PEAR::raiseError("Error: Database name contains a quote or semi-colon", null, PEAR_ERROR_DIE);
+ }
+
+ $this->_database = $database;
+
+ $this->_connect();
+ $table = trim($table);
+
+ // a little bit of sanity testing.
+ if ((false !== strpos($table,"'")) || (false !== strpos($table,";"))) {
+ return PEAR::raiseError("Error: Table contains a quote or semi-colon", null, PEAR_ERROR_DIE);
+ }
+ $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
+
+
+ $options = PEAR::getStaticProperty('DB_DataObject','options');
+ $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
+ $is_MDB2 = ($db_driver != 'DB') ? true : false;
+
+ if (!$is_MDB2) {
+ // try getting a list of schema tables first. (postgres)
+ $__DB->expectError(DB_ERROR_UNSUPPORTED);
+ $this->tables = $__DB->getListOf('schema.tables');
+ $__DB->popExpect();
+ } else {
+ /**
+ * set portability and some modules to fetch the informations
+ */
+ $__DB->setOption('portability', MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_FIX_CASE);
+ $__DB->loadModule('Manager');
+ $__DB->loadModule('Reverse');
+ }
+ $quotedTable = !empty($options['quote_identifiers']) ?
+ $__DB->quoteIdentifier($table) : $table;
+
+ if (!$is_MDB2) {
+ $defs = $__DB->tableInfo($quotedTable);
+ } else {
+ $defs = $__DB->reverse->tableInfo($quotedTable);
+ foreach ($defs as $k => $v) {
+ if (!isset($defs[$k]['length'])) {
+ continue;
+ }
+ $defs[$k]['len'] = $defs[$k]['length'];
+ }
+ }
+
+
+
+
+ if (PEAR::isError($defs)) {
+ return $defs;
+ }
+ if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 2) {
+ $this->debug("getting def for $database/$table",'fillTable');
+ $this->debug(print_r($defs,true),'defs');
+ }
+ // cast all definitions to objects - as we deal with that better.
+
+
+ foreach($defs as $def) {
+ if (is_array($def)) {
+ $this->_definitions[$table][] = (object) $def;
+ }
+ }
+
+ $this->table = trim($table);
+ $ret = $this->_generateDefinitionsTable();
+
+ $_DB_DATAOBJECT['INI'][$database][$table] = $ret['table'];
+ $_DB_DATAOBJECT['INI'][$database][$table.'__keys'] = $ret['keys'];
+ return false;
+
+ }
+
+ /**
+ * Generate getter methods for class definition
+ *
+ * @param string $input Existing class contents
+ * @return string
+ * @access public
+ */
+ function _generateGetters($input)
+ {
+
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ $getters = '';
+
+ // only generate if option is set to true
+ if (empty($options['generate_getters'])) {
+ return '';
+ }
+
+ // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
+ $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
+
+ $getters .= "\n\n";
+ $defs = $this->_definitions[$this->table];
+
+ // loop through properties and create getter methods
+ foreach ($defs = $defs as $t) {
+
+ // build mehtod name
+ $methodName = 'get' . $this->getMethodNameFromColumnName($t->name);
+
+ if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
+ continue;
+ }
+
+ $getters .= " /**\n";
+ $getters .= " * Getter for \${$t->name}\n";
+ $getters .= " *\n";
+ $getters .= (stristr($t->flags, 'multiple_key')) ? " * @return object\n"
+ : " * @return {$t->type}\n";
+ $getters .= " * @access public\n";
+ $getters .= " */\n";
+ $getters .= (substr(phpversion(),0,1) > 4) ? ' public '
+ : ' ';
+ $getters .= "function $methodName() {\n";
+ $getters .= " return \$this->{$t->name};\n";
+ $getters .= " }\n\n";
+ }
+
+
+ return $getters;
+ }
+
+
+ /**
+ * Generate setter methods for class definition
+ *
+ * @param string Existing class contents
+ * @return string
+ * @access public
+ */
+ function _generateSetters($input)
+ {
+
+ $options = &PEAR::getStaticProperty('DB_DataObject','options');
+ $setters = '';
+
+ // only generate if option is set to true
+ if (empty($options['generate_setters'])) {
+ return '';
+ }
+
+ // remove auto-generated code from input to be able to check if the method exists outside of the auto-code
+ $input = preg_replace('/(\n|\r\n)\s*###START_AUTOCODE(\n|\r\n).*(\n|\r\n)\s*###END_AUTOCODE(\n|\r\n)/s', '', $input);
+
+ $setters .= "\n";
+ $defs = $this->_definitions[$this->table];
+
+ // loop through properties and create setter methods
+ foreach ($defs = $defs as $t) {
+
+ // build mehtod name
+ $methodName = 'set' . $this->getMethodNameFromColumnName($t->name);
+
+ if (!strlen(trim($t->name)) || preg_match("/function[\s]+[&]?$methodName\(/i", $input)) {
+ continue;
+ }
+
+ $setters .= " /**\n";
+ $setters .= " * Setter for \${$t->name}\n";
+ $setters .= " *\n";
+ $setters .= " * @param mixed input value\n";
+ $setters .= " * @access public\n";
+ $setters .= " */\n";
+ $setters .= (substr(phpversion(),0,1) > 4) ? ' public '
+ : ' ';
+ $setters .= "function $methodName(\$value) {\n";
+ $setters .= " \$this->{$t->name} = \$value;\n";
+ $setters .= " }\n\n";
+ }
+
+
+ return $setters;
+ }
+ /**
+ * Generate table Function - used when generator_no_ini is set.
+ *
+ * @param array table array.
+ * @return string
+ * @access public
+ */
+ function _generateTableFunction($def)
+ {
+ $defines = explode(',','INT,STR,DATE,TIME,BOOL,TXT,BLOB,NOTNULL,MYSQLTIMESTAMP');
+
+ $ret = "\n" .
+ " function table()\n" .
+ " {\n" .
+ " return array(\n";
+
+ foreach($def as $k=>$v) {
+ $str = '0';
+ foreach($defines as $dn) {
+ if ($v & constant('DB_DATAOBJECT_' . $dn)) {
+ $str .= ' + DB_DATAOBJECT_' . $dn;
+ }
+ }
+ if (strlen($str) > 1) {
+ $str = substr($str,3); // strip the 0 +
+ }
+ // hopefully addslashes is good enough here!!!
+ $ret .= ' \''.addslashes($k).'\' => ' . $str . ",\n";
+ }
+ return $ret . " );\n" .
+ " }\n";
+
+
+
+ }
+ /**
+ * Generate keys Function - used generator_no_ini is set.
+ *
+ * @param array keys array.
+ * @return string
+ * @access public
+ */
+ function _generateKeysFunction($def)
+ {
+
+ $ret = "\n" .
+ " function keys()\n" .
+ " {\n" .
+ " return array(";
+
+ foreach($def as $k=>$type) {
+ // hopefully addslashes is good enough here!!!
+ $ret .= '\''.addslashes($k).'\', ';
+ }
+ $ret = preg_replace('#, $#', '', $ret);
+ return $ret . ");\n" .
+ " }\n";
+
+
+
+ }
+ /**
+ * Generate sequenceKey Function - used generator_no_ini is set.
+ *
+ * @param array table and key definition.
+ * @return string
+ * @access public
+ */
+ function _generateSequenceKeyFunction($def)
+ {
+
+ //print_r($def);
+ // DB_DataObject::debugLevel(5);
+ global $_DB_DATAOBJECT;
+ // print_r($def);
+
+
+ $dbtype = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
+ $realkeys = $def['keys'];
+ $keys = array_keys($realkeys);
+ $usekey = isset($keys[0]) ? $keys[0] : false;
+ $table = $def['table'];
+
+
+ $seqname = false;
+
+
+
+
+ $ar = array(false,false,false);
+ if ($usekey !== false) {
+ if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
+ $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
+ if (strpos($usekey,':') !== false) {
+ list($usekey,$seqname) = explode(':',$usekey);
+ }
+ }
+
+ if (in_array($dbtype , array( 'mysql', 'mysqli', 'mssql', 'ifx')) &&
+ ($table[$usekey] & DB_DATAOBJECT_INT) &&
+ isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
+ ) {
+ // use native sequence keys.
+ $ar = array($usekey,true,$seqname);
+ } else {
+ // use generated sequence keys..
+ if ($table[$usekey] & DB_DATAOBJECT_INT) {
+ $ar = array($usekey,false,$seqname);
+ }
+ }
+ }
+
+
+
+
+ $ret = "\n" .
+ " function sequenceKey() // keyname, use native, native name\n" .
+ " {\n" .
+ " return array(";
+ foreach($ar as $v) {
+ switch (gettype($v)) {
+ case 'boolean':
+ $ret .= ($v ? 'true' : 'false') . ', ';
+ break;
+
+ case 'string':
+ $ret .= "'" . $v . "', ";
+ break;
+
+ default: // eak
+ $ret .= "null, ";
+
+ }
+ }
+ $ret = preg_replace('#, $#', '', $ret);
+ return $ret . ");\n" .
+ " }\n";
+
+ }
+ /**
+ * Generate defaults Function - used generator_add_defaults or generator_no_ini is set.
+ * Only supports mysql and mysqli ... welcome ideas for more..
+ *
+ *
+ * @param array table and key definition.
+ * @return string
+ * @access public
+ */
+ function _generateDefaultsFunction($table,$defs)
+ {
+ $__DB= &$GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'][$this->_database_dsn_md5];
+ if (!in_array($__DB->phptype, array('mysql','mysqli'))) {
+ return; // cant handle non-mysql introspection for defaults.
+ }
+ $options = PEAR::getStaticProperty('DB_DataObject','options');
+ $db_driver = empty($options['db_driver']) ? 'DB' : $options['db_driver'];
+ $method = $db_driver == 'DB' ? 'getAll' : 'queryAll';
+ $res = $__DB->$method('DESCRIBE ' . $table,DB_FETCHMODE_ASSOC);
+ $defaults = array();
+ foreach($res as $ar) {
+ // this is initially very dumb... -> and it may mess up..
+ $type = $defs[$ar['Field']];
+
+ switch (true) {
+
+ case (is_null( $ar['Default'])):
+ $defaults[$ar['Field']] = 'null';
+ break;
+
+ case ($type & DB_DATAOBJECT_DATE):
+ case ($type & DB_DATAOBJECT_TIME):
+ case ($type & DB_DATAOBJECT_MYSQLTIMESTAMP): // not supported yet..
+ break;
+
+ case ($type & DB_DATAOBJECT_BOOL):
+ $defaults[$ar['Field']] = (int)(boolean) $ar['Default'];
+ break;
+
+
+ case ($type & DB_DATAOBJECT_STR):
+ $defaults[$ar['Field']] = "'" . addslashes($ar['Default']) . "'";
+ break;
+
+
+ default: // hopefully eveything else... - numbers etc.
+ if (!strlen($ar['Default'])) {
+ continue;
+ }
+ if (is_numeric($ar['Default'])) {
+ $defaults[$ar['Field']] = $ar['Default'];
+ }
+ break;
+
+ }
+ //var_dump(array($ar['Field'], $ar['Default'], $defaults[$ar['Field']]));
+ }
+ if (empty($defaults)) {
+ return;
+ }
+
+ $ret = "\n" .
+ " function defaults() // column default values \n" .
+ " {\n" .
+ " return array(\n";
+ foreach($defaults as $k=>$v) {
+ $ret .= ' \''.addslashes($k).'\' => ' . $v . ",\n";
+ }
+ return $ret . " );\n" .
+ " }\n";
+
+
+
+
+ }
+
+
+
+
+
+}
diff --git a/extlib/DB/DataObject/createTables.php b/extlib/DB/DataObject/createTables.php
new file mode 100755
index 000000000..c0659574e
--- /dev/null
+++ b/extlib/DB/DataObject/createTables.php
@@ -0,0 +1,59 @@
+#!/usr/bin/php -q
+<?php
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Alan Knowles <alan@akbkhome.com>
+// +----------------------------------------------------------------------+
+//
+// $Id: createTables.php,v 1.24 2006/01/13 01:27:55 alan_k Exp $
+//
+
+// since this version doesnt use overload,
+// and I assume anyone using custom generators should add this..
+
+define('DB_DATAOBJECT_NO_OVERLOAD',1);
+
+//require_once 'DB/DataObject/Generator.php';
+require_once 'DB/DataObject/Generator.php';
+
+if (!ini_get('register_argc_argv')) {
+ PEAR::raiseError("\nERROR: You must turn register_argc_argv On in you php.ini file for this to work\neg.\n\nregister_argc_argv = On\n\n", null, PEAR_ERROR_DIE);
+ exit;
+}
+
+if (!@$_SERVER['argv'][1]) {
+ PEAR::raiseError("\nERROR: createTable.php usage:\n\nC:\php\pear\DB\DataObjects\createTable.php example.ini\n\n", null, PEAR_ERROR_DIE);
+ exit;
+}
+
+$config = parse_ini_file($_SERVER['argv'][1], true);
+foreach($config as $class=>$values) {
+ $options = &PEAR::getStaticProperty($class,'options');
+ $options = $values;
+}
+
+
+$options = &PEAR::getStaticProperty('DB_DataObject','options');
+if (empty($options)) {
+ PEAR::raiseError("\nERROR: could not read ini file\n\n", null, PEAR_ERROR_DIE);
+ exit;
+}
+set_time_limit(0);
+
+// use debug level from file if set..
+DB_DataObject::debugLevel(isset($options['debug']) ? $options['debug'] : 1);
+
+$generator = new DB_DataObject_Generator;
+$generator->start();
+
diff --git a/extlib/DB/common.php b/extlib/DB/common.php
new file mode 100644
index 000000000..c51323d25
--- /dev/null
+++ b/extlib/DB/common.php
@@ -0,0 +1,2262 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Contains the DB_common base class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: common.php,v 1.144 2007/11/26 22:54:03 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the PEAR class so it can be extended from
+ */
+require_once 'PEAR.php';
+
+/**
+ * DB_common is the base class from which each database driver class extends
+ *
+ * All common methods are declared here. If a given DBMS driver contains
+ * a particular method, that method will overload the one here.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_common extends PEAR
+{
+ // {{{ properties
+
+ /**
+ * The current default fetch mode
+ * @var integer
+ */
+ var $fetchmode = DB_FETCHMODE_ORDERED;
+
+ /**
+ * The name of the class into which results should be fetched when
+ * DB_FETCHMODE_OBJECT is in effect
+ *
+ * @var string
+ */
+ var $fetchmode_object_class = 'stdClass';
+
+ /**
+ * Was a connection present when the object was serialized()?
+ * @var bool
+ * @see DB_common::__sleep(), DB_common::__wake()
+ */
+ var $was_connected = null;
+
+ /**
+ * The most recently executed query
+ * @var string
+ */
+ var $last_query = '';
+
+ /**
+ * Run-time configuration options
+ *
+ * The 'optimize' option has been deprecated. Use the 'portability'
+ * option instead.
+ *
+ * @var array
+ * @see DB_common::setOption()
+ */
+ var $options = array(
+ 'result_buffering' => 500,
+ 'persistent' => false,
+ 'ssl' => false,
+ 'debug' => 0,
+ 'seqname_format' => '%s_seq',
+ 'autofree' => false,
+ 'portability' => DB_PORTABILITY_NONE,
+ 'optimize' => 'performance', // Deprecated. Use 'portability'.
+ );
+
+ /**
+ * The parameters from the most recently executed query
+ * @var array
+ * @since Property available since Release 1.7.0
+ */
+ var $last_parameters = array();
+
+ /**
+ * The elements from each prepared statement
+ * @var array
+ */
+ var $prepare_tokens = array();
+
+ /**
+ * The data types of the various elements in each prepared statement
+ * @var array
+ */
+ var $prepare_types = array();
+
+ /**
+ * The prepared queries
+ * @var array
+ */
+ var $prepared_queries = array();
+
+ /**
+ * Flag indicating that the last query was a manipulation query.
+ * @access protected
+ * @var boolean
+ */
+ var $_last_query_manip = false;
+
+ /**
+ * Flag indicating that the next query <em>must</em> be a manipulation
+ * query.
+ * @access protected
+ * @var boolean
+ */
+ var $_next_query_manip = false;
+
+
+ // }}}
+ // {{{ DB_common
+
+ /**
+ * This constructor calls <kbd>$this->PEAR('DB_Error')</kbd>
+ *
+ * @return void
+ */
+ function DB_common()
+ {
+ $this->PEAR('DB_Error');
+ }
+
+ // }}}
+ // {{{ __sleep()
+
+ /**
+ * Automatically indicates which properties should be saved
+ * when PHP's serialize() function is called
+ *
+ * @return array the array of properties names that should be saved
+ */
+ function __sleep()
+ {
+ if ($this->connection) {
+ // Don't disconnect(), people use serialize() for many reasons
+ $this->was_connected = true;
+ } else {
+ $this->was_connected = false;
+ }
+ if (isset($this->autocommit)) {
+ return array('autocommit',
+ 'dbsyntax',
+ 'dsn',
+ 'features',
+ 'fetchmode',
+ 'fetchmode_object_class',
+ 'options',
+ 'was_connected',
+ );
+ } else {
+ return array('dbsyntax',
+ 'dsn',
+ 'features',
+ 'fetchmode',
+ 'fetchmode_object_class',
+ 'options',
+ 'was_connected',
+ );
+ }
+ }
+
+ // }}}
+ // {{{ __wakeup()
+
+ /**
+ * Automatically reconnects to the database when PHP's unserialize()
+ * function is called
+ *
+ * The reconnection attempt is only performed if the object was connected
+ * at the time PHP's serialize() function was run.
+ *
+ * @return void
+ */
+ function __wakeup()
+ {
+ if ($this->was_connected) {
+ $this->connect($this->dsn, $this->options);
+ }
+ }
+
+ // }}}
+ // {{{ __toString()
+
+ /**
+ * Automatic string conversion for PHP 5
+ *
+ * @return string a string describing the current PEAR DB object
+ *
+ * @since Method available since Release 1.7.0
+ */
+ function __toString()
+ {
+ $info = strtolower(get_class($this));
+ $info .= ': (phptype=' . $this->phptype .
+ ', dbsyntax=' . $this->dbsyntax .
+ ')';
+ if ($this->connection) {
+ $info .= ' [connected]';
+ }
+ return $info;
+ }
+
+ // }}}
+ // {{{ toString()
+
+ /**
+ * DEPRECATED: String conversion method
+ *
+ * @return string a string describing the current PEAR DB object
+ *
+ * @deprecated Method deprecated in Release 1.7.0
+ */
+ function toString()
+ {
+ return $this->__toString();
+ }
+
+ // }}}
+ // {{{ quoteString()
+
+ /**
+ * DEPRECATED: Quotes a string so it can be safely used within string
+ * delimiters in a query
+ *
+ * @param string $string the string to be quoted
+ *
+ * @return string the quoted string
+ *
+ * @see DB_common::quoteSmart(), DB_common::escapeSimple()
+ * @deprecated Method deprecated some time before Release 1.2
+ */
+ function quoteString($string)
+ {
+ $string = $this->quote($string);
+ if ($string{0} == "'") {
+ return substr($string, 1, -1);
+ }
+ return $string;
+ }
+
+ // }}}
+ // {{{ quote()
+
+ /**
+ * DEPRECATED: Quotes a string so it can be safely used in a query
+ *
+ * @param string $string the string to quote
+ *
+ * @return string the quoted string or the string <samp>NULL</samp>
+ * if the value submitted is <kbd>null</kbd>.
+ *
+ * @see DB_common::quoteSmart(), DB_common::escapeSimple()
+ * @deprecated Deprecated in release 1.6.0
+ */
+ function quote($string = null)
+ {
+ return ($string === null) ? 'NULL'
+ : "'" . str_replace("'", "''", $string) . "'";
+ }
+
+ // }}}
+ // {{{ quoteIdentifier()
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ *
+ * Delimiting style depends on which database driver is being used.
+ *
+ * NOTE: just because you CAN use delimited identifiers doesn't mean
+ * you SHOULD use them. In general, they end up causing way more
+ * problems than they solve.
+ *
+ * Portability is broken by using the following characters inside
+ * delimited identifiers:
+ * + backtick (<kbd>`</kbd>) -- due to MySQL
+ * + double quote (<kbd>"</kbd>) -- due to Oracle
+ * + brackets (<kbd>[</kbd> or <kbd>]</kbd>) -- due to Access
+ *
+ * Delimited identifiers are known to generally work correctly under
+ * the following drivers:
+ * + mssql
+ * + mysql
+ * + mysqli
+ * + oci8
+ * + odbc(access)
+ * + odbc(db2)
+ * + pgsql
+ * + sqlite
+ * + sybase (must execute <kbd>set quoted_identifier on</kbd> sometime
+ * prior to use)
+ *
+ * InterBase doesn't seem to be able to use delimited identifiers
+ * via PHP 4. They work fine under PHP 5.
+ *
+ * @param string $str the identifier name to be quoted
+ *
+ * @return string the quoted identifier
+ *
+ * @since Method available since Release 1.6.0
+ */
+ function quoteIdentifier($str)
+ {
+ return '"' . str_replace('"', '""', $str) . '"';
+ }
+
+ // }}}
+ // {{{ quoteSmart()
+
+ /**
+ * Formats input so it can be safely used in a query
+ *
+ * The output depends on the PHP data type of input and the database
+ * type being used.
+ *
+ * @param mixed $in the data to be formatted
+ *
+ * @return mixed the formatted data. The format depends on the input's
+ * PHP type:
+ * <ul>
+ * <li>
+ * <kbd>input</kbd> -> <samp>returns</samp>
+ * </li>
+ * <li>
+ * <kbd>null</kbd> -> the string <samp>NULL</samp>
+ * </li>
+ * <li>
+ * <kbd>integer</kbd> or <kbd>double</kbd> -> the unquoted number
+ * </li>
+ * <li>
+ * <kbd>bool</kbd> -> output depends on the driver in use
+ * Most drivers return integers: <samp>1</samp> if
+ * <kbd>true</kbd> or <samp>0</samp> if
+ * <kbd>false</kbd>.
+ * Some return strings: <samp>TRUE</samp> if
+ * <kbd>true</kbd> or <samp>FALSE</samp> if
+ * <kbd>false</kbd>.
+ * Finally one returns strings: <samp>T</samp> if
+ * <kbd>true</kbd> or <samp>F</samp> if
+ * <kbd>false</kbd>. Here is a list of each DBMS,
+ * the values returned and the suggested column type:
+ * <ul>
+ * <li>
+ * <kbd>dbase</kbd> -> <samp>T/F</samp>
+ * (<kbd>Logical</kbd>)
+ * </li>
+ * <li>
+ * <kbd>fbase</kbd> -> <samp>TRUE/FALSE</samp>
+ * (<kbd>BOOLEAN</kbd>)
+ * </li>
+ * <li>
+ * <kbd>ibase</kbd> -> <samp>1/0</samp>
+ * (<kbd>SMALLINT</kbd>) [1]
+ * </li>
+ * <li>
+ * <kbd>ifx</kbd> -> <samp>1/0</samp>
+ * (<kbd>SMALLINT</kbd>) [1]
+ * </li>
+ * <li>
+ * <kbd>msql</kbd> -> <samp>1/0</samp>
+ * (<kbd>INTEGER</kbd>)
+ * </li>
+ * <li>
+ * <kbd>mssql</kbd> -> <samp>1/0</samp>
+ * (<kbd>BIT</kbd>)
+ * </li>
+ * <li>
+ * <kbd>mysql</kbd> -> <samp>1/0</samp>
+ * (<kbd>TINYINT(1)</kbd>)
+ * </li>
+ * <li>
+ * <kbd>mysqli</kbd> -> <samp>1/0</samp>
+ * (<kbd>TINYINT(1)</kbd>)
+ * </li>
+ * <li>
+ * <kbd>oci8</kbd> -> <samp>1/0</samp>
+ * (<kbd>NUMBER(1)</kbd>)
+ * </li>
+ * <li>
+ * <kbd>odbc</kbd> -> <samp>1/0</samp>
+ * (<kbd>SMALLINT</kbd>) [1]
+ * </li>
+ * <li>
+ * <kbd>pgsql</kbd> -> <samp>TRUE/FALSE</samp>
+ * (<kbd>BOOLEAN</kbd>)
+ * </li>
+ * <li>
+ * <kbd>sqlite</kbd> -> <samp>1/0</samp>
+ * (<kbd>INTEGER</kbd>)
+ * </li>
+ * <li>
+ * <kbd>sybase</kbd> -> <samp>1/0</samp>
+ * (<kbd>TINYINT(1)</kbd>)
+ * </li>
+ * </ul>
+ * [1] Accommodate the lowest common denominator because not all
+ * versions of have <kbd>BOOLEAN</kbd>.
+ * </li>
+ * <li>
+ * other (including strings and numeric strings) ->
+ * the data with single quotes escaped by preceeding
+ * single quotes, backslashes are escaped by preceeding
+ * backslashes, then the whole string is encapsulated
+ * between single quotes
+ * </li>
+ * </ul>
+ *
+ * @see DB_common::escapeSimple()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteSmart($in)
+ {
+ if (is_int($in)) {
+ return $in;
+ } elseif (is_float($in)) {
+ return $this->quoteFloat($in);
+ } elseif (is_bool($in)) {
+ return $this->quoteBoolean($in);
+ } elseif (is_null($in)) {
+ return 'NULL';
+ } else {
+ if ($this->dbsyntax == 'access'
+ && preg_match('/^#.+#$/', $in))
+ {
+ return $this->escapeSimple($in);
+ }
+ return "'" . $this->escapeSimple($in) . "'";
+ }
+ }
+
+ // }}}
+ // {{{ quoteBoolean()
+
+ /**
+ * Formats a boolean value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param boolean the boolean value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteBoolean($boolean) {
+ return $boolean ? '1' : '0';
+ }
+
+ // }}}
+ // {{{ quoteFloat()
+
+ /**
+ * Formats a float value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param float the float value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteFloat($float) {
+ return "'".$this->escapeSimple(str_replace(',', '.', strval(floatval($float))))."'";
+ }
+
+ // }}}
+ // {{{ escapeSimple()
+
+ /**
+ * Escapes a string according to the current DBMS's standards
+ *
+ * In SQLite, this makes things safe for inserts/updates, but may
+ * cause problems when performing text comparisons against columns
+ * containing binary data. See the
+ * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
+ *
+ * @param string $str the string to be escaped
+ *
+ * @return string the escaped string
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function escapeSimple($str)
+ {
+ return str_replace("'", "''", $str);
+ }
+
+ // }}}
+ // {{{ provides()
+
+ /**
+ * Tells whether the present driver supports a given feature
+ *
+ * @param string $feature the feature you're curious about
+ *
+ * @return bool whether this driver supports $feature
+ */
+ function provides($feature)
+ {
+ return $this->features[$feature];
+ }
+
+ // }}}
+ // {{{ setFetchMode()
+
+ /**
+ * Sets the fetch mode that should be used by default for query results
+ *
+ * @param integer $fetchmode DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC
+ * or DB_FETCHMODE_OBJECT
+ * @param string $object_class the class name of the object to be returned
+ * by the fetch methods when the
+ * DB_FETCHMODE_OBJECT mode is selected.
+ * If no class is specified by default a cast
+ * to object from the assoc array row will be
+ * done. There is also the posibility to use
+ * and extend the 'DB_row' class.
+ *
+ * @see DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT
+ */
+ function setFetchMode($fetchmode, $object_class = 'stdClass')
+ {
+ switch ($fetchmode) {
+ case DB_FETCHMODE_OBJECT:
+ $this->fetchmode_object_class = $object_class;
+ case DB_FETCHMODE_ORDERED:
+ case DB_FETCHMODE_ASSOC:
+ $this->fetchmode = $fetchmode;
+ break;
+ default:
+ return $this->raiseError('invalid fetchmode mode');
+ }
+ }
+
+ // }}}
+ // {{{ setOption()
+
+ /**
+ * Sets run-time configuration options for PEAR DB
+ *
+ * Options, their data types, default values and description:
+ * <ul>
+ * <li>
+ * <var>autofree</var> <kbd>boolean</kbd> = <samp>false</samp>
+ * <br />should results be freed automatically when there are no
+ * more rows?
+ * </li><li>
+ * <var>result_buffering</var> <kbd>integer</kbd> = <samp>500</samp>
+ * <br />how many rows of the result set should be buffered?
+ * <br />In mysql: mysql_unbuffered_query() is used instead of
+ * mysql_query() if this value is 0. (Release 1.7.0)
+ * <br />In oci8: this value is passed to ocisetprefetch().
+ * (Release 1.7.0)
+ * </li><li>
+ * <var>debug</var> <kbd>integer</kbd> = <samp>0</samp>
+ * <br />debug level
+ * </li><li>
+ * <var>persistent</var> <kbd>boolean</kbd> = <samp>false</samp>
+ * <br />should the connection be persistent?
+ * </li><li>
+ * <var>portability</var> <kbd>integer</kbd> = <samp>DB_PORTABILITY_NONE</samp>
+ * <br />portability mode constant (see below)
+ * </li><li>
+ * <var>seqname_format</var> <kbd>string</kbd> = <samp>%s_seq</samp>
+ * <br />the sprintf() format string used on sequence names. This
+ * format is applied to sequence names passed to
+ * createSequence(), nextID() and dropSequence().
+ * </li><li>
+ * <var>ssl</var> <kbd>boolean</kbd> = <samp>false</samp>
+ * <br />use ssl to connect?
+ * </li>
+ * </ul>
+ *
+ * -----------------------------------------
+ *
+ * PORTABILITY MODES
+ *
+ * These modes are bitwised, so they can be combined using <kbd>|</kbd>
+ * and removed using <kbd>^</kbd>. See the examples section below on how
+ * to do this.
+ *
+ * <samp>DB_PORTABILITY_NONE</samp>
+ * turn off all portability features
+ *
+ * This mode gets automatically turned on if the deprecated
+ * <var>optimize</var> option gets set to <samp>performance</samp>.
+ *
+ *
+ * <samp>DB_PORTABILITY_LOWERCASE</samp>
+ * convert names of tables and fields to lower case when using
+ * <kbd>get*()</kbd>, <kbd>fetch*()</kbd> and <kbd>tableInfo()</kbd>
+ *
+ * This mode gets automatically turned on in the following databases
+ * if the deprecated option <var>optimize</var> gets set to
+ * <samp>portability</samp>:
+ * + oci8
+ *
+ *
+ * <samp>DB_PORTABILITY_RTRIM</samp>
+ * right trim the data output by <kbd>get*()</kbd> <kbd>fetch*()</kbd>
+ *
+ *
+ * <samp>DB_PORTABILITY_DELETE_COUNT</samp>
+ * force reporting the number of rows deleted
+ *
+ * Some DBMS's don't count the number of rows deleted when performing
+ * simple <kbd>DELETE FROM tablename</kbd> queries. This portability
+ * mode tricks such DBMS's into telling the count by adding
+ * <samp>WHERE 1=1</samp> to the end of <kbd>DELETE</kbd> queries.
+ *
+ * This mode gets automatically turned on in the following databases
+ * if the deprecated option <var>optimize</var> gets set to
+ * <samp>portability</samp>:
+ * + fbsql
+ * + mysql
+ * + mysqli
+ * + sqlite
+ *
+ *
+ * <samp>DB_PORTABILITY_NUMROWS</samp>
+ * enable hack that makes <kbd>numRows()</kbd> work in Oracle
+ *
+ * This mode gets automatically turned on in the following databases
+ * if the deprecated option <var>optimize</var> gets set to
+ * <samp>portability</samp>:
+ * + oci8
+ *
+ *
+ * <samp>DB_PORTABILITY_ERRORS</samp>
+ * makes certain error messages in certain drivers compatible
+ * with those from other DBMS's
+ *
+ * + mysql, mysqli: change unique/primary key constraints
+ * DB_ERROR_ALREADY_EXISTS -> DB_ERROR_CONSTRAINT
+ *
+ * + odbc(access): MS's ODBC driver reports 'no such field' as code
+ * 07001, which means 'too few parameters.' When this option is on
+ * that code gets mapped to DB_ERROR_NOSUCHFIELD.
+ * DB_ERROR_MISMATCH -> DB_ERROR_NOSUCHFIELD
+ *
+ * <samp>DB_PORTABILITY_NULL_TO_EMPTY</samp>
+ * convert null values to empty strings in data output by get*() and
+ * fetch*(). Needed because Oracle considers empty strings to be null,
+ * while most other DBMS's know the difference between empty and null.
+ *
+ *
+ * <samp>DB_PORTABILITY_ALL</samp>
+ * turn on all portability features
+ *
+ * -----------------------------------------
+ *
+ * Example 1. Simple setOption() example
+ * <code>
+ * $db->setOption('autofree', true);
+ * </code>
+ *
+ * Example 2. Portability for lowercasing and trimming
+ * <code>
+ * $db->setOption('portability',
+ * DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_RTRIM);
+ * </code>
+ *
+ * Example 3. All portability options except trimming
+ * <code>
+ * $db->setOption('portability',
+ * DB_PORTABILITY_ALL ^ DB_PORTABILITY_RTRIM);
+ * </code>
+ *
+ * @param string $option option name
+ * @param mixed $value value for the option
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::$options
+ */
+ function setOption($option, $value)
+ {
+ if (isset($this->options[$option])) {
+ $this->options[$option] = $value;
+
+ /*
+ * Backwards compatibility check for the deprecated 'optimize'
+ * option. Done here in case settings change after connecting.
+ */
+ if ($option == 'optimize') {
+ if ($value == 'portability') {
+ switch ($this->phptype) {
+ case 'oci8':
+ $this->options['portability'] =
+ DB_PORTABILITY_LOWERCASE |
+ DB_PORTABILITY_NUMROWS;
+ break;
+ case 'fbsql':
+ case 'mysql':
+ case 'mysqli':
+ case 'sqlite':
+ $this->options['portability'] =
+ DB_PORTABILITY_DELETE_COUNT;
+ break;
+ }
+ } else {
+ $this->options['portability'] = DB_PORTABILITY_NONE;
+ }
+ }
+
+ return DB_OK;
+ }
+ return $this->raiseError("unknown option $option");
+ }
+
+ // }}}
+ // {{{ getOption()
+
+ /**
+ * Returns the value of an option
+ *
+ * @param string $option the option name you're curious about
+ *
+ * @return mixed the option's value
+ */
+ function getOption($option)
+ {
+ if (isset($this->options[$option])) {
+ return $this->options[$option];
+ }
+ return $this->raiseError("unknown option $option");
+ }
+
+ // }}}
+ // {{{ prepare()
+
+ /**
+ * Prepares a query for multiple execution with execute()
+ *
+ * Creates a query that can be run multiple times. Each time it is run,
+ * the placeholders, if any, will be replaced by the contents of
+ * execute()'s $data argument.
+ *
+ * Three types of placeholders can be used:
+ * + <kbd>?</kbd> scalar value (i.e. strings, integers). The system
+ * will automatically quote and escape the data.
+ * + <kbd>!</kbd> value is inserted 'as is'
+ * + <kbd>&</kbd> requires a file name. The file's contents get
+ * inserted into the query (i.e. saving binary
+ * data in a db)
+ *
+ * Example 1.
+ * <code>
+ * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
+ * $data = array(
+ * "John's text",
+ * "'it''s good'",
+ * 'filename.txt'
+ * );
+ * $res = $db->execute($sth, $data);
+ * </code>
+ *
+ * Use backslashes to escape placeholder characters if you don't want
+ * them to be interpreted as placeholders:
+ * <pre>
+ * "UPDATE foo SET col=? WHERE col='over \& under'"
+ * </pre>
+ *
+ * With some database backends, this is emulated.
+ *
+ * {@internal ibase and oci8 have their own prepare() methods.}}
+ *
+ * @param string $query the query to be prepared
+ *
+ * @return mixed DB statement resource on success. A DB_Error object
+ * on failure.
+ *
+ * @see DB_common::execute()
+ */
+ function prepare($query)
+ {
+ $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+ PREG_SPLIT_DELIM_CAPTURE);
+ $token = 0;
+ $types = array();
+ $newtokens = array();
+
+ foreach ($tokens as $val) {
+ switch ($val) {
+ case '?':
+ $types[$token++] = DB_PARAM_SCALAR;
+ break;
+ case '&':
+ $types[$token++] = DB_PARAM_OPAQUE;
+ break;
+ case '!':
+ $types[$token++] = DB_PARAM_MISC;
+ break;
+ default:
+ $newtokens[] = preg_replace('/\\\([&?!])/', "\\1", $val);
+ }
+ }
+
+ $this->prepare_tokens[] = &$newtokens;
+ end($this->prepare_tokens);
+
+ $k = key($this->prepare_tokens);
+ $this->prepare_types[$k] = $types;
+ $this->prepared_queries[$k] = implode(' ', $newtokens);
+
+ return $k;
+ }
+
+ // }}}
+ // {{{ autoPrepare()
+
+ /**
+ * Automaticaly generates an insert or update query and pass it to prepare()
+ *
+ * @param string $table the table name
+ * @param array $table_fields the array of field names
+ * @param int $mode a type of query to make:
+ * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+ * @param string $where for update queries: the WHERE clause to
+ * append to the SQL statement. Don't
+ * include the "WHERE" keyword.
+ *
+ * @return resource the query handle
+ *
+ * @uses DB_common::prepare(), DB_common::buildManipSQL()
+ */
+ function autoPrepare($table, $table_fields, $mode = DB_AUTOQUERY_INSERT,
+ $where = false)
+ {
+ $query = $this->buildManipSQL($table, $table_fields, $mode, $where);
+ if (DB::isError($query)) {
+ return $query;
+ }
+ return $this->prepare($query);
+ }
+
+ // }}}
+ // {{{ autoExecute()
+
+ /**
+ * Automaticaly generates an insert or update query and call prepare()
+ * and execute() with it
+ *
+ * @param string $table the table name
+ * @param array $fields_values the associative array where $key is a
+ * field name and $value its value
+ * @param int $mode a type of query to make:
+ * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+ * @param string $where for update queries: the WHERE clause to
+ * append to the SQL statement. Don't
+ * include the "WHERE" keyword.
+ *
+ * @return mixed a new DB_result object for successful SELECT queries
+ * or DB_OK for successul data manipulation queries.
+ * A DB_Error object on failure.
+ *
+ * @uses DB_common::autoPrepare(), DB_common::execute()
+ */
+ function autoExecute($table, $fields_values, $mode = DB_AUTOQUERY_INSERT,
+ $where = false)
+ {
+ $sth = $this->autoPrepare($table, array_keys($fields_values), $mode,
+ $where);
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+ $ret = $this->execute($sth, array_values($fields_values));
+ $this->freePrepared($sth);
+ return $ret;
+
+ }
+
+ // }}}
+ // {{{ buildManipSQL()
+
+ /**
+ * Produces an SQL query string for autoPrepare()
+ *
+ * Example:
+ * <pre>
+ * buildManipSQL('table_sql', array('field1', 'field2', 'field3'),
+ * DB_AUTOQUERY_INSERT);
+ * </pre>
+ *
+ * That returns
+ * <samp>
+ * INSERT INTO table_sql (field1,field2,field3) VALUES (?,?,?)
+ * </samp>
+ *
+ * NOTES:
+ * - This belongs more to a SQL Builder class, but this is a simple
+ * facility.
+ * - Be carefull! If you don't give a $where param with an UPDATE
+ * query, all the records of the table will be updated!
+ *
+ * @param string $table the table name
+ * @param array $table_fields the array of field names
+ * @param int $mode a type of query to make:
+ * DB_AUTOQUERY_INSERT or DB_AUTOQUERY_UPDATE
+ * @param string $where for update queries: the WHERE clause to
+ * append to the SQL statement. Don't
+ * include the "WHERE" keyword.
+ *
+ * @return string the sql query for autoPrepare()
+ */
+ function buildManipSQL($table, $table_fields, $mode, $where = false)
+ {
+ if (count($table_fields) == 0) {
+ return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+ $first = true;
+ switch ($mode) {
+ case DB_AUTOQUERY_INSERT:
+ $values = '';
+ $names = '';
+ foreach ($table_fields as $value) {
+ if ($first) {
+ $first = false;
+ } else {
+ $names .= ',';
+ $values .= ',';
+ }
+ $names .= $value;
+ $values .= '?';
+ }
+ return "INSERT INTO $table ($names) VALUES ($values)";
+ case DB_AUTOQUERY_UPDATE:
+ $set = '';
+ foreach ($table_fields as $value) {
+ if ($first) {
+ $first = false;
+ } else {
+ $set .= ',';
+ }
+ $set .= "$value = ?";
+ }
+ $sql = "UPDATE $table SET $set";
+ if ($where) {
+ $sql .= " WHERE $where";
+ }
+ return $sql;
+ default:
+ return $this->raiseError(DB_ERROR_SYNTAX);
+ }
+ }
+
+ // }}}
+ // {{{ execute()
+
+ /**
+ * Executes a DB statement prepared with prepare()
+ *
+ * Example 1.
+ * <code>
+ * $sth = $db->prepare('INSERT INTO tbl (a, b, c) VALUES (?, !, &)');
+ * $data = array(
+ * "John's text",
+ * "'it''s good'",
+ * 'filename.txt'
+ * );
+ * $res = $db->execute($sth, $data);
+ * </code>
+ *
+ * @param resource $stmt a DB statement resource returned from prepare()
+ * @param mixed $data array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return mixed a new DB_result object for successful SELECT queries
+ * or DB_OK for successul data manipulation queries.
+ * A DB_Error object on failure.
+ *
+ * {@internal ibase and oci8 have their own execute() methods.}}
+ *
+ * @see DB_common::prepare()
+ */
+ function &execute($stmt, $data = array())
+ {
+ $realquery = $this->executeEmulateQuery($stmt, $data);
+ if (DB::isError($realquery)) {
+ return $realquery;
+ }
+ $result = $this->simpleQuery($realquery);
+
+ if ($result === DB_OK || DB::isError($result)) {
+ return $result;
+ } else {
+ $tmp = new DB_result($this, $result);
+ return $tmp;
+ }
+ }
+
+ // }}}
+ // {{{ executeEmulateQuery()
+
+ /**
+ * Emulates executing prepared statements if the DBMS not support them
+ *
+ * @param resource $stmt a DB statement resource returned from execute()
+ * @param mixed $data array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return mixed a string containing the real query run when emulating
+ * prepare/execute. A DB_Error object on failure.
+ *
+ * @access protected
+ * @see DB_common::execute()
+ */
+ function executeEmulateQuery($stmt, $data = array())
+ {
+ $stmt = (int)$stmt;
+ $data = (array)$data;
+ $this->last_parameters = $data;
+
+ if (count($this->prepare_types[$stmt]) != count($data)) {
+ $this->last_query = $this->prepared_queries[$stmt];
+ return $this->raiseError(DB_ERROR_MISMATCH);
+ }
+
+ $realquery = $this->prepare_tokens[$stmt][0];
+
+ $i = 0;
+ foreach ($data as $value) {
+ if ($this->prepare_types[$stmt][$i] == DB_PARAM_SCALAR) {
+ $realquery .= $this->quoteSmart($value);
+ } elseif ($this->prepare_types[$stmt][$i] == DB_PARAM_OPAQUE) {
+ $fp = @fopen($value, 'rb');
+ if (!$fp) {
+ return $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+ }
+ $realquery .= $this->quoteSmart(fread($fp, filesize($value)));
+ fclose($fp);
+ } else {
+ $realquery .= $value;
+ }
+
+ $realquery .= $this->prepare_tokens[$stmt][++$i];
+ }
+
+ return $realquery;
+ }
+
+ // }}}
+ // {{{ executeMultiple()
+
+ /**
+ * Performs several execute() calls on the same statement handle
+ *
+ * $data must be an array indexed numerically
+ * from 0, one execute call is done for every "row" in the array.
+ *
+ * If an error occurs during execute(), executeMultiple() does not
+ * execute the unfinished rows, but rather returns that error.
+ *
+ * @param resource $stmt query handle from prepare()
+ * @param array $data numeric array containing the
+ * data to insert into the query
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::prepare(), DB_common::execute()
+ */
+ function executeMultiple($stmt, $data)
+ {
+ foreach ($data as $value) {
+ $res = $this->execute($stmt, $value);
+ if (DB::isError($res)) {
+ return $res;
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freePrepared()
+
+ /**
+ * Frees the internal resources associated with a prepared query
+ *
+ * @param resource $stmt the prepared statement's PHP resource
+ * @param bool $free_resource should the PHP resource be freed too?
+ * Use false if you need to get data
+ * from the result set later.
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_common::prepare()
+ */
+ function freePrepared($stmt, $free_resource = true)
+ {
+ $stmt = (int)$stmt;
+ if (isset($this->prepare_tokens[$stmt])) {
+ unset($this->prepare_tokens[$stmt]);
+ unset($this->prepare_types[$stmt]);
+ unset($this->prepared_queries[$stmt]);
+ return true;
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ modifyQuery()
+
+ /**
+ * Changes a query string for various DBMS specific reasons
+ *
+ * It is defined here to ensure all drivers have this method available.
+ *
+ * @param string $query the query string to modify
+ *
+ * @return string the modified query string
+ *
+ * @access protected
+ * @see DB_mysql::modifyQuery(), DB_oci8::modifyQuery(),
+ * DB_sqlite::modifyQuery()
+ */
+ function modifyQuery($query)
+ {
+ return $query;
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * It is defined here to assure that all implementations
+ * have this method defined.
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ return $query;
+ }
+
+ // }}}
+ // {{{ query()
+
+ /**
+ * Sends a query to the database server
+ *
+ * The query string can be either a normal statement to be sent directly
+ * to the server OR if <var>$params</var> are passed the query can have
+ * placeholders and it will be passed through prepare() and execute().
+ *
+ * @param string $query the SQL query or the statement to prepare
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return mixed a new DB_result object for successful SELECT queries
+ * or DB_OK for successul data manipulation queries.
+ * A DB_Error object on failure.
+ *
+ * @see DB_result, DB_common::prepare(), DB_common::execute()
+ */
+ function &query($query, $params = array())
+ {
+ if (sizeof($params) > 0) {
+ $sth = $this->prepare($query);
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+ $ret = $this->execute($sth, $params);
+ $this->freePrepared($sth, false);
+ return $ret;
+ } else {
+ $this->last_parameters = array();
+ $result = $this->simpleQuery($query);
+ if ($result === DB_OK || DB::isError($result)) {
+ return $result;
+ } else {
+ $tmp = new DB_result($this, $result);
+ return $tmp;
+ }
+ }
+ }
+
+ // }}}
+ // {{{ limitQuery()
+
+ /**
+ * Generates and executes a LIMIT query
+ *
+ * @param string $query the query
+ * @param intr $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return mixed a new DB_result object for successful SELECT queries
+ * or DB_OK for successul data manipulation queries.
+ * A DB_Error object on failure.
+ */
+ function &limitQuery($query, $from, $count, $params = array())
+ {
+ $query = $this->modifyLimitQuery($query, $from, $count, $params);
+ if (DB::isError($query)){
+ return $query;
+ }
+ $result = $this->query($query, $params);
+ if (is_a($result, 'DB_result')) {
+ $result->setOption('limit_from', $from);
+ $result->setOption('limit_count', $count);
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ getOne()
+
+ /**
+ * Fetches the first column of the first row from a query result
+ *
+ * Takes care of doing the query and freeing the results when finished.
+ *
+ * @param string $query the SQL query
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return mixed the returned value of the query.
+ * A DB_Error object on failure.
+ */
+ function &getOne($query, $params = array())
+ {
+ $params = (array)$params;
+ // modifyLimitQuery() would be nice here, but it causes BC issues
+ if (sizeof($params) > 0) {
+ $sth = $this->prepare($query);
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+ $res = $this->execute($sth, $params);
+ $this->freePrepared($sth);
+ } else {
+ $res = $this->query($query);
+ }
+
+ if (DB::isError($res)) {
+ return $res;
+ }
+
+ $err = $res->fetchInto($row, DB_FETCHMODE_ORDERED);
+ $res->free();
+
+ if ($err !== DB_OK) {
+ return $err;
+ }
+
+ return $row[0];
+ }
+
+ // }}}
+ // {{{ getRow()
+
+ /**
+ * Fetches the first row of data returned from a query result
+ *
+ * Takes care of doing the query and freeing the results when finished.
+ *
+ * @param string $query the SQL query
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ * @param int $fetchmode the fetch mode to use
+ *
+ * @return array the first row of results as an array.
+ * A DB_Error object on failure.
+ */
+ function &getRow($query, $params = array(),
+ $fetchmode = DB_FETCHMODE_DEFAULT)
+ {
+ // compat check, the params and fetchmode parameters used to
+ // have the opposite order
+ if (!is_array($params)) {
+ if (is_array($fetchmode)) {
+ if ($params === null) {
+ $tmp = DB_FETCHMODE_DEFAULT;
+ } else {
+ $tmp = $params;
+ }
+ $params = $fetchmode;
+ $fetchmode = $tmp;
+ } elseif ($params !== null) {
+ $fetchmode = $params;
+ $params = array();
+ }
+ }
+ // modifyLimitQuery() would be nice here, but it causes BC issues
+ if (sizeof($params) > 0) {
+ $sth = $this->prepare($query);
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+ $res = $this->execute($sth, $params);
+ $this->freePrepared($sth);
+ } else {
+ $res = $this->query($query);
+ }
+
+ if (DB::isError($res)) {
+ return $res;
+ }
+
+ $err = $res->fetchInto($row, $fetchmode);
+
+ $res->free();
+
+ if ($err !== DB_OK) {
+ return $err;
+ }
+
+ return $row;
+ }
+
+ // }}}
+ // {{{ getCol()
+
+ /**
+ * Fetches a single column from a query result and returns it as an
+ * indexed array
+ *
+ * @param string $query the SQL query
+ * @param mixed $col which column to return (integer [column number,
+ * starting at 0] or string [column name])
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return array the results as an array. A DB_Error object on failure.
+ *
+ * @see DB_common::query()
+ */
+ function &getCol($query, $col = 0, $params = array())
+ {
+ $params = (array)$params;
+ if (sizeof($params) > 0) {
+ $sth = $this->prepare($query);
+
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+
+ $res = $this->execute($sth, $params);
+ $this->freePrepared($sth);
+ } else {
+ $res = $this->query($query);
+ }
+
+ if (DB::isError($res)) {
+ return $res;
+ }
+
+ $fetchmode = is_int($col) ? DB_FETCHMODE_ORDERED : DB_FETCHMODE_ASSOC;
+
+ if (!is_array($row = $res->fetchRow($fetchmode))) {
+ $ret = array();
+ } else {
+ if (!array_key_exists($col, $row)) {
+ $ret = $this->raiseError(DB_ERROR_NOSUCHFIELD);
+ } else {
+ $ret = array($row[$col]);
+ while (is_array($row = $res->fetchRow($fetchmode))) {
+ $ret[] = $row[$col];
+ }
+ }
+ }
+
+ $res->free();
+
+ if (DB::isError($row)) {
+ $ret = $row;
+ }
+
+ return $ret;
+ }
+
+ // }}}
+ // {{{ getAssoc()
+
+ /**
+ * Fetches an entire query result and returns it as an
+ * associative array using the first column as the key
+ *
+ * If the result set contains more than two columns, the value
+ * will be an array of the values from column 2-n. If the result
+ * set contains only two columns, the returned value will be a
+ * scalar with the value of the second column (unless forced to an
+ * array with the $force_array parameter). A DB error code is
+ * returned on errors. If the result set contains fewer than two
+ * columns, a DB_ERROR_TRUNCATED error is returned.
+ *
+ * For example, if the table "mytable" contains:
+ *
+ * <pre>
+ * ID TEXT DATE
+ * --------------------------------
+ * 1 'one' 944679408
+ * 2 'two' 944679408
+ * 3 'three' 944679408
+ * </pre>
+ *
+ * Then the call getAssoc('SELECT id,text FROM mytable') returns:
+ * <pre>
+ * array(
+ * '1' => 'one',
+ * '2' => 'two',
+ * '3' => 'three',
+ * )
+ * </pre>
+ *
+ * ...while the call getAssoc('SELECT id,text,date FROM mytable') returns:
+ * <pre>
+ * array(
+ * '1' => array('one', '944679408'),
+ * '2' => array('two', '944679408'),
+ * '3' => array('three', '944679408')
+ * )
+ * </pre>
+ *
+ * If the more than one row occurs with the same value in the
+ * first column, the last row overwrites all previous ones by
+ * default. Use the $group parameter if you don't want to
+ * overwrite like this. Example:
+ *
+ * <pre>
+ * getAssoc('SELECT category,id,name FROM mytable', false, null,
+ * DB_FETCHMODE_ASSOC, true) returns:
+ *
+ * array(
+ * '1' => array(array('id' => '4', 'name' => 'number four'),
+ * array('id' => '6', 'name' => 'number six')
+ * ),
+ * '9' => array(array('id' => '4', 'name' => 'number four'),
+ * array('id' => '6', 'name' => 'number six')
+ * )
+ * )
+ * </pre>
+ *
+ * Keep in mind that database functions in PHP usually return string
+ * values for results regardless of the database's internal type.
+ *
+ * @param string $query the SQL query
+ * @param bool $force_array used only when the query returns
+ * exactly two columns. If true, the values
+ * of the returned array will be one-element
+ * arrays instead of scalars.
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of
+ * items passed must match quantity of
+ * placeholders in query: meaning 1
+ * placeholder for non-array parameters or
+ * 1 placeholder per array element.
+ * @param int $fetchmode the fetch mode to use
+ * @param bool $group if true, the values of the returned array
+ * is wrapped in another array. If the same
+ * key value (in the first column) repeats
+ * itself, the values will be appended to
+ * this array instead of overwriting the
+ * existing values.
+ *
+ * @return array the associative array containing the query results.
+ * A DB_Error object on failure.
+ */
+ function &getAssoc($query, $force_array = false, $params = array(),
+ $fetchmode = DB_FETCHMODE_DEFAULT, $group = false)
+ {
+ $params = (array)$params;
+ if (sizeof($params) > 0) {
+ $sth = $this->prepare($query);
+
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+
+ $res = $this->execute($sth, $params);
+ $this->freePrepared($sth);
+ } else {
+ $res = $this->query($query);
+ }
+
+ if (DB::isError($res)) {
+ return $res;
+ }
+ if ($fetchmode == DB_FETCHMODE_DEFAULT) {
+ $fetchmode = $this->fetchmode;
+ }
+ $cols = $res->numCols();
+
+ if ($cols < 2) {
+ $tmp = $this->raiseError(DB_ERROR_TRUNCATED);
+ return $tmp;
+ }
+
+ $results = array();
+
+ if ($cols > 2 || $force_array) {
+ // return array values
+ // XXX this part can be optimized
+ if ($fetchmode == DB_FETCHMODE_ASSOC) {
+ while (is_array($row = $res->fetchRow(DB_FETCHMODE_ASSOC))) {
+ reset($row);
+ $key = current($row);
+ unset($row[key($row)]);
+ if ($group) {
+ $results[$key][] = $row;
+ } else {
+ $results[$key] = $row;
+ }
+ }
+ } elseif ($fetchmode == DB_FETCHMODE_OBJECT) {
+ while ($row = $res->fetchRow(DB_FETCHMODE_OBJECT)) {
+ $arr = get_object_vars($row);
+ $key = current($arr);
+ if ($group) {
+ $results[$key][] = $row;
+ } else {
+ $results[$key] = $row;
+ }
+ }
+ } else {
+ while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
+ // we shift away the first element to get
+ // indices running from 0 again
+ $key = array_shift($row);
+ if ($group) {
+ $results[$key][] = $row;
+ } else {
+ $results[$key] = $row;
+ }
+ }
+ }
+ if (DB::isError($row)) {
+ $results = $row;
+ }
+ } else {
+ // return scalar values
+ while (is_array($row = $res->fetchRow(DB_FETCHMODE_ORDERED))) {
+ if ($group) {
+ $results[$row[0]][] = $row[1];
+ } else {
+ $results[$row[0]] = $row[1];
+ }
+ }
+ if (DB::isError($row)) {
+ $results = $row;
+ }
+ }
+
+ $res->free();
+
+ return $results;
+ }
+
+ // }}}
+ // {{{ getAll()
+
+ /**
+ * Fetches all of the rows from a query result
+ *
+ * @param string $query the SQL query
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of
+ * items passed must match quantity of
+ * placeholders in query: meaning 1
+ * placeholder for non-array parameters or
+ * 1 placeholder per array element.
+ * @param int $fetchmode the fetch mode to use:
+ * + DB_FETCHMODE_ORDERED
+ * + DB_FETCHMODE_ASSOC
+ * + DB_FETCHMODE_ORDERED | DB_FETCHMODE_FLIPPED
+ * + DB_FETCHMODE_ASSOC | DB_FETCHMODE_FLIPPED
+ *
+ * @return array the nested array. A DB_Error object on failure.
+ */
+ function &getAll($query, $params = array(),
+ $fetchmode = DB_FETCHMODE_DEFAULT)
+ {
+ // compat check, the params and fetchmode parameters used to
+ // have the opposite order
+ if (!is_array($params)) {
+ if (is_array($fetchmode)) {
+ if ($params === null) {
+ $tmp = DB_FETCHMODE_DEFAULT;
+ } else {
+ $tmp = $params;
+ }
+ $params = $fetchmode;
+ $fetchmode = $tmp;
+ } elseif ($params !== null) {
+ $fetchmode = $params;
+ $params = array();
+ }
+ }
+
+ if (sizeof($params) > 0) {
+ $sth = $this->prepare($query);
+
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+
+ $res = $this->execute($sth, $params);
+ $this->freePrepared($sth);
+ } else {
+ $res = $this->query($query);
+ }
+
+ if ($res === DB_OK || DB::isError($res)) {
+ return $res;
+ }
+
+ $results = array();
+ while (DB_OK === $res->fetchInto($row, $fetchmode)) {
+ if ($fetchmode & DB_FETCHMODE_FLIPPED) {
+ foreach ($row as $key => $val) {
+ $results[$key][] = $val;
+ }
+ } else {
+ $results[] = $row;
+ }
+ }
+
+ $res->free();
+
+ if (DB::isError($row)) {
+ $tmp = $this->raiseError($row);
+ return $tmp;
+ }
+ return $results;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Determines the number of rows in a query result
+ *
+ * @param resource $result the query result idenifier produced by PHP
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function numRows($result)
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ getSequenceName()
+
+ /**
+ * Generates the name used inside the database for a sequence
+ *
+ * The createSequence() docblock contains notes about storing sequence
+ * names.
+ *
+ * @param string $sqn the sequence's public name
+ *
+ * @return string the sequence's name in the backend
+ *
+ * @access protected
+ * @see DB_common::createSequence(), DB_common::dropSequence(),
+ * DB_common::nextID(), DB_common::setOption()
+ */
+ function getSequenceName($sqn)
+ {
+ return sprintf($this->getOption('seqname_format'),
+ preg_replace('/[^a-z0-9_.]/i', '_', $sqn));
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::dropSequence(),
+ * DB_common::getSequenceName()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ createSequence()
+
+ /**
+ * Creates a new sequence
+ *
+ * The name of a given sequence is determined by passing the string
+ * provided in the <var>$seq_name</var> argument through PHP's sprintf()
+ * function using the value from the <var>seqname_format</var> option as
+ * the sprintf()'s format argument.
+ *
+ * <var>seqname_format</var> is set via setOption().
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_common::nextID()
+ */
+ function createSequence($seq_name)
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_common::nextID()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ raiseError()
+
+ /**
+ * Communicates an error and invoke error callbacks, etc
+ *
+ * Basically a wrapper for PEAR::raiseError without the message string.
+ *
+ * @param mixed integer error code, or a PEAR error object (all
+ * other parameters are ignored if this parameter is
+ * an object
+ * @param int error mode, see PEAR_Error docs
+ * @param mixed if error mode is PEAR_ERROR_TRIGGER, this is the
+ * error level (E_USER_NOTICE etc). If error mode is
+ * PEAR_ERROR_CALLBACK, this is the callback function,
+ * either as a function name, or as an array of an
+ * object and method name. For other error modes this
+ * parameter is ignored.
+ * @param string extra debug information. Defaults to the last
+ * query and native error code.
+ * @param mixed native error code, integer or string depending the
+ * backend
+ * @param mixed dummy parameter for E_STRICT compatibility with
+ * PEAR::raiseError
+ * @param mixed dummy parameter for E_STRICT compatibility with
+ * PEAR::raiseError
+ *
+ * @return object the PEAR_Error object
+ *
+ * @see PEAR_Error
+ */
+ function &raiseError($code = DB_ERROR, $mode = null, $options = null,
+ $userinfo = null, $nativecode = null, $dummy1 = null,
+ $dummy2 = null)
+ {
+ // The error is yet a DB error object
+ if (is_object($code)) {
+ // because we the static PEAR::raiseError, our global
+ // handler should be used if it is set
+ if ($mode === null && !empty($this->_default_error_mode)) {
+ $mode = $this->_default_error_mode;
+ $options = $this->_default_error_options;
+ }
+ $tmp = PEAR::raiseError($code, null, $mode, $options,
+ null, null, true);
+ return $tmp;
+ }
+
+ if ($userinfo === null) {
+ $userinfo = $this->last_query;
+ }
+
+ if ($nativecode) {
+ $userinfo .= ' [nativecode=' . trim($nativecode) . ']';
+ } else {
+ $userinfo .= ' [DB Error: ' . DB::errorMessage($code) . ']';
+ }
+
+ $tmp = PEAR::raiseError(null, $code, $mode, $options, $userinfo,
+ 'DB_Error', true);
+ return $tmp;
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return mixed the DBMS' error code. A DB_Error object on failure.
+ */
+ function errorNative()
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Maps native error codes to DB's portable ones
+ *
+ * Uses the <var>$errorcode_map</var> property defined in each driver.
+ *
+ * @param string|int $nativecode the error code returned by the DBMS
+ *
+ * @return int the portable DB error code. Return DB_ERROR if the
+ * current driver doesn't have a mapping for the
+ * $nativecode submitted.
+ */
+ function errorCode($nativecode)
+ {
+ if (isset($this->errorcode_map[$nativecode])) {
+ return $this->errorcode_map[$nativecode];
+ }
+ // Fall back to DB_ERROR if there was no mapping.
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ errorMessage()
+
+ /**
+ * Maps a DB error code to a textual message
+ *
+ * @param integer $dbcode the DB error code
+ *
+ * @return string the error message corresponding to the error code
+ * submitted. FALSE if the error code is unknown.
+ *
+ * @see DB::errorMessage()
+ */
+ function errorMessage($dbcode)
+ {
+ return DB::errorMessage($this->errorcode_map[$dbcode]);
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * The format of the resulting array depends on which <var>$mode</var>
+ * you select. The sample output below is based on this query:
+ * <pre>
+ * SELECT tblFoo.fldID, tblFoo.fldPhone, tblBar.fldId
+ * FROM tblFoo
+ * JOIN tblBar ON tblFoo.fldId = tblBar.fldId
+ * </pre>
+ *
+ * <ul>
+ * <li>
+ *
+ * <kbd>null</kbd> (default)
+ * <pre>
+ * [0] => Array (
+ * [table] => tblFoo
+ * [name] => fldId
+ * [type] => int
+ * [len] => 11
+ * [flags] => primary_key not_null
+ * )
+ * [1] => Array (
+ * [table] => tblFoo
+ * [name] => fldPhone
+ * [type] => string
+ * [len] => 20
+ * [flags] =>
+ * )
+ * [2] => Array (
+ * [table] => tblBar
+ * [name] => fldId
+ * [type] => int
+ * [len] => 11
+ * [flags] => primary_key not_null
+ * )
+ * </pre>
+ *
+ * </li><li>
+ *
+ * <kbd>DB_TABLEINFO_ORDER</kbd>
+ *
+ * <p>In addition to the information found in the default output,
+ * a notation of the number of columns is provided by the
+ * <samp>num_fields</samp> element while the <samp>order</samp>
+ * element provides an array with the column names as the keys and
+ * their location index number (corresponding to the keys in the
+ * the default output) as the values.</p>
+ *
+ * <p>If a result set has identical field names, the last one is
+ * used.</p>
+ *
+ * <pre>
+ * [num_fields] => 3
+ * [order] => Array (
+ * [fldId] => 2
+ * [fldTrans] => 1
+ * )
+ * </pre>
+ *
+ * </li><li>
+ *
+ * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>
+ *
+ * <p>Similar to <kbd>DB_TABLEINFO_ORDER</kbd> but adds more
+ * dimensions to the array in which the table names are keys and
+ * the field names are sub-keys. This is helpful for queries that
+ * join tables which have identical field names.</p>
+ *
+ * <pre>
+ * [num_fields] => 3
+ * [ordertable] => Array (
+ * [tblFoo] => Array (
+ * [fldId] => 0
+ * [fldPhone] => 1
+ * )
+ * [tblBar] => Array (
+ * [fldId] => 2
+ * )
+ * )
+ * </pre>
+ *
+ * </li>
+ * </ul>
+ *
+ * The <samp>flags</samp> element contains a space separated list
+ * of extra information about the field. This data is inconsistent
+ * between DBMS's due to the way each DBMS works.
+ * + <samp>primary_key</samp>
+ * + <samp>unique_key</samp>
+ * + <samp>multiple_key</samp>
+ * + <samp>not_null</samp>
+ *
+ * Most DBMS's only provide the <samp>table</samp> and <samp>flags</samp>
+ * elements if <var>$result</var> is a table name. The following DBMS's
+ * provide full information from queries:
+ * + fbsql
+ * + mysql
+ *
+ * If the 'portability' option has <samp>DB_PORTABILITY_LOWERCASE</samp>
+ * turned on, the names of tables and fields will be lowercased.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode either unused or one of the tableInfo modes:
+ * <kbd>DB_TABLEINFO_ORDERTABLE</kbd>,
+ * <kbd>DB_TABLEINFO_ORDER</kbd> or
+ * <kbd>DB_TABLEINFO_FULL</kbd> (which does both).
+ * These are bitwise, so the first two can be
+ * combined using <kbd>|</kbd>.
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::setOption()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ /*
+ * If the DB_<driver> class has a tableInfo() method, that one
+ * overrides this one. But, if the driver doesn't have one,
+ * this method runs and tells users about that fact.
+ */
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ getTables()
+
+ /**
+ * Lists the tables in the current database
+ *
+ * @return array the list of tables. A DB_Error object on failure.
+ *
+ * @deprecated Method deprecated some time before Release 1.2
+ */
+ function getTables()
+ {
+ return $this->getListOf('tables');
+ }
+
+ // }}}
+ // {{{ getListOf()
+
+ /**
+ * Lists internal database information
+ *
+ * @param string $type type of information being sought.
+ * Common items being sought are:
+ * tables, databases, users, views, functions
+ * Each DBMS's has its own capabilities.
+ *
+ * @return array an array listing the items sought.
+ * A DB DB_Error object on failure.
+ */
+ function getListOf($type)
+ {
+ $sql = $this->getSpecialQuery($type);
+ if ($sql === null) {
+ $this->last_query = '';
+ return $this->raiseError(DB_ERROR_UNSUPPORTED);
+ } elseif (is_int($sql) || DB::isError($sql)) {
+ // Previous error
+ return $this->raiseError($sql);
+ } elseif (is_array($sql)) {
+ // Already the result
+ return $sql;
+ }
+ // Launch this query
+ return $this->getCol($sql);
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ return $this->raiseError(DB_ERROR_UNSUPPORTED);
+ }
+
+ // }}}
+ // {{{ nextQueryIsManip()
+
+ /**
+ * Sets (or unsets) a flag indicating that the next query will be a
+ * manipulation query, regardless of the usual DB::isManip() heuristics.
+ *
+ * @param boolean true to set the flag overriding the isManip() behaviour,
+ * false to clear it and fall back onto isManip()
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function nextQueryIsManip($manip)
+ {
+ $this->_next_query_manip = $manip;
+ }
+
+ // }}}
+ // {{{ _checkManip()
+
+ /**
+ * Checks if the given query is a manipulation query. This also takes into
+ * account the _next_query_manip flag and sets the _last_query_manip flag
+ * (and resets _next_query_manip) according to the result.
+ *
+ * @param string The query to check.
+ *
+ * @return boolean true if the query is a manipulation query, false
+ * otherwise
+ *
+ * @access protected
+ */
+ function _checkManip($query)
+ {
+ if ($this->_next_query_manip || DB::isManip($query)) {
+ $this->_last_query_manip = true;
+ } else {
+ $this->_last_query_manip = false;
+ }
+ $this->_next_query_manip = false;
+ return $this->_last_query_manip;
+ $manip = $this->_next_query_manip;
+ }
+
+ // }}}
+ // {{{ _rtrimArrayValues()
+
+ /**
+ * Right-trims all strings in an array
+ *
+ * @param array $array the array to be trimmed (passed by reference)
+ *
+ * @return void
+ *
+ * @access protected
+ */
+ function _rtrimArrayValues(&$array)
+ {
+ foreach ($array as $key => $value) {
+ if (is_string($value)) {
+ $array[$key] = rtrim($value);
+ }
+ }
+ }
+
+ // }}}
+ // {{{ _convertNullArrayValuesToEmpty()
+
+ /**
+ * Converts all null values in an array to empty strings
+ *
+ * @param array $array the array to be de-nullified (passed by reference)
+ *
+ * @return void
+ *
+ * @access protected
+ */
+ function _convertNullArrayValuesToEmpty(&$array)
+ {
+ foreach ($array as $key => $value) {
+ if (is_null($value)) {
+ $array[$key] = '';
+ }
+ }
+ }
+
+ // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/dbase.php b/extlib/DB/dbase.php
new file mode 100644
index 000000000..67afc897d
--- /dev/null
+++ b/extlib/DB/dbase.php
@@ -0,0 +1,510 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's dbase extension
+ * for interacting with dBase databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: dbase.php,v 1.45 2007/09/21 13:40:41 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's dbase extension
+ * for interacting with dBase databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category Database
+ * @package DB
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_dbase extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'dbase';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'dbase';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => false,
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => false,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => false,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * A means of emulating result resources
+ * @var array
+ */
+ var $res_row = array();
+
+ /**
+ * The quantity of results so far
+ *
+ * For emulating result resources.
+ *
+ * @var integer
+ */
+ var $result = 0;
+
+ /**
+ * Maps dbase data type id's to human readable strings
+ *
+ * The human readable values are based on the output of PHP's
+ * dbase_get_header_info() function.
+ *
+ * @var array
+ * @since Property available since Release 1.7.0
+ */
+ var $types = array(
+ 'C' => 'character',
+ 'D' => 'date',
+ 'L' => 'boolean',
+ 'M' => 'memo',
+ 'N' => 'number',
+ );
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_dbase()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database and create it if it doesn't exist
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's dbase driver supports the following extra DSN options:
+ * + mode An integer specifying the read/write mode to use
+ * (0 = read only, 1 = write only, 2 = read/write).
+ * Available since PEAR DB 1.7.0.
+ * + fields An array of arrays that PHP's dbase_create() function needs
+ * to create a new database. This information is used if the
+ * dBase file specified in the "database" segment of the DSN
+ * does not exist. For more info, see the PHP manual's
+ * {@link http://php.net/dbase_create dbase_create()} page.
+ * Available since PEAR DB 1.7.0.
+ *
+ * Example of how to connect and establish a new dBase file if necessary:
+ * <code>
+ * require_once 'DB.php';
+ *
+ * $dsn = array(
+ * 'phptype' => 'dbase',
+ * 'database' => '/path/and/name/of/dbase/file',
+ * 'mode' => 2,
+ * 'fields' => array(
+ * array('a', 'N', 5, 0),
+ * array('b', 'C', 40),
+ * array('c', 'C', 255),
+ * array('d', 'C', 20),
+ * ),
+ * );
+ * $options = array(
+ * 'debug' => 2,
+ * 'portability' => DB_PORTABILITY_ALL,
+ * );
+ *
+ * $db = DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('dbase')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ /*
+ * Turn track_errors on for entire script since $php_errormsg
+ * is the only way to find errors from the dbase extension.
+ */
+ @ini_set('track_errors', 1);
+ $php_errormsg = '';
+
+ if (!file_exists($dsn['database'])) {
+ $this->dsn['mode'] = 2;
+ if (empty($dsn['fields']) || !is_array($dsn['fields'])) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ 'the dbase file does not exist and '
+ . 'it could not be created because '
+ . 'the "fields" element of the DSN '
+ . 'is not properly set');
+ }
+ $this->connection = @dbase_create($dsn['database'],
+ $dsn['fields']);
+ if (!$this->connection) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ 'the dbase file does not exist and '
+ . 'the attempt to create it failed: '
+ . $php_errormsg);
+ }
+ } else {
+ if (!isset($this->dsn['mode'])) {
+ $this->dsn['mode'] = 0;
+ }
+ $this->connection = @dbase_open($dsn['database'],
+ $this->dsn['mode']);
+ if (!$this->connection) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @dbase_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ &query()
+
+ function &query($query = null)
+ {
+ // emulate result resources
+ $this->res_row[(int)$this->result] = 0;
+ $tmp = new DB_result($this, $this->result++);
+ return $tmp;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum === null) {
+ $rownum = $this->res_row[(int)$result]++;
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @dbase_get_record_with_names($this->connection, $rownum);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @dbase_get_record($this->connection, $rownum);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set.
+ *
+ * This method is a no-op for dbase, as there aren't result resources in
+ * the same sense as most other database backends.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return true;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($foo)
+ {
+ return @dbase_numfields($this->connection);
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($foo)
+ {
+ return @dbase_numrecords($this->connection);
+ }
+
+ // }}}
+ // {{{ quoteBoolean()
+
+ /**
+ * Formats a boolean value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param boolean the boolean value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteBoolean($boolean) {
+ return $boolean ? 'T' : 'F';
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about the current database
+ *
+ * @param mixed $result THIS IS UNUSED IN DBASE. The current database
+ * is examined regardless of what is provided here.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ * @since Method available since Release 1.7.0
+ */
+ function tableInfo($result = null, $mode = null)
+ {
+ if (function_exists('dbase_get_header_info')) {
+ $id = @dbase_get_header_info($this->connection);
+ if (!$id && $php_errormsg) {
+ return $this->raiseError(DB_ERROR,
+ null, null, null,
+ $php_errormsg);
+ }
+ } else {
+ /*
+ * This segment for PHP 4 is loosely based on code by
+ * Hadi Rusiah <deegos@yahoo.com> in the comments on
+ * the dBase reference page in the PHP manual.
+ */
+ $db = @fopen($this->dsn['database'], 'r');
+ if (!$db) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+
+ $id = array();
+ $i = 0;
+
+ $line = fread($db, 32);
+ while (!feof($db)) {
+ $line = fread($db, 32);
+ if (substr($line, 0, 1) == chr(13)) {
+ break;
+ } else {
+ $pos = strpos(substr($line, 0, 10), chr(0));
+ $pos = ($pos == 0 ? 10 : $pos);
+ $id[$i] = array(
+ 'name' => substr($line, 0, $pos),
+ 'type' => $this->types[substr($line, 11, 1)],
+ 'length' => ord(substr($line, 16, 1)),
+ 'precision' => ord(substr($line, 17, 1)),
+ );
+ }
+ $i++;
+ }
+
+ fclose($db);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $res = array();
+ $count = count($id);
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $res[$i] = array(
+ 'table' => $this->dsn['database'],
+ 'name' => $case_func($id[$i]['name']),
+ 'type' => $id[$i]['type'],
+ 'len' => $id[$i]['length'],
+ 'flags' => ''
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ return $res;
+ }
+
+ // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/fbsql.php b/extlib/DB/fbsql.php
new file mode 100644
index 000000000..4de4078f7
--- /dev/null
+++ b/extlib/DB/fbsql.php
@@ -0,0 +1,769 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's fbsql extension
+ * for interacting with FrontBase databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Frank M. Kromann <frank@frontbase.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: fbsql.php,v 1.88 2007/07/06 05:19:21 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's fbsql extension
+ * for interacting with FrontBase databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category Database
+ * @package DB
+ * @author Frank M. Kromann <frank@frontbase.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ * @since Class functional since Release 1.7.0
+ */
+class DB_fbsql extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'fbsql';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'fbsql';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'alter',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ 22 => DB_ERROR_SYNTAX,
+ 85 => DB_ERROR_ALREADY_EXISTS,
+ 108 => DB_ERROR_SYNTAX,
+ 116 => DB_ERROR_NOSUCHTABLE,
+ 124 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 215 => DB_ERROR_NOSUCHFIELD,
+ 217 => DB_ERROR_INVALID_NUMBER,
+ 226 => DB_ERROR_NOSUCHFIELD,
+ 231 => DB_ERROR_INVALID,
+ 239 => DB_ERROR_TRUNCATED,
+ 251 => DB_ERROR_SYNTAX,
+ 266 => DB_ERROR_NOT_FOUND,
+ 357 => DB_ERROR_CONSTRAINT_NOT_NULL,
+ 358 => DB_ERROR_CONSTRAINT,
+ 360 => DB_ERROR_CONSTRAINT,
+ 361 => DB_ERROR_CONSTRAINT,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_fbsql()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('fbsql')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $params = array(
+ $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
+ $dsn['username'] ? $dsn['username'] : null,
+ $dsn['password'] ? $dsn['password'] : null,
+ );
+
+ $connect_function = $persistent ? 'fbsql_pconnect' : 'fbsql_connect';
+
+ $ini = ini_get('track_errors');
+ $php_errormsg = '';
+ if ($ini) {
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ } else {
+ @ini_set('track_errors', 1);
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ @ini_set('track_errors', $ini);
+ }
+
+ if (!$this->connection) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+
+ if ($dsn['database']) {
+ if (!@fbsql_select_db($dsn['database'], $this->connection)) {
+ return $this->fbsqlRaiseError();
+ }
+ }
+
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @fbsql_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ $result = @fbsql_query("$query;", $this->connection);
+ if (!$result) {
+ return $this->fbsqlRaiseError();
+ }
+ // Determine which queries that should return data, and which
+ // should return an error code only.
+ if ($this->_checkManip($query)) {
+ return DB_OK;
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal fbsql result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return @fbsql_next_result($result);
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@fbsql_data_seek($result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @fbsql_fetch_array($result, FBSQL_ASSOC);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @fbsql_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? fbsql_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff=false)
+ {
+ if ($onoff) {
+ $this->query("SET COMMIT TRUE");
+ } else {
+ $this->query("SET COMMIT FALSE");
+ }
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ @fbsql_commit($this->connection);
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ @fbsql_rollback($this->connection);
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @fbsql_num_fields($result);
+ if (!$cols) {
+ return $this->fbsqlRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @fbsql_num_rows($result);
+ if ($rows === null) {
+ return $this->fbsqlRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->_last_query_manip) {
+ $result = @fbsql_affected_rows($this->connection);
+ } else {
+ $result = 0;
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_fbsql::createSequence(), DB_fbsql::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ do {
+ $repeat = 0;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query('SELECT UNIQUE FROM ' . $seqname);
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+ $repeat = 1;
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $result;
+ }
+ } else {
+ $repeat = 0;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->fbsqlRaiseError();
+ }
+ $result->fetchInto($tmp, DB_FETCHMODE_ORDERED);
+ return $tmp[0];
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_fbsql::nextID(), DB_fbsql::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $res = $this->query('CREATE TABLE ' . $seqname
+ . ' (id INTEGER NOT NULL,'
+ . ' PRIMARY KEY(id))');
+ if ($res) {
+ $res = $this->query('SET UNIQUE = 0 FOR ' . $seqname);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_fbsql::nextID(), DB_fbsql::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name)
+ . ' RESTRICT');
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ if (DB::isManip($query) || $this->_next_query_manip) {
+ return preg_replace('/^([\s(])*SELECT/i',
+ "\\1SELECT TOP($count)", $query);
+ } else {
+ return preg_replace('/([\s(])*SELECT/i',
+ "\\1SELECT TOP($from, $count)", $query);
+ }
+ }
+
+ // }}}
+ // {{{ quoteBoolean()
+
+ /**
+ * Formats a boolean value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param boolean the boolean value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteBoolean($boolean) {
+ return $boolean ? 'TRUE' : 'FALSE';
+ }
+
+ // }}}
+ // {{{ quoteFloat()
+
+ /**
+ * Formats a float value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param float the float value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteFloat($float) {
+ return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
+ }
+
+ // }}}
+ // {{{ fbsqlRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_fbsql::errorNative(), DB_common::errorCode()
+ */
+ function fbsqlRaiseError($errno = null)
+ {
+ if ($errno === null) {
+ $errno = $this->errorCode(fbsql_errno($this->connection));
+ }
+ return $this->raiseError($errno, null, null, null,
+ @fbsql_error($this->connection));
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return int the DBMS' error code
+ */
+ function errorNative()
+ {
+ return @fbsql_errno($this->connection);
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @fbsql_list_fields($this->dsn['database'],
+ $result, $this->connection);
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->fbsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @fbsql_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $res[$i] = array(
+ 'table' => $case_func(@fbsql_field_table($id, $i)),
+ 'name' => $case_func(@fbsql_field_name($id, $i)),
+ 'type' => @fbsql_field_type($id, $i),
+ 'len' => @fbsql_field_len($id, $i),
+ 'flags' => @fbsql_field_flags($id, $i),
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @fbsql_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SELECT "table_name" FROM information_schema.tables'
+ . ' t0, information_schema.schemata t1'
+ . ' WHERE t0.schema_pk=t1.schema_pk AND'
+ . ' "table_type" = \'BASE TABLE\''
+ . ' AND "schema_name" = current_schema';
+ case 'views':
+ return 'SELECT "table_name" FROM information_schema.tables'
+ . ' t0, information_schema.schemata t1'
+ . ' WHERE t0.schema_pk=t1.schema_pk AND'
+ . ' "table_type" = \'VIEW\''
+ . ' AND "schema_name" = current_schema';
+ case 'users':
+ return 'SELECT "user_name" from information_schema.users';
+ case 'functions':
+ return 'SELECT "routine_name" FROM'
+ . ' information_schema.psm_routines'
+ . ' t0, information_schema.schemata t1'
+ . ' WHERE t0.schema_pk=t1.schema_pk'
+ . ' AND "routine_kind"=\'FUNCTION\''
+ . ' AND "schema_name" = current_schema';
+ case 'procedures':
+ return 'SELECT "routine_name" FROM'
+ . ' information_schema.psm_routines'
+ . ' t0, information_schema.schemata t1'
+ . ' WHERE t0.schema_pk=t1.schema_pk'
+ . ' AND "routine_kind"=\'PROCEDURE\''
+ . ' AND "schema_name" = current_schema';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/ibase.php b/extlib/DB/ibase.php
new file mode 100644
index 000000000..ee19c5589
--- /dev/null
+++ b/extlib/DB/ibase.php
@@ -0,0 +1,1082 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's interbase extension
+ * for interacting with Interbase and Firebird databases
+ *
+ * While this class works with PHP 4, PHP's InterBase extension is
+ * unstable in PHP 4. Use PHP 5.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: ibase.php,v 1.116 2007/09/21 13:40:41 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's interbase extension
+ * for interacting with Interbase and Firebird databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * While this class works with PHP 4, PHP's InterBase extension is
+ * unstable in PHP 4. Use PHP 5.
+ *
+ * NOTICE: limitQuery() only works for Firebird.
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ * @since Class became stable in Release 1.7.0
+ */
+class DB_ibase extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'ibase';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'ibase';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * NOTE: only firebird supports limit.
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => false,
+ 'new_link' => false,
+ 'numrows' => 'emulate',
+ 'pconnect' => true,
+ 'prepare' => true,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ -104 => DB_ERROR_SYNTAX,
+ -150 => DB_ERROR_ACCESS_VIOLATION,
+ -151 => DB_ERROR_ACCESS_VIOLATION,
+ -155 => DB_ERROR_NOSUCHTABLE,
+ -157 => DB_ERROR_NOSUCHFIELD,
+ -158 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ -170 => DB_ERROR_MISMATCH,
+ -171 => DB_ERROR_MISMATCH,
+ -172 => DB_ERROR_INVALID,
+ // -204 => // Covers too many errors, need to use regex on msg
+ -205 => DB_ERROR_NOSUCHFIELD,
+ -206 => DB_ERROR_NOSUCHFIELD,
+ -208 => DB_ERROR_INVALID,
+ -219 => DB_ERROR_NOSUCHTABLE,
+ -297 => DB_ERROR_CONSTRAINT,
+ -303 => DB_ERROR_INVALID,
+ -413 => DB_ERROR_INVALID_NUMBER,
+ -530 => DB_ERROR_CONSTRAINT,
+ -551 => DB_ERROR_ACCESS_VIOLATION,
+ -552 => DB_ERROR_ACCESS_VIOLATION,
+ // -607 => // Covers too many errors, need to use regex on msg
+ -625 => DB_ERROR_CONSTRAINT_NOT_NULL,
+ -803 => DB_ERROR_CONSTRAINT,
+ -804 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ // -902 => // Covers too many errors, need to use regex on msg
+ -904 => DB_ERROR_CONNECT_FAILED,
+ -922 => DB_ERROR_NOSUCHDB,
+ -923 => DB_ERROR_CONNECT_FAILED,
+ -924 => DB_ERROR_CONNECT_FAILED
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * The number of rows affected by a data manipulation query
+ * @var integer
+ * @access private
+ */
+ var $affected = 0;
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The prepared statement handle from the most recently executed statement
+ *
+ * {@internal Mainly here because the InterBase/Firebird API is only
+ * able to retrieve data from result sets if the statemnt handle is
+ * still in scope.}}
+ *
+ * @var resource
+ */
+ var $last_stmt;
+
+ /**
+ * Is the given prepared statement a data manipulation query?
+ * @var array
+ * @access private
+ */
+ var $manip_query = array();
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_ibase()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's ibase driver supports the following extra DSN options:
+ * + buffers The number of database buffers to allocate for the
+ * server-side cache.
+ * + charset The default character set for a database.
+ * + dialect The default SQL dialect for any statement
+ * executed within a connection. Defaults to the
+ * highest one supported by client libraries.
+ * Functional only with InterBase 6 and up.
+ * + role Functional only with InterBase 5 and up.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('interbase')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+ if ($this->dbsyntax == 'firebird') {
+ $this->features['limit'] = 'alter';
+ }
+
+ $params = array(
+ $dsn['hostspec']
+ ? ($dsn['hostspec'] . ':' . $dsn['database'])
+ : $dsn['database'],
+ $dsn['username'] ? $dsn['username'] : null,
+ $dsn['password'] ? $dsn['password'] : null,
+ isset($dsn['charset']) ? $dsn['charset'] : null,
+ isset($dsn['buffers']) ? $dsn['buffers'] : null,
+ isset($dsn['dialect']) ? $dsn['dialect'] : null,
+ isset($dsn['role']) ? $dsn['role'] : null,
+ );
+
+ $connect_function = $persistent ? 'ibase_pconnect' : 'ibase_connect';
+
+ $this->connection = @call_user_func_array($connect_function, $params);
+ if (!$this->connection) {
+ return $this->ibaseRaiseError(DB_ERROR_CONNECT_FAILED);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @ibase_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ $result = @ibase_query($this->connection, $query);
+
+ if (!$result) {
+ return $this->ibaseRaiseError();
+ }
+ if ($this->autocommit && $ismanip) {
+ @ibase_commit($this->connection);
+ }
+ if ($ismanip) {
+ $this->affected = $result;
+ return DB_OK;
+ } else {
+ $this->affected = 0;
+ return $result;
+ }
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * Only works with Firebird.
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ if ($this->dsn['dbsyntax'] == 'firebird') {
+ $query = preg_replace('/^([\s(])*SELECT/i',
+ "SELECT FIRST $count SKIP $from", $query);
+ }
+ return $query;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal ibase result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ if (function_exists('ibase_fetch_assoc')) {
+ $arr = @ibase_fetch_assoc($result);
+ } else {
+ $arr = get_object_vars(ibase_fetch_object($result));
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @ibase_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? ibase_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ freeQuery()
+
+ function freeQuery($query)
+ {
+ return is_resource($query) ? ibase_free_query($query) : false;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if (is_integer($this->affected)) {
+ return $this->affected;
+ }
+ return $this->ibaseRaiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @ibase_num_fields($result);
+ if (!$cols) {
+ return $this->ibaseRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ prepare()
+
+ /**
+ * Prepares a query for multiple execution with execute().
+ *
+ * prepare() requires a generic query as string like <code>
+ * INSERT INTO numbers VALUES (?, ?, ?)
+ * </code>. The <kbd>?</kbd> characters are placeholders.
+ *
+ * Three types of placeholders can be used:
+ * + <kbd>?</kbd> a quoted scalar value, i.e. strings, integers
+ * + <kbd>!</kbd> value is inserted 'as is'
+ * + <kbd>&</kbd> requires a file name. The file's contents get
+ * inserted into the query (i.e. saving binary
+ * data in a db)
+ *
+ * Use backslashes to escape placeholder characters if you don't want
+ * them to be interpreted as placeholders. Example: <code>
+ * "UPDATE foo SET col=? WHERE col='over \& under'"
+ * </code>
+ *
+ * @param string $query query to be prepared
+ * @return mixed DB statement resource on success. DB_Error on failure.
+ */
+ function prepare($query)
+ {
+ $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+ PREG_SPLIT_DELIM_CAPTURE);
+ $token = 0;
+ $types = array();
+ $newquery = '';
+
+ foreach ($tokens as $key => $val) {
+ switch ($val) {
+ case '?':
+ $types[$token++] = DB_PARAM_SCALAR;
+ break;
+ case '&':
+ $types[$token++] = DB_PARAM_OPAQUE;
+ break;
+ case '!':
+ $types[$token++] = DB_PARAM_MISC;
+ break;
+ default:
+ $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
+ $newquery .= $tokens[$key] . '?';
+ }
+ }
+
+ $newquery = substr($newquery, 0, -1);
+ $this->last_query = $query;
+ $newquery = $this->modifyQuery($newquery);
+ $stmt = @ibase_prepare($this->connection, $newquery);
+
+ if ($stmt === false) {
+ $stmt = $this->ibaseRaiseError();
+ } else {
+ $this->prepare_types[(int)$stmt] = $types;
+ $this->manip_query[(int)$stmt] = DB::isManip($query);
+ }
+
+ return $stmt;
+ }
+
+ // }}}
+ // {{{ execute()
+
+ /**
+ * Executes a DB statement prepared with prepare().
+ *
+ * @param resource $stmt a DB statement resource returned from prepare()
+ * @param mixed $data array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 for non-array items or the
+ * quantity of elements in the array.
+ * @return object a new DB_Result or a DB_Error when fail
+ * @see DB_ibase::prepare()
+ * @access public
+ */
+ function &execute($stmt, $data = array())
+ {
+ $data = (array)$data;
+ $this->last_parameters = $data;
+
+ $types = $this->prepare_types[(int)$stmt];
+ if (count($types) != count($data)) {
+ $tmp = $this->raiseError(DB_ERROR_MISMATCH);
+ return $tmp;
+ }
+
+ $i = 0;
+ foreach ($data as $key => $value) {
+ if ($types[$i] == DB_PARAM_MISC) {
+ /*
+ * ibase doesn't seem to have the ability to pass a
+ * parameter along unchanged, so strip off quotes from start
+ * and end, plus turn two single quotes to one single quote,
+ * in order to avoid the quotes getting escaped by
+ * ibase and ending up in the database.
+ */
+ $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
+ $data[$key] = str_replace("''", "'", $data[$key]);
+ } elseif ($types[$i] == DB_PARAM_OPAQUE) {
+ $fp = @fopen($data[$key], 'rb');
+ if (!$fp) {
+ $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+ return $tmp;
+ }
+ $data[$key] = fread($fp, filesize($data[$key]));
+ fclose($fp);
+ }
+ $i++;
+ }
+
+ array_unshift($data, $stmt);
+
+ $res = call_user_func_array('ibase_execute', $data);
+ if (!$res) {
+ $tmp = $this->ibaseRaiseError();
+ return $tmp;
+ }
+ /* XXX need this?
+ if ($this->autocommit && $this->manip_query[(int)$stmt]) {
+ @ibase_commit($this->connection);
+ }*/
+ $this->last_stmt = $stmt;
+ if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) {
+ $this->_last_query_manip = true;
+ $this->_next_query_manip = false;
+ $tmp = DB_OK;
+ } else {
+ $this->_last_query_manip = false;
+ $tmp = new DB_result($this, $res);
+ }
+ return $tmp;
+ }
+
+ /**
+ * Frees the internal resources associated with a prepared query
+ *
+ * @param resource $stmt the prepared statement's PHP resource
+ * @param bool $free_resource should the PHP resource be freed too?
+ * Use false if you need to get data
+ * from the result set later.
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_ibase::prepare()
+ */
+ function freePrepared($stmt, $free_resource = true)
+ {
+ if (!is_resource($stmt)) {
+ return false;
+ }
+ if ($free_resource) {
+ @ibase_free_query($stmt);
+ }
+ unset($this->prepare_tokens[(int)$stmt]);
+ unset($this->prepare_types[(int)$stmt]);
+ unset($this->manip_query[(int)$stmt]);
+ return true;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ $this->autocommit = $onoff ? 1 : 0;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ return @ibase_commit($this->connection);
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ return @ibase_rollback($this->connection);
+ }
+
+ // }}}
+ // {{{ transactionInit()
+
+ function transactionInit($trans_args = 0)
+ {
+ return $trans_args
+ ? @ibase_trans($trans_args, $this->connection)
+ : @ibase_trans();
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_ibase::createSequence(), DB_ibase::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $sqn = strtoupper($this->getSequenceName($seq_name));
+ $repeat = 0;
+ do {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("SELECT GEN_ID(${sqn}, 1) "
+ . 'FROM RDB$GENERATORS '
+ . "WHERE RDB\$GENERATOR_NAME='${sqn}'");
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result)) {
+ $repeat = 1;
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $result;
+ }
+ } else {
+ $repeat = 0;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ $result->free();
+ return $arr[0];
+ }
+
+ // }}}
+ // {{{ createSequence()
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_ibase::nextID(), DB_ibase::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $sqn = strtoupper($this->getSequenceName($seq_name));
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("CREATE GENERATOR ${sqn}");
+ $this->popErrorHandling();
+
+ return $result;
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_ibase::nextID(), DB_ibase::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DELETE FROM RDB$GENERATORS '
+ . "WHERE RDB\$GENERATOR_NAME='"
+ . strtoupper($this->getSequenceName($seq_name))
+ . "'");
+ }
+
+ // }}}
+ // {{{ _ibaseFieldFlags()
+
+ /**
+ * Get the column's flags
+ *
+ * Supports "primary_key", "unique_key", "not_null", "default",
+ * "computed" and "blob".
+ *
+ * @param string $field_name the name of the field
+ * @param string $table_name the name of the table
+ *
+ * @return string the flags
+ *
+ * @access private
+ */
+ function _ibaseFieldFlags($field_name, $table_name)
+ {
+ $sql = 'SELECT R.RDB$CONSTRAINT_TYPE CTYPE'
+ .' FROM RDB$INDEX_SEGMENTS I'
+ .' JOIN RDB$RELATION_CONSTRAINTS R ON I.RDB$INDEX_NAME=R.RDB$INDEX_NAME'
+ .' WHERE I.RDB$FIELD_NAME=\'' . $field_name . '\''
+ .' AND UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\'';
+
+ $result = @ibase_query($this->connection, $sql);
+ if (!$result) {
+ return $this->ibaseRaiseError();
+ }
+
+ $flags = '';
+ if ($obj = @ibase_fetch_object($result)) {
+ @ibase_free_result($result);
+ if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'PRIMARY KEY') {
+ $flags .= 'primary_key ';
+ }
+ if (isset($obj->CTYPE) && trim($obj->CTYPE) == 'UNIQUE') {
+ $flags .= 'unique_key ';
+ }
+ }
+
+ $sql = 'SELECT R.RDB$NULL_FLAG AS NFLAG,'
+ .' R.RDB$DEFAULT_SOURCE AS DSOURCE,'
+ .' F.RDB$FIELD_TYPE AS FTYPE,'
+ .' F.RDB$COMPUTED_SOURCE AS CSOURCE'
+ .' FROM RDB$RELATION_FIELDS R '
+ .' JOIN RDB$FIELDS F ON R.RDB$FIELD_SOURCE=F.RDB$FIELD_NAME'
+ .' WHERE UPPER(R.RDB$RELATION_NAME)=\'' . strtoupper($table_name) . '\''
+ .' AND R.RDB$FIELD_NAME=\'' . $field_name . '\'';
+
+ $result = @ibase_query($this->connection, $sql);
+ if (!$result) {
+ return $this->ibaseRaiseError();
+ }
+ if ($obj = @ibase_fetch_object($result)) {
+ @ibase_free_result($result);
+ if (isset($obj->NFLAG)) {
+ $flags .= 'not_null ';
+ }
+ if (isset($obj->DSOURCE)) {
+ $flags .= 'default ';
+ }
+ if (isset($obj->CSOURCE)) {
+ $flags .= 'computed ';
+ }
+ if (isset($obj->FTYPE) && $obj->FTYPE == 261) {
+ $flags .= 'blob ';
+ }
+ }
+
+ return trim($flags);
+ }
+
+ // }}}
+ // {{{ ibaseRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_ibase::errorNative(), DB_ibase::errorCode()
+ */
+ function &ibaseRaiseError($errno = null)
+ {
+ if ($errno === null) {
+ $errno = $this->errorCode($this->errorNative());
+ }
+ $tmp = $this->raiseError($errno, null, null, null, @ibase_errmsg());
+ return $tmp;
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return int the DBMS' error code. NULL if there is no error code.
+ *
+ * @since Method available since Release 1.7.0
+ */
+ function errorNative()
+ {
+ if (function_exists('ibase_errcode')) {
+ return @ibase_errcode();
+ }
+ if (preg_match('/^Dynamic SQL Error SQL error code = ([0-9-]+)/i',
+ @ibase_errmsg(), $m)) {
+ return (int)$m[1];
+ }
+ return null;
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Maps native error codes to DB's portable ones
+ *
+ * @param int $nativecode the error code returned by the DBMS
+ *
+ * @return int the portable DB error code. Return DB_ERROR if the
+ * current driver doesn't have a mapping for the
+ * $nativecode submitted.
+ *
+ * @since Method available since Release 1.7.0
+ */
+ function errorCode($nativecode = null)
+ {
+ if (isset($this->errorcode_map[$nativecode])) {
+ return $this->errorcode_map[$nativecode];
+ }
+
+ static $error_regexps;
+ if (!isset($error_regexps)) {
+ $error_regexps = array(
+ '/generator .* is not defined/'
+ => DB_ERROR_SYNTAX, // for compat. w ibase_errcode()
+ '/table.*(not exist|not found|unknown)/i'
+ => DB_ERROR_NOSUCHTABLE,
+ '/table .* already exists/i'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/unsuccessful metadata update .* failed attempt to store duplicate value/i'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/unsuccessful metadata update .* not found/i'
+ => DB_ERROR_NOT_FOUND,
+ '/validation error for column .* value "\*\*\* null/i'
+ => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '/violation of [\w ]+ constraint/i'
+ => DB_ERROR_CONSTRAINT,
+ '/conversion error from string/i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/no permission for/i'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/arithmetic exception, numeric overflow, or string truncation/i'
+ => DB_ERROR_INVALID,
+ '/feature is not supported/i'
+ => DB_ERROR_NOT_CAPABLE,
+ );
+ }
+
+ $errormsg = @ibase_errmsg();
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match($regexp, $errormsg)) {
+ return $code;
+ }
+ }
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+ * is a table name.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @ibase_query($this->connection,
+ "SELECT * FROM $result WHERE 1=0");
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->ibaseRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @ibase_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $info = @ibase_field_info($id, $i);
+ $res[$i] = array(
+ 'table' => $got_string ? $case_func($result) : '',
+ 'name' => $case_func($info['name']),
+ 'type' => $info['type'],
+ 'len' => $info['length'],
+ 'flags' => ($got_string)
+ ? $this->_ibaseFieldFlags($info['name'], $result)
+ : '',
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @ibase_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SELECT DISTINCT R.RDB$RELATION_NAME FROM '
+ . 'RDB$RELATION_FIELDS R WHERE R.RDB$SYSTEM_FLAG=0';
+ case 'views':
+ return 'SELECT DISTINCT RDB$VIEW_NAME from RDB$VIEW_RELATIONS';
+ case 'users':
+ return 'SELECT DISTINCT RDB$USER FROM RDB$USER_PRIVILEGES';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/ifx.php b/extlib/DB/ifx.php
new file mode 100644
index 000000000..baa6f2867
--- /dev/null
+++ b/extlib/DB/ifx.php
@@ -0,0 +1,683 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's ifx extension
+ * for interacting with Informix databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: ifx.php,v 1.75 2007/07/06 05:19:21 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's ifx extension
+ * for interacting with Informix databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * More info on Informix errors can be found at:
+ * http://www.informix.com/answers/english/ierrors.htm
+ *
+ * TODO:
+ * - set needed env Informix vars on connect
+ * - implement native prepare/execute
+ *
+ * @category Database
+ * @package DB
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_ifx extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'ifx';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'ifx';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'emulate',
+ 'new_link' => false,
+ 'numrows' => 'emulate',
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ '-201' => DB_ERROR_SYNTAX,
+ '-206' => DB_ERROR_NOSUCHTABLE,
+ '-217' => DB_ERROR_NOSUCHFIELD,
+ '-236' => DB_ERROR_VALUE_COUNT_ON_ROW,
+ '-239' => DB_ERROR_CONSTRAINT,
+ '-253' => DB_ERROR_SYNTAX,
+ '-268' => DB_ERROR_CONSTRAINT,
+ '-292' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '-310' => DB_ERROR_ALREADY_EXISTS,
+ '-316' => DB_ERROR_ALREADY_EXISTS,
+ '-319' => DB_ERROR_NOT_FOUND,
+ '-329' => DB_ERROR_NODBSELECTED,
+ '-346' => DB_ERROR_CONSTRAINT,
+ '-386' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '-391' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '-554' => DB_ERROR_SYNTAX,
+ '-691' => DB_ERROR_CONSTRAINT,
+ '-692' => DB_ERROR_CONSTRAINT,
+ '-703' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '-1202' => DB_ERROR_DIVZERO,
+ '-1204' => DB_ERROR_INVALID_DATE,
+ '-1205' => DB_ERROR_INVALID_DATE,
+ '-1206' => DB_ERROR_INVALID_DATE,
+ '-1209' => DB_ERROR_INVALID_DATE,
+ '-1210' => DB_ERROR_INVALID_DATE,
+ '-1212' => DB_ERROR_INVALID_DATE,
+ '-1213' => DB_ERROR_INVALID_NUMBER,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The quantity of transactions begun
+ *
+ * {@internal While this is private, it can't actually be designated
+ * private in PHP 5 because it is directly accessed in the test suite.}}
+ *
+ * @var integer
+ * @access private
+ */
+ var $transaction_opcount = 0;
+
+ /**
+ * The number of rows affected by a data manipulation query
+ * @var integer
+ * @access private
+ */
+ var $affected = 0;
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_ifx()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('informix') &&
+ !PEAR::loadExtension('Informix'))
+ {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $dbhost = $dsn['hostspec'] ? '@' . $dsn['hostspec'] : '';
+ $dbname = $dsn['database'] ? $dsn['database'] . $dbhost : '';
+ $user = $dsn['username'] ? $dsn['username'] : '';
+ $pw = $dsn['password'] ? $dsn['password'] : '';
+
+ $connect_function = $persistent ? 'ifx_pconnect' : 'ifx_connect';
+
+ $this->connection = @$connect_function($dbname, $user, $pw);
+ if (!is_resource($this->connection)) {
+ return $this->ifxRaiseError(DB_ERROR_CONNECT_FAILED);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @ifx_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ $this->affected = null;
+ if (preg_match('/(SELECT|EXECUTE)/i', $query)) { //TESTME: Use !DB::isManip()?
+ // the scroll is needed for fetching absolute row numbers
+ // in a select query result
+ $result = @ifx_query($query, $this->connection, IFX_SCROLL);
+ } else {
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @ifx_query('BEGIN WORK', $this->connection);
+ if (!$result) {
+ return $this->ifxRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ $result = @ifx_query($query, $this->connection);
+ }
+ if (!$result) {
+ return $this->ifxRaiseError();
+ }
+ $this->affected = @ifx_affected_rows($result);
+ // Determine which queries should return data, and which
+ // should return an error code only.
+ if (preg_match('/(SELECT|EXECUTE)/i', $query)) {
+ return $result;
+ }
+ // XXX Testme: free results inside a transaction
+ // may cause to stop it and commit the work?
+
+ // Result has to be freed even with a insert or update
+ @ifx_free_result($result);
+
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal ifx result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->_last_query_manip) {
+ return $this->affected;
+ } else {
+ return 0;
+ }
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if (($rownum !== null) && ($rownum < 0)) {
+ return null;
+ }
+ if ($rownum === null) {
+ /*
+ * Even though fetch_row() should return the next row if
+ * $rownum is null, it doesn't in all cases. Bug 598.
+ */
+ $rownum = 'NEXT';
+ } else {
+ // Index starts at row 1, unlike most DBMS's starting at 0.
+ $rownum++;
+ }
+ if (!$arr = @ifx_fetch_row($result, $rownum)) {
+ return null;
+ }
+ if ($fetchmode !== DB_FETCHMODE_ASSOC) {
+ $i=0;
+ $order = array();
+ foreach ($arr as $val) {
+ $order[$i++] = $val;
+ }
+ $arr = $order;
+ } elseif ($fetchmode == DB_FETCHMODE_ASSOC &&
+ $this->options['portability'] & DB_PORTABILITY_LOWERCASE)
+ {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ if (!$cols = @ifx_num_fields($result)) {
+ return $this->ifxRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? ifx_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = true)
+ {
+ // XXX if $this->transaction_opcount > 0, we should probably
+ // issue a warning here.
+ $this->autocommit = $onoff ? true : false;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if ($this->transaction_opcount > 0) {
+ $result = @ifx_query('COMMIT WORK', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->ifxRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if ($this->transaction_opcount > 0) {
+ $result = @ifx_query('ROLLBACK WORK', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->ifxRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ ifxRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_ifx::errorNative(), DB_ifx::errorCode()
+ */
+ function ifxRaiseError($errno = null)
+ {
+ if ($errno === null) {
+ $errno = $this->errorCode(ifx_error());
+ }
+ return $this->raiseError($errno, null, null, null,
+ $this->errorNative());
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code and message produced by the last query
+ *
+ * @return string the DBMS' error code and message
+ */
+ function errorNative()
+ {
+ return @ifx_error() . ' ' . @ifx_errormsg();
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Maps native error codes to DB's portable ones.
+ *
+ * Requires that the DB implementation's constructor fills
+ * in the <var>$errorcode_map</var> property.
+ *
+ * @param string $nativecode error code returned by the database
+ * @return int a portable DB error code, or DB_ERROR if this DB
+ * implementation has no mapping for the given error code.
+ */
+ function errorCode($nativecode)
+ {
+ if (ereg('SQLCODE=(.*)]', $nativecode, $match)) {
+ $code = $match[1];
+ if (isset($this->errorcode_map[$code])) {
+ return $this->errorcode_map[$code];
+ }
+ }
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * NOTE: only supports 'table' if <var>$result</var> is a table name.
+ *
+ * If analyzing a query result and the result has duplicate field names,
+ * an error will be raised saying
+ * <samp>can't distinguish duplicate field names</samp>.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ * @since Method available since Release 1.6.0
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @ifx_query("SELECT * FROM $result WHERE 1=0",
+ $this->connection);
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->ifxRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ $flds = @ifx_fieldproperties($id);
+ $count = @ifx_num_fields($id);
+
+ if (count($flds) != $count) {
+ return $this->raiseError("can't distinguish duplicate field names");
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $i = 0;
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ foreach ($flds as $key => $value) {
+ $props = explode(';', $value);
+ $res[$i] = array(
+ 'table' => $got_string ? $case_func($result) : '',
+ 'name' => $case_func($key),
+ 'type' => $props[0],
+ 'len' => $props[1],
+ 'flags' => $props[4] == 'N' ? 'not_null' : '',
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ $i++;
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @ifx_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SELECT tabname FROM systables WHERE tabid >= 100';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/msql.php b/extlib/DB/msql.php
new file mode 100644
index 000000000..34854f472
--- /dev/null
+++ b/extlib/DB/msql.php
@@ -0,0 +1,831 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's msql extension
+ * for interacting with Mini SQL databases
+ *
+ * PHP's mSQL extension did weird things with NULL values prior to PHP
+ * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
+ * those versions.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: msql.php,v 1.64 2007/09/21 13:40:41 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's msql extension
+ * for interacting with Mini SQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * PHP's mSQL extension did weird things with NULL values prior to PHP
+ * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
+ * those versions.
+ *
+ * @category Database
+ * @package DB
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ * @since Class not functional until Release 1.7.0
+ */
+class DB_msql extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'msql';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'msql';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'emulate',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => false,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * The query result resource created by PHP
+ *
+ * Used to make affectedRows() work. Only contains the result for
+ * data manipulation queries. Contains false for other queries.
+ *
+ * @var resource
+ * @access private
+ */
+ var $_result;
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_msql()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * Example of how to connect:
+ * <code>
+ * require_once 'DB.php';
+ *
+ * // $dsn = 'msql://hostname/dbname'; // use a TCP connection
+ * $dsn = 'msql:///dbname'; // use a socket
+ * $options = array(
+ * 'portability' => DB_PORTABILITY_ALL,
+ * );
+ *
+ * $db = DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('msql')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $params = array();
+ if ($dsn['hostspec']) {
+ $params[] = $dsn['port']
+ ? $dsn['hostspec'] . ',' . $dsn['port']
+ : $dsn['hostspec'];
+ }
+
+ $connect_function = $persistent ? 'msql_pconnect' : 'msql_connect';
+
+ $ini = ini_get('track_errors');
+ $php_errormsg = '';
+ if ($ini) {
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ } else {
+ @ini_set('track_errors', 1);
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ @ini_set('track_errors', $ini);
+ }
+
+ if (!$this->connection) {
+ if (($err = @msql_error()) != '') {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $err);
+ } else {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+ }
+
+ if (!@msql_select_db($dsn['database'], $this->connection)) {
+ return $this->msqlRaiseError();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @msql_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ $result = @msql_query($query, $this->connection);
+ if (!$result) {
+ return $this->msqlRaiseError();
+ }
+ // Determine which queries that should return data, and which
+ // should return an error code only.
+ if ($this->_checkManip($query)) {
+ $this->_result = $result;
+ return DB_OK;
+ } else {
+ $this->_result = false;
+ return $result;
+ }
+ }
+
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal msql result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * PHP's mSQL extension did weird things with NULL values prior to PHP
+ * 4.3.11 and 5.0.4. Make sure your version of PHP meets or exceeds
+ * those versions.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@msql_data_seek($result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @msql_fetch_array($result, MSQL_ASSOC);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @msql_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? msql_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @msql_num_fields($result);
+ if (!$cols) {
+ return $this->msqlRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @msql_num_rows($result);
+ if ($rows === false) {
+ return $this->msqlRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ affected()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if (!$this->_result) {
+ return 0;
+ }
+ return msql_affected_rows($this->_result);
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_msql::createSequence(), DB_msql::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $repeat = false;
+ do {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("SELECT _seq FROM ${seqname}");
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+ $repeat = true;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->createSequence($seq_name);
+ $this->popErrorHandling();
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ } else {
+ $repeat = false;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ $result->free();
+ return $arr[0];
+ }
+
+ // }}}
+ // {{{ createSequence()
+
+ /**
+ * Creates a new sequence
+ *
+ * Also creates a new table to associate the sequence with. Uses
+ * a separate table to ensure portability with other drivers.
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_msql::nextID(), DB_msql::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $res = $this->query('CREATE TABLE ' . $seqname
+ . ' (id INTEGER NOT NULL)');
+ if (DB::isError($res)) {
+ return $res;
+ }
+ $res = $this->query("CREATE SEQUENCE ON ${seqname}");
+ return $res;
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_msql::nextID(), DB_msql::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ quoteIdentifier()
+
+ /**
+ * mSQL does not support delimited identifiers
+ *
+ * @param string $str the identifier name to be quoted
+ *
+ * @return object a DB_Error object
+ *
+ * @see DB_common::quoteIdentifier()
+ * @since Method available since Release 1.7.0
+ */
+ function quoteIdentifier($str)
+ {
+ return $this->raiseError(DB_ERROR_UNSUPPORTED);
+ }
+
+ // }}}
+ // {{{ quoteFloat()
+
+ /**
+ * Formats a float value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param float the float value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteFloat($float) {
+ return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
+ }
+
+ // }}}
+ // {{{ escapeSimple()
+
+ /**
+ * Escapes a string according to the current DBMS's standards
+ *
+ * @param string $str the string to be escaped
+ *
+ * @return string the escaped string
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.7.0
+ */
+ function escapeSimple($str)
+ {
+ return addslashes($str);
+ }
+
+ // }}}
+ // {{{ msqlRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_msql::errorNative(), DB_msql::errorCode()
+ */
+ function msqlRaiseError($errno = null)
+ {
+ $native = $this->errorNative();
+ if ($errno === null) {
+ $errno = $this->errorCode($native);
+ }
+ return $this->raiseError($errno, null, null, null, $native);
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error message produced by the last query
+ *
+ * @return string the DBMS' error message
+ */
+ function errorNative()
+ {
+ return @msql_error();
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Determines PEAR::DB error code from the database's text error message
+ *
+ * @param string $errormsg the error message returned from the database
+ *
+ * @return integer the error number from a DB_ERROR* constant
+ */
+ function errorCode($errormsg)
+ {
+ static $error_regexps;
+
+ // PHP 5.2+ prepends the function name to $php_errormsg, so we need
+ // this hack to work around it, per bug #9599.
+ $errormsg = preg_replace('/^msql[a-z_]+\(\): /', '', $errormsg);
+
+ if (!isset($error_regexps)) {
+ $error_regexps = array(
+ '/^Access to database denied/i'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/^Bad index name/i'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/^Bad order field/i'
+ => DB_ERROR_SYNTAX,
+ '/^Bad type for comparison/i'
+ => DB_ERROR_SYNTAX,
+ '/^Can\'t perform LIKE on/i'
+ => DB_ERROR_SYNTAX,
+ '/^Can\'t use TEXT fields in LIKE comparison/i'
+ => DB_ERROR_SYNTAX,
+ '/^Couldn\'t create temporary table/i'
+ => DB_ERROR_CANNOT_CREATE,
+ '/^Error creating table file/i'
+ => DB_ERROR_CANNOT_CREATE,
+ '/^Field .* cannot be null$/i'
+ => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '/^Index (field|condition) .* cannot be null$/i'
+ => DB_ERROR_SYNTAX,
+ '/^Invalid date format/i'
+ => DB_ERROR_INVALID_DATE,
+ '/^Invalid time format/i'
+ => DB_ERROR_INVALID,
+ '/^Literal value for .* is wrong type$/i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/^No Database Selected/i'
+ => DB_ERROR_NODBSELECTED,
+ '/^No value specified for field/i'
+ => DB_ERROR_VALUE_COUNT_ON_ROW,
+ '/^Non unique value for unique index/i'
+ => DB_ERROR_CONSTRAINT,
+ '/^Out of memory for temporary table/i'
+ => DB_ERROR_CANNOT_CREATE,
+ '/^Permission denied/i'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/^Reference to un-selected table/i'
+ => DB_ERROR_SYNTAX,
+ '/^syntax error/i'
+ => DB_ERROR_SYNTAX,
+ '/^Table .* exists$/i'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/^Unknown database/i'
+ => DB_ERROR_NOSUCHDB,
+ '/^Unknown field/i'
+ => DB_ERROR_NOSUCHFIELD,
+ '/^Unknown (index|system variable)/i'
+ => DB_ERROR_NOT_FOUND,
+ '/^Unknown table/i'
+ => DB_ERROR_NOSUCHTABLE,
+ '/^Unqualified field/i'
+ => DB_ERROR_SYNTAX,
+ );
+ }
+
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match($regexp, $errormsg)) {
+ return $code;
+ }
+ }
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::setOption()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @msql_query("SELECT * FROM $result",
+ $this->connection);
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->raiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @msql_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $tmp = @msql_fetch_field($id);
+
+ $flags = '';
+ if ($tmp->not_null) {
+ $flags .= 'not_null ';
+ }
+ if ($tmp->unique) {
+ $flags .= 'unique_key ';
+ }
+ $flags = trim($flags);
+
+ $res[$i] = array(
+ 'table' => $case_func($tmp->table),
+ 'name' => $case_func($tmp->name),
+ 'type' => $tmp->type,
+ 'len' => msql_field_len($id, $i),
+ 'flags' => $flags,
+ );
+
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @msql_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtain a list of a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return array the array containing the list of objects requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'databases':
+ $id = @msql_list_dbs($this->connection);
+ break;
+ case 'tables':
+ $id = @msql_list_tables($this->dsn['database'],
+ $this->connection);
+ break;
+ default:
+ return null;
+ }
+ if (!$id) {
+ return $this->msqlRaiseError();
+ }
+ $out = array();
+ while ($row = @msql_fetch_row($id)) {
+ $out[] = $row[0];
+ }
+ return $out;
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/mssql.php b/extlib/DB/mssql.php
new file mode 100644
index 000000000..511a2b686
--- /dev/null
+++ b/extlib/DB/mssql.php
@@ -0,0 +1,963 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mssql extension
+ * for interacting with Microsoft SQL Server databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: mssql.php,v 1.92 2007/09/21 13:40:41 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mssql extension
+ * for interacting with Microsoft SQL Server databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * DB's mssql driver is only for Microsfoft SQL Server databases.
+ *
+ * If you're connecting to a Sybase database, you MUST specify "sybase"
+ * as the "phptype" in the DSN.
+ *
+ * This class only works correctly if you have compiled PHP using
+ * --with-mssql=[dir_to_FreeTDS].
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_mssql extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'mssql';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'mssql';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'emulate',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ // XXX Add here error codes ie: 'S100E' => DB_ERROR_SYNTAX
+ var $errorcode_map = array(
+ 102 => DB_ERROR_SYNTAX,
+ 110 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 155 => DB_ERROR_NOSUCHFIELD,
+ 156 => DB_ERROR_SYNTAX,
+ 170 => DB_ERROR_SYNTAX,
+ 207 => DB_ERROR_NOSUCHFIELD,
+ 208 => DB_ERROR_NOSUCHTABLE,
+ 245 => DB_ERROR_INVALID_NUMBER,
+ 319 => DB_ERROR_SYNTAX,
+ 321 => DB_ERROR_NOSUCHFIELD,
+ 325 => DB_ERROR_SYNTAX,
+ 336 => DB_ERROR_SYNTAX,
+ 515 => DB_ERROR_CONSTRAINT_NOT_NULL,
+ 547 => DB_ERROR_CONSTRAINT,
+ 1018 => DB_ERROR_SYNTAX,
+ 1035 => DB_ERROR_SYNTAX,
+ 1913 => DB_ERROR_ALREADY_EXISTS,
+ 2209 => DB_ERROR_SYNTAX,
+ 2223 => DB_ERROR_SYNTAX,
+ 2248 => DB_ERROR_SYNTAX,
+ 2256 => DB_ERROR_SYNTAX,
+ 2257 => DB_ERROR_SYNTAX,
+ 2627 => DB_ERROR_CONSTRAINT,
+ 2714 => DB_ERROR_ALREADY_EXISTS,
+ 3607 => DB_ERROR_DIVZERO,
+ 3701 => DB_ERROR_NOSUCHTABLE,
+ 7630 => DB_ERROR_SYNTAX,
+ 8134 => DB_ERROR_DIVZERO,
+ 9303 => DB_ERROR_SYNTAX,
+ 9317 => DB_ERROR_SYNTAX,
+ 9318 => DB_ERROR_SYNTAX,
+ 9331 => DB_ERROR_SYNTAX,
+ 9332 => DB_ERROR_SYNTAX,
+ 15253 => DB_ERROR_SYNTAX,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The quantity of transactions begun
+ *
+ * {@internal While this is private, it can't actually be designated
+ * private in PHP 5 because it is directly accessed in the test suite.}}
+ *
+ * @var integer
+ * @access private
+ */
+ var $transaction_opcount = 0;
+
+ /**
+ * The database specified in the DSN
+ *
+ * It's a fix to allow calls to different databases in the same script.
+ *
+ * @var string
+ * @access private
+ */
+ var $_db = null;
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_mssql()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('mssql') && !PEAR::loadExtension('sybase')
+ && !PEAR::loadExtension('sybase_ct'))
+ {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $params = array(
+ $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost',
+ $dsn['username'] ? $dsn['username'] : null,
+ $dsn['password'] ? $dsn['password'] : null,
+ );
+ if ($dsn['port']) {
+ $params[0] .= ((substr(PHP_OS, 0, 3) == 'WIN') ? ',' : ':')
+ . $dsn['port'];
+ }
+
+ $connect_function = $persistent ? 'mssql_pconnect' : 'mssql_connect';
+
+ $this->connection = @call_user_func_array($connect_function, $params);
+
+ if (!$this->connection) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ @mssql_get_last_message());
+ }
+ if ($dsn['database']) {
+ if (!@mssql_select_db($dsn['database'], $this->connection)) {
+ return $this->raiseError(DB_ERROR_NODBSELECTED,
+ null, null, null,
+ @mssql_get_last_message());
+ }
+ $this->_db = $dsn['database'];
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @mssql_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ if (!@mssql_select_db($this->_db, $this->connection)) {
+ return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $query = $this->modifyQuery($query);
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @mssql_query('BEGIN TRAN', $this->connection);
+ if (!$result) {
+ return $this->mssqlRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ $result = @mssql_query($query, $this->connection);
+ if (!$result) {
+ return $this->mssqlRaiseError();
+ }
+ // Determine which queries that should return data, and which
+ // should return an error code only.
+ return $ismanip ? DB_OK : $result;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal mssql result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return @mssql_next_result($result);
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@mssql_data_seek($result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @mssql_fetch_assoc($result);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @mssql_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? mssql_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @mssql_num_fields($result);
+ if (!$cols) {
+ return $this->mssqlRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @mssql_num_rows($result);
+ if ($rows === false) {
+ return $this->mssqlRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ // XXX if $this->transaction_opcount > 0, we should probably
+ // issue a warning here.
+ $this->autocommit = $onoff ? true : false;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if ($this->transaction_opcount > 0) {
+ if (!@mssql_select_db($this->_db, $this->connection)) {
+ return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $result = @mssql_query('COMMIT TRAN', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->mssqlRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if ($this->transaction_opcount > 0) {
+ if (!@mssql_select_db($this->_db, $this->connection)) {
+ return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $result = @mssql_query('ROLLBACK TRAN', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->mssqlRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->_last_query_manip) {
+ $res = @mssql_query('select @@rowcount', $this->connection);
+ if (!$res) {
+ return $this->mssqlRaiseError();
+ }
+ $ar = @mssql_fetch_row($res);
+ if (!$ar) {
+ $result = 0;
+ } else {
+ @mssql_free_result($res);
+ $result = $ar[0];
+ }
+ } else {
+ $result = 0;
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_mssql::createSequence(), DB_mssql::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ if (!@mssql_select_db($this->_db, $this->connection)) {
+ return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $repeat = 0;
+ do {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result) &&
+ ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
+ {
+ $repeat = 1;
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ } elseif (!DB::isError($result)) {
+ $result = $this->query("SELECT IDENT_CURRENT('$seqname')");
+ if (DB::isError($result)) {
+ /* Fallback code for MS SQL Server 7.0, which doesn't have
+ * IDENT_CURRENT. This is *not* safe for concurrent
+ * requests, and really, if you're using it, you're in a
+ * world of hurt. Nevertheless, it's here to ensure BC. See
+ * bug #181 for the gory details.*/
+ $result = $this->query("SELECT @@IDENTITY FROM $seqname");
+ }
+ $repeat = 0;
+ } else {
+ $repeat = false;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ return $result[0];
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_mssql::nextID(), DB_mssql::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ return $this->query('CREATE TABLE '
+ . $this->getSequenceName($seq_name)
+ . ' ([id] [int] IDENTITY (1, 1) NOT NULL,'
+ . ' [vapor] [int] NULL)');
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_mssql::nextID(), DB_mssql::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ quoteIdentifier()
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ *
+ * @param string $str identifier name to be quoted
+ *
+ * @return string quoted identifier string
+ *
+ * @see DB_common::quoteIdentifier()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteIdentifier($str)
+ {
+ return '[' . str_replace(']', ']]', $str) . ']';
+ }
+
+ // }}}
+ // {{{ mssqlRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_mssql::errorNative(), DB_mssql::errorCode()
+ */
+ function mssqlRaiseError($code = null)
+ {
+ $message = @mssql_get_last_message();
+ if (!$code) {
+ $code = $this->errorNative();
+ }
+ return $this->raiseError($this->errorCode($code, $message),
+ null, null, null, "$code - $message");
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return int the DBMS' error code
+ */
+ function errorNative()
+ {
+ $res = @mssql_query('select @@ERROR as ErrorCode', $this->connection);
+ if (!$res) {
+ return DB_ERROR;
+ }
+ $row = @mssql_fetch_row($res);
+ return $row[0];
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Determines PEAR::DB error code from mssql's native codes.
+ *
+ * If <var>$nativecode</var> isn't known yet, it will be looked up.
+ *
+ * @param mixed $nativecode mssql error code, if known
+ * @return integer an error number from a DB error constant
+ * @see errorNative()
+ */
+ function errorCode($nativecode = null, $msg = '')
+ {
+ if (!$nativecode) {
+ $nativecode = $this->errorNative();
+ }
+ if (isset($this->errorcode_map[$nativecode])) {
+ if ($nativecode == 3701
+ && preg_match('/Cannot drop the index/i', $msg))
+ {
+ return DB_ERROR_NOT_FOUND;
+ }
+ return $this->errorcode_map[$nativecode];
+ } else {
+ return DB_ERROR;
+ }
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+ * is a table name.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ if (!@mssql_select_db($this->_db, $this->connection)) {
+ return $this->mssqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $id = @mssql_query("SELECT * FROM $result WHERE 1=0",
+ $this->connection);
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->mssqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @mssql_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ if ($got_string) {
+ $flags = $this->_mssql_field_flags($result,
+ @mssql_field_name($id, $i));
+ if (DB::isError($flags)) {
+ return $flags;
+ }
+ } else {
+ $flags = '';
+ }
+
+ $res[$i] = array(
+ 'table' => $got_string ? $case_func($result) : '',
+ 'name' => $case_func(@mssql_field_name($id, $i)),
+ 'type' => @mssql_field_type($id, $i),
+ 'len' => @mssql_field_length($id, $i),
+ 'flags' => $flags,
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @mssql_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ _mssql_field_flags()
+
+ /**
+ * Get a column's flags
+ *
+ * Supports "not_null", "primary_key",
+ * "auto_increment" (mssql identity), "timestamp" (mssql timestamp),
+ * "unique_key" (mssql unique index, unique check or primary_key) and
+ * "multiple_key" (multikey index)
+ *
+ * mssql timestamp is NOT similar to the mysql timestamp so this is maybe
+ * not useful at all - is the behaviour of mysql_field_flags that primary
+ * keys are alway unique? is the interpretation of multiple_key correct?
+ *
+ * @param string $table the table name
+ * @param string $column the field name
+ *
+ * @return string the flags
+ *
+ * @access private
+ * @author Joern Barthel <j_barthel@web.de>
+ */
+ function _mssql_field_flags($table, $column)
+ {
+ static $tableName = null;
+ static $flags = array();
+
+ if ($table != $tableName) {
+
+ $flags = array();
+ $tableName = $table;
+
+ // get unique and primary keys
+ $res = $this->getAll("EXEC SP_HELPINDEX $table", DB_FETCHMODE_ASSOC);
+ if (DB::isError($res)) {
+ return $res;
+ }
+
+ foreach ($res as $val) {
+ $keys = explode(', ', $val['index_keys']);
+
+ if (sizeof($keys) > 1) {
+ foreach ($keys as $key) {
+ $this->_add_flag($flags[$key], 'multiple_key');
+ }
+ }
+
+ if (strpos($val['index_description'], 'primary key')) {
+ foreach ($keys as $key) {
+ $this->_add_flag($flags[$key], 'primary_key');
+ }
+ } elseif (strpos($val['index_description'], 'unique')) {
+ foreach ($keys as $key) {
+ $this->_add_flag($flags[$key], 'unique_key');
+ }
+ }
+ }
+
+ // get auto_increment, not_null and timestamp
+ $res = $this->getAll("EXEC SP_COLUMNS $table", DB_FETCHMODE_ASSOC);
+ if (DB::isError($res)) {
+ return $res;
+ }
+
+ foreach ($res as $val) {
+ $val = array_change_key_case($val, CASE_LOWER);
+ if ($val['nullable'] == '0') {
+ $this->_add_flag($flags[$val['column_name']], 'not_null');
+ }
+ if (strpos($val['type_name'], 'identity')) {
+ $this->_add_flag($flags[$val['column_name']], 'auto_increment');
+ }
+ if (strpos($val['type_name'], 'timestamp')) {
+ $this->_add_flag($flags[$val['column_name']], 'timestamp');
+ }
+ }
+ }
+
+ if (array_key_exists($column, $flags)) {
+ return(implode(' ', $flags[$column]));
+ }
+ return '';
+ }
+
+ // }}}
+ // {{{ _add_flag()
+
+ /**
+ * Adds a string to the flags array if the flag is not yet in there
+ * - if there is no flag present the array is created
+ *
+ * @param array &$array the reference to the flag-array
+ * @param string $value the flag value
+ *
+ * @return void
+ *
+ * @access private
+ * @author Joern Barthel <j_barthel@web.de>
+ */
+ function _add_flag(&$array, $value)
+ {
+ if (!is_array($array)) {
+ $array = array($value);
+ } elseif (!in_array($value, $array)) {
+ array_push($array, $value);
+ }
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return "SELECT name FROM sysobjects WHERE type = 'U'"
+ . ' ORDER BY name';
+ case 'views':
+ return "SELECT name FROM sysobjects WHERE type = 'V'";
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/mysql.php b/extlib/DB/mysql.php
new file mode 100644
index 000000000..c67254520
--- /dev/null
+++ b/extlib/DB/mysql.php
@@ -0,0 +1,1045 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mysql extension
+ * for interacting with MySQL databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: mysql.php,v 1.126 2007/09/21 13:32:52 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mysql extension
+ * for interacting with MySQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_mysql extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'mysql';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'mysql';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'alter',
+ 'new_link' => '4.2.0',
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ 1004 => DB_ERROR_CANNOT_CREATE,
+ 1005 => DB_ERROR_CANNOT_CREATE,
+ 1006 => DB_ERROR_CANNOT_CREATE,
+ 1007 => DB_ERROR_ALREADY_EXISTS,
+ 1008 => DB_ERROR_CANNOT_DROP,
+ 1022 => DB_ERROR_ALREADY_EXISTS,
+ 1044 => DB_ERROR_ACCESS_VIOLATION,
+ 1046 => DB_ERROR_NODBSELECTED,
+ 1048 => DB_ERROR_CONSTRAINT,
+ 1049 => DB_ERROR_NOSUCHDB,
+ 1050 => DB_ERROR_ALREADY_EXISTS,
+ 1051 => DB_ERROR_NOSUCHTABLE,
+ 1054 => DB_ERROR_NOSUCHFIELD,
+ 1061 => DB_ERROR_ALREADY_EXISTS,
+ 1062 => DB_ERROR_ALREADY_EXISTS,
+ 1064 => DB_ERROR_SYNTAX,
+ 1091 => DB_ERROR_NOT_FOUND,
+ 1100 => DB_ERROR_NOT_LOCKED,
+ 1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 1142 => DB_ERROR_ACCESS_VIOLATION,
+ 1146 => DB_ERROR_NOSUCHTABLE,
+ 1216 => DB_ERROR_CONSTRAINT,
+ 1217 => DB_ERROR_CONSTRAINT,
+ 1356 => DB_ERROR_DIVZERO,
+ 1451 => DB_ERROR_CONSTRAINT,
+ 1452 => DB_ERROR_CONSTRAINT,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The quantity of transactions begun
+ *
+ * {@internal While this is private, it can't actually be designated
+ * private in PHP 5 because it is directly accessed in the test suite.}}
+ *
+ * @var integer
+ * @access private
+ */
+ var $transaction_opcount = 0;
+
+ /**
+ * The database specified in the DSN
+ *
+ * It's a fix to allow calls to different databases in the same script.
+ *
+ * @var string
+ * @access private
+ */
+ var $_db = '';
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_mysql()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's mysql driver supports the following extra DSN options:
+ * + new_link If set to true, causes subsequent calls to connect()
+ * to return a new connection link instead of the
+ * existing one. WARNING: this is not portable to
+ * other DBMS's. Available since PEAR DB 1.7.0.
+ * + client_flags Any combination of MYSQL_CLIENT_* constants.
+ * Only used if PHP is at version 4.3.0 or greater.
+ * Available since PEAR DB 1.7.0.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('mysql')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $params = array();
+ if ($dsn['protocol'] && $dsn['protocol'] == 'unix') {
+ $params[0] = ':' . $dsn['socket'];
+ } else {
+ $params[0] = $dsn['hostspec'] ? $dsn['hostspec']
+ : 'localhost';
+ if ($dsn['port']) {
+ $params[0] .= ':' . $dsn['port'];
+ }
+ }
+ $params[] = $dsn['username'] ? $dsn['username'] : null;
+ $params[] = $dsn['password'] ? $dsn['password'] : null;
+
+ if (!$persistent) {
+ if (isset($dsn['new_link'])
+ && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+ {
+ $params[] = true;
+ } else {
+ $params[] = false;
+ }
+ }
+ if (version_compare(phpversion(), '4.3.0', '>=')) {
+ $params[] = isset($dsn['client_flags'])
+ ? $dsn['client_flags'] : null;
+ }
+
+ $connect_function = $persistent ? 'mysql_pconnect' : 'mysql_connect';
+
+ $ini = ini_get('track_errors');
+ $php_errormsg = '';
+ if ($ini) {
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ } else {
+ @ini_set('track_errors', 1);
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ @ini_set('track_errors', $ini);
+ }
+
+ if (!$this->connection) {
+ if (($err = @mysql_error()) != '') {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $err);
+ } else {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+ }
+
+ if ($dsn['database']) {
+ if (!@mysql_select_db($dsn['database'], $this->connection)) {
+ return $this->mysqlRaiseError();
+ }
+ $this->_db = $dsn['database'];
+ }
+
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @mysql_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * Generally uses mysql_query(). If you want to use
+ * mysql_unbuffered_query() set the "result_buffering" option to 0 using
+ * setOptions(). This option was added in Release 1.7.0.
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ if ($this->_db) {
+ if (!@mysql_select_db($this->_db, $this->connection)) {
+ return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @mysql_query('SET AUTOCOMMIT=0', $this->connection);
+ $result = @mysql_query('BEGIN', $this->connection);
+ if (!$result) {
+ return $this->mysqlRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ if (!$this->options['result_buffering']) {
+ $result = @mysql_unbuffered_query($query, $this->connection);
+ } else {
+ $result = @mysql_query($query, $this->connection);
+ }
+ if (!$result) {
+ return $this->mysqlRaiseError();
+ }
+ if (is_resource($result)) {
+ return $result;
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal mysql result pointer to the next available result
+ *
+ * This method has not been implemented yet.
+ *
+ * @param a valid sql result resource
+ *
+ * @return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@mysql_data_seek($result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @mysql_fetch_array($result, MYSQL_ASSOC);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @mysql_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ /*
+ * Even though this DBMS already trims output, we do this because
+ * a field might have intentional whitespace at the end that
+ * gets removed by DB_PORTABILITY_RTRIM under another driver.
+ */
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? mysql_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @mysql_num_fields($result);
+ if (!$cols) {
+ return $this->mysqlRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @mysql_num_rows($result);
+ if ($rows === null) {
+ return $this->mysqlRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ // XXX if $this->transaction_opcount > 0, we should probably
+ // issue a warning here.
+ $this->autocommit = $onoff ? true : false;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if ($this->transaction_opcount > 0) {
+ if ($this->_db) {
+ if (!@mysql_select_db($this->_db, $this->connection)) {
+ return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ $result = @mysql_query('COMMIT', $this->connection);
+ $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->mysqlRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if ($this->transaction_opcount > 0) {
+ if ($this->_db) {
+ if (!@mysql_select_db($this->_db, $this->connection)) {
+ return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ $result = @mysql_query('ROLLBACK', $this->connection);
+ $result = @mysql_query('SET AUTOCOMMIT=1', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->mysqlRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->_last_query_manip) {
+ return @mysql_affected_rows($this->connection);
+ } else {
+ return 0;
+ }
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_mysql::createSequence(), DB_mysql::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ do {
+ $repeat = 0;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("UPDATE ${seqname} ".
+ 'SET id=LAST_INSERT_ID(id+1)');
+ $this->popErrorHandling();
+ if ($result === DB_OK) {
+ // COMMON CASE
+ $id = @mysql_insert_id($this->connection);
+ if ($id != 0) {
+ return $id;
+ }
+ // EMPTY SEQ TABLE
+ // Sequence table must be empty for some reason, so fill
+ // it and return 1 and obtain a user-level lock
+ $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ if ($result == 0) {
+ // Failed to get the lock
+ return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
+ }
+
+ // add the default value
+ $result = $this->query("REPLACE INTO ${seqname} (id) VALUES (0)");
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+
+ // Release the lock
+ $result = $this->getOne('SELECT RELEASE_LOCK('
+ . "'${seqname}_lock')");
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ // We know what the result will be, so no need to try again
+ return 1;
+
+ } elseif ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE)
+ {
+ // ONDEMAND TABLE CREATION
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ } else {
+ $repeat = 1;
+ }
+
+ } elseif (DB::isError($result) &&
+ $result->getCode() == DB_ERROR_ALREADY_EXISTS)
+ {
+ // BACKWARDS COMPAT
+ // see _BCsequence() comment
+ $result = $this->_BCsequence($seqname);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $repeat = 1;
+ }
+ } while ($repeat);
+
+ return $this->raiseError($result);
+ }
+
+ // }}}
+ // {{{ createSequence()
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_mysql::nextID(), DB_mysql::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $res = $this->query('CREATE TABLE ' . $seqname
+ . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'
+ . ' PRIMARY KEY(id))');
+ if (DB::isError($res)) {
+ return $res;
+ }
+ // insert yields value 1, nextId call will generate ID 2
+ $res = $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
+ if (DB::isError($res)) {
+ return $res;
+ }
+ // so reset to zero
+ return $this->query("UPDATE ${seqname} SET id = 0");
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_mysql::nextID(), DB_mysql::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ _BCsequence()
+
+ /**
+ * Backwards compatibility with old sequence emulation implementation
+ * (clean up the dupes)
+ *
+ * @param string $seqname the sequence name to clean up
+ *
+ * @return bool true on success. A DB_Error object on failure.
+ *
+ * @access private
+ */
+ function _BCsequence($seqname)
+ {
+ // Obtain a user-level lock... this will release any previous
+ // application locks, but unlike LOCK TABLES, it does not abort
+ // the current transaction and is much less frequently used.
+ $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+ if (DB::isError($result)) {
+ return $result;
+ }
+ if ($result == 0) {
+ // Failed to get the lock, can't do the conversion, bail
+ // with a DB_ERROR_NOT_LOCKED error
+ return $this->mysqlRaiseError(DB_ERROR_NOT_LOCKED);
+ }
+
+ $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
+ if (DB::isError($highest_id)) {
+ return $highest_id;
+ }
+ // This should kill all rows except the highest
+ // We should probably do something if $highest_id isn't
+ // numeric, but I'm at a loss as how to handle that...
+ $result = $this->query('DELETE FROM ' . $seqname
+ . " WHERE id <> $highest_id");
+ if (DB::isError($result)) {
+ return $result;
+ }
+
+ // If another thread has been waiting for this lock,
+ // it will go thru the above procedure, but will have no
+ // real effect
+ $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
+ if (DB::isError($result)) {
+ return $result;
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ quoteIdentifier()
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ * (WARNING: using names that require this is a REALLY BAD IDEA)
+ *
+ * WARNING: Older versions of MySQL can't handle the backtick
+ * character (<kbd>`</kbd>) in table or column names.
+ *
+ * @param string $str identifier name to be quoted
+ *
+ * @return string quoted identifier string
+ *
+ * @see DB_common::quoteIdentifier()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteIdentifier($str)
+ {
+ return '`' . str_replace('`', '``', $str) . '`';
+ }
+
+ // }}}
+ // {{{ quote()
+
+ /**
+ * @deprecated Deprecated in release 1.6.0
+ */
+ function quote($str)
+ {
+ return $this->quoteSmart($str);
+ }
+
+ // }}}
+ // {{{ escapeSimple()
+
+ /**
+ * Escapes a string according to the current DBMS's standards
+ *
+ * @param string $str the string to be escaped
+ *
+ * @return string the escaped string
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function escapeSimple($str)
+ {
+ if (function_exists('mysql_real_escape_string')) {
+ return @mysql_real_escape_string($str, $this->connection);
+ } else {
+ return @mysql_escape_string($str);
+ }
+ }
+
+ // }}}
+ // {{{ modifyQuery()
+
+ /**
+ * Changes a query string for various DBMS specific reasons
+ *
+ * This little hack lets you know how many rows were deleted
+ * when running a "DELETE FROM table" query. Only implemented
+ * if the DB_PORTABILITY_DELETE_COUNT portability option is on.
+ *
+ * @param string $query the query string to modify
+ *
+ * @return string the modified query string
+ *
+ * @access protected
+ * @see DB_common::setOption()
+ */
+ function modifyQuery($query)
+ {
+ if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
+ // "DELETE FROM table" gives 0 affected rows in MySQL.
+ // This little hack lets you know how many rows were deleted.
+ if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+ $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+ 'DELETE FROM \1 WHERE 1=1', $query);
+ }
+ }
+ return $query;
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ if (DB::isManip($query) || $this->_next_query_manip) {
+ return $query . " LIMIT $count";
+ } else {
+ return $query . " LIMIT $from, $count";
+ }
+ }
+
+ // }}}
+ // {{{ mysqlRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_mysql::errorNative(), DB_common::errorCode()
+ */
+ function mysqlRaiseError($errno = null)
+ {
+ if ($errno === null) {
+ if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+ $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
+ $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
+ $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
+ } else {
+ // Doing this in case mode changes during runtime.
+ $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
+ $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
+ $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
+ }
+ $errno = $this->errorCode(mysql_errno($this->connection));
+ }
+ return $this->raiseError($errno, null, null, null,
+ @mysql_errno($this->connection) . ' ** ' .
+ @mysql_error($this->connection));
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return int the DBMS' error code
+ */
+ function errorNative()
+ {
+ return @mysql_errno($this->connection);
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ // Fix for bug #11580.
+ if ($this->_db) {
+ if (!@mysql_select_db($this->_db, $this->connection)) {
+ return $this->mysqlRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @mysql_query("SELECT * FROM $result LIMIT 0",
+ $this->connection);
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->mysqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @mysql_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $res[$i] = array(
+ 'table' => $case_func(@mysql_field_table($id, $i)),
+ 'name' => $case_func(@mysql_field_name($id, $i)),
+ 'type' => @mysql_field_type($id, $i),
+ 'len' => @mysql_field_len($id, $i),
+ 'flags' => @mysql_field_flags($id, $i),
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @mysql_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SHOW TABLES';
+ case 'users':
+ return 'SELECT DISTINCT User FROM mysql.user';
+ case 'databases':
+ return 'SHOW DATABASES';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/mysqli.php b/extlib/DB/mysqli.php
new file mode 100644
index 000000000..c6941b170
--- /dev/null
+++ b/extlib/DB/mysqli.php
@@ -0,0 +1,1092 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's mysqli extension
+ * for interacting with MySQL databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: mysqli.php,v 1.82 2007/09/21 13:40:41 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's mysqli extension
+ * for interacting with MySQL databases
+ *
+ * This is for MySQL versions 4.1 and above. Requires PHP 5.
+ *
+ * Note that persistent connections no longer exist.
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category Database
+ * @package DB
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ * @since Class functional since Release 1.6.3
+ */
+class DB_mysqli extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'mysqli';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'mysqli';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'alter',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => false,
+ 'prepare' => false,
+ 'ssl' => true,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ 1004 => DB_ERROR_CANNOT_CREATE,
+ 1005 => DB_ERROR_CANNOT_CREATE,
+ 1006 => DB_ERROR_CANNOT_CREATE,
+ 1007 => DB_ERROR_ALREADY_EXISTS,
+ 1008 => DB_ERROR_CANNOT_DROP,
+ 1022 => DB_ERROR_ALREADY_EXISTS,
+ 1044 => DB_ERROR_ACCESS_VIOLATION,
+ 1046 => DB_ERROR_NODBSELECTED,
+ 1048 => DB_ERROR_CONSTRAINT,
+ 1049 => DB_ERROR_NOSUCHDB,
+ 1050 => DB_ERROR_ALREADY_EXISTS,
+ 1051 => DB_ERROR_NOSUCHTABLE,
+ 1054 => DB_ERROR_NOSUCHFIELD,
+ 1061 => DB_ERROR_ALREADY_EXISTS,
+ 1062 => DB_ERROR_ALREADY_EXISTS,
+ 1064 => DB_ERROR_SYNTAX,
+ 1091 => DB_ERROR_NOT_FOUND,
+ 1100 => DB_ERROR_NOT_LOCKED,
+ 1136 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 1142 => DB_ERROR_ACCESS_VIOLATION,
+ 1146 => DB_ERROR_NOSUCHTABLE,
+ 1216 => DB_ERROR_CONSTRAINT,
+ 1217 => DB_ERROR_CONSTRAINT,
+ 1356 => DB_ERROR_DIVZERO,
+ 1451 => DB_ERROR_CONSTRAINT,
+ 1452 => DB_ERROR_CONSTRAINT,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The quantity of transactions begun
+ *
+ * {@internal While this is private, it can't actually be designated
+ * private in PHP 5 because it is directly accessed in the test suite.}}
+ *
+ * @var integer
+ * @access private
+ */
+ var $transaction_opcount = 0;
+
+ /**
+ * The database specified in the DSN
+ *
+ * It's a fix to allow calls to different databases in the same script.
+ *
+ * @var string
+ * @access private
+ */
+ var $_db = '';
+
+ /**
+ * Array for converting MYSQLI_*_FLAG constants to text values
+ * @var array
+ * @access public
+ * @since Property available since Release 1.6.5
+ */
+ var $mysqli_flags = array(
+ MYSQLI_NOT_NULL_FLAG => 'not_null',
+ MYSQLI_PRI_KEY_FLAG => 'primary_key',
+ MYSQLI_UNIQUE_KEY_FLAG => 'unique_key',
+ MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key',
+ MYSQLI_BLOB_FLAG => 'blob',
+ MYSQLI_UNSIGNED_FLAG => 'unsigned',
+ MYSQLI_ZEROFILL_FLAG => 'zerofill',
+ MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment',
+ MYSQLI_TIMESTAMP_FLAG => 'timestamp',
+ MYSQLI_SET_FLAG => 'set',
+ // MYSQLI_NUM_FLAG => 'numeric', // unnecessary
+ // MYSQLI_PART_KEY_FLAG => 'multiple_key', // duplicatvie
+ MYSQLI_GROUP_FLAG => 'group_by'
+ );
+
+ /**
+ * Array for converting MYSQLI_TYPE_* constants to text values
+ * @var array
+ * @access public
+ * @since Property available since Release 1.6.5
+ */
+ var $mysqli_types = array(
+ MYSQLI_TYPE_DECIMAL => 'decimal',
+ MYSQLI_TYPE_TINY => 'tinyint',
+ MYSQLI_TYPE_SHORT => 'int',
+ MYSQLI_TYPE_LONG => 'int',
+ MYSQLI_TYPE_FLOAT => 'float',
+ MYSQLI_TYPE_DOUBLE => 'double',
+ // MYSQLI_TYPE_NULL => 'DEFAULT NULL', // let flags handle it
+ MYSQLI_TYPE_TIMESTAMP => 'timestamp',
+ MYSQLI_TYPE_LONGLONG => 'bigint',
+ MYSQLI_TYPE_INT24 => 'mediumint',
+ MYSQLI_TYPE_DATE => 'date',
+ MYSQLI_TYPE_TIME => 'time',
+ MYSQLI_TYPE_DATETIME => 'datetime',
+ MYSQLI_TYPE_YEAR => 'year',
+ MYSQLI_TYPE_NEWDATE => 'date',
+ MYSQLI_TYPE_ENUM => 'enum',
+ MYSQLI_TYPE_SET => 'set',
+ MYSQLI_TYPE_TINY_BLOB => 'tinyblob',
+ MYSQLI_TYPE_MEDIUM_BLOB => 'mediumblob',
+ MYSQLI_TYPE_LONG_BLOB => 'longblob',
+ MYSQLI_TYPE_BLOB => 'blob',
+ MYSQLI_TYPE_VAR_STRING => 'varchar',
+ MYSQLI_TYPE_STRING => 'char',
+ MYSQLI_TYPE_GEOMETRY => 'geometry',
+ /* These constants are conditionally compiled in ext/mysqli, so we'll
+ * define them by number rather than constant. */
+ 16 => 'bit',
+ 246 => 'decimal',
+ );
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_mysqli()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's mysqli driver supports the following extra DSN options:
+ * + When the 'ssl' $option passed to DB::connect() is true:
+ * + key The path to the key file.
+ * + cert The path to the certificate file.
+ * + ca The path to the certificate authority file.
+ * + capath The path to a directory that contains trusted SSL
+ * CA certificates in pem format.
+ * + cipher The list of allowable ciphers for SSL encryption.
+ *
+ * Example of how to connect using SSL:
+ * <code>
+ * require_once 'DB.php';
+ *
+ * $dsn = array(
+ * 'phptype' => 'mysqli',
+ * 'username' => 'someuser',
+ * 'password' => 'apasswd',
+ * 'hostspec' => 'localhost',
+ * 'database' => 'thedb',
+ * 'key' => 'client-key.pem',
+ * 'cert' => 'client-cert.pem',
+ * 'ca' => 'cacert.pem',
+ * 'capath' => '/path/to/ca/dir',
+ * 'cipher' => 'AES',
+ * );
+ *
+ * $options = array(
+ * 'ssl' => true,
+ * );
+ *
+ * $db = DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('mysqli')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $ini = ini_get('track_errors');
+ @ini_set('track_errors', 1);
+ $php_errormsg = '';
+
+ if (((int) $this->getOption('ssl')) === 1) {
+ $init = mysqli_init();
+ mysqli_ssl_set(
+ $init,
+ empty($dsn['key']) ? null : $dsn['key'],
+ empty($dsn['cert']) ? null : $dsn['cert'],
+ empty($dsn['ca']) ? null : $dsn['ca'],
+ empty($dsn['capath']) ? null : $dsn['capath'],
+ empty($dsn['cipher']) ? null : $dsn['cipher']
+ );
+ if ($this->connection = @mysqli_real_connect(
+ $init,
+ $dsn['hostspec'],
+ $dsn['username'],
+ $dsn['password'],
+ $dsn['database'],
+ $dsn['port'],
+ $dsn['socket']))
+ {
+ $this->connection = $init;
+ }
+ } else {
+ $this->connection = @mysqli_connect(
+ $dsn['hostspec'],
+ $dsn['username'],
+ $dsn['password'],
+ $dsn['database'],
+ $dsn['port'],
+ $dsn['socket']
+ );
+ }
+
+ @ini_set('track_errors', $ini);
+
+ if (!$this->connection) {
+ if (($err = @mysqli_connect_error()) != '') {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $err);
+ } else {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+ }
+
+ if ($dsn['database']) {
+ $this->_db = $dsn['database'];
+ }
+
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @mysqli_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ if ($this->_db) {
+ if (!@mysqli_select_db($this->connection, $this->_db)) {
+ return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=0');
+ $result = @mysqli_query($this->connection, 'BEGIN');
+ if (!$result) {
+ return $this->mysqliRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ $result = @mysqli_query($this->connection, $query);
+ if (!$result) {
+ return $this->mysqliRaiseError();
+ }
+ if (is_object($result)) {
+ return $result;
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal mysql result pointer to the next available result.
+ *
+ * This method has not been implemented yet.
+ *
+ * @param resource $result a valid sql result resource
+ * @return false
+ * @access public
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@mysqli_data_seek($result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @mysqli_fetch_array($result, MYSQLI_ASSOC);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @mysqli_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ /*
+ * Even though this DBMS already trims output, we do this because
+ * a field might have intentional whitespace at the end that
+ * gets removed by DB_PORTABILITY_RTRIM under another driver.
+ */
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? mysqli_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @mysqli_num_fields($result);
+ if (!$cols) {
+ return $this->mysqliRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @mysqli_num_rows($result);
+ if ($rows === null) {
+ return $this->mysqliRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ // XXX if $this->transaction_opcount > 0, we should probably
+ // issue a warning here.
+ $this->autocommit = $onoff ? true : false;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if ($this->transaction_opcount > 0) {
+ if ($this->_db) {
+ if (!@mysqli_select_db($this->connection, $this->_db)) {
+ return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ $result = @mysqli_query($this->connection, 'COMMIT');
+ $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1');
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->mysqliRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if ($this->transaction_opcount > 0) {
+ if ($this->_db) {
+ if (!@mysqli_select_db($this->connection, $this->_db)) {
+ return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+ $result = @mysqli_query($this->connection, 'ROLLBACK');
+ $result = @mysqli_query($this->connection, 'SET AUTOCOMMIT=1');
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->mysqliRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->_last_query_manip) {
+ return @mysqli_affected_rows($this->connection);
+ } else {
+ return 0;
+ }
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_mysqli::createSequence(), DB_mysqli::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ do {
+ $repeat = 0;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query('UPDATE ' . $seqname
+ . ' SET id = LAST_INSERT_ID(id + 1)');
+ $this->popErrorHandling();
+ if ($result === DB_OK) {
+ // COMMON CASE
+ $id = @mysqli_insert_id($this->connection);
+ if ($id != 0) {
+ return $id;
+ }
+
+ // EMPTY SEQ TABLE
+ // Sequence table must be empty for some reason,
+ // so fill it and return 1
+ // Obtain a user-level lock
+ $result = $this->getOne('SELECT GET_LOCK('
+ . "'${seqname}_lock', 10)");
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ if ($result == 0) {
+ return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED);
+ }
+
+ // add the default value
+ $result = $this->query('REPLACE INTO ' . $seqname
+ . ' (id) VALUES (0)');
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+
+ // Release the lock
+ $result = $this->getOne('SELECT RELEASE_LOCK('
+ . "'${seqname}_lock')");
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ // We know what the result will be, so no need to try again
+ return 1;
+
+ } elseif ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE)
+ {
+ // ONDEMAND TABLE CREATION
+ $result = $this->createSequence($seq_name);
+
+ // Since createSequence initializes the ID to be 1,
+ // we do not need to retrieve the ID again (or we will get 2)
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ } else {
+ // First ID of a newly created sequence is 1
+ return 1;
+ }
+
+ } elseif (DB::isError($result) &&
+ $result->getCode() == DB_ERROR_ALREADY_EXISTS)
+ {
+ // BACKWARDS COMPAT
+ // see _BCsequence() comment
+ $result = $this->_BCsequence($seqname);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $repeat = 1;
+ }
+ } while ($repeat);
+
+ return $this->raiseError($result);
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_mysqli::nextID(), DB_mysqli::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $res = $this->query('CREATE TABLE ' . $seqname
+ . ' (id INTEGER UNSIGNED AUTO_INCREMENT NOT NULL,'
+ . ' PRIMARY KEY(id))');
+ if (DB::isError($res)) {
+ return $res;
+ }
+ // insert yields value 1, nextId call will generate ID 2
+ return $this->query("INSERT INTO ${seqname} (id) VALUES (0)");
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_mysql::nextID(), DB_mysql::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ _BCsequence()
+
+ /**
+ * Backwards compatibility with old sequence emulation implementation
+ * (clean up the dupes)
+ *
+ * @param string $seqname the sequence name to clean up
+ *
+ * @return bool true on success. A DB_Error object on failure.
+ *
+ * @access private
+ */
+ function _BCsequence($seqname)
+ {
+ // Obtain a user-level lock... this will release any previous
+ // application locks, but unlike LOCK TABLES, it does not abort
+ // the current transaction and is much less frequently used.
+ $result = $this->getOne("SELECT GET_LOCK('${seqname}_lock',10)");
+ if (DB::isError($result)) {
+ return $result;
+ }
+ if ($result == 0) {
+ // Failed to get the lock, can't do the conversion, bail
+ // with a DB_ERROR_NOT_LOCKED error
+ return $this->mysqliRaiseError(DB_ERROR_NOT_LOCKED);
+ }
+
+ $highest_id = $this->getOne("SELECT MAX(id) FROM ${seqname}");
+ if (DB::isError($highest_id)) {
+ return $highest_id;
+ }
+
+ // This should kill all rows except the highest
+ // We should probably do something if $highest_id isn't
+ // numeric, but I'm at a loss as how to handle that...
+ $result = $this->query('DELETE FROM ' . $seqname
+ . " WHERE id <> $highest_id");
+ if (DB::isError($result)) {
+ return $result;
+ }
+
+ // If another thread has been waiting for this lock,
+ // it will go thru the above procedure, but will have no
+ // real effect
+ $result = $this->getOne("SELECT RELEASE_LOCK('${seqname}_lock')");
+ if (DB::isError($result)) {
+ return $result;
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ quoteIdentifier()
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ * (WARNING: using names that require this is a REALLY BAD IDEA)
+ *
+ * WARNING: Older versions of MySQL can't handle the backtick
+ * character (<kbd>`</kbd>) in table or column names.
+ *
+ * @param string $str identifier name to be quoted
+ *
+ * @return string quoted identifier string
+ *
+ * @see DB_common::quoteIdentifier()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteIdentifier($str)
+ {
+ return '`' . str_replace('`', '``', $str) . '`';
+ }
+
+ // }}}
+ // {{{ escapeSimple()
+
+ /**
+ * Escapes a string according to the current DBMS's standards
+ *
+ * @param string $str the string to be escaped
+ *
+ * @return string the escaped string
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function escapeSimple($str)
+ {
+ return @mysqli_real_escape_string($this->connection, $str);
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ if (DB::isManip($query) || $this->_next_query_manip) {
+ return $query . " LIMIT $count";
+ } else {
+ return $query . " LIMIT $from, $count";
+ }
+ }
+
+ // }}}
+ // {{{ mysqliRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_mysqli::errorNative(), DB_common::errorCode()
+ */
+ function mysqliRaiseError($errno = null)
+ {
+ if ($errno === null) {
+ if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+ $this->errorcode_map[1022] = DB_ERROR_CONSTRAINT;
+ $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT_NOT_NULL;
+ $this->errorcode_map[1062] = DB_ERROR_CONSTRAINT;
+ } else {
+ // Doing this in case mode changes during runtime.
+ $this->errorcode_map[1022] = DB_ERROR_ALREADY_EXISTS;
+ $this->errorcode_map[1048] = DB_ERROR_CONSTRAINT;
+ $this->errorcode_map[1062] = DB_ERROR_ALREADY_EXISTS;
+ }
+ $errno = $this->errorCode(mysqli_errno($this->connection));
+ }
+ return $this->raiseError($errno, null, null, null,
+ @mysqli_errno($this->connection) . ' ** ' .
+ @mysqli_error($this->connection));
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return int the DBMS' error code
+ */
+ function errorNative()
+ {
+ return @mysqli_errno($this->connection);
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::setOption()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ // Fix for bug #11580.
+ if ($this->_db) {
+ if (!@mysqli_select_db($this->connection, $this->_db)) {
+ return $this->mysqliRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ }
+
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @mysqli_query($this->connection,
+ "SELECT * FROM $result LIMIT 0");
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_a($id, 'mysqli_result')) {
+ return $this->mysqliRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @mysqli_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $tmp = @mysqli_fetch_field($id);
+
+ $flags = '';
+ foreach ($this->mysqli_flags as $const => $means) {
+ if ($tmp->flags & $const) {
+ $flags .= $means . ' ';
+ }
+ }
+ if ($tmp->def) {
+ $flags .= 'default_' . rawurlencode($tmp->def);
+ }
+ $flags = trim($flags);
+
+ $res[$i] = array(
+ 'table' => $case_func($tmp->table),
+ 'name' => $case_func($tmp->name),
+ 'type' => isset($this->mysqli_types[$tmp->type])
+ ? $this->mysqli_types[$tmp->type]
+ : 'unknown',
+ // http://bugs.php.net/?id=36579
+ 'len' => $tmp->length,
+ 'flags' => $flags,
+ );
+
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @mysqli_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SHOW TABLES';
+ case 'users':
+ return 'SELECT DISTINCT User FROM mysql.user';
+ case 'databases':
+ return 'SHOW DATABASES';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/oci8.php b/extlib/DB/oci8.php
new file mode 100644
index 000000000..d30794871
--- /dev/null
+++ b/extlib/DB/oci8.php
@@ -0,0 +1,1156 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's oci8 extension
+ * for interacting with Oracle databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author James L. Pine <jlp@valinux.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: oci8.php,v 1.116 2007/11/28 02:22:39 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's oci8 extension
+ * for interacting with Oracle databases
+ *
+ * Definitely works with versions 8 and 9 of Oracle.
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * Be aware... OCIError() only appears to return anything when given a
+ * statement, so functions return the generic DB_ERROR instead of more
+ * useful errors that have to do with feedback from the database.
+ *
+ * @category Database
+ * @package DB
+ * @author James L. Pine <jlp@valinux.com>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_oci8 extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'oci8';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'oci8';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'alter',
+ 'new_link' => '5.0.0',
+ 'numrows' => 'subquery',
+ 'pconnect' => true,
+ 'prepare' => true,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ 1 => DB_ERROR_CONSTRAINT,
+ 900 => DB_ERROR_SYNTAX,
+ 904 => DB_ERROR_NOSUCHFIELD,
+ 913 => DB_ERROR_VALUE_COUNT_ON_ROW,
+ 921 => DB_ERROR_SYNTAX,
+ 923 => DB_ERROR_SYNTAX,
+ 942 => DB_ERROR_NOSUCHTABLE,
+ 955 => DB_ERROR_ALREADY_EXISTS,
+ 1400 => DB_ERROR_CONSTRAINT_NOT_NULL,
+ 1401 => DB_ERROR_INVALID,
+ 1407 => DB_ERROR_CONSTRAINT_NOT_NULL,
+ 1418 => DB_ERROR_NOT_FOUND,
+ 1476 => DB_ERROR_DIVZERO,
+ 1722 => DB_ERROR_INVALID_NUMBER,
+ 2289 => DB_ERROR_NOSUCHTABLE,
+ 2291 => DB_ERROR_CONSTRAINT,
+ 2292 => DB_ERROR_CONSTRAINT,
+ 2449 => DB_ERROR_CONSTRAINT,
+ 12899 => DB_ERROR_INVALID,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * Stores the $data passed to execute() in the oci8 driver
+ *
+ * Gets reset to array() when simpleQuery() is run.
+ *
+ * Needed in case user wants to call numRows() after prepare/execute
+ * was used.
+ *
+ * @var array
+ * @access private
+ */
+ var $_data = array();
+
+ /**
+ * The result or statement handle from the most recently executed query
+ * @var resource
+ */
+ var $last_stmt;
+
+ /**
+ * Is the given prepared statement a data manipulation query?
+ * @var array
+ * @access private
+ */
+ var $manip_query = array();
+
+ /**
+ * Store of prepared SQL queries.
+ * @var array
+ * @access private
+ */
+ var $_prepared_queries = array();
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_oci8()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * If PHP is at version 5.0.0 or greater:
+ * + Generally, oci_connect() or oci_pconnect() are used.
+ * + But if the new_link DSN option is set to true, oci_new_connect()
+ * is used.
+ *
+ * When using PHP version 4.x, OCILogon() or OCIPLogon() are used.
+ *
+ * PEAR DB's oci8 driver supports the following extra DSN options:
+ * + charset The character set to be used on the connection.
+ * Only used if PHP is at version 5.0.0 or greater
+ * and the Oracle server is at 9.2 or greater.
+ * Available since PEAR DB 1.7.0.
+ * + new_link If set to true, causes subsequent calls to
+ * connect() to return a new connection link
+ * instead of the existing one. WARNING: this is
+ * not portable to other DBMS's.
+ * Available since PEAR DB 1.7.0.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('oci8')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ // Backwards compatibility with DB < 1.7.0
+ if (empty($dsn['database']) && !empty($dsn['hostspec'])) {
+ $db = $dsn['hostspec'];
+ } else {
+ $db = $dsn['database'];
+ }
+
+ if (function_exists('oci_connect')) {
+ if (isset($dsn['new_link'])
+ && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+ {
+ $connect_function = 'oci_new_connect';
+ } else {
+ $connect_function = $persistent ? 'oci_pconnect'
+ : 'oci_connect';
+ }
+ if (isset($this->dsn['port']) && $this->dsn['port']) {
+ $db = '//'.$db.':'.$this->dsn['port'];
+ }
+
+ $char = empty($dsn['charset']) ? null : $dsn['charset'];
+ $this->connection = @$connect_function($dsn['username'],
+ $dsn['password'],
+ $db,
+ $char);
+ $error = OCIError();
+ if (!empty($error) && $error['code'] == 12541) {
+ // Couldn't find TNS listener. Try direct connection.
+ $this->connection = @$connect_function($dsn['username'],
+ $dsn['password'],
+ null,
+ $char);
+ }
+ } else {
+ $connect_function = $persistent ? 'OCIPLogon' : 'OCILogon';
+ if ($db) {
+ $this->connection = @$connect_function($dsn['username'],
+ $dsn['password'],
+ $db);
+ } elseif ($dsn['username'] || $dsn['password']) {
+ $this->connection = @$connect_function($dsn['username'],
+ $dsn['password']);
+ }
+ }
+
+ if (!$this->connection) {
+ $error = OCIError();
+ $error = (is_array($error)) ? $error['message'] : null;
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $error);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ if (function_exists('oci_close')) {
+ $ret = @oci_close($this->connection);
+ } else {
+ $ret = @OCILogOff($this->connection);
+ }
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * To determine how many rows of a result set get buffered using
+ * ocisetprefetch(), see the "result_buffering" option in setOptions().
+ * This option was added in Release 1.7.0.
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $this->_data = array();
+ $this->last_parameters = array();
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ $result = @OCIParse($this->connection, $query);
+ if (!$result) {
+ return $this->oci8RaiseError();
+ }
+ if ($this->autocommit) {
+ $success = @OCIExecute($result,OCI_COMMIT_ON_SUCCESS);
+ } else {
+ $success = @OCIExecute($result,OCI_DEFAULT);
+ }
+ if (!$success) {
+ return $this->oci8RaiseError($result);
+ }
+ $this->last_stmt = $result;
+ if ($this->_checkManip($query)) {
+ return DB_OK;
+ } else {
+ @ocisetprefetch($result, $this->options['result_buffering']);
+ return $result;
+ }
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal oracle result pointer to the next available result
+ *
+ * @param a valid oci8 result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $moredata = @OCIFetchInto($result,$arr,OCI_ASSOC+OCI_RETURN_NULLS+OCI_RETURN_LOBS);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE &&
+ $moredata)
+ {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $moredata = OCIFetchInto($result,$arr,OCI_RETURN_NULLS+OCI_RETURN_LOBS);
+ }
+ if (!$moredata) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? OCIFreeStatement($result) : false;
+ }
+
+ /**
+ * Frees the internal resources associated with a prepared query
+ *
+ * @param resource $stmt the prepared statement's resource
+ * @param bool $free_resource should the PHP resource be freed too?
+ * Use false if you need to get data
+ * from the result set later.
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_oci8::prepare()
+ */
+ function freePrepared($stmt, $free_resource = true)
+ {
+ if (!is_resource($stmt)) {
+ return false;
+ }
+ if ($free_resource) {
+ @ocifreestatement($stmt);
+ }
+ if (isset($this->prepare_types[(int)$stmt])) {
+ unset($this->prepare_types[(int)$stmt]);
+ unset($this->manip_query[(int)$stmt]);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * Only works if the DB_PORTABILITY_NUMROWS portability option
+ * is turned on.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows(), DB_common::setOption()
+ */
+ function numRows($result)
+ {
+ // emulate numRows for Oracle. yuck.
+ if ($this->options['portability'] & DB_PORTABILITY_NUMROWS &&
+ $result === $this->last_stmt)
+ {
+ $countquery = 'SELECT COUNT(*) FROM ('.$this->last_query.')';
+ $save_query = $this->last_query;
+ $save_stmt = $this->last_stmt;
+
+ $count = $this->query($countquery);
+
+ // Restore the last query and statement.
+ $this->last_query = $save_query;
+ $this->last_stmt = $save_stmt;
+
+ if (DB::isError($count) ||
+ DB::isError($row = $count->fetchRow(DB_FETCHMODE_ORDERED)))
+ {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ return $row[0];
+ }
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @OCINumCols($result);
+ if (!$cols) {
+ return $this->oci8RaiseError($result);
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ prepare()
+
+ /**
+ * Prepares a query for multiple execution with execute().
+ *
+ * With oci8, this is emulated.
+ *
+ * prepare() requires a generic query as string like <code>
+ * INSERT INTO numbers VALUES (?, ?, ?)
+ * </code>. The <kbd>?</kbd> characters are placeholders.
+ *
+ * Three types of placeholders can be used:
+ * + <kbd>?</kbd> a quoted scalar value, i.e. strings, integers
+ * + <kbd>!</kbd> value is inserted 'as is'
+ * + <kbd>&</kbd> requires a file name. The file's contents get
+ * inserted into the query (i.e. saving binary
+ * data in a db)
+ *
+ * Use backslashes to escape placeholder characters if you don't want
+ * them to be interpreted as placeholders. Example: <code>
+ * "UPDATE foo SET col=? WHERE col='over \& under'"
+ * </code>
+ *
+ * @param string $query the query to be prepared
+ *
+ * @return mixed DB statement resource on success. DB_Error on failure.
+ *
+ * @see DB_oci8::execute()
+ */
+ function prepare($query)
+ {
+ $tokens = preg_split('/((?<!\\\)[&?!])/', $query, -1,
+ PREG_SPLIT_DELIM_CAPTURE);
+ $binds = count($tokens) - 1;
+ $token = 0;
+ $types = array();
+ $newquery = '';
+
+ foreach ($tokens as $key => $val) {
+ switch ($val) {
+ case '?':
+ $types[$token++] = DB_PARAM_SCALAR;
+ unset($tokens[$key]);
+ break;
+ case '&':
+ $types[$token++] = DB_PARAM_OPAQUE;
+ unset($tokens[$key]);
+ break;
+ case '!':
+ $types[$token++] = DB_PARAM_MISC;
+ unset($tokens[$key]);
+ break;
+ default:
+ $tokens[$key] = preg_replace('/\\\([&?!])/', "\\1", $val);
+ if ($key != $binds) {
+ $newquery .= $tokens[$key] . ':bind' . $token;
+ } else {
+ $newquery .= $tokens[$key];
+ }
+ }
+ }
+
+ $this->last_query = $query;
+ $newquery = $this->modifyQuery($newquery);
+ if (!$stmt = @OCIParse($this->connection, $newquery)) {
+ return $this->oci8RaiseError();
+ }
+ $this->prepare_types[(int)$stmt] = $types;
+ $this->manip_query[(int)$stmt] = DB::isManip($query);
+ $this->_prepared_queries[(int)$stmt] = $newquery;
+ return $stmt;
+ }
+
+ // }}}
+ // {{{ execute()
+
+ /**
+ * Executes a DB statement prepared with prepare().
+ *
+ * To determine how many rows of a result set get buffered using
+ * ocisetprefetch(), see the "result_buffering" option in setOptions().
+ * This option was added in Release 1.7.0.
+ *
+ * @param resource $stmt a DB statement resource returned from prepare()
+ * @param mixed $data array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 for non-array items or the
+ * quantity of elements in the array.
+ *
+ * @return mixed returns an oic8 result resource for successful SELECT
+ * queries, DB_OK for other successful queries.
+ * A DB error object is returned on failure.
+ *
+ * @see DB_oci8::prepare()
+ */
+ function &execute($stmt, $data = array())
+ {
+ $data = (array)$data;
+ $this->last_parameters = $data;
+ $this->last_query = $this->_prepared_queries[(int)$stmt];
+ $this->_data = $data;
+
+ $types = $this->prepare_types[(int)$stmt];
+ if (count($types) != count($data)) {
+ $tmp = $this->raiseError(DB_ERROR_MISMATCH);
+ return $tmp;
+ }
+
+ $i = 0;
+ foreach ($data as $key => $value) {
+ if ($types[$i] == DB_PARAM_MISC) {
+ /*
+ * Oracle doesn't seem to have the ability to pass a
+ * parameter along unchanged, so strip off quotes from start
+ * and end, plus turn two single quotes to one single quote,
+ * in order to avoid the quotes getting escaped by
+ * Oracle and ending up in the database.
+ */
+ $data[$key] = preg_replace("/^'(.*)'$/", "\\1", $data[$key]);
+ $data[$key] = str_replace("''", "'", $data[$key]);
+ } elseif ($types[$i] == DB_PARAM_OPAQUE) {
+ $fp = @fopen($data[$key], 'rb');
+ if (!$fp) {
+ $tmp = $this->raiseError(DB_ERROR_ACCESS_VIOLATION);
+ return $tmp;
+ }
+ $data[$key] = fread($fp, filesize($data[$key]));
+ fclose($fp);
+ } elseif ($types[$i] == DB_PARAM_SCALAR) {
+ // Floats have to be converted to a locale-neutral
+ // representation.
+ if (is_float($data[$key])) {
+ $data[$key] = $this->quoteFloat($data[$key]);
+ }
+ }
+ if (!@OCIBindByName($stmt, ':bind' . $i, $data[$key], -1)) {
+ $tmp = $this->oci8RaiseError($stmt);
+ return $tmp;
+ }
+ $this->last_query = preg_replace("/:bind$i/",$this->quoteSmart($data[$key]),$this->last_query,1);
+ $i++;
+ }
+ if ($this->autocommit) {
+ $success = @OCIExecute($stmt, OCI_COMMIT_ON_SUCCESS);
+ } else {
+ $success = @OCIExecute($stmt, OCI_DEFAULT);
+ }
+ if (!$success) {
+ $tmp = $this->oci8RaiseError($stmt);
+ return $tmp;
+ }
+ $this->last_stmt = $stmt;
+ if ($this->manip_query[(int)$stmt] || $this->_next_query_manip) {
+ $this->_last_query_manip = true;
+ $this->_next_query_manip = false;
+ $tmp = DB_OK;
+ } else {
+ $this->_last_query_manip = false;
+ @ocisetprefetch($stmt, $this->options['result_buffering']);
+ $tmp = new DB_result($this, $stmt);
+ }
+ return $tmp;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ $this->autocommit = (bool)$onoff;;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ $result = @OCICommit($this->connection);
+ if (!$result) {
+ return $this->oci8RaiseError();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ $result = @OCIRollback($this->connection);
+ if (!$result) {
+ return $this->oci8RaiseError();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->last_stmt === false) {
+ return $this->oci8RaiseError();
+ }
+ $result = @OCIRowCount($this->last_stmt);
+ if ($result === false) {
+ return $this->oci8RaiseError($this->last_stmt);
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ modifyQuery()
+
+ /**
+ * Changes a query string for various DBMS specific reasons
+ *
+ * "SELECT 2+2" must be "SELECT 2+2 FROM dual" in Oracle.
+ *
+ * @param string $query the query string to modify
+ *
+ * @return string the modified query string
+ *
+ * @access protected
+ */
+ function modifyQuery($query)
+ {
+ if (preg_match('/^\s*SELECT/i', $query) &&
+ !preg_match('/\sFROM\s/i', $query)) {
+ $query .= ' FROM dual';
+ }
+ return $query;
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ // Let Oracle return the name of the columns instead of
+ // coding a "home" SQL parser
+
+ if (count($params)) {
+ $result = $this->prepare("SELECT * FROM ($query) "
+ . 'WHERE NULL = NULL');
+ $tmp = $this->execute($result, $params);
+ } else {
+ $q_fields = "SELECT * FROM ($query) WHERE NULL = NULL";
+
+ if (!$result = @OCIParse($this->connection, $q_fields)) {
+ $this->last_query = $q_fields;
+ return $this->oci8RaiseError();
+ }
+ if (!@OCIExecute($result, OCI_DEFAULT)) {
+ $this->last_query = $q_fields;
+ return $this->oci8RaiseError($result);
+ }
+ }
+
+ $ncols = OCINumCols($result);
+ $cols = array();
+ for ( $i = 1; $i <= $ncols; $i++ ) {
+ $cols[] = '"' . OCIColumnName($result, $i) . '"';
+ }
+ $fields = implode(', ', $cols);
+ // XXX Test that (tip by John Lim)
+ //if (preg_match('/^\s*SELECT\s+/is', $query, $match)) {
+ // // Introduce the FIRST_ROWS Oracle query optimizer
+ // $query = substr($query, strlen($match[0]), strlen($query));
+ // $query = "SELECT /* +FIRST_ROWS */ " . $query;
+ //}
+
+ // Construct the query
+ // more at: http://marc.theaimsgroup.com/?l=php-db&m=99831958101212&w=2
+ // Perhaps this could be optimized with the use of Unions
+ $query = "SELECT $fields FROM".
+ " (SELECT rownum as linenum, $fields FROM".
+ " ($query)".
+ ' WHERE rownum <= '. ($from + $count) .
+ ') WHERE linenum >= ' . ++$from;
+ return $query;
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_oci8::createSequence(), DB_oci8::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $repeat = 0;
+ do {
+ $this->expectError(DB_ERROR_NOSUCHTABLE);
+ $result = $this->query("SELECT ${seqname}.nextval FROM dual");
+ $this->popExpect();
+ if ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+ $repeat = 1;
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ } else {
+ $repeat = 0;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ return $arr[0];
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_oci8::nextID(), DB_oci8::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ return $this->query('CREATE SEQUENCE '
+ . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_oci8::nextID(), DB_oci8::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP SEQUENCE '
+ . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ oci8RaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_oci8::errorNative(), DB_oci8::errorCode()
+ */
+ function oci8RaiseError($errno = null)
+ {
+ if ($errno === null) {
+ $error = @OCIError($this->connection);
+ return $this->raiseError($this->errorCode($error['code']),
+ null, null, null, $error['message']);
+ } elseif (is_resource($errno)) {
+ $error = @OCIError($errno);
+ return $this->raiseError($this->errorCode($error['code']),
+ null, null, null, $error['message']);
+ }
+ return $this->raiseError($this->errorCode($errno));
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code produced by the last query
+ *
+ * @return int the DBMS' error code. FALSE if the code could not be
+ * determined
+ */
+ function errorNative()
+ {
+ if (is_resource($this->last_stmt)) {
+ $error = @OCIError($this->last_stmt);
+ } else {
+ $error = @OCIError($this->connection);
+ }
+ if (is_array($error)) {
+ return $error['code'];
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+ * is a table name.
+ *
+ * NOTE: flags won't contain index information.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $res = array();
+
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $result = strtoupper($result);
+ $q_fields = 'SELECT column_name, data_type, data_length, '
+ . 'nullable '
+ . 'FROM user_tab_columns '
+ . "WHERE table_name='$result' ORDER BY column_id";
+
+ $this->last_query = $q_fields;
+
+ if (!$stmt = @OCIParse($this->connection, $q_fields)) {
+ return $this->oci8RaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+ if (!@OCIExecute($stmt, OCI_DEFAULT)) {
+ return $this->oci8RaiseError($stmt);
+ }
+
+ $i = 0;
+ while (@OCIFetch($stmt)) {
+ $res[$i] = array(
+ 'table' => $case_func($result),
+ 'name' => $case_func(@OCIResult($stmt, 1)),
+ 'type' => @OCIResult($stmt, 2),
+ 'len' => @OCIResult($stmt, 3),
+ 'flags' => (@OCIResult($stmt, 4) == 'N') ? 'not_null' : '',
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ $i++;
+ }
+
+ if ($mode) {
+ $res['num_fields'] = $i;
+ }
+ @OCIFreeStatement($stmt);
+
+ } else {
+ if (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $result = $result->result;
+ }
+
+ $res = array();
+
+ if ($result === $this->last_stmt) {
+ $count = @OCINumCols($result);
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+ for ($i = 0; $i < $count; $i++) {
+ $res[$i] = array(
+ 'table' => '',
+ 'name' => $case_func(@OCIColumnName($result, $i+1)),
+ 'type' => @OCIColumnType($result, $i+1),
+ 'len' => @OCIColumnSize($result, $i+1),
+ 'flags' => '',
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+ } else {
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE);
+ }
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SELECT table_name FROM user_tables';
+ case 'synonyms':
+ return 'SELECT synonym_name FROM user_synonyms';
+ case 'views':
+ return 'SELECT view_name FROM user_views';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+ // {{{ quoteFloat()
+
+ /**
+ * Formats a float value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param float the float value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteFloat($float) {
+ return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/odbc.php b/extlib/DB/odbc.php
new file mode 100644
index 000000000..eba43659a
--- /dev/null
+++ b/extlib/DB/odbc.php
@@ -0,0 +1,883 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's odbc extension
+ * for interacting with databases via ODBC connections
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: odbc.php,v 1.81 2007/07/06 05:19:21 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's odbc extension
+ * for interacting with databases via ODBC connections
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * More info on ODBC errors could be found here:
+ * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/trblsql/tr_err_odbc_5stz.asp
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <ssb@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_odbc extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'odbc';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'sql92';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * NOTE: The feature set of the following drivers are different than
+ * the default:
+ * + solid: 'transactions' = true
+ * + navision: 'limit' = false
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'emulate',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => false,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ '01004' => DB_ERROR_TRUNCATED,
+ '07001' => DB_ERROR_MISMATCH,
+ '21S01' => DB_ERROR_VALUE_COUNT_ON_ROW,
+ '21S02' => DB_ERROR_MISMATCH,
+ '22001' => DB_ERROR_INVALID,
+ '22003' => DB_ERROR_INVALID_NUMBER,
+ '22005' => DB_ERROR_INVALID_NUMBER,
+ '22008' => DB_ERROR_INVALID_DATE,
+ '22012' => DB_ERROR_DIVZERO,
+ '23000' => DB_ERROR_CONSTRAINT,
+ '23502' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '23503' => DB_ERROR_CONSTRAINT,
+ '23504' => DB_ERROR_CONSTRAINT,
+ '23505' => DB_ERROR_CONSTRAINT,
+ '24000' => DB_ERROR_INVALID,
+ '34000' => DB_ERROR_INVALID,
+ '37000' => DB_ERROR_SYNTAX,
+ '42000' => DB_ERROR_SYNTAX,
+ '42601' => DB_ERROR_SYNTAX,
+ 'IM001' => DB_ERROR_UNSUPPORTED,
+ 'S0000' => DB_ERROR_NOSUCHTABLE,
+ 'S0001' => DB_ERROR_ALREADY_EXISTS,
+ 'S0002' => DB_ERROR_NOSUCHTABLE,
+ 'S0011' => DB_ERROR_ALREADY_EXISTS,
+ 'S0012' => DB_ERROR_NOT_FOUND,
+ 'S0021' => DB_ERROR_ALREADY_EXISTS,
+ 'S0022' => DB_ERROR_NOSUCHFIELD,
+ 'S1009' => DB_ERROR_INVALID,
+ 'S1090' => DB_ERROR_INVALID,
+ 'S1C00' => DB_ERROR_NOT_CAPABLE,
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * The number of rows affected by a data manipulation query
+ * @var integer
+ * @access private
+ */
+ var $affected = 0;
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_odbc()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's odbc driver supports the following extra DSN options:
+ * + cursor The type of cursor to be used for this connection.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('odbc')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+ switch ($this->dbsyntax) {
+ case 'access':
+ case 'db2':
+ case 'solid':
+ $this->features['transactions'] = true;
+ break;
+ case 'navision':
+ $this->features['limit'] = false;
+ }
+
+ /*
+ * This is hear for backwards compatibility. Should have been using
+ * 'database' all along, but prior to 1.6.0RC3 'hostspec' was used.
+ */
+ if ($dsn['database']) {
+ $odbcdsn = $dsn['database'];
+ } elseif ($dsn['hostspec']) {
+ $odbcdsn = $dsn['hostspec'];
+ } else {
+ $odbcdsn = 'localhost';
+ }
+
+ $connect_function = $persistent ? 'odbc_pconnect' : 'odbc_connect';
+
+ if (empty($dsn['cursor'])) {
+ $this->connection = @$connect_function($odbcdsn, $dsn['username'],
+ $dsn['password']);
+ } else {
+ $this->connection = @$connect_function($odbcdsn, $dsn['username'],
+ $dsn['password'],
+ $dsn['cursor']);
+ }
+
+ if (!is_resource($this->connection)) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $this->errorNative());
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $err = @odbc_close($this->connection);
+ $this->connection = null;
+ return $err;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ $result = @odbc_exec($this->connection, $query);
+ if (!$result) {
+ return $this->odbcRaiseError(); // XXX ERRORMSG
+ }
+ // Determine which queries that should return data, and which
+ // should return an error code only.
+ if ($this->_checkManip($query)) {
+ $this->affected = $result; // For affectedRows()
+ return DB_OK;
+ }
+ $this->affected = 0;
+ return $result;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal odbc result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return @odbc_next_result($result);
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ $arr = array();
+ if ($rownum !== null) {
+ $rownum++; // ODBC first row is 1
+ if (version_compare(phpversion(), '4.2.0', 'ge')) {
+ $cols = @odbc_fetch_into($result, $arr, $rownum);
+ } else {
+ $cols = @odbc_fetch_into($result, $rownum, $arr);
+ }
+ } else {
+ $cols = @odbc_fetch_into($result, $arr);
+ }
+ if (!$cols) {
+ return null;
+ }
+ if ($fetchmode !== DB_FETCHMODE_ORDERED) {
+ for ($i = 0; $i < count($arr); $i++) {
+ $colName = @odbc_field_name($result, $i+1);
+ $a[$colName] = $arr[$i];
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $a = array_change_key_case($a, CASE_LOWER);
+ }
+ $arr = $a;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? odbc_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @odbc_num_fields($result);
+ if (!$cols) {
+ return $this->odbcRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if (empty($this->affected)) { // In case of SELECT stms
+ return 0;
+ }
+ $nrows = @odbc_num_rows($this->affected);
+ if ($nrows == -1) {
+ return $this->odbcRaiseError();
+ }
+ return $nrows;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * Not all ODBC drivers support this functionality. If they don't
+ * a DB_Error object for DB_ERROR_UNSUPPORTED is returned.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $nrows = @odbc_num_rows($result);
+ if ($nrows == -1) {
+ return $this->odbcRaiseError(DB_ERROR_UNSUPPORTED);
+ }
+ if ($nrows === false) {
+ return $this->odbcRaiseError();
+ }
+ return $nrows;
+ }
+
+ // }}}
+ // {{{ quoteIdentifier()
+
+ /**
+ * Quotes a string so it can be safely used as a table or column name
+ *
+ * Use 'mssql' as the dbsyntax in the DB DSN only if you've unchecked
+ * "Use ANSI quoted identifiers" when setting up the ODBC data source.
+ *
+ * @param string $str identifier name to be quoted
+ *
+ * @return string quoted identifier string
+ *
+ * @see DB_common::quoteIdentifier()
+ * @since Method available since Release 1.6.0
+ */
+ function quoteIdentifier($str)
+ {
+ switch ($this->dsn['dbsyntax']) {
+ case 'access':
+ return '[' . $str . ']';
+ case 'mssql':
+ case 'sybase':
+ return '[' . str_replace(']', ']]', $str) . ']';
+ case 'mysql':
+ case 'mysqli':
+ return '`' . $str . '`';
+ default:
+ return '"' . str_replace('"', '""', $str) . '"';
+ }
+ }
+
+ // }}}
+ // {{{ quote()
+
+ /**
+ * @deprecated Deprecated in release 1.6.0
+ * @internal
+ */
+ function quote($str)
+ {
+ return $this->quoteSmart($str);
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_odbc::createSequence(), DB_odbc::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $repeat = 0;
+ do {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("update ${seqname} set id = id + 1");
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+ $repeat = 1;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->createSequence($seq_name);
+ $this->popErrorHandling();
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $result = $this->query("insert into ${seqname} (id) values(0)");
+ } else {
+ $repeat = 0;
+ }
+ } while ($repeat);
+
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+
+ $result = $this->query("select id from ${seqname}");
+ if (DB::isError($result)) {
+ return $result;
+ }
+
+ $row = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ if (DB::isError($row || !$row)) {
+ return $row;
+ }
+
+ return $row[0];
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_odbc::nextID(), DB_odbc::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ return $this->query('CREATE TABLE '
+ . $this->getSequenceName($seq_name)
+ . ' (id integer NOT NULL,'
+ . ' PRIMARY KEY(id))');
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_odbc::nextID(), DB_odbc::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ if (!@odbc_autocommit($this->connection, $onoff)) {
+ return $this->odbcRaiseError();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if (!@odbc_commit($this->connection)) {
+ return $this->odbcRaiseError();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if (!@odbc_rollback($this->connection)) {
+ return $this->odbcRaiseError();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ odbcRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_odbc::errorNative(), DB_common::errorCode()
+ */
+ function odbcRaiseError($errno = null)
+ {
+ if ($errno === null) {
+ switch ($this->dbsyntax) {
+ case 'access':
+ if ($this->options['portability'] & DB_PORTABILITY_ERRORS) {
+ $this->errorcode_map['07001'] = DB_ERROR_NOSUCHFIELD;
+ } else {
+ // Doing this in case mode changes during runtime.
+ $this->errorcode_map['07001'] = DB_ERROR_MISMATCH;
+ }
+
+ $native_code = odbc_error($this->connection);
+
+ // S1000 is for "General Error." Let's be more specific.
+ if ($native_code == 'S1000') {
+ $errormsg = odbc_errormsg($this->connection);
+ static $error_regexps;
+ if (!isset($error_regexps)) {
+ $error_regexps = array(
+ '/includes related records.$/i' => DB_ERROR_CONSTRAINT,
+ '/cannot contain a Null value/i' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ );
+ }
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match($regexp, $errormsg)) {
+ return $this->raiseError($code,
+ null, null, null,
+ $native_code . ' ' . $errormsg);
+ }
+ }
+ $errno = DB_ERROR;
+ } else {
+ $errno = $this->errorCode($native_code);
+ }
+ break;
+ default:
+ $errno = $this->errorCode(odbc_error($this->connection));
+ }
+ }
+ return $this->raiseError($errno, null, null, null,
+ $this->errorNative());
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error code and message produced by the last query
+ *
+ * @return string the DBMS' error code and message
+ */
+ function errorNative()
+ {
+ if (!is_resource($this->connection)) {
+ return @odbc_error() . ' ' . @odbc_errormsg();
+ }
+ return @odbc_error($this->connection) . ' ' . @odbc_errormsg($this->connection);
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ * @since Method available since Release 1.7.0
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @odbc_exec($this->connection, "SELECT * FROM $result");
+ if (!$id) {
+ return $this->odbcRaiseError();
+ }
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->odbcRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @odbc_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $col = $i + 1;
+ $res[$i] = array(
+ 'table' => $got_string ? $case_func($result) : '',
+ 'name' => $case_func(@odbc_field_name($id, $col)),
+ 'type' => @odbc_field_type($id, $col),
+ 'len' => @odbc_field_len($id, $col),
+ 'flags' => '',
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @odbc_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * Thanks to symbol1@gmail.com and Philippe.Jausions@11abacus.com.
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the list of objects requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ * @since Method available since Release 1.7.0
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'databases':
+ if (!function_exists('odbc_data_source')) {
+ return null;
+ }
+ $res = @odbc_data_source($this->connection, SQL_FETCH_FIRST);
+ if (is_array($res)) {
+ $out = array($res['server']);
+ while($res = @odbc_data_source($this->connection,
+ SQL_FETCH_NEXT))
+ {
+ $out[] = $res['server'];
+ }
+ return $out;
+ } else {
+ return $this->odbcRaiseError();
+ }
+ break;
+ case 'tables':
+ case 'schema.tables':
+ $keep = 'TABLE';
+ break;
+ case 'views':
+ $keep = 'VIEW';
+ break;
+ default:
+ return null;
+ }
+
+ /*
+ * Removing non-conforming items in the while loop rather than
+ * in the odbc_tables() call because some backends choke on this:
+ * odbc_tables($this->connection, '', '', '', 'TABLE')
+ */
+ $res = @odbc_tables($this->connection);
+ if (!$res) {
+ return $this->odbcRaiseError();
+ }
+ $out = array();
+ while ($row = odbc_fetch_array($res)) {
+ if ($row['TABLE_TYPE'] != $keep) {
+ continue;
+ }
+ if ($type == 'schema.tables') {
+ $out[] = $row['TABLE_SCHEM'] . '.' . $row['TABLE_NAME'];
+ } else {
+ $out[] = $row['TABLE_NAME'];
+ }
+ }
+ return $out;
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/pgsql.php b/extlib/DB/pgsql.php
new file mode 100644
index 000000000..6030bb4c1
--- /dev/null
+++ b/extlib/DB/pgsql.php
@@ -0,0 +1,1135 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's pgsql extension
+ * for interacting with PostgreSQL databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Rui Hirokawa <hirokawa@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: pgsql.php,v 1.139 2007/11/28 02:19:44 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's pgsql extension
+ * for interacting with PostgreSQL databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * @category Database
+ * @package DB
+ * @author Rui Hirokawa <hirokawa@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_pgsql extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'pgsql';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'pgsql';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'alter',
+ 'new_link' => '4.3.0',
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => true,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The quantity of transactions begun
+ *
+ * {@internal While this is private, it can't actually be designated
+ * private in PHP 5 because it is directly accessed in the test suite.}}
+ *
+ * @var integer
+ * @access private
+ */
+ var $transaction_opcount = 0;
+
+ /**
+ * The number of rows affected by a data manipulation query
+ * @var integer
+ */
+ var $affected = 0;
+
+ /**
+ * The current row being looked at in fetchInto()
+ * @var array
+ * @access private
+ */
+ var $row = array();
+
+ /**
+ * The number of rows in a given result set
+ * @var array
+ * @access private
+ */
+ var $_num_rows = array();
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_pgsql()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's pgsql driver supports the following extra DSN options:
+ * + connect_timeout How many seconds to wait for a connection to
+ * be established. Available since PEAR DB 1.7.0.
+ * + new_link If set to true, causes subsequent calls to
+ * connect() to return a new connection link
+ * instead of the existing one. WARNING: this is
+ * not portable to other DBMS's. Available only
+ * if PHP is >= 4.3.0 and PEAR DB is >= 1.7.0.
+ * + options Command line options to be sent to the server.
+ * Available since PEAR DB 1.6.4.
+ * + service Specifies a service name in pg_service.conf that
+ * holds additional connection parameters.
+ * Available since PEAR DB 1.7.0.
+ * + sslmode How should SSL be used when connecting? Values:
+ * disable, allow, prefer or require.
+ * Available since PEAR DB 1.7.0.
+ * + tty This was used to specify where to send server
+ * debug output. Available since PEAR DB 1.6.4.
+ *
+ * Example of connecting to a new link via a socket:
+ * <code>
+ * require_once 'DB.php';
+ *
+ * $dsn = 'pgsql://user:pass@unix(/tmp)/dbname?new_link=true';
+ * $options = array(
+ * 'portability' => DB_PORTABILITY_ALL,
+ * );
+ *
+ * $db = DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @link http://www.postgresql.org/docs/current/static/libpq.html#LIBPQ-CONNECT
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('pgsql')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $protocol = $dsn['protocol'] ? $dsn['protocol'] : 'tcp';
+
+ $params = array('');
+ if ($protocol == 'tcp') {
+ if ($dsn['hostspec']) {
+ $params[0] .= 'host=' . $dsn['hostspec'];
+ }
+ if ($dsn['port']) {
+ $params[0] .= ' port=' . $dsn['port'];
+ }
+ } elseif ($protocol == 'unix') {
+ // Allow for pg socket in non-standard locations.
+ if ($dsn['socket']) {
+ $params[0] .= 'host=' . $dsn['socket'];
+ }
+ if ($dsn['port']) {
+ $params[0] .= ' port=' . $dsn['port'];
+ }
+ }
+ if ($dsn['database']) {
+ $params[0] .= ' dbname=\'' . addslashes($dsn['database']) . '\'';
+ }
+ if ($dsn['username']) {
+ $params[0] .= ' user=\'' . addslashes($dsn['username']) . '\'';
+ }
+ if ($dsn['password']) {
+ $params[0] .= ' password=\'' . addslashes($dsn['password']) . '\'';
+ }
+ if (!empty($dsn['options'])) {
+ $params[0] .= ' options=' . $dsn['options'];
+ }
+ if (!empty($dsn['tty'])) {
+ $params[0] .= ' tty=' . $dsn['tty'];
+ }
+ if (!empty($dsn['connect_timeout'])) {
+ $params[0] .= ' connect_timeout=' . $dsn['connect_timeout'];
+ }
+ if (!empty($dsn['sslmode'])) {
+ $params[0] .= ' sslmode=' . $dsn['sslmode'];
+ }
+ if (!empty($dsn['service'])) {
+ $params[0] .= ' service=' . $dsn['service'];
+ }
+
+ if (isset($dsn['new_link'])
+ && ($dsn['new_link'] == 'true' || $dsn['new_link'] === true))
+ {
+ if (version_compare(phpversion(), '4.3.0', '>=')) {
+ $params[] = PGSQL_CONNECT_FORCE_NEW;
+ }
+ }
+
+ $connect_function = $persistent ? 'pg_pconnect' : 'pg_connect';
+
+ $ini = ini_get('track_errors');
+ $php_errormsg = '';
+ if ($ini) {
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ } else {
+ @ini_set('track_errors', 1);
+ $this->connection = @call_user_func_array($connect_function,
+ $params);
+ @ini_set('track_errors', $ini);
+ }
+
+ if (!$this->connection) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ $php_errormsg);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @pg_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @pg_exec($this->connection, 'begin;');
+ if (!$result) {
+ return $this->pgsqlRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ $result = @pg_exec($this->connection, $query);
+ if (!$result) {
+ return $this->pgsqlRaiseError();
+ }
+
+ /*
+ * Determine whether queries produce affected rows, result or nothing.
+ *
+ * This logic was introduced in version 1.1 of the file by ssb,
+ * though the regex has been modified slightly since then.
+ *
+ * PostgreSQL commands:
+ * ABORT, ALTER, BEGIN, CLOSE, CLUSTER, COMMIT, COPY,
+ * CREATE, DECLARE, DELETE, DROP TABLE, EXPLAIN, FETCH,
+ * GRANT, INSERT, LISTEN, LOAD, LOCK, MOVE, NOTIFY, RESET,
+ * REVOKE, ROLLBACK, SELECT, SELECT INTO, SET, SHOW,
+ * UNLISTEN, UPDATE, VACUUM
+ */
+ if ($ismanip) {
+ $this->affected = @pg_affected_rows($result);
+ return DB_OK;
+ } elseif (preg_match('/^\s*\(*\s*(SELECT|EXPLAIN|FETCH|SHOW)\s/si',
+ $query))
+ {
+ $this->row[(int)$result] = 0; // reset the row counter.
+ $numrows = $this->numRows($result);
+ if (is_object($numrows)) {
+ return $numrows;
+ }
+ $this->_num_rows[(int)$result] = $numrows;
+ $this->affected = 0;
+ return $result;
+ } else {
+ $this->affected = 0;
+ return DB_OK;
+ }
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal pgsql result pointer to the next available result
+ *
+ * @param a valid fbsql result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ $result_int = (int)$result;
+ $rownum = ($rownum !== null) ? $rownum : $this->row[$result_int];
+ if ($rownum >= $this->_num_rows[$result_int]) {
+ return null;
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @pg_fetch_array($result, $rownum, PGSQL_ASSOC);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @pg_fetch_row($result, $rownum);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ $this->row[$result_int] = ++$rownum;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ if (is_resource($result)) {
+ unset($this->row[(int)$result]);
+ unset($this->_num_rows[(int)$result]);
+ $this->affected = 0;
+ return @pg_freeresult($result);
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ quote()
+
+ /**
+ * @deprecated Deprecated in release 1.6.0
+ * @internal
+ */
+ function quote($str)
+ {
+ return $this->quoteSmart($str);
+ }
+
+ // }}}
+ // {{{ quoteBoolean()
+
+ /**
+ * Formats a boolean value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param boolean the boolean value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteBoolean($boolean) {
+ return $boolean ? 'TRUE' : 'FALSE';
+ }
+
+ // }}}
+ // {{{ escapeSimple()
+
+ /**
+ * Escapes a string according to the current DBMS's standards
+ *
+ * {@internal PostgreSQL treats a backslash as an escape character,
+ * so they are escaped as well.
+ *
+ * @param string $str the string to be escaped
+ *
+ * @return string the escaped string
+ *
+ * @see DB_common::quoteSmart()
+ * @since Method available since Release 1.6.0
+ */
+ function escapeSimple($str)
+ {
+ if (function_exists('pg_escape_string')) {
+ /* This fixes an undocumented BC break in PHP 5.2.0 which changed
+ * the prototype of pg_escape_string. I'm not thrilled about having
+ * to sniff the PHP version, quite frankly, but it's the only way
+ * to deal with the problem. Revision 1.331.2.13.2.10 on
+ * php-src/ext/pgsql/pgsql.c (PHP_5_2 branch) is to blame, for the
+ * record. */
+ if (version_compare(PHP_VERSION, '5.2.0', '>=')) {
+ return pg_escape_string($this->connection, $str);
+ } else {
+ return pg_escape_string($str);
+ }
+ } else {
+ return str_replace("'", "''", str_replace('\\', '\\\\', $str));
+ }
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @pg_numfields($result);
+ if (!$cols) {
+ return $this->pgsqlRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @pg_numrows($result);
+ if ($rows === null) {
+ return $this->pgsqlRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ // XXX if $this->transaction_opcount > 0, we should probably
+ // issue a warning here.
+ $this->autocommit = $onoff ? true : false;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if ($this->transaction_opcount > 0) {
+ // (disabled) hack to shut up error messages from libpq.a
+ //@fclose(@fopen("php://stderr", "w"));
+ $result = @pg_exec($this->connection, 'end;');
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->pgsqlRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if ($this->transaction_opcount > 0) {
+ $result = @pg_exec($this->connection, 'abort;');
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->pgsqlRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ return $this->affected;
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_pgsql::createSequence(), DB_pgsql::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $repeat = false;
+ do {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("SELECT NEXTVAL('${seqname}')");
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE) {
+ $repeat = true;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->createSequence($seq_name);
+ $this->popErrorHandling();
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ } else {
+ $repeat = false;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $arr = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ $result->free();
+ return $arr[0];
+ }
+
+ // }}}
+ // {{{ createSequence()
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_pgsql::nextID(), DB_pgsql::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $result = $this->query("CREATE SEQUENCE ${seqname}");
+ return $result;
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_pgsql::nextID(), DB_pgsql::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP SEQUENCE '
+ . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ return "$query LIMIT $count OFFSET $from";
+ }
+
+ // }}}
+ // {{{ pgsqlRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_pgsql::errorNative(), DB_pgsql::errorCode()
+ */
+ function pgsqlRaiseError($errno = null)
+ {
+ $native = $this->errorNative();
+ if (!$native) {
+ $native = 'Database connection has been lost.';
+ $errno = DB_ERROR_CONNECT_FAILED;
+ }
+ if ($errno === null) {
+ $errno = $this->errorCode($native);
+ }
+ return $this->raiseError($errno, null, null, null, $native);
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error message produced by the last query
+ *
+ * {@internal Error messages are used instead of error codes
+ * in order to support older versions of PostgreSQL.}}
+ *
+ * @return string the DBMS' error message
+ */
+ function errorNative()
+ {
+ return @pg_errormessage($this->connection);
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Determines PEAR::DB error code from the database's text error message.
+ *
+ * @param string $errormsg error message returned from the database
+ * @return integer an error number from a DB error constant
+ */
+ function errorCode($errormsg)
+ {
+ static $error_regexps;
+ if (!isset($error_regexps)) {
+ $error_regexps = array(
+ '/column .* (of relation .*)?does not exist/i'
+ => DB_ERROR_NOSUCHFIELD,
+ '/(relation|sequence|table).*does not exist|class .* not found/i'
+ => DB_ERROR_NOSUCHTABLE,
+ '/index .* does not exist/'
+ => DB_ERROR_NOT_FOUND,
+ '/relation .* already exists/i'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/(divide|division) by zero$/i'
+ => DB_ERROR_DIVZERO,
+ '/pg_atoi: error in .*: can\'t parse /i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/invalid input syntax for( type)? (integer|numeric)/i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/value .* is out of range for type \w*int/i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/integer out of range/i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/value too long for type character/i'
+ => DB_ERROR_INVALID,
+ '/attribute .* not found|relation .* does not have attribute/i'
+ => DB_ERROR_NOSUCHFIELD,
+ '/column .* specified in USING clause does not exist in (left|right) table/i'
+ => DB_ERROR_NOSUCHFIELD,
+ '/parser: parse error at or near/i'
+ => DB_ERROR_SYNTAX,
+ '/syntax error at/'
+ => DB_ERROR_SYNTAX,
+ '/column reference .* is ambiguous/i'
+ => DB_ERROR_SYNTAX,
+ '/permission denied/'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/violates not-null constraint/'
+ => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '/violates [\w ]+ constraint/'
+ => DB_ERROR_CONSTRAINT,
+ '/referential integrity violation/'
+ => DB_ERROR_CONSTRAINT,
+ '/more expressions than target columns/i'
+ => DB_ERROR_VALUE_COUNT_ON_ROW,
+ );
+ }
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match($regexp, $errormsg)) {
+ return $code;
+ }
+ }
+ // Fall back to DB_ERROR if there was no mapping.
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+ * is a table name.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @pg_exec($this->connection, "SELECT * FROM $result LIMIT 0");
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->pgsqlRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @pg_numfields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $res[$i] = array(
+ 'table' => $got_string ? $case_func($result) : '',
+ 'name' => $case_func(@pg_fieldname($id, $i)),
+ 'type' => @pg_fieldtype($id, $i),
+ 'len' => @pg_fieldsize($id, $i),
+ 'flags' => $got_string
+ ? $this->_pgFieldFlags($id, $i, $result)
+ : '',
+ );
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @pg_freeresult($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ _pgFieldFlags()
+
+ /**
+ * Get a column's flags
+ *
+ * Supports "not_null", "default_value", "primary_key", "unique_key"
+ * and "multiple_key". The default value is passed through
+ * rawurlencode() in case there are spaces in it.
+ *
+ * @param int $resource the PostgreSQL result identifier
+ * @param int $num_field the field number
+ *
+ * @return string the flags
+ *
+ * @access private
+ */
+ function _pgFieldFlags($resource, $num_field, $table_name)
+ {
+ $field_name = @pg_fieldname($resource, $num_field);
+
+ // Check if there's a schema in $table_name and update things
+ // accordingly.
+ $from = 'pg_attribute f, pg_class tab, pg_type typ';
+ if (strpos($table_name, '.') !== false) {
+ $from .= ', pg_namespace nsp';
+ list($schema, $table) = explode('.', $table_name);
+ $tableWhere = "tab.relname = '$table' AND tab.relnamespace = nsp.oid AND nsp.nspname = '$schema'";
+ } else {
+ $tableWhere = "tab.relname = '$table_name'";
+ }
+
+ $result = @pg_exec($this->connection, "SELECT f.attnotnull, f.atthasdef
+ FROM $from
+ WHERE tab.relname = typ.typname
+ AND typ.typrelid = f.attrelid
+ AND f.attname = '$field_name'
+ AND $tableWhere");
+ if (@pg_numrows($result) > 0) {
+ $row = @pg_fetch_row($result, 0);
+ $flags = ($row[0] == 't') ? 'not_null ' : '';
+
+ if ($row[1] == 't') {
+ $result = @pg_exec($this->connection, "SELECT a.adsrc
+ FROM $from, pg_attrdef a
+ WHERE tab.relname = typ.typname AND typ.typrelid = f.attrelid
+ AND f.attrelid = a.adrelid AND f.attname = '$field_name'
+ AND $tableWhere AND f.attnum = a.adnum");
+ $row = @pg_fetch_row($result, 0);
+ $num = preg_replace("/'(.*)'::\w+/", "\\1", $row[0]);
+ $flags .= 'default_' . rawurlencode($num) . ' ';
+ }
+ } else {
+ $flags = '';
+ }
+ $result = @pg_exec($this->connection, "SELECT i.indisunique, i.indisprimary, i.indkey
+ FROM $from, pg_index i
+ WHERE tab.relname = typ.typname
+ AND typ.typrelid = f.attrelid
+ AND f.attrelid = i.indrelid
+ AND f.attname = '$field_name'
+ AND $tableWhere");
+ $count = @pg_numrows($result);
+
+ for ($i = 0; $i < $count ; $i++) {
+ $row = @pg_fetch_row($result, $i);
+ $keys = explode(' ', $row[2]);
+
+ if (in_array($num_field + 1, $keys)) {
+ $flags .= ($row[0] == 't' && $row[1] == 'f') ? 'unique_key ' : '';
+ $flags .= ($row[1] == 't') ? 'primary_key ' : '';
+ if (count($keys) > 1)
+ $flags .= 'multiple_key ';
+ }
+ }
+
+ return trim($flags);
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return 'SELECT c.relname AS "Name"'
+ . ' FROM pg_class c, pg_user u'
+ . ' WHERE c.relowner = u.usesysid'
+ . " AND c.relkind = 'r'"
+ . ' AND NOT EXISTS'
+ . ' (SELECT 1 FROM pg_views'
+ . ' WHERE viewname = c.relname)'
+ . " AND c.relname !~ '^(pg_|sql_)'"
+ . ' UNION'
+ . ' SELECT c.relname AS "Name"'
+ . ' FROM pg_class c'
+ . " WHERE c.relkind = 'r'"
+ . ' AND NOT EXISTS'
+ . ' (SELECT 1 FROM pg_views'
+ . ' WHERE viewname = c.relname)'
+ . ' AND NOT EXISTS'
+ . ' (SELECT 1 FROM pg_user'
+ . ' WHERE usesysid = c.relowner)'
+ . " AND c.relname !~ '^pg_'";
+ case 'schema.tables':
+ return "SELECT schemaname || '.' || tablename"
+ . ' AS "Name"'
+ . ' FROM pg_catalog.pg_tables'
+ . ' WHERE schemaname NOT IN'
+ . " ('pg_catalog', 'information_schema', 'pg_toast')";
+ case 'schema.views':
+ return "SELECT schemaname || '.' || viewname from pg_views WHERE schemaname"
+ . " NOT IN ('information_schema', 'pg_catalog')";
+ case 'views':
+ // Table cols: viewname | viewowner | definition
+ return 'SELECT viewname from pg_views WHERE schemaname'
+ . " NOT IN ('information_schema', 'pg_catalog')";
+ case 'users':
+ // cols: usename |usesysid|usecreatedb|usetrace|usesuper|usecatupd|passwd |valuntil
+ return 'SELECT usename FROM pg_user';
+ case 'databases':
+ return 'SELECT datname FROM pg_database';
+ case 'functions':
+ case 'procedures':
+ return 'SELECT proname FROM pg_proc WHERE proowner <> 1';
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+ // {{{ _checkManip()
+
+ /**
+ * Checks if the given query is a manipulation query. This also takes into
+ * account the _next_query_manip flag and sets the _last_query_manip flag
+ * (and resets _next_query_manip) according to the result.
+ *
+ * @param string The query to check.
+ *
+ * @return boolean true if the query is a manipulation query, false
+ * otherwise
+ *
+ * @access protected
+ */
+ function _checkManip($query)
+ {
+ return (preg_match('/^\s*(SAVEPOINT|RELEASE)\s+/i', $query)
+ || parent::_checkManip($query));
+ }
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/sqlite.php b/extlib/DB/sqlite.php
new file mode 100644
index 000000000..5c4b396e5
--- /dev/null
+++ b/extlib/DB/sqlite.php
@@ -0,0 +1,960 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's sqlite extension
+ * for interacting with SQLite databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Urs Gehrig <urs@circle.ch>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
+ * @version CVS: $Id: sqlite.php,v 1.118 2007/11/26 22:57:18 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's sqlite extension
+ * for interacting with SQLite databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * NOTICE: This driver needs PHP's track_errors ini setting to be on.
+ * It is automatically turned on when connecting to the database.
+ * Make sure your scripts don't turn it off.
+ *
+ * @category Database
+ * @package DB
+ * @author Urs Gehrig <urs@circle.ch>
+ * @author Mika Tuupola <tuupola@appelsiini.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_sqlite extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'sqlite';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'sqlite';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'alter',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => false,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ *
+ * {@internal Error codes according to sqlite_exec. See the online
+ * manual at http://sqlite.org/c_interface.html for info.
+ * This error handling based on sqlite_exec is not yet implemented.}}
+ *
+ * @var array
+ */
+ var $errorcode_map = array(
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * SQLite data types
+ *
+ * @link http://www.sqlite.org/datatypes.html
+ *
+ * @var array
+ */
+ var $keywords = array (
+ 'BLOB' => '',
+ 'BOOLEAN' => '',
+ 'CHARACTER' => '',
+ 'CLOB' => '',
+ 'FLOAT' => '',
+ 'INTEGER' => '',
+ 'KEY' => '',
+ 'NATIONAL' => '',
+ 'NUMERIC' => '',
+ 'NVARCHAR' => '',
+ 'PRIMARY' => '',
+ 'TEXT' => '',
+ 'TIMESTAMP' => '',
+ 'UNIQUE' => '',
+ 'VARCHAR' => '',
+ 'VARYING' => '',
+ );
+
+ /**
+ * The most recent error message from $php_errormsg
+ * @var string
+ * @access private
+ */
+ var $_lasterror = '';
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_sqlite()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's sqlite driver supports the following extra DSN options:
+ * + mode The permissions for the database file, in four digit
+ * chmod octal format (eg "0600").
+ *
+ * Example of connecting to a database in read-only mode:
+ * <code>
+ * require_once 'DB.php';
+ *
+ * $dsn = 'sqlite:///path/and/name/of/db/file?mode=0400';
+ * $options = array(
+ * 'portability' => DB_PORTABILITY_ALL,
+ * );
+ *
+ * $db = DB::connect($dsn, $options);
+ * if (PEAR::isError($db)) {
+ * die($db->getMessage());
+ * }
+ * </code>
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('sqlite')) {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ if (!$dsn['database']) {
+ return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
+ }
+
+ if ($dsn['database'] !== ':memory:') {
+ if (!file_exists($dsn['database'])) {
+ if (!touch($dsn['database'])) {
+ return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
+ }
+ if (!isset($dsn['mode']) ||
+ !is_numeric($dsn['mode']))
+ {
+ $mode = 0644;
+ } else {
+ $mode = octdec($dsn['mode']);
+ }
+ if (!chmod($dsn['database'], $mode)) {
+ return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
+ }
+ if (!file_exists($dsn['database'])) {
+ return $this->sqliteRaiseError(DB_ERROR_NOT_FOUND);
+ }
+ }
+ if (!is_file($dsn['database'])) {
+ return $this->sqliteRaiseError(DB_ERROR_INVALID);
+ }
+ if (!is_readable($dsn['database'])) {
+ return $this->sqliteRaiseError(DB_ERROR_ACCESS_VIOLATION);
+ }
+ }
+
+ $connect_function = $persistent ? 'sqlite_popen' : 'sqlite_open';
+
+ // track_errors must remain on for simpleQuery()
+ @ini_set('track_errors', 1);
+ $php_errormsg = '';
+
+ if (!$this->connection = @$connect_function($dsn['database'])) {
+ return $this->raiseError(DB_ERROR_NODBSELECTED,
+ null, null, null,
+ $php_errormsg);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @sqlite_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * NOTICE: This method needs PHP's track_errors ini setting to be on.
+ * It is automatically turned on when connecting to the database.
+ * Make sure your scripts don't turn it off.
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ $query = $this->modifyQuery($query);
+
+ $php_errormsg = '';
+
+ $result = @sqlite_query($query, $this->connection);
+ $this->_lasterror = $php_errormsg ? $php_errormsg : '';
+
+ $this->result = $result;
+ if (!$this->result) {
+ return $this->sqliteRaiseError(null);
+ }
+
+ // sqlite_query() seems to allways return a resource
+ // so cant use that. Using $ismanip instead
+ if (!$ismanip) {
+ $numRows = $this->numRows($result);
+ if (is_object($numRows)) {
+ // we've got PEAR_Error
+ return $numRows;
+ }
+ return $result;
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal sqlite result pointer to the next available result
+ *
+ * @param resource $result the valid sqlite result resource
+ *
+ * @return bool true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@sqlite_seek($this->result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ $arr = @sqlite_fetch_array($result, SQLITE_ASSOC);
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+
+ /* Remove extraneous " characters from the fields in the result.
+ * Fixes bug #11716. */
+ if (is_array($arr) && count($arr) > 0) {
+ $strippedArr = array();
+ foreach ($arr as $field => $value) {
+ $strippedArr[trim($field, '"')] = $value;
+ }
+ $arr = $strippedArr;
+ }
+ } else {
+ $arr = @sqlite_fetch_array($result, SQLITE_NUM);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ /*
+ * Even though this DBMS already trims output, we do this because
+ * a field might have intentional whitespace at the end that
+ * gets removed by DB_PORTABILITY_RTRIM under another driver.
+ */
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult(&$result)
+ {
+ // XXX No native free?
+ if (!is_resource($result)) {
+ return false;
+ }
+ $result = null;
+ return true;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @sqlite_num_fields($result);
+ if (!$cols) {
+ return $this->sqliteRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @sqlite_num_rows($result);
+ if ($rows === null) {
+ return $this->sqliteRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ affected()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ return @sqlite_changes($this->connection);
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_sqlite::nextID(), DB_sqlite::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_sqlite::nextID(), DB_sqlite::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ $query = 'CREATE TABLE ' . $seqname .
+ ' (id INTEGER UNSIGNED PRIMARY KEY) ';
+ $result = $this->query($query);
+ if (DB::isError($result)) {
+ return($result);
+ }
+ $query = "CREATE TRIGGER ${seqname}_cleanup AFTER INSERT ON $seqname
+ BEGIN
+ DELETE FROM $seqname WHERE id<LAST_INSERT_ROWID();
+ END ";
+ $result = $this->query($query);
+ if (DB::isError($result)) {
+ return($result);
+ }
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_sqlite::createSequence(), DB_sqlite::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+
+ do {
+ $repeat = 0;
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("INSERT INTO $seqname (id) VALUES (NULL)");
+ $this->popErrorHandling();
+ if ($result === DB_OK) {
+ $id = @sqlite_last_insert_rowid($this->connection);
+ if ($id != 0) {
+ return $id;
+ }
+ } elseif ($ondemand && DB::isError($result) &&
+ $result->getCode() == DB_ERROR_NOSUCHTABLE)
+ {
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ } else {
+ $repeat = 1;
+ }
+ }
+ } while ($repeat);
+
+ return $this->raiseError($result);
+ }
+
+ // }}}
+ // {{{ getDbFileStats()
+
+ /**
+ * Get the file stats for the current database
+ *
+ * Possible arguments are dev, ino, mode, nlink, uid, gid, rdev, size,
+ * atime, mtime, ctime, blksize, blocks or a numeric key between
+ * 0 and 12.
+ *
+ * @param string $arg the array key for stats()
+ *
+ * @return mixed an array on an unspecified key, integer on a passed
+ * arg and false at a stats error
+ */
+ function getDbFileStats($arg = '')
+ {
+ $stats = stat($this->dsn['database']);
+ if ($stats == false) {
+ return false;
+ }
+ if (is_array($stats)) {
+ if (is_numeric($arg)) {
+ if (((int)$arg <= 12) & ((int)$arg >= 0)) {
+ return false;
+ }
+ return $stats[$arg ];
+ }
+ if (array_key_exists(trim($arg), $stats)) {
+ return $stats[$arg ];
+ }
+ }
+ return $stats;
+ }
+
+ // }}}
+ // {{{ escapeSimple()
+
+ /**
+ * Escapes a string according to the current DBMS's standards
+ *
+ * In SQLite, this makes things safe for inserts/updates, but may
+ * cause problems when performing text comparisons against columns
+ * containing binary data. See the
+ * {@link http://php.net/sqlite_escape_string PHP manual} for more info.
+ *
+ * @param string $str the string to be escaped
+ *
+ * @return string the escaped string
+ *
+ * @since Method available since Release 1.6.1
+ * @see DB_common::escapeSimple()
+ */
+ function escapeSimple($str)
+ {
+ return @sqlite_escape_string($str);
+ }
+
+ // }}}
+ // {{{ modifyLimitQuery()
+
+ /**
+ * Adds LIMIT clauses to a query string according to current DBMS standards
+ *
+ * @param string $query the query to modify
+ * @param int $from the row to start to fetching (0 = the first row)
+ * @param int $count the numbers of rows to fetch
+ * @param mixed $params array, string or numeric data to be used in
+ * execution of the statement. Quantity of items
+ * passed must match quantity of placeholders in
+ * query: meaning 1 placeholder for non-array
+ * parameters or 1 placeholder per array element.
+ *
+ * @return string the query string with LIMIT clauses added
+ *
+ * @access protected
+ */
+ function modifyLimitQuery($query, $from, $count, $params = array())
+ {
+ return "$query LIMIT $count OFFSET $from";
+ }
+
+ // }}}
+ // {{{ modifyQuery()
+
+ /**
+ * Changes a query string for various DBMS specific reasons
+ *
+ * This little hack lets you know how many rows were deleted
+ * when running a "DELETE FROM table" query. Only implemented
+ * if the DB_PORTABILITY_DELETE_COUNT portability option is on.
+ *
+ * @param string $query the query string to modify
+ *
+ * @return string the modified query string
+ *
+ * @access protected
+ * @see DB_common::setOption()
+ */
+ function modifyQuery($query)
+ {
+ if ($this->options['portability'] & DB_PORTABILITY_DELETE_COUNT) {
+ if (preg_match('/^\s*DELETE\s+FROM\s+(\S+)\s*$/i', $query)) {
+ $query = preg_replace('/^\s*DELETE\s+FROM\s+(\S+)\s*$/',
+ 'DELETE FROM \1 WHERE 1=1', $query);
+ }
+ }
+ return $query;
+ }
+
+ // }}}
+ // {{{ sqliteRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_sqlite::errorNative(), DB_sqlite::errorCode()
+ */
+ function sqliteRaiseError($errno = null)
+ {
+ $native = $this->errorNative();
+ if ($errno === null) {
+ $errno = $this->errorCode($native);
+ }
+
+ $errorcode = @sqlite_last_error($this->connection);
+ $userinfo = "$errorcode ** $this->last_query";
+
+ return $this->raiseError($errno, null, null, $userinfo, $native);
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error message produced by the last query
+ *
+ * {@internal This is used to retrieve more meaningfull error messages
+ * because sqlite_last_error() does not provide adequate info.}}
+ *
+ * @return string the DBMS' error message
+ */
+ function errorNative()
+ {
+ return $this->_lasterror;
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Determines PEAR::DB error code from the database's text error message
+ *
+ * @param string $errormsg the error message returned from the database
+ *
+ * @return integer the DB error number
+ */
+ function errorCode($errormsg)
+ {
+ static $error_regexps;
+
+ // PHP 5.2+ prepends the function name to $php_errormsg, so we need
+ // this hack to work around it, per bug #9599.
+ $errormsg = preg_replace('/^sqlite[a-z_]+\(\): /', '', $errormsg);
+
+ if (!isset($error_regexps)) {
+ $error_regexps = array(
+ '/^no such table:/' => DB_ERROR_NOSUCHTABLE,
+ '/^no such index:/' => DB_ERROR_NOT_FOUND,
+ '/^(table|index) .* already exists$/' => DB_ERROR_ALREADY_EXISTS,
+ '/PRIMARY KEY must be unique/i' => DB_ERROR_CONSTRAINT,
+ '/is not unique/' => DB_ERROR_CONSTRAINT,
+ '/columns .* are not unique/i' => DB_ERROR_CONSTRAINT,
+ '/uniqueness constraint failed/' => DB_ERROR_CONSTRAINT,
+ '/may not be NULL/' => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '/^no such column:/' => DB_ERROR_NOSUCHFIELD,
+ '/no column named/' => DB_ERROR_NOSUCHFIELD,
+ '/column not present in both tables/i' => DB_ERROR_NOSUCHFIELD,
+ '/^near ".*": syntax error$/' => DB_ERROR_SYNTAX,
+ '/[0-9]+ values for [0-9]+ columns/i' => DB_ERROR_VALUE_COUNT_ON_ROW,
+ );
+ }
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match($regexp, $errormsg)) {
+ return $code;
+ }
+ }
+ // Fall back to DB_ERROR if there was no mapping.
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table
+ *
+ * @param string $result a string containing the name of a table
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ * @since Method available since Release 1.7.0
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ $id = @sqlite_array_query($this->connection,
+ "PRAGMA table_info('$result');",
+ SQLITE_ASSOC);
+ $got_string = true;
+ } else {
+ $this->last_query = '';
+ return $this->raiseError(DB_ERROR_NOT_CAPABLE, null, null, null,
+ 'This DBMS can not obtain tableInfo' .
+ ' from result sets');
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = count($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ if (strpos($id[$i]['type'], '(') !== false) {
+ $bits = explode('(', $id[$i]['type']);
+ $type = $bits[0];
+ $len = rtrim($bits[1],')');
+ } else {
+ $type = $id[$i]['type'];
+ $len = 0;
+ }
+
+ $flags = '';
+ if ($id[$i]['pk']) {
+ $flags .= 'primary_key ';
+ }
+ if ($id[$i]['notnull']) {
+ $flags .= 'not_null ';
+ }
+ if ($id[$i]['dflt_value'] !== null) {
+ $flags .= 'default_' . rawurlencode($id[$i]['dflt_value']);
+ }
+ $flags = trim($flags);
+
+ $res[$i] = array(
+ 'table' => $case_func($result),
+ 'name' => $case_func($id[$i]['name']),
+ 'type' => $type,
+ 'len' => $len,
+ 'flags' => $flags,
+ );
+
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ return $res;
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ * @param array $args SQLITE DRIVER ONLY: a private array of arguments
+ * used by the getSpecialQuery(). Do not use
+ * this directly.
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type, $args = array())
+ {
+ if (!is_array($args)) {
+ return $this->raiseError('no key specified', null, null, null,
+ 'Argument has to be an array.');
+ }
+
+ switch ($type) {
+ case 'master':
+ return 'SELECT * FROM sqlite_master;';
+ case 'tables':
+ return "SELECT name FROM sqlite_master WHERE type='table' "
+ . 'UNION ALL SELECT name FROM sqlite_temp_master '
+ . "WHERE type='table' ORDER BY name;";
+ case 'schema':
+ return 'SELECT sql FROM (SELECT * FROM sqlite_master '
+ . 'UNION ALL SELECT * FROM sqlite_temp_master) '
+ . "WHERE type!='meta' "
+ . 'ORDER BY tbl_name, type DESC, name;';
+ case 'schemax':
+ case 'schema_x':
+ /*
+ * Use like:
+ * $res = $db->query($db->getSpecialQuery('schema_x',
+ * array('table' => 'table3')));
+ */
+ return 'SELECT sql FROM (SELECT * FROM sqlite_master '
+ . 'UNION ALL SELECT * FROM sqlite_temp_master) '
+ . "WHERE tbl_name LIKE '{$args['table']}' "
+ . "AND type!='meta' "
+ . 'ORDER BY type DESC, name;';
+ case 'alter':
+ /*
+ * SQLite does not support ALTER TABLE; this is a helper query
+ * to handle this. 'table' represents the table name, 'rows'
+ * the news rows to create, 'save' the row(s) to keep _with_
+ * the data.
+ *
+ * Use like:
+ * $args = array(
+ * 'table' => $table,
+ * 'rows' => "id INTEGER PRIMARY KEY, firstname TEXT, surname TEXT, datetime TEXT",
+ * 'save' => "NULL, titel, content, datetime"
+ * );
+ * $res = $db->query( $db->getSpecialQuery('alter', $args));
+ */
+ $rows = strtr($args['rows'], $this->keywords);
+
+ $q = array(
+ 'BEGIN TRANSACTION',
+ "CREATE TEMPORARY TABLE {$args['table']}_backup ({$args['rows']})",
+ "INSERT INTO {$args['table']}_backup SELECT {$args['save']} FROM {$args['table']}",
+ "DROP TABLE {$args['table']}",
+ "CREATE TABLE {$args['table']} ({$args['rows']})",
+ "INSERT INTO {$args['table']} SELECT {$rows} FROM {$args['table']}_backup",
+ "DROP TABLE {$args['table']}_backup",
+ 'COMMIT',
+ );
+
+ /*
+ * This is a dirty hack, since the above query will not get
+ * executed with a single query call so here the query method
+ * will be called directly and return a select instead.
+ */
+ foreach ($q as $query) {
+ $this->query($query);
+ }
+ return "SELECT * FROM {$args['table']};";
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/storage.php b/extlib/DB/storage.php
new file mode 100644
index 000000000..ffa2d9447
--- /dev/null
+++ b/extlib/DB/storage.php
@@ -0,0 +1,506 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Provides an object interface to a table row
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <stig@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: storage.php,v 1.24 2007/08/12 05:27:25 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB class so it can be extended from
+ */
+require_once 'DB.php';
+
+/**
+ * Provides an object interface to a table row
+ *
+ * It lets you add, delete and change rows using objects rather than SQL
+ * statements.
+ *
+ * @category Database
+ * @package DB
+ * @author Stig Bakken <stig@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_storage extends PEAR
+{
+ // {{{ properties
+
+ /** the name of the table (or view, if the backend database supports
+ updates in views) we hold data from */
+ var $_table = null;
+
+ /** which column(s) in the table contains primary keys, can be a
+ string for single-column primary keys, or an array of strings
+ for multiple-column primary keys */
+ var $_keycolumn = null;
+
+ /** DB connection handle used for all transactions */
+ var $_dbh = null;
+
+ /** an assoc with the names of database fields stored as properties
+ in this object */
+ var $_properties = array();
+
+ /** an assoc with the names of the properties in this object that
+ have been changed since they were fetched from the database */
+ var $_changes = array();
+
+ /** flag that decides if data in this object can be changed.
+ objects that don't have their table's key column in their
+ property lists will be flagged as read-only. */
+ var $_readonly = false;
+
+ /** function or method that implements a validator for fields that
+ are set, this validator function returns true if the field is
+ valid, false if not */
+ var $_validator = null;
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * Constructor
+ *
+ * @param $table string the name of the database table
+ *
+ * @param $keycolumn mixed string with name of key column, or array of
+ * strings if the table has a primary key of more than one column
+ *
+ * @param $dbh object database connection object
+ *
+ * @param $validator mixed function or method used to validate
+ * each new value, called with three parameters: the name of the
+ * field/column that is changing, a reference to the new value and
+ * a reference to this object
+ *
+ */
+ function DB_storage($table, $keycolumn, &$dbh, $validator = null)
+ {
+ $this->PEAR('DB_Error');
+ $this->_table = $table;
+ $this->_keycolumn = $keycolumn;
+ $this->_dbh = $dbh;
+ $this->_readonly = false;
+ $this->_validator = $validator;
+ }
+
+ // }}}
+ // {{{ _makeWhere()
+
+ /**
+ * Utility method to build a "WHERE" clause to locate ourselves in
+ * the table.
+ *
+ * XXX future improvement: use rowids?
+ *
+ * @access private
+ */
+ function _makeWhere($keyval = null)
+ {
+ if (is_array($this->_keycolumn)) {
+ if ($keyval === null) {
+ for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
+ $keyval[] = $this->{$this->_keycolumn[$i]};
+ }
+ }
+ $whereclause = '';
+ for ($i = 0; $i < sizeof($this->_keycolumn); $i++) {
+ if ($i > 0) {
+ $whereclause .= ' AND ';
+ }
+ $whereclause .= $this->_keycolumn[$i];
+ if (is_null($keyval[$i])) {
+ // there's not much point in having a NULL key,
+ // but we support it anyway
+ $whereclause .= ' IS NULL';
+ } else {
+ $whereclause .= ' = ' . $this->_dbh->quote($keyval[$i]);
+ }
+ }
+ } else {
+ if ($keyval === null) {
+ $keyval = @$this->{$this->_keycolumn};
+ }
+ $whereclause = $this->_keycolumn;
+ if (is_null($keyval)) {
+ // there's not much point in having a NULL key,
+ // but we support it anyway
+ $whereclause .= ' IS NULL';
+ } else {
+ $whereclause .= ' = ' . $this->_dbh->quote($keyval);
+ }
+ }
+ return $whereclause;
+ }
+
+ // }}}
+ // {{{ setup()
+
+ /**
+ * Method used to initialize a DB_storage object from the
+ * configured table.
+ *
+ * @param $keyval mixed the key[s] of the row to fetch (string or array)
+ *
+ * @return int DB_OK on success, a DB error if not
+ */
+ function setup($keyval)
+ {
+ $whereclause = $this->_makeWhere($keyval);
+ $query = 'SELECT * FROM ' . $this->_table . ' WHERE ' . $whereclause;
+ $sth = $this->_dbh->query($query);
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+ $row = $sth->fetchRow(DB_FETCHMODE_ASSOC);
+ if (DB::isError($row)) {
+ return $row;
+ }
+ if (!$row) {
+ return $this->raiseError(null, DB_ERROR_NOT_FOUND, null, null,
+ $query, null, true);
+ }
+ foreach ($row as $key => $value) {
+ $this->_properties[$key] = true;
+ $this->$key = $value;
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ insert()
+
+ /**
+ * Create a new (empty) row in the configured table for this
+ * object.
+ */
+ function insert($newpk)
+ {
+ if (is_array($this->_keycolumn)) {
+ $primarykey = $this->_keycolumn;
+ } else {
+ $primarykey = array($this->_keycolumn);
+ }
+ settype($newpk, "array");
+ for ($i = 0; $i < sizeof($primarykey); $i++) {
+ $pkvals[] = $this->_dbh->quote($newpk[$i]);
+ }
+
+ $sth = $this->_dbh->query("INSERT INTO $this->_table (" .
+ implode(",", $primarykey) . ") VALUES(" .
+ implode(",", $pkvals) . ")");
+ if (DB::isError($sth)) {
+ return $sth;
+ }
+ if (sizeof($newpk) == 1) {
+ $newpk = $newpk[0];
+ }
+ $this->setup($newpk);
+ }
+
+ // }}}
+ // {{{ toString()
+
+ /**
+ * Output a simple description of this DB_storage object.
+ * @return string object description
+ */
+ function toString()
+ {
+ $info = strtolower(get_class($this));
+ $info .= " (table=";
+ $info .= $this->_table;
+ $info .= ", keycolumn=";
+ if (is_array($this->_keycolumn)) {
+ $info .= "(" . implode(",", $this->_keycolumn) . ")";
+ } else {
+ $info .= $this->_keycolumn;
+ }
+ $info .= ", dbh=";
+ if (is_object($this->_dbh)) {
+ $info .= $this->_dbh->toString();
+ } else {
+ $info .= "null";
+ }
+ $info .= ")";
+ if (sizeof($this->_properties)) {
+ $info .= " [loaded, key=";
+ $keyname = $this->_keycolumn;
+ if (is_array($keyname)) {
+ $info .= "(";
+ for ($i = 0; $i < sizeof($keyname); $i++) {
+ if ($i > 0) {
+ $info .= ",";
+ }
+ $info .= $this->$keyname[$i];
+ }
+ $info .= ")";
+ } else {
+ $info .= $this->$keyname;
+ }
+ $info .= "]";
+ }
+ if (sizeof($this->_changes)) {
+ $info .= " [modified]";
+ }
+ return $info;
+ }
+
+ // }}}
+ // {{{ dump()
+
+ /**
+ * Dump the contents of this object to "standard output".
+ */
+ function dump()
+ {
+ foreach ($this->_properties as $prop => $foo) {
+ print "$prop = ";
+ print htmlentities($this->$prop);
+ print "<br />\n";
+ }
+ }
+
+ // }}}
+ // {{{ &create()
+
+ /**
+ * Static method used to create new DB storage objects.
+ * @param $data assoc. array where the keys are the names
+ * of properties/columns
+ * @return object a new instance of DB_storage or a subclass of it
+ */
+ function &create($table, &$data)
+ {
+ $classname = strtolower(get_class($this));
+ $obj = new $classname($table);
+ foreach ($data as $name => $value) {
+ $obj->_properties[$name] = true;
+ $obj->$name = &$value;
+ }
+ return $obj;
+ }
+
+ // }}}
+ // {{{ loadFromQuery()
+
+ /**
+ * Loads data into this object from the given query. If this
+ * object already contains table data, changes will be saved and
+ * the object re-initialized first.
+ *
+ * @param $query SQL query
+ *
+ * @param $params parameter list in case you want to use
+ * prepare/execute mode
+ *
+ * @return int DB_OK on success, DB_WARNING_READ_ONLY if the
+ * returned object is read-only (because the object's specified
+ * key column was not found among the columns returned by $query),
+ * or another DB error code in case of errors.
+ */
+// XXX commented out for now
+/*
+ function loadFromQuery($query, $params = null)
+ {
+ if (sizeof($this->_properties)) {
+ if (sizeof($this->_changes)) {
+ $this->store();
+ $this->_changes = array();
+ }
+ $this->_properties = array();
+ }
+ $rowdata = $this->_dbh->getRow($query, DB_FETCHMODE_ASSOC, $params);
+ if (DB::isError($rowdata)) {
+ return $rowdata;
+ }
+ reset($rowdata);
+ $found_keycolumn = false;
+ while (list($key, $value) = each($rowdata)) {
+ if ($key == $this->_keycolumn) {
+ $found_keycolumn = true;
+ }
+ $this->_properties[$key] = true;
+ $this->$key = &$value;
+ unset($value); // have to unset, or all properties will
+ // refer to the same value
+ }
+ if (!$found_keycolumn) {
+ $this->_readonly = true;
+ return DB_WARNING_READ_ONLY;
+ }
+ return DB_OK;
+ }
+ */
+
+ // }}}
+ // {{{ set()
+
+ /**
+ * Modify an attriute value.
+ */
+ function set($property, $newvalue)
+ {
+ // only change if $property is known and object is not
+ // read-only
+ if ($this->_readonly) {
+ return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
+ null, null, null, true);
+ }
+ if (@isset($this->_properties[$property])) {
+ if (empty($this->_validator)) {
+ $valid = true;
+ } else {
+ $valid = @call_user_func($this->_validator,
+ $this->_table,
+ $property,
+ $newvalue,
+ $this->$property,
+ $this);
+ }
+ if ($valid) {
+ $this->$property = $newvalue;
+ if (empty($this->_changes[$property])) {
+ $this->_changes[$property] = 0;
+ } else {
+ $this->_changes[$property]++;
+ }
+ } else {
+ return $this->raiseError(null, DB_ERROR_INVALID, null,
+ null, "invalid field: $property",
+ null, true);
+ }
+ return true;
+ }
+ return $this->raiseError(null, DB_ERROR_NOSUCHFIELD, null,
+ null, "unknown field: $property",
+ null, true);
+ }
+
+ // }}}
+ // {{{ &get()
+
+ /**
+ * Fetch an attribute value.
+ *
+ * @param string attribute name
+ *
+ * @return attribute contents, or null if the attribute name is
+ * unknown
+ */
+ function &get($property)
+ {
+ // only return if $property is known
+ if (isset($this->_properties[$property])) {
+ return $this->$property;
+ }
+ $tmp = null;
+ return $tmp;
+ }
+
+ // }}}
+ // {{{ _DB_storage()
+
+ /**
+ * Destructor, calls DB_storage::store() if there are changes
+ * that are to be kept.
+ */
+ function _DB_storage()
+ {
+ if (sizeof($this->_changes)) {
+ $this->store();
+ }
+ $this->_properties = array();
+ $this->_changes = array();
+ $this->_table = null;
+ }
+
+ // }}}
+ // {{{ store()
+
+ /**
+ * Stores changes to this object in the database.
+ *
+ * @return DB_OK or a DB error
+ */
+ function store()
+ {
+ $params = array();
+ $vars = array();
+ foreach ($this->_changes as $name => $foo) {
+ $params[] = &$this->$name;
+ $vars[] = $name . ' = ?';
+ }
+ if ($vars) {
+ $query = 'UPDATE ' . $this->_table . ' SET ' .
+ implode(', ', $vars) . ' WHERE ' .
+ $this->_makeWhere();
+ $stmt = $this->_dbh->prepare($query);
+ $res = $this->_dbh->execute($stmt, $params);
+ if (DB::isError($res)) {
+ return $res;
+ }
+ $this->_changes = array();
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ remove()
+
+ /**
+ * Remove the row represented by this object from the database.
+ *
+ * @return mixed DB_OK or a DB error
+ */
+ function remove()
+ {
+ if ($this->_readonly) {
+ return $this->raiseError(null, DB_WARNING_READ_ONLY, null,
+ null, null, null, true);
+ }
+ $query = 'DELETE FROM ' . $this->_table .' WHERE '.
+ $this->_makeWhere();
+ $res = $this->_dbh->query($query);
+ if (DB::isError($res)) {
+ return $res;
+ }
+ foreach ($this->_properties as $prop => $foo) {
+ unset($this->$prop);
+ }
+ $this->_properties = array();
+ $this->_changes = array();
+ return DB_OK;
+ }
+
+ // }}}
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/DB/sybase.php b/extlib/DB/sybase.php
new file mode 100644
index 000000000..3befbf6ea
--- /dev/null
+++ b/extlib/DB/sybase.php
@@ -0,0 +1,942 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * The PEAR DB driver for PHP's sybase extension
+ * for interacting with Sybase databases
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Antônio Carlos Venâncio Júnior <floripa@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: sybase.php,v 1.87 2007/09/21 13:40:42 aharvey Exp $
+ * @link http://pear.php.net/package/DB
+ */
+
+/**
+ * Obtain the DB_common class so it can be extended from
+ */
+require_once 'DB/common.php';
+
+/**
+ * The methods PEAR DB uses to interact with PHP's sybase extension
+ * for interacting with Sybase databases
+ *
+ * These methods overload the ones declared in DB_common.
+ *
+ * WARNING: This driver may fail with multiple connections under the
+ * same user/pass/host and different databases.
+ *
+ * @category Database
+ * @package DB
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Antônio Carlos Venâncio Júnior <floripa@php.net>
+ * @author Daniel Convissor <danielc@php.net>
+ * @copyright 1997-2007 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.14RC1
+ * @link http://pear.php.net/package/DB
+ */
+class DB_sybase extends DB_common
+{
+ // {{{ properties
+
+ /**
+ * The DB driver type (mysql, oci8, odbc, etc.)
+ * @var string
+ */
+ var $phptype = 'sybase';
+
+ /**
+ * The database syntax variant to be used (db2, access, etc.), if any
+ * @var string
+ */
+ var $dbsyntax = 'sybase';
+
+ /**
+ * The capabilities of this DB implementation
+ *
+ * The 'new_link' element contains the PHP version that first provided
+ * new_link support for this DBMS. Contains false if it's unsupported.
+ *
+ * Meaning of the 'limit' element:
+ * + 'emulate' = emulate with fetch row by number
+ * + 'alter' = alter the query
+ * + false = skip rows
+ *
+ * @var array
+ */
+ var $features = array(
+ 'limit' => 'emulate',
+ 'new_link' => false,
+ 'numrows' => true,
+ 'pconnect' => true,
+ 'prepare' => false,
+ 'ssl' => false,
+ 'transactions' => true,
+ );
+
+ /**
+ * A mapping of native error codes to DB error codes
+ * @var array
+ */
+ var $errorcode_map = array(
+ );
+
+ /**
+ * The raw database connection created by PHP
+ * @var resource
+ */
+ var $connection;
+
+ /**
+ * The DSN information for connecting to a database
+ * @var array
+ */
+ var $dsn = array();
+
+
+ /**
+ * Should data manipulation queries be committed automatically?
+ * @var bool
+ * @access private
+ */
+ var $autocommit = true;
+
+ /**
+ * The quantity of transactions begun
+ *
+ * {@internal While this is private, it can't actually be designated
+ * private in PHP 5 because it is directly accessed in the test suite.}}
+ *
+ * @var integer
+ * @access private
+ */
+ var $transaction_opcount = 0;
+
+ /**
+ * The database specified in the DSN
+ *
+ * It's a fix to allow calls to different databases in the same script.
+ *
+ * @var string
+ * @access private
+ */
+ var $_db = '';
+
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * This constructor calls <kbd>$this->DB_common()</kbd>
+ *
+ * @return void
+ */
+ function DB_sybase()
+ {
+ $this->DB_common();
+ }
+
+ // }}}
+ // {{{ connect()
+
+ /**
+ * Connect to the database server, log in and open the database
+ *
+ * Don't call this method directly. Use DB::connect() instead.
+ *
+ * PEAR DB's sybase driver supports the following extra DSN options:
+ * + appname The application name to use on this connection.
+ * Available since PEAR DB 1.7.0.
+ * + charset The character set to use on this connection.
+ * Available since PEAR DB 1.7.0.
+ *
+ * @param array $dsn the data source name
+ * @param bool $persistent should the connection be persistent?
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function connect($dsn, $persistent = false)
+ {
+ if (!PEAR::loadExtension('sybase') &&
+ !PEAR::loadExtension('sybase_ct'))
+ {
+ return $this->raiseError(DB_ERROR_EXTENSION_NOT_FOUND);
+ }
+
+ $this->dsn = $dsn;
+ if ($dsn['dbsyntax']) {
+ $this->dbsyntax = $dsn['dbsyntax'];
+ }
+
+ $dsn['hostspec'] = $dsn['hostspec'] ? $dsn['hostspec'] : 'localhost';
+ $dsn['password'] = !empty($dsn['password']) ? $dsn['password'] : false;
+ $dsn['charset'] = isset($dsn['charset']) ? $dsn['charset'] : false;
+ $dsn['appname'] = isset($dsn['appname']) ? $dsn['appname'] : false;
+
+ $connect_function = $persistent ? 'sybase_pconnect' : 'sybase_connect';
+
+ if ($dsn['username']) {
+ $this->connection = @$connect_function($dsn['hostspec'],
+ $dsn['username'],
+ $dsn['password'],
+ $dsn['charset'],
+ $dsn['appname']);
+ } else {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ 'The DSN did not contain a username.');
+ }
+
+ if (!$this->connection) {
+ return $this->raiseError(DB_ERROR_CONNECT_FAILED,
+ null, null, null,
+ @sybase_get_last_message());
+ }
+
+ if ($dsn['database']) {
+ if (!@sybase_select_db($dsn['database'], $this->connection)) {
+ return $this->raiseError(DB_ERROR_NODBSELECTED,
+ null, null, null,
+ @sybase_get_last_message());
+ }
+ $this->_db = $dsn['database'];
+ }
+
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ disconnect()
+
+ /**
+ * Disconnects from the database server
+ *
+ * @return bool TRUE on success, FALSE on failure
+ */
+ function disconnect()
+ {
+ $ret = @sybase_close($this->connection);
+ $this->connection = null;
+ return $ret;
+ }
+
+ // }}}
+ // {{{ simpleQuery()
+
+ /**
+ * Sends a query to the database server
+ *
+ * @param string the SQL query string
+ *
+ * @return mixed + a PHP result resrouce for successful SELECT queries
+ * + the DB_OK constant for other successful queries
+ * + a DB_Error object on failure
+ */
+ function simpleQuery($query)
+ {
+ $ismanip = $this->_checkManip($query);
+ $this->last_query = $query;
+ if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
+ return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $query = $this->modifyQuery($query);
+ if (!$this->autocommit && $ismanip) {
+ if ($this->transaction_opcount == 0) {
+ $result = @sybase_query('BEGIN TRANSACTION', $this->connection);
+ if (!$result) {
+ return $this->sybaseRaiseError();
+ }
+ }
+ $this->transaction_opcount++;
+ }
+ $result = @sybase_query($query, $this->connection);
+ if (!$result) {
+ return $this->sybaseRaiseError();
+ }
+ if (is_resource($result)) {
+ return $result;
+ }
+ // Determine which queries that should return data, and which
+ // should return an error code only.
+ return $ismanip ? DB_OK : $result;
+ }
+
+ // }}}
+ // {{{ nextResult()
+
+ /**
+ * Move the internal sybase result pointer to the next available result
+ *
+ * @param a valid sybase result resource
+ *
+ * @access public
+ *
+ * @return true if a result is available otherwise return false
+ */
+ function nextResult($result)
+ {
+ return false;
+ }
+
+ // }}}
+ // {{{ fetchInto()
+
+ /**
+ * Places a row from the result set into the given array
+ *
+ * Formating of the array and the data therein are configurable.
+ * See DB_result::fetchInto() for more information.
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::fetchInto() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result the query result resource
+ * @param array $arr the referenced array to put the data in
+ * @param int $fetchmode how the resulting array should be indexed
+ * @param int $rownum the row number to fetch (0 = first row)
+ *
+ * @return mixed DB_OK on success, NULL when the end of a result set is
+ * reached or on failure
+ *
+ * @see DB_result::fetchInto()
+ */
+ function fetchInto($result, &$arr, $fetchmode, $rownum = null)
+ {
+ if ($rownum !== null) {
+ if (!@sybase_data_seek($result, $rownum)) {
+ return null;
+ }
+ }
+ if ($fetchmode & DB_FETCHMODE_ASSOC) {
+ if (function_exists('sybase_fetch_assoc')) {
+ $arr = @sybase_fetch_assoc($result);
+ } else {
+ if ($arr = @sybase_fetch_array($result)) {
+ foreach ($arr as $key => $value) {
+ if (is_int($key)) {
+ unset($arr[$key]);
+ }
+ }
+ }
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE && $arr) {
+ $arr = array_change_key_case($arr, CASE_LOWER);
+ }
+ } else {
+ $arr = @sybase_fetch_row($result);
+ }
+ if (!$arr) {
+ return null;
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_RTRIM) {
+ $this->_rtrimArrayValues($arr);
+ }
+ if ($this->options['portability'] & DB_PORTABILITY_NULL_TO_EMPTY) {
+ $this->_convertNullArrayValuesToEmpty($arr);
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ freeResult()
+
+ /**
+ * Deletes the result set and frees the memory occupied by the result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::free() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return bool TRUE on success, FALSE if $result is invalid
+ *
+ * @see DB_result::free()
+ */
+ function freeResult($result)
+ {
+ return is_resource($result) ? sybase_free_result($result) : false;
+ }
+
+ // }}}
+ // {{{ numCols()
+
+ /**
+ * Gets the number of columns in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numCols() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of columns. A DB_Error object on failure.
+ *
+ * @see DB_result::numCols()
+ */
+ function numCols($result)
+ {
+ $cols = @sybase_num_fields($result);
+ if (!$cols) {
+ return $this->sybaseRaiseError();
+ }
+ return $cols;
+ }
+
+ // }}}
+ // {{{ numRows()
+
+ /**
+ * Gets the number of rows in a result set
+ *
+ * This method is not meant to be called directly. Use
+ * DB_result::numRows() instead. It can't be declared "protected"
+ * because DB_result is a separate object.
+ *
+ * @param resource $result PHP's query result resource
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ *
+ * @see DB_result::numRows()
+ */
+ function numRows($result)
+ {
+ $rows = @sybase_num_rows($result);
+ if ($rows === false) {
+ return $this->sybaseRaiseError();
+ }
+ return $rows;
+ }
+
+ // }}}
+ // {{{ affectedRows()
+
+ /**
+ * Determines the number of rows affected by a data maniuplation query
+ *
+ * 0 is returned for queries that don't manipulate data.
+ *
+ * @return int the number of rows. A DB_Error object on failure.
+ */
+ function affectedRows()
+ {
+ if ($this->_last_query_manip) {
+ $result = @sybase_affected_rows($this->connection);
+ } else {
+ $result = 0;
+ }
+ return $result;
+ }
+
+ // }}}
+ // {{{ nextId()
+
+ /**
+ * Returns the next free id in a sequence
+ *
+ * @param string $seq_name name of the sequence
+ * @param boolean $ondemand when true, the seqence is automatically
+ * created if it does not exist
+ *
+ * @return int the next id number in the sequence.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::nextID(), DB_common::getSequenceName(),
+ * DB_sybase::createSequence(), DB_sybase::dropSequence()
+ */
+ function nextId($seq_name, $ondemand = true)
+ {
+ $seqname = $this->getSequenceName($seq_name);
+ if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
+ return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $repeat = 0;
+ do {
+ $this->pushErrorHandling(PEAR_ERROR_RETURN);
+ $result = $this->query("INSERT INTO $seqname (vapor) VALUES (0)");
+ $this->popErrorHandling();
+ if ($ondemand && DB::isError($result) &&
+ ($result->getCode() == DB_ERROR || $result->getCode() == DB_ERROR_NOSUCHTABLE))
+ {
+ $repeat = 1;
+ $result = $this->createSequence($seq_name);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ } elseif (!DB::isError($result)) {
+ $result = $this->query("SELECT @@IDENTITY FROM $seqname");
+ $repeat = 0;
+ } else {
+ $repeat = false;
+ }
+ } while ($repeat);
+ if (DB::isError($result)) {
+ return $this->raiseError($result);
+ }
+ $result = $result->fetchRow(DB_FETCHMODE_ORDERED);
+ return $result[0];
+ }
+
+ /**
+ * Creates a new sequence
+ *
+ * @param string $seq_name name of the new sequence
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::createSequence(), DB_common::getSequenceName(),
+ * DB_sybase::nextID(), DB_sybase::dropSequence()
+ */
+ function createSequence($seq_name)
+ {
+ return $this->query('CREATE TABLE '
+ . $this->getSequenceName($seq_name)
+ . ' (id numeric(10, 0) IDENTITY NOT NULL,'
+ . ' vapor int NULL)');
+ }
+
+ // }}}
+ // {{{ dropSequence()
+
+ /**
+ * Deletes a sequence
+ *
+ * @param string $seq_name name of the sequence to be deleted
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ *
+ * @see DB_common::dropSequence(), DB_common::getSequenceName(),
+ * DB_sybase::nextID(), DB_sybase::createSequence()
+ */
+ function dropSequence($seq_name)
+ {
+ return $this->query('DROP TABLE ' . $this->getSequenceName($seq_name));
+ }
+
+ // }}}
+ // {{{ quoteFloat()
+
+ /**
+ * Formats a float value for use within a query in a locale-independent
+ * manner.
+ *
+ * @param float the float value to be quoted.
+ * @return string the quoted string.
+ * @see DB_common::quoteSmart()
+ * @since Method available since release 1.7.8.
+ */
+ function quoteFloat($float) {
+ return $this->escapeSimple(str_replace(',', '.', strval(floatval($float))));
+ }
+
+ // }}}
+ // {{{ autoCommit()
+
+ /**
+ * Enables or disables automatic commits
+ *
+ * @param bool $onoff true turns it on, false turns it off
+ *
+ * @return int DB_OK on success. A DB_Error object if the driver
+ * doesn't support auto-committing transactions.
+ */
+ function autoCommit($onoff = false)
+ {
+ // XXX if $this->transaction_opcount > 0, we should probably
+ // issue a warning here.
+ $this->autocommit = $onoff ? true : false;
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ commit()
+
+ /**
+ * Commits the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function commit()
+ {
+ if ($this->transaction_opcount > 0) {
+ if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
+ return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $result = @sybase_query('COMMIT', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->sybaseRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ rollback()
+
+ /**
+ * Reverts the current transaction
+ *
+ * @return int DB_OK on success. A DB_Error object on failure.
+ */
+ function rollback()
+ {
+ if ($this->transaction_opcount > 0) {
+ if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
+ return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $result = @sybase_query('ROLLBACK', $this->connection);
+ $this->transaction_opcount = 0;
+ if (!$result) {
+ return $this->sybaseRaiseError();
+ }
+ }
+ return DB_OK;
+ }
+
+ // }}}
+ // {{{ sybaseRaiseError()
+
+ /**
+ * Produces a DB_Error object regarding the current problem
+ *
+ * @param int $errno if the error is being manually raised pass a
+ * DB_ERROR* constant here. If this isn't passed
+ * the error information gathered from the DBMS.
+ *
+ * @return object the DB_Error object
+ *
+ * @see DB_common::raiseError(),
+ * DB_sybase::errorNative(), DB_sybase::errorCode()
+ */
+ function sybaseRaiseError($errno = null)
+ {
+ $native = $this->errorNative();
+ if ($errno === null) {
+ $errno = $this->errorCode($native);
+ }
+ return $this->raiseError($errno, null, null, null, $native);
+ }
+
+ // }}}
+ // {{{ errorNative()
+
+ /**
+ * Gets the DBMS' native error message produced by the last query
+ *
+ * @return string the DBMS' error message
+ */
+ function errorNative()
+ {
+ return @sybase_get_last_message();
+ }
+
+ // }}}
+ // {{{ errorCode()
+
+ /**
+ * Determines PEAR::DB error code from the database's text error message.
+ *
+ * @param string $errormsg error message returned from the database
+ * @return integer an error number from a DB error constant
+ */
+ function errorCode($errormsg)
+ {
+ static $error_regexps;
+
+ // PHP 5.2+ prepends the function name to $php_errormsg, so we need
+ // this hack to work around it, per bug #9599.
+ $errormsg = preg_replace('/^sybase[a-z_]+\(\): /', '', $errormsg);
+
+ if (!isset($error_regexps)) {
+ $error_regexps = array(
+ '/Incorrect syntax near/'
+ => DB_ERROR_SYNTAX,
+ '/^Unclosed quote before the character string [\"\'].*[\"\']\./'
+ => DB_ERROR_SYNTAX,
+ '/Implicit conversion (from datatype|of NUMERIC value)/i'
+ => DB_ERROR_INVALID_NUMBER,
+ '/Cannot drop the table [\"\'].+[\"\'], because it doesn\'t exist in the system catalogs\./'
+ => DB_ERROR_NOSUCHTABLE,
+ '/Only the owner of object [\"\'].+[\"\'] or a user with System Administrator \(SA\) role can run this command\./'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/^.+ permission denied on object .+, database .+, owner .+/'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/^.* permission denied, database .+, owner .+/'
+ => DB_ERROR_ACCESS_VIOLATION,
+ '/[^.*] not found\./'
+ => DB_ERROR_NOSUCHTABLE,
+ '/There is already an object named/'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/Invalid column name/'
+ => DB_ERROR_NOSUCHFIELD,
+ '/does not allow null values/'
+ => DB_ERROR_CONSTRAINT_NOT_NULL,
+ '/Command has been aborted/'
+ => DB_ERROR_CONSTRAINT,
+ '/^Cannot drop the index .* because it doesn\'t exist/i'
+ => DB_ERROR_NOT_FOUND,
+ '/^There is already an index/i'
+ => DB_ERROR_ALREADY_EXISTS,
+ '/^There are fewer columns in the INSERT statement than values specified/i'
+ => DB_ERROR_VALUE_COUNT_ON_ROW,
+ '/Divide by zero/i'
+ => DB_ERROR_DIVZERO,
+ );
+ }
+
+ foreach ($error_regexps as $regexp => $code) {
+ if (preg_match($regexp, $errormsg)) {
+ return $code;
+ }
+ }
+ return DB_ERROR;
+ }
+
+ // }}}
+ // {{{ tableInfo()
+
+ /**
+ * Returns information about a table or a result set
+ *
+ * NOTE: only supports 'table' and 'flags' if <var>$result</var>
+ * is a table name.
+ *
+ * @param object|string $result DB_result object from a query or a
+ * string containing the name of a table.
+ * While this also accepts a query result
+ * resource identifier, this behavior is
+ * deprecated.
+ * @param int $mode a valid tableInfo mode
+ *
+ * @return array an associative array with the information requested.
+ * A DB_Error object on failure.
+ *
+ * @see DB_common::tableInfo()
+ * @since Method available since Release 1.6.0
+ */
+ function tableInfo($result, $mode = null)
+ {
+ if (is_string($result)) {
+ /*
+ * Probably received a table name.
+ * Create a result resource identifier.
+ */
+ if ($this->_db && !@sybase_select_db($this->_db, $this->connection)) {
+ return $this->sybaseRaiseError(DB_ERROR_NODBSELECTED);
+ }
+ $id = @sybase_query("SELECT * FROM $result WHERE 1=0",
+ $this->connection);
+ $got_string = true;
+ } elseif (isset($result->result)) {
+ /*
+ * Probably received a result object.
+ * Extract the result resource identifier.
+ */
+ $id = $result->result;
+ $got_string = false;
+ } else {
+ /*
+ * Probably received a result resource identifier.
+ * Copy it.
+ * Deprecated. Here for compatibility only.
+ */
+ $id = $result;
+ $got_string = false;
+ }
+
+ if (!is_resource($id)) {
+ return $this->sybaseRaiseError(DB_ERROR_NEED_MORE_DATA);
+ }
+
+ if ($this->options['portability'] & DB_PORTABILITY_LOWERCASE) {
+ $case_func = 'strtolower';
+ } else {
+ $case_func = 'strval';
+ }
+
+ $count = @sybase_num_fields($id);
+ $res = array();
+
+ if ($mode) {
+ $res['num_fields'] = $count;
+ }
+
+ for ($i = 0; $i < $count; $i++) {
+ $f = @sybase_fetch_field($id, $i);
+ // column_source is often blank
+ $res[$i] = array(
+ 'table' => $got_string
+ ? $case_func($result)
+ : $case_func($f->column_source),
+ 'name' => $case_func($f->name),
+ 'type' => $f->type,
+ 'len' => $f->max_length,
+ 'flags' => '',
+ );
+ if ($res[$i]['table']) {
+ $res[$i]['flags'] = $this->_sybase_field_flags(
+ $res[$i]['table'], $res[$i]['name']);
+ }
+ if ($mode & DB_TABLEINFO_ORDER) {
+ $res['order'][$res[$i]['name']] = $i;
+ }
+ if ($mode & DB_TABLEINFO_ORDERTABLE) {
+ $res['ordertable'][$res[$i]['table']][$res[$i]['name']] = $i;
+ }
+ }
+
+ // free the result only if we were called on a table
+ if ($got_string) {
+ @sybase_free_result($id);
+ }
+ return $res;
+ }
+
+ // }}}
+ // {{{ _sybase_field_flags()
+
+ /**
+ * Get the flags for a field
+ *
+ * Currently supports:
+ * + <samp>unique_key</samp> (unique index, unique check or primary_key)
+ * + <samp>multiple_key</samp> (multi-key index)
+ *
+ * @param string $table the table name
+ * @param string $column the field name
+ *
+ * @return string space delimited string of flags. Empty string if none.
+ *
+ * @access private
+ */
+ function _sybase_field_flags($table, $column)
+ {
+ static $tableName = null;
+ static $flags = array();
+
+ if ($table != $tableName) {
+ $flags = array();
+ $tableName = $table;
+
+ /* We're running sp_helpindex directly because it doesn't exist in
+ * older versions of ASE -- unfortunately, we can't just use
+ * DB::isError() because the user may be using callback error
+ * handling. */
+ $res = @sybase_query("sp_helpindex $table", $this->connection);
+
+ if ($res === false || $res === true) {
+ // Fake a valid response for BC reasons.
+ return '';
+ }
+
+ while (($val = sybase_fetch_assoc($res)) !== false) {
+ if (!isset($val['index_keys'])) {
+ /* No useful information returned. Break and be done with
+ * it, which preserves the pre-1.7.9 behaviour. */
+ break;
+ }
+
+ $keys = explode(', ', trim($val['index_keys']));
+
+ if (sizeof($keys) > 1) {
+ foreach ($keys as $key) {
+ $this->_add_flag($flags[$key], 'multiple_key');
+ }
+ }
+
+ if (strpos($val['index_description'], 'unique')) {
+ foreach ($keys as $key) {
+ $this->_add_flag($flags[$key], 'unique_key');
+ }
+ }
+ }
+
+ sybase_free_result($res);
+
+ }
+
+ if (array_key_exists($column, $flags)) {
+ return(implode(' ', $flags[$column]));
+ }
+
+ return '';
+ }
+
+ // }}}
+ // {{{ _add_flag()
+
+ /**
+ * Adds a string to the flags array if the flag is not yet in there
+ * - if there is no flag present the array is created
+ *
+ * @param array $array reference of flags array to add a value to
+ * @param mixed $value value to add to the flag array
+ *
+ * @return void
+ *
+ * @access private
+ */
+ function _add_flag(&$array, $value)
+ {
+ if (!is_array($array)) {
+ $array = array($value);
+ } elseif (!in_array($value, $array)) {
+ array_push($array, $value);
+ }
+ }
+
+ // }}}
+ // {{{ getSpecialQuery()
+
+ /**
+ * Obtains the query string needed for listing a given type of objects
+ *
+ * @param string $type the kind of objects you want to retrieve
+ *
+ * @return string the SQL query string or null if the driver doesn't
+ * support the object type requested
+ *
+ * @access protected
+ * @see DB_common::getListOf()
+ */
+ function getSpecialQuery($type)
+ {
+ switch ($type) {
+ case 'tables':
+ return "SELECT name FROM sysobjects WHERE type = 'U'"
+ . ' ORDER BY name';
+ case 'views':
+ return "SELECT name FROM sysobjects WHERE type = 'V'";
+ default:
+ return null;
+ }
+ }
+
+ // }}}
+
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+
+?>
diff --git a/extlib/Mail.php b/extlib/Mail.php
new file mode 100644
index 000000000..3a0c1a9cb
--- /dev/null
+++ b/extlib/Mail.php
@@ -0,0 +1,238 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Chuck Hagenbuch <chuck@horde.org> |
+// +----------------------------------------------------------------------+
+//
+// $Id: Mail.php,v 1.17 2006/09/15 03:41:18 jon Exp $
+
+require_once 'PEAR.php';
+
+/**
+ * PEAR's Mail:: interface. Defines the interface for implementing
+ * mailers under the PEAR hierarchy, and provides supporting functions
+ * useful in multiple mailer backends.
+ *
+ * @access public
+ * @version $Revision: 1.17 $
+ * @package Mail
+ */
+class Mail
+{
+ /**
+ * Line terminator used for separating header lines.
+ * @var string
+ */
+ var $sep = "\r\n";
+
+ /**
+ * Provides an interface for generating Mail:: objects of various
+ * types
+ *
+ * @param string $driver The kind of Mail:: object to instantiate.
+ * @param array $params The parameters to pass to the Mail:: object.
+ * @return object Mail a instance of the driver class or if fails a PEAR Error
+ * @access public
+ */
+ function &factory($driver, $params = array())
+ {
+ $driver = strtolower($driver);
+ @include_once 'Mail/' . $driver . '.php';
+ $class = 'Mail_' . $driver;
+ if (class_exists($class)) {
+ $mailer = new $class($params);
+ return $mailer;
+ } else {
+ return PEAR::raiseError('Unable to find class for driver ' . $driver);
+ }
+ }
+
+ /**
+ * Implements Mail::send() function using php's built-in mail()
+ * command.
+ *
+ * @param mixed $recipients Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ *
+ * @param array $headers The array of headers to send with the mail, in an
+ * associative array, where the array key is the
+ * header name (ie, 'Subject'), and the array value
+ * is the header value (ie, 'test'). The header
+ * produced from those values would be 'Subject:
+ * test'.
+ *
+ * @param string $body The full text of the message body, including any
+ * Mime parts, etc.
+ *
+ * @return mixed Returns true on success, or a PEAR_Error
+ * containing a descriptive error message on
+ * failure.
+ * @access public
+ * @deprecated use Mail_mail::send instead
+ */
+ function send($recipients, $headers, $body)
+ {
+ $this->_sanitizeHeaders($headers);
+
+ // if we're passed an array of recipients, implode it.
+ if (is_array($recipients)) {
+ $recipients = implode(', ', $recipients);
+ }
+
+ // get the Subject out of the headers array so that we can
+ // pass it as a seperate argument to mail().
+ $subject = '';
+ if (isset($headers['Subject'])) {
+ $subject = $headers['Subject'];
+ unset($headers['Subject']);
+ }
+
+ // flatten the headers out.
+ list(,$text_headers) = Mail::prepareHeaders($headers);
+
+ return mail($recipients, $subject, $body, $text_headers);
+
+ }
+
+ /**
+ * Sanitize an array of mail headers by removing any additional header
+ * strings present in a legitimate header's value. The goal of this
+ * filter is to prevent mail injection attacks.
+ *
+ * @param array $headers The associative array of headers to sanitize.
+ *
+ * @access private
+ */
+ function _sanitizeHeaders(&$headers)
+ {
+ foreach ($headers as $key => $value) {
+ $headers[$key] =
+ preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i',
+ null, $value);
+ }
+ }
+
+ /**
+ * Take an array of mail headers and return a string containing
+ * text usable in sending a message.
+ *
+ * @param array $headers The array of headers to prepare, in an associative
+ * array, where the array key is the header name (ie,
+ * 'Subject'), and the array value is the header
+ * value (ie, 'test'). The header produced from those
+ * values would be 'Subject: test'.
+ *
+ * @return mixed Returns false if it encounters a bad address,
+ * otherwise returns an array containing two
+ * elements: Any From: address found in the headers,
+ * and the plain text version of the headers.
+ * @access private
+ */
+ function prepareHeaders($headers)
+ {
+ $lines = array();
+ $from = null;
+
+ foreach ($headers as $key => $value) {
+ if (strcasecmp($key, 'From') === 0) {
+ include_once 'Mail/RFC822.php';
+ $parser = &new Mail_RFC822();
+ $addresses = $parser->parseAddressList($value, 'localhost', false);
+ if (PEAR::isError($addresses)) {
+ return $addresses;
+ }
+
+ $from = $addresses[0]->mailbox . '@' . $addresses[0]->host;
+
+ // Reject envelope From: addresses with spaces.
+ if (strstr($from, ' ')) {
+ return false;
+ }
+
+ $lines[] = $key . ': ' . $value;
+ } elseif (strcasecmp($key, 'Received') === 0) {
+ $received = array();
+ if (is_array($value)) {
+ foreach ($value as $line) {
+ $received[] = $key . ': ' . $line;
+ }
+ }
+ else {
+ $received[] = $key . ': ' . $value;
+ }
+ // Put Received: headers at the top. Spam detectors often
+ // flag messages with Received: headers after the Subject:
+ // as spam.
+ $lines = array_merge($received, $lines);
+ } else {
+ // If $value is an array (i.e., a list of addresses), convert
+ // it to a comma-delimited string of its elements (addresses).
+ if (is_array($value)) {
+ $value = implode(', ', $value);
+ }
+ $lines[] = $key . ': ' . $value;
+ }
+ }
+
+ return array($from, join($this->sep, $lines));
+ }
+
+ /**
+ * Take a set of recipients and parse them, returning an array of
+ * bare addresses (forward paths) that can be passed to sendmail
+ * or an smtp server with the rcpt to: command.
+ *
+ * @param mixed Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid.
+ *
+ * @return mixed An array of forward paths (bare addresses) or a PEAR_Error
+ * object if the address list could not be parsed.
+ * @access private
+ */
+ function parseRecipients($recipients)
+ {
+ include_once 'Mail/RFC822.php';
+
+ // if we're passed an array, assume addresses are valid and
+ // implode them before parsing.
+ if (is_array($recipients)) {
+ $recipients = implode(', ', $recipients);
+ }
+
+ // Parse recipients, leaving out all personal info. This is
+ // for smtp recipients, etc. All relevant personal information
+ // should already be in the headers.
+ $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false);
+
+ // If parseAddressList() returned a PEAR_Error object, just return it.
+ if (PEAR::isError($addresses)) {
+ return $addresses;
+ }
+
+ $recipients = array();
+ if (is_array($addresses)) {
+ foreach ($addresses as $ob) {
+ $recipients[] = $ob->mailbox . '@' . $ob->host;
+ }
+ }
+
+ return $recipients;
+ }
+
+}
diff --git a/extlib/Mail/RFC822.php b/extlib/Mail/RFC822.php
new file mode 100644
index 000000000..8714df2e2
--- /dev/null
+++ b/extlib/Mail/RFC822.php
@@ -0,0 +1,940 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2001-2002, Richard Heyes |
+// | All rights reserved. |
+// | |
+// | Redistribution and use in source and binary forms, with or without |
+// | modification, are permitted provided that the following conditions |
+// | are met: |
+// | |
+// | o Redistributions of source code must retain the above copyright |
+// | notice, this list of conditions and the following disclaimer. |
+// | o Redistributions in binary form must reproduce the above copyright |
+// | notice, this list of conditions and the following disclaimer in the |
+// | documentation and/or other materials provided with the distribution.|
+// | o The names of the authors may not be used to endorse or promote |
+// | products derived from this software without specific prior written |
+// | permission. |
+// | |
+// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+// | |
+// +-----------------------------------------------------------------------+
+// | Authors: Richard Heyes <richard@phpguru.org> |
+// | Chuck Hagenbuch <chuck@horde.org> |
+// +-----------------------------------------------------------------------+
+
+/**
+ * RFC 822 Email address list validation Utility
+ *
+ * What is it?
+ *
+ * This class will take an address string, and parse it into it's consituent
+ * parts, be that either addresses, groups, or combinations. Nested groups
+ * are not supported. The structure it returns is pretty straight forward,
+ * and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
+ * print_r() to view the structure.
+ *
+ * How do I use it?
+ *
+ * $address_string = 'My Group: "Richard" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
+ * $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
+ * print_r($structure);
+ *
+ * @author Richard Heyes <richard@phpguru.org>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @version $Revision: 1.24 $
+ * @license BSD
+ * @package Mail
+ */
+class Mail_RFC822 {
+
+ /**
+ * The address being parsed by the RFC822 object.
+ * @var string $address
+ */
+ var $address = '';
+
+ /**
+ * The default domain to use for unqualified addresses.
+ * @var string $default_domain
+ */
+ var $default_domain = 'localhost';
+
+ /**
+ * Should we return a nested array showing groups, or flatten everything?
+ * @var boolean $nestGroups
+ */
+ var $nestGroups = true;
+
+ /**
+ * Whether or not to validate atoms for non-ascii characters.
+ * @var boolean $validate
+ */
+ var $validate = true;
+
+ /**
+ * The array of raw addresses built up as we parse.
+ * @var array $addresses
+ */
+ var $addresses = array();
+
+ /**
+ * The final array of parsed address information that we build up.
+ * @var array $structure
+ */
+ var $structure = array();
+
+ /**
+ * The current error message, if any.
+ * @var string $error
+ */
+ var $error = null;
+
+ /**
+ * An internal counter/pointer.
+ * @var integer $index
+ */
+ var $index = null;
+
+ /**
+ * The number of groups that have been found in the address list.
+ * @var integer $num_groups
+ * @access public
+ */
+ var $num_groups = 0;
+
+ /**
+ * A variable so that we can tell whether or not we're inside a
+ * Mail_RFC822 object.
+ * @var boolean $mailRFC822
+ */
+ var $mailRFC822 = true;
+
+ /**
+ * A limit after which processing stops
+ * @var int $limit
+ */
+ var $limit = null;
+
+ /**
+ * Sets up the object. The address must either be set here or when
+ * calling parseAddressList(). One or the other.
+ *
+ * @access public
+ * @param string $address The address(es) to validate.
+ * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost.
+ * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
+ * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+ *
+ * @return object Mail_RFC822 A new Mail_RFC822 object.
+ */
+ function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+ {
+ if (isset($address)) $this->address = $address;
+ if (isset($default_domain)) $this->default_domain = $default_domain;
+ if (isset($nest_groups)) $this->nestGroups = $nest_groups;
+ if (isset($validate)) $this->validate = $validate;
+ if (isset($limit)) $this->limit = $limit;
+ }
+
+ /**
+ * Starts the whole process. The address must either be set here
+ * or when creating the object. One or the other.
+ *
+ * @access public
+ * @param string $address The address(es) to validate.
+ * @param string $default_domain Default domain/host etc.
+ * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
+ * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
+ *
+ * @return array A structured array of addresses.
+ */
+ function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
+ {
+ if (!isset($this) || !isset($this->mailRFC822)) {
+ $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
+ return $obj->parseAddressList();
+ }
+
+ if (isset($address)) $this->address = $address;
+ if (isset($default_domain)) $this->default_domain = $default_domain;
+ if (isset($nest_groups)) $this->nestGroups = $nest_groups;
+ if (isset($validate)) $this->validate = $validate;
+ if (isset($limit)) $this->limit = $limit;
+
+ $this->structure = array();
+ $this->addresses = array();
+ $this->error = null;
+ $this->index = null;
+
+ // Unfold any long lines in $this->address.
+ $this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
+ $this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
+
+ while ($this->address = $this->_splitAddresses($this->address));
+
+ if ($this->address === false || isset($this->error)) {
+ require_once 'PEAR.php';
+ return PEAR::raiseError($this->error);
+ }
+
+ // Validate each address individually. If we encounter an invalid
+ // address, stop iterating and return an error immediately.
+ foreach ($this->addresses as $address) {
+ $valid = $this->_validateAddress($address);
+
+ if ($valid === false || isset($this->error)) {
+ require_once 'PEAR.php';
+ return PEAR::raiseError($this->error);
+ }
+
+ if (!$this->nestGroups) {
+ $this->structure = array_merge($this->structure, $valid);
+ } else {
+ $this->structure[] = $valid;
+ }
+ }
+
+ return $this->structure;
+ }
+
+ /**
+ * Splits an address into separate addresses.
+ *
+ * @access private
+ * @param string $address The addresses to split.
+ * @return boolean Success or failure.
+ */
+ function _splitAddresses($address)
+ {
+ if (!empty($this->limit) && count($this->addresses) == $this->limit) {
+ return '';
+ }
+
+ if ($this->_isGroup($address) && !isset($this->error)) {
+ $split_char = ';';
+ $is_group = true;
+ } elseif (!isset($this->error)) {
+ $split_char = ',';
+ $is_group = false;
+ } elseif (isset($this->error)) {
+ return false;
+ }
+
+ // Split the string based on the above ten or so lines.
+ $parts = explode($split_char, $address);
+ $string = $this->_splitCheck($parts, $split_char);
+
+ // If a group...
+ if ($is_group) {
+ // If $string does not contain a colon outside of
+ // brackets/quotes etc then something's fubar.
+
+ // First check there's a colon at all:
+ if (strpos($string, ':') === false) {
+ $this->error = 'Invalid address: ' . $string;
+ return false;
+ }
+
+ // Now check it's outside of brackets/quotes:
+ if (!$this->_splitCheck(explode(':', $string), ':')) {
+ return false;
+ }
+
+ // We must have a group at this point, so increase the counter:
+ $this->num_groups++;
+ }
+
+ // $string now contains the first full address/group.
+ // Add to the addresses array.
+ $this->addresses[] = array(
+ 'address' => trim($string),
+ 'group' => $is_group
+ );
+
+ // Remove the now stored address from the initial line, the +1
+ // is to account for the explode character.
+ $address = trim(substr($address, strlen($string) + 1));
+
+ // If the next char is a comma and this was a group, then
+ // there are more addresses, otherwise, if there are any more
+ // chars, then there is another address.
+ if ($is_group && substr($address, 0, 1) == ','){
+ $address = trim(substr($address, 1));
+ return $address;
+
+ } elseif (strlen($address) > 0) {
+ return $address;
+
+ } else {
+ return '';
+ }
+
+ // If you got here then something's off
+ return false;
+ }
+
+ /**
+ * Checks for a group at the start of the string.
+ *
+ * @access private
+ * @param string $address The address to check.
+ * @return boolean Whether or not there is a group at the start of the string.
+ */
+ function _isGroup($address)
+ {
+ // First comma not in quotes, angles or escaped:
+ $parts = explode(',', $address);
+ $string = $this->_splitCheck($parts, ',');
+
+ // Now we have the first address, we can reliably check for a
+ // group by searching for a colon that's not escaped or in
+ // quotes or angle brackets.
+ if (count($parts = explode(':', $string)) > 1) {
+ $string2 = $this->_splitCheck($parts, ':');
+ return ($string2 !== $string);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * A common function that will check an exploded string.
+ *
+ * @access private
+ * @param array $parts The exloded string.
+ * @param string $char The char that was exploded on.
+ * @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
+ */
+ function _splitCheck($parts, $char)
+ {
+ $string = $parts[0];
+
+ for ($i = 0; $i < count($parts); $i++) {
+ if ($this->_hasUnclosedQuotes($string)
+ || $this->_hasUnclosedBrackets($string, '<>')
+ || $this->_hasUnclosedBrackets($string, '[]')
+ || $this->_hasUnclosedBrackets($string, '()')
+ || substr($string, -1) == '\\') {
+ if (isset($parts[$i + 1])) {
+ $string = $string . $char . $parts[$i + 1];
+ } else {
+ $this->error = 'Invalid address spec. Unclosed bracket or quotes';
+ return false;
+ }
+ } else {
+ $this->index = $i;
+ break;
+ }
+ }
+
+ return $string;
+ }
+
+ /**
+ * Checks if a string has unclosed quotes or not.
+ *
+ * @access private
+ * @param string $string The string to check.
+ * @return boolean True if there are unclosed quotes inside the string,
+ * false otherwise.
+ */
+ function _hasUnclosedQuotes($string)
+ {
+ $string = trim($string);
+ $iMax = strlen($string);
+ $in_quote = false;
+ $i = $slashes = 0;
+
+ for (; $i < $iMax; ++$i) {
+ switch ($string[$i]) {
+ case '\\':
+ ++$slashes;
+ break;
+
+ case '"':
+ if ($slashes % 2 == 0) {
+ $in_quote = !$in_quote;
+ }
+ // Fall through to default action below.
+
+ default:
+ $slashes = 0;
+ break;
+ }
+ }
+
+ return $in_quote;
+ }
+
+ /**
+ * Checks if a string has an unclosed brackets or not. IMPORTANT:
+ * This function handles both angle brackets and square brackets;
+ *
+ * @access private
+ * @param string $string The string to check.
+ * @param string $chars The characters to check for.
+ * @return boolean True if there are unclosed brackets inside the string, false otherwise.
+ */
+ function _hasUnclosedBrackets($string, $chars)
+ {
+ $num_angle_start = substr_count($string, $chars[0]);
+ $num_angle_end = substr_count($string, $chars[1]);
+
+ $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
+ $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
+
+ if ($num_angle_start < $num_angle_end) {
+ $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
+ return false;
+ } else {
+ return ($num_angle_start > $num_angle_end);
+ }
+ }
+
+ /**
+ * Sub function that is used only by hasUnclosedBrackets().
+ *
+ * @access private
+ * @param string $string The string to check.
+ * @param integer &$num The number of occurences.
+ * @param string $char The character to count.
+ * @return integer The number of occurences of $char in $string, adjusted for backslashes.
+ */
+ function _hasUnclosedBracketsSub($string, &$num, $char)
+ {
+ $parts = explode($char, $string);
+ for ($i = 0; $i < count($parts); $i++){
+ if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
+ $num--;
+ if (isset($parts[$i + 1]))
+ $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
+ }
+
+ return $num;
+ }
+
+ /**
+ * Function to begin checking the address.
+ *
+ * @access private
+ * @param string $address The address to validate.
+ * @return mixed False on failure, or a structured array of address information on success.
+ */
+ function _validateAddress($address)
+ {
+ $is_group = false;
+ $addresses = array();
+
+ if ($address['group']) {
+ $is_group = true;
+
+ // Get the group part of the name
+ $parts = explode(':', $address['address']);
+ $groupname = $this->_splitCheck($parts, ':');
+ $structure = array();
+
+ // And validate the group part of the name.
+ if (!$this->_validatePhrase($groupname)){
+ $this->error = 'Group name did not validate.';
+ return false;
+ } else {
+ // Don't include groups if we are not nesting
+ // them. This avoids returning invalid addresses.
+ if ($this->nestGroups) {
+ $structure = new stdClass;
+ $structure->groupname = $groupname;
+ }
+ }
+
+ $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
+ }
+
+ // If a group then split on comma and put into an array.
+ // Otherwise, Just put the whole address in an array.
+ if ($is_group) {
+ while (strlen($address['address']) > 0) {
+ $parts = explode(',', $address['address']);
+ $addresses[] = $this->_splitCheck($parts, ',');
+ $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
+ }
+ } else {
+ $addresses[] = $address['address'];
+ }
+
+ // Check that $addresses is set, if address like this:
+ // Groupname:;
+ // Then errors were appearing.
+ if (!count($addresses)){
+ $this->error = 'Empty group.';
+ return false;
+ }
+
+ // Trim the whitespace from all of the address strings.
+ array_map('trim', $addresses);
+
+ // Validate each mailbox.
+ // Format could be one of: name <geezer@domain.com>
+ // geezer@domain.com
+ // geezer
+ // ... or any other format valid by RFC 822.
+ for ($i = 0; $i < count($addresses); $i++) {
+ if (!$this->validateMailbox($addresses[$i])) {
+ if (empty($this->error)) {
+ $this->error = 'Validation failed for: ' . $addresses[$i];
+ }
+ return false;
+ }
+ }
+
+ // Nested format
+ if ($this->nestGroups) {
+ if ($is_group) {
+ $structure->addresses = $addresses;
+ } else {
+ $structure = $addresses[0];
+ }
+
+ // Flat format
+ } else {
+ if ($is_group) {
+ $structure = array_merge($structure, $addresses);
+ } else {
+ $structure = $addresses;
+ }
+ }
+
+ return $structure;
+ }
+
+ /**
+ * Function to validate a phrase.
+ *
+ * @access private
+ * @param string $phrase The phrase to check.
+ * @return boolean Success or failure.
+ */
+ function _validatePhrase($phrase)
+ {
+ // Splits on one or more Tab or space.
+ $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
+
+ $phrase_parts = array();
+ while (count($parts) > 0){
+ $phrase_parts[] = $this->_splitCheck($parts, ' ');
+ for ($i = 0; $i < $this->index + 1; $i++)
+ array_shift($parts);
+ }
+
+ foreach ($phrase_parts as $part) {
+ // If quoted string:
+ if (substr($part, 0, 1) == '"') {
+ if (!$this->_validateQuotedString($part)) {
+ return false;
+ }
+ continue;
+ }
+
+ // Otherwise it's an atom:
+ if (!$this->_validateAtom($part)) return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Function to validate an atom which from rfc822 is:
+ * atom = 1*<any CHAR except specials, SPACE and CTLs>
+ *
+ * If validation ($this->validate) has been turned off, then
+ * validateAtom() doesn't actually check anything. This is so that you
+ * can split a list of addresses up before encoding personal names
+ * (umlauts, etc.), for example.
+ *
+ * @access private
+ * @param string $atom The string to check.
+ * @return boolean Success or failure.
+ */
+ function _validateAtom($atom)
+ {
+ if (!$this->validate) {
+ // Validation has been turned off; assume the atom is okay.
+ return true;
+ }
+
+ // Check for any char from ASCII 0 - ASCII 127
+ if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
+ return false;
+ }
+
+ // Check for specials:
+ if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
+ return false;
+ }
+
+ // Check for control characters (ASCII 0-31):
+ if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Function to validate quoted string, which is:
+ * quoted-string = <"> *(qtext/quoted-pair) <">
+ *
+ * @access private
+ * @param string $qstring The string to check
+ * @return boolean Success or failure.
+ */
+ function _validateQuotedString($qstring)
+ {
+ // Leading and trailing "
+ $qstring = substr($qstring, 1, -1);
+
+ // Perform check, removing quoted characters first.
+ return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring));
+ }
+
+ /**
+ * Function to validate a mailbox, which is:
+ * mailbox = addr-spec ; simple address
+ * / phrase route-addr ; name and route-addr
+ *
+ * @access public
+ * @param string &$mailbox The string to check.
+ * @return boolean Success or failure.
+ */
+ function validateMailbox(&$mailbox)
+ {
+ // A couple of defaults.
+ $phrase = '';
+ $comment = '';
+ $comments = array();
+
+ // Catch any RFC822 comments and store them separately.
+ $_mailbox = $mailbox;
+ while (strlen(trim($_mailbox)) > 0) {
+ $parts = explode('(', $_mailbox);
+ $before_comment = $this->_splitCheck($parts, '(');
+ if ($before_comment != $_mailbox) {
+ // First char should be a (.
+ $comment = substr(str_replace($before_comment, '', $_mailbox), 1);
+ $parts = explode(')', $comment);
+ $comment = $this->_splitCheck($parts, ')');
+ $comments[] = $comment;
+
+ // +1 is for the trailing )
+ $_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
+ } else {
+ break;
+ }
+ }
+
+ foreach ($comments as $comment) {
+ $mailbox = str_replace("($comment)", '', $mailbox);
+ }
+
+ $mailbox = trim($mailbox);
+
+ // Check for name + route-addr
+ if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
+ $parts = explode('<', $mailbox);
+ $name = $this->_splitCheck($parts, '<');
+
+ $phrase = trim($name);
+ $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
+
+ if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
+ return false;
+ }
+
+ // Only got addr-spec
+ } else {
+ // First snip angle brackets if present.
+ if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') {
+ $addr_spec = substr($mailbox, 1, -1);
+ } else {
+ $addr_spec = $mailbox;
+ }
+
+ if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+ return false;
+ }
+ }
+
+ // Construct the object that will be returned.
+ $mbox = new stdClass();
+
+ // Add the phrase (even if empty) and comments
+ $mbox->personal = $phrase;
+ $mbox->comment = isset($comments) ? $comments : array();
+
+ if (isset($route_addr)) {
+ $mbox->mailbox = $route_addr['local_part'];
+ $mbox->host = $route_addr['domain'];
+ $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
+ } else {
+ $mbox->mailbox = $addr_spec['local_part'];
+ $mbox->host = $addr_spec['domain'];
+ }
+
+ $mailbox = $mbox;
+ return true;
+ }
+
+ /**
+ * This function validates a route-addr which is:
+ * route-addr = "<" [route] addr-spec ">"
+ *
+ * Angle brackets have already been removed at the point of
+ * getting to this function.
+ *
+ * @access private
+ * @param string $route_addr The string to check.
+ * @return mixed False on failure, or an array containing validated address/route information on success.
+ */
+ function _validateRouteAddr($route_addr)
+ {
+ // Check for colon.
+ if (strpos($route_addr, ':') !== false) {
+ $parts = explode(':', $route_addr);
+ $route = $this->_splitCheck($parts, ':');
+ } else {
+ $route = $route_addr;
+ }
+
+ // If $route is same as $route_addr then the colon was in
+ // quotes or brackets or, of course, non existent.
+ if ($route === $route_addr){
+ unset($route);
+ $addr_spec = $route_addr;
+ if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+ return false;
+ }
+ } else {
+ // Validate route part.
+ if (($route = $this->_validateRoute($route)) === false) {
+ return false;
+ }
+
+ $addr_spec = substr($route_addr, strlen($route . ':'));
+
+ // Validate addr-spec part.
+ if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
+ return false;
+ }
+ }
+
+ if (isset($route)) {
+ $return['adl'] = $route;
+ } else {
+ $return['adl'] = '';
+ }
+
+ $return = array_merge($return, $addr_spec);
+ return $return;
+ }
+
+ /**
+ * Function to validate a route, which is:
+ * route = 1#("@" domain) ":"
+ *
+ * @access private
+ * @param string $route The string to check.
+ * @return mixed False on failure, or the validated $route on success.
+ */
+ function _validateRoute($route)
+ {
+ // Split on comma.
+ $domains = explode(',', trim($route));
+
+ foreach ($domains as $domain) {
+ $domain = str_replace('@', '', trim($domain));
+ if (!$this->_validateDomain($domain)) return false;
+ }
+
+ return $route;
+ }
+
+ /**
+ * Function to validate a domain, though this is not quite what
+ * you expect of a strict internet domain.
+ *
+ * domain = sub-domain *("." sub-domain)
+ *
+ * @access private
+ * @param string $domain The string to check.
+ * @return mixed False on failure, or the validated domain on success.
+ */
+ function _validateDomain($domain)
+ {
+ // Note the different use of $subdomains and $sub_domains
+ $subdomains = explode('.', $domain);
+
+ while (count($subdomains) > 0) {
+ $sub_domains[] = $this->_splitCheck($subdomains, '.');
+ for ($i = 0; $i < $this->index + 1; $i++)
+ array_shift($subdomains);
+ }
+
+ foreach ($sub_domains as $sub_domain) {
+ if (!$this->_validateSubdomain(trim($sub_domain)))
+ return false;
+ }
+
+ // Managed to get here, so return input.
+ return $domain;
+ }
+
+ /**
+ * Function to validate a subdomain:
+ * subdomain = domain-ref / domain-literal
+ *
+ * @access private
+ * @param string $subdomain The string to check.
+ * @return boolean Success or failure.
+ */
+ function _validateSubdomain($subdomain)
+ {
+ if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
+ if (!$this->_validateDliteral($arr[1])) return false;
+ } else {
+ if (!$this->_validateAtom($subdomain)) return false;
+ }
+
+ // Got here, so return successful.
+ return true;
+ }
+
+ /**
+ * Function to validate a domain literal:
+ * domain-literal = "[" *(dtext / quoted-pair) "]"
+ *
+ * @access private
+ * @param string $dliteral The string to check.
+ * @return boolean Success or failure.
+ */
+ function _validateDliteral($dliteral)
+ {
+ return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
+ }
+
+ /**
+ * Function to validate an addr-spec.
+ *
+ * addr-spec = local-part "@" domain
+ *
+ * @access private
+ * @param string $addr_spec The string to check.
+ * @return mixed False on failure, or the validated addr-spec on success.
+ */
+ function _validateAddrSpec($addr_spec)
+ {
+ $addr_spec = trim($addr_spec);
+
+ // Split on @ sign if there is one.
+ if (strpos($addr_spec, '@') !== false) {
+ $parts = explode('@', $addr_spec);
+ $local_part = $this->_splitCheck($parts, '@');
+ $domain = substr($addr_spec, strlen($local_part . '@'));
+
+ // No @ sign so assume the default domain.
+ } else {
+ $local_part = $addr_spec;
+ $domain = $this->default_domain;
+ }
+
+ if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
+ if (($domain = $this->_validateDomain($domain)) === false) return false;
+
+ // Got here so return successful.
+ return array('local_part' => $local_part, 'domain' => $domain);
+ }
+
+ /**
+ * Function to validate the local part of an address:
+ * local-part = word *("." word)
+ *
+ * @access private
+ * @param string $local_part
+ * @return mixed False on failure, or the validated local part on success.
+ */
+ function _validateLocalPart($local_part)
+ {
+ $parts = explode('.', $local_part);
+ $words = array();
+
+ // Split the local_part into words.
+ while (count($parts) > 0){
+ $words[] = $this->_splitCheck($parts, '.');
+ for ($i = 0; $i < $this->index + 1; $i++) {
+ array_shift($parts);
+ }
+ }
+
+ // Validate each word.
+ foreach ($words as $word) {
+ // If this word contains an unquoted space, it is invalid. (6.2.4)
+ if (strpos($word, ' ') && $word[0] !== '"')
+ {
+ return false;
+ }
+
+ if ($this->_validatePhrase(trim($word)) === false) return false;
+ }
+
+ // Managed to get here, so return the input.
+ return $local_part;
+ }
+
+ /**
+ * Returns an approximate count of how many addresses are in the
+ * given string. This is APPROXIMATE as it only splits based on a
+ * comma which has no preceding backslash. Could be useful as
+ * large amounts of addresses will end up producing *large*
+ * structures when used with parseAddressList().
+ *
+ * @param string $data Addresses to count
+ * @return int Approximate count
+ */
+ function approximateCount($data)
+ {
+ return count(preg_split('/(?<!\\\\),/', $data));
+ }
+
+ /**
+ * This is a email validating function separate to the rest of the
+ * class. It simply validates whether an email is of the common
+ * internet form: <user>@<domain>. This can be sufficient for most
+ * people. Optional stricter mode can be utilised which restricts
+ * mailbox characters allowed to alphanumeric, full stop, hyphen
+ * and underscore.
+ *
+ * @param string $data Address to check
+ * @param boolean $strict Optional stricter mode
+ * @return mixed False if it fails, an indexed array
+ * username/domain if it matches
+ */
+ function isValidInetAddress($data, $strict = false)
+ {
+ $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
+ if (preg_match($regex, trim($data), $matches)) {
+ return array($matches[1], $matches[2]);
+ } else {
+ return false;
+ }
+ }
+
+}
diff --git a/extlib/Mail/mail.php b/extlib/Mail/mail.php
new file mode 100644
index 000000000..b13d69565
--- /dev/null
+++ b/extlib/Mail/mail.php
@@ -0,0 +1,143 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Chuck Hagenbuch <chuck@horde.org> |
+// +----------------------------------------------------------------------+
+//
+// $Id: mail.php,v 1.20 2007/10/06 17:00:00 chagenbu Exp $
+
+/**
+ * internal PHP-mail() implementation of the PEAR Mail:: interface.
+ * @package Mail
+ * @version $Revision: 1.20 $
+ */
+class Mail_mail extends Mail {
+
+ /**
+ * Any arguments to pass to the mail() function.
+ * @var string
+ */
+ var $_params = '';
+
+ /**
+ * Constructor.
+ *
+ * Instantiates a new Mail_mail:: object based on the parameters
+ * passed in.
+ *
+ * @param array $params Extra arguments for the mail() function.
+ */
+ function Mail_mail($params = null)
+ {
+ // The other mail implementations accept parameters as arrays.
+ // In the interest of being consistent, explode an array into
+ // a string of parameter arguments.
+ if (is_array($params)) {
+ $this->_params = join(' ', $params);
+ } else {
+ $this->_params = $params;
+ }
+
+ /* Because the mail() function may pass headers as command
+ * line arguments, we can't guarantee the use of the standard
+ * "\r\n" separator. Instead, we use the system's native line
+ * separator. */
+ if (defined('PHP_EOL')) {
+ $this->sep = PHP_EOL;
+ } else {
+ $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
+ }
+ }
+
+ /**
+ * Implements Mail_mail::send() function using php's built-in mail()
+ * command.
+ *
+ * @param mixed $recipients Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ *
+ * @param array $headers The array of headers to send with the mail, in an
+ * associative array, where the array key is the
+ * header name (ie, 'Subject'), and the array value
+ * is the header value (ie, 'test'). The header
+ * produced from those values would be 'Subject:
+ * test'.
+ *
+ * @param string $body The full text of the message body, including any
+ * Mime parts, etc.
+ *
+ * @return mixed Returns true on success, or a PEAR_Error
+ * containing a descriptive error message on
+ * failure.
+ *
+ * @access public
+ */
+ function send($recipients, $headers, $body)
+ {
+ if (!is_array($headers)) {
+ return PEAR::raiseError('$headers must be an array');
+ }
+
+ $result = $this->_sanitizeHeaders($headers);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ // If we're passed an array of recipients, implode it.
+ if (is_array($recipients)) {
+ $recipients = implode(', ', $recipients);
+ }
+
+ // Get the Subject out of the headers array so that we can
+ // pass it as a seperate argument to mail().
+ $subject = '';
+ if (isset($headers['Subject'])) {
+ $subject = $headers['Subject'];
+ unset($headers['Subject']);
+ }
+
+ // Also remove the To: header. The mail() function will add its own
+ // To: header based on the contents of $recipients.
+ unset($headers['To']);
+
+ // Flatten the headers out.
+ $headerElements = $this->prepareHeaders($headers);
+ if (is_a($headerElements, 'PEAR_Error')) {
+ return $headerElements;
+ }
+ list(, $text_headers) = $headerElements;
+
+ // We only use mail()'s optional fifth parameter if the additional
+ // parameters have been provided and we're not running in safe mode.
+ if (empty($this->_params) || ini_get('safe_mode')) {
+ $result = mail($recipients, $subject, $body, $text_headers);
+ } else {
+ $result = mail($recipients, $subject, $body, $text_headers,
+ $this->_params);
+ }
+
+ // If the mail() function returned failure, we need to create a
+ // PEAR_Error object and return it instead of the boolean result.
+ if ($result === false) {
+ $result = PEAR::raiseError('mail() returned failure');
+ }
+
+ return $result;
+ }
+
+}
diff --git a/extlib/Mail/mock.php b/extlib/Mail/mock.php
new file mode 100644
index 000000000..971dae6a0
--- /dev/null
+++ b/extlib/Mail/mock.php
@@ -0,0 +1,119 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Chuck Hagenbuch <chuck@horde.org> |
+// +----------------------------------------------------------------------+
+//
+// $Id: mock.php,v 1.1 2007/12/08 17:57:54 chagenbu Exp $
+//
+
+/**
+ * Mock implementation of the PEAR Mail:: interface for testing.
+ * @access public
+ * @package Mail
+ * @version $Revision: 1.1 $
+ */
+class Mail_mock extends Mail {
+
+ /**
+ * Array of messages that have been sent with the mock.
+ *
+ * @var array
+ * @access public
+ */
+ var $sentMessages = array();
+
+ /**
+ * Callback before sending mail.
+ *
+ * @var callback
+ */
+ var $_preSendCallback;
+
+ /**
+ * Callback after sending mai.
+ *
+ * @var callback
+ */
+ var $_postSendCallback;
+
+ /**
+ * Constructor.
+ *
+ * Instantiates a new Mail_mock:: object based on the parameters
+ * passed in. It looks for the following parameters, both optional:
+ * preSendCallback Called before an email would be sent.
+ * postSendCallback Called after an email would have been sent.
+ *
+ * @param array Hash containing any parameters.
+ * @access public
+ */
+ function Mail_mock($params)
+ {
+ if (isset($params['preSendCallback']) &&
+ is_callable($params['preSendCallback'])) {
+ $this->_preSendCallback = $params['preSendCallback'];
+ }
+
+ if (isset($params['postSendCallback']) &&
+ is_callable($params['postSendCallback'])) {
+ $this->_postSendCallback = $params['postSendCallback'];
+ }
+ }
+
+ /**
+ * Implements Mail_mock::send() function. Silently discards all
+ * mail.
+ *
+ * @param mixed $recipients Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ *
+ * @param array $headers The array of headers to send with the mail, in an
+ * associative array, where the array key is the
+ * header name (ie, 'Subject'), and the array value
+ * is the header value (ie, 'test'). The header
+ * produced from those values would be 'Subject:
+ * test'.
+ *
+ * @param string $body The full text of the message body, including any
+ * Mime parts, etc.
+ *
+ * @return mixed Returns true on success, or a PEAR_Error
+ * containing a descriptive error message on
+ * failure.
+ * @access public
+ */
+ function send($recipients, $headers, $body)
+ {
+ if ($this->_preSendCallback) {
+ call_user_func_array($this->_preSendCallback,
+ array(&$this, $recipients, $headers, $body));
+ }
+
+ $entry = array('recipients' => $recipients, 'headers' => $headers, 'body' => $body);
+ $this->sentMessages[] = $entry;
+
+ if ($this->_postSendCallback) {
+ call_user_func_array($this->_postSendCallback,
+ array(&$this, $recipients, $headers, $body));
+ }
+
+ return true;
+ }
+
+}
diff --git a/extlib/Mail/null.php b/extlib/Mail/null.php
new file mode 100644
index 000000000..982bfa45b
--- /dev/null
+++ b/extlib/Mail/null.php
@@ -0,0 +1,60 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Phil Kernick <philk@rotfl.com.au> |
+// +----------------------------------------------------------------------+
+//
+// $Id: null.php,v 1.2 2004/04/06 05:19:03 jon Exp $
+//
+
+/**
+ * Null implementation of the PEAR Mail:: interface.
+ * @access public
+ * @package Mail
+ * @version $Revision: 1.2 $
+ */
+class Mail_null extends Mail {
+
+ /**
+ * Implements Mail_null::send() function. Silently discards all
+ * mail.
+ *
+ * @param mixed $recipients Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ *
+ * @param array $headers The array of headers to send with the mail, in an
+ * associative array, where the array key is the
+ * header name (ie, 'Subject'), and the array value
+ * is the header value (ie, 'test'). The header
+ * produced from those values would be 'Subject:
+ * test'.
+ *
+ * @param string $body The full text of the message body, including any
+ * Mime parts, etc.
+ *
+ * @return mixed Returns true on success, or a PEAR_Error
+ * containing a descriptive error message on
+ * failure.
+ * @access public
+ */
+ function send($recipients, $headers, $body)
+ {
+ return true;
+ }
+
+}
diff --git a/extlib/Mail/sendmail.php b/extlib/Mail/sendmail.php
new file mode 100644
index 000000000..cd248e61d
--- /dev/null
+++ b/extlib/Mail/sendmail.php
@@ -0,0 +1,170 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Chuck Hagenbuch <chuck@horde.org> |
+// +----------------------------------------------------------------------+
+
+/**
+ * Sendmail implementation of the PEAR Mail:: interface.
+ * @access public
+ * @package Mail
+ * @version $Revision: 1.19 $
+ */
+class Mail_sendmail extends Mail {
+
+ /**
+ * The location of the sendmail or sendmail wrapper binary on the
+ * filesystem.
+ * @var string
+ */
+ var $sendmail_path = '/usr/sbin/sendmail';
+
+ /**
+ * Any extra command-line parameters to pass to the sendmail or
+ * sendmail wrapper binary.
+ * @var string
+ */
+ var $sendmail_args = '-i';
+
+ /**
+ * Constructor.
+ *
+ * Instantiates a new Mail_sendmail:: object based on the parameters
+ * passed in. It looks for the following parameters:
+ * sendmail_path The location of the sendmail binary on the
+ * filesystem. Defaults to '/usr/sbin/sendmail'.
+ *
+ * sendmail_args Any extra parameters to pass to the sendmail
+ * or sendmail wrapper binary.
+ *
+ * If a parameter is present in the $params array, it replaces the
+ * default.
+ *
+ * @param array $params Hash containing any parameters different from the
+ * defaults.
+ * @access public
+ */
+ function Mail_sendmail($params)
+ {
+ if (isset($params['sendmail_path'])) {
+ $this->sendmail_path = $params['sendmail_path'];
+ }
+ if (isset($params['sendmail_args'])) {
+ $this->sendmail_args = $params['sendmail_args'];
+ }
+
+ /*
+ * Because we need to pass message headers to the sendmail program on
+ * the commandline, we can't guarantee the use of the standard "\r\n"
+ * separator. Instead, we use the system's native line separator.
+ */
+ if (defined('PHP_EOL')) {
+ $this->sep = PHP_EOL;
+ } else {
+ $this->sep = (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n";
+ }
+ }
+
+ /**
+ * Implements Mail::send() function using the sendmail
+ * command-line binary.
+ *
+ * @param mixed $recipients Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ *
+ * @param array $headers The array of headers to send with the mail, in an
+ * associative array, where the array key is the
+ * header name (ie, 'Subject'), and the array value
+ * is the header value (ie, 'test'). The header
+ * produced from those values would be 'Subject:
+ * test'.
+ *
+ * @param string $body The full text of the message body, including any
+ * Mime parts, etc.
+ *
+ * @return mixed Returns true on success, or a PEAR_Error
+ * containing a descriptive error message on
+ * failure.
+ * @access public
+ */
+ function send($recipients, $headers, $body)
+ {
+ if (!is_array($headers)) {
+ return PEAR::raiseError('$headers must be an array');
+ }
+
+ $result = $this->_sanitizeHeaders($headers);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $recipients = $this->parseRecipients($recipients);
+ if (is_a($recipients, 'PEAR_Error')) {
+ return $recipients;
+ }
+ $recipients = escapeShellCmd(implode(' ', $recipients));
+
+ $headerElements = $this->prepareHeaders($headers);
+ if (is_a($headerElements, 'PEAR_Error')) {
+ return $headerElements;
+ }
+ list($from, $text_headers) = $headerElements;
+
+ /* Since few MTAs are going to allow this header to be forged
+ * unless it's in the MAIL FROM: exchange, we'll use
+ * Return-Path instead of From: if it's set. */
+ if (!empty($headers['Return-Path'])) {
+ $from = $headers['Return-Path'];
+ }
+
+ if (!isset($from)) {
+ return PEAR::raiseError('No from address given.');
+ } elseif (strpos($from, ' ') !== false ||
+ strpos($from, ';') !== false ||
+ strpos($from, '&') !== false ||
+ strpos($from, '`') !== false) {
+ return PEAR::raiseError('From address specified with dangerous characters.');
+ }
+
+ $from = escapeShellCmd($from);
+ $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w');
+ if (!$mail) {
+ return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.');
+ }
+
+ // Write the headers following by two newlines: one to end the headers
+ // section and a second to separate the headers block from the body.
+ fputs($mail, $text_headers . $this->sep . $this->sep);
+
+ fputs($mail, $body);
+ $result = pclose($mail);
+ if (version_compare(phpversion(), '4.2.3') == -1) {
+ // With older php versions, we need to shift the pclose
+ // result to get the exit code.
+ $result = $result >> 8 & 0xFF;
+ }
+
+ if ($result != 0) {
+ return PEAR::raiseError('sendmail returned error code ' . $result,
+ $result);
+ }
+
+ return true;
+ }
+
+}
diff --git a/extlib/Mail/smtp.php b/extlib/Mail/smtp.php
new file mode 100644
index 000000000..baf3a962b
--- /dev/null
+++ b/extlib/Mail/smtp.php
@@ -0,0 +1,407 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Chuck Hagenbuch <chuck@horde.org> |
+// | Jon Parise <jon@php.net> |
+// +----------------------------------------------------------------------+
+
+/** Error: Failed to create a Net_SMTP object */
+define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000);
+
+/** Error: Failed to connect to SMTP server */
+define('PEAR_MAIL_SMTP_ERROR_CONNECT', 10001);
+
+/** Error: SMTP authentication failure */
+define('PEAR_MAIL_SMTP_ERROR_AUTH', 10002);
+
+/** Error: No From: address has been provided */
+define('PEAR_MAIL_SMTP_ERROR_FROM', 10003);
+
+/** Error: Failed to set sender */
+define('PEAR_MAIL_SMTP_ERROR_SENDER', 10004);
+
+/** Error: Failed to add recipient */
+define('PEAR_MAIL_SMTP_ERROR_RECIPIENT', 10005);
+
+/** Error: Failed to send data */
+define('PEAR_MAIL_SMTP_ERROR_DATA', 10006);
+
+/**
+ * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ * @access public
+ * @package Mail
+ * @version $Revision: 1.33 $
+ */
+class Mail_smtp extends Mail {
+
+ /**
+ * SMTP connection object.
+ *
+ * @var object
+ * @access private
+ */
+ var $_smtp = null;
+
+ /**
+ * The list of service extension parameters to pass to the Net_SMTP
+ * mailFrom() command.
+ * @var array
+ */
+ var $_extparams = array();
+
+ /**
+ * The SMTP host to connect to.
+ * @var string
+ */
+ var $host = 'localhost';
+
+ /**
+ * The port the SMTP server is on.
+ * @var integer
+ */
+ var $port = 25;
+
+ /**
+ * Should SMTP authentication be used?
+ *
+ * This value may be set to true, false or the name of a specific
+ * authentication method.
+ *
+ * If the value is set to true, the Net_SMTP package will attempt to use
+ * the best authentication method advertised by the remote SMTP server.
+ *
+ * @var mixed
+ */
+ var $auth = false;
+
+ /**
+ * The username to use if the SMTP server requires authentication.
+ * @var string
+ */
+ var $username = '';
+
+ /**
+ * The password to use if the SMTP server requires authentication.
+ * @var string
+ */
+ var $password = '';
+
+ /**
+ * Hostname or domain that will be sent to the remote SMTP server in the
+ * HELO / EHLO message.
+ *
+ * @var string
+ */
+ var $localhost = 'localhost';
+
+ /**
+ * SMTP connection timeout value. NULL indicates no timeout.
+ *
+ * @var integer
+ */
+ var $timeout = null;
+
+ /**
+ * Turn on Net_SMTP debugging?
+ *
+ * @var boolean $debug
+ */
+ var $debug = false;
+
+ /**
+ * Indicates whether or not the SMTP connection should persist over
+ * multiple calls to the send() method.
+ *
+ * @var boolean
+ */
+ var $persist = false;
+
+ /**
+ * Use SMTP command pipelining (specified in RFC 2920) if the SMTP server
+ * supports it. This speeds up delivery over high-latency connections. By
+ * default, use the default value supplied by Net_SMTP.
+ * @var bool
+ */
+ var $pipelining;
+
+ /**
+ * Constructor.
+ *
+ * Instantiates a new Mail_smtp:: object based on the parameters
+ * passed in. It looks for the following parameters:
+ * host The server to connect to. Defaults to localhost.
+ * port The port to connect to. Defaults to 25.
+ * auth SMTP authentication. Defaults to none.
+ * username The username to use for SMTP auth. No default.
+ * password The password to use for SMTP auth. No default.
+ * localhost The local hostname / domain. Defaults to localhost.
+ * timeout The SMTP connection timeout. Defaults to none.
+ * verp Whether to use VERP or not. Defaults to false.
+ * DEPRECATED as of 1.2.0 (use setMailParams()).
+ * debug Activate SMTP debug mode? Defaults to false.
+ * persist Should the SMTP connection persist?
+ * pipelining Use SMTP command pipelining
+ *
+ * If a parameter is present in the $params array, it replaces the
+ * default.
+ *
+ * @param array Hash containing any parameters different from the
+ * defaults.
+ * @access public
+ */
+ function Mail_smtp($params)
+ {
+ if (isset($params['host'])) $this->host = $params['host'];
+ if (isset($params['port'])) $this->port = $params['port'];
+ if (isset($params['auth'])) $this->auth = $params['auth'];
+ if (isset($params['username'])) $this->username = $params['username'];
+ if (isset($params['password'])) $this->password = $params['password'];
+ if (isset($params['localhost'])) $this->localhost = $params['localhost'];
+ if (isset($params['timeout'])) $this->timeout = $params['timeout'];
+ if (isset($params['debug'])) $this->debug = (bool)$params['debug'];
+ if (isset($params['persist'])) $this->persist = (bool)$params['persist'];
+ if (isset($params['pipelining'])) $this->pipelining = (bool)$params['pipelining'];
+
+ // Deprecated options
+ if (isset($params['verp'])) {
+ $this->addServiceExtensionParameter('XVERP', is_bool($params['verp']) ? null : $params['verp']);
+ }
+
+ register_shutdown_function(array(&$this, '_Mail_smtp'));
+ }
+
+ /**
+ * Destructor implementation to ensure that we disconnect from any
+ * potentially-alive persistent SMTP connections.
+ */
+ function _Mail_smtp()
+ {
+ $this->disconnect();
+ }
+
+ /**
+ * Implements Mail::send() function using SMTP.
+ *
+ * @param mixed $recipients Either a comma-seperated list of recipients
+ * (RFC822 compliant), or an array of recipients,
+ * each RFC822 valid. This may contain recipients not
+ * specified in the headers, for Bcc:, resending
+ * messages, etc.
+ *
+ * @param array $headers The array of headers to send with the mail, in an
+ * associative array, where the array key is the
+ * header name (e.g., 'Subject'), and the array value
+ * is the header value (e.g., 'test'). The header
+ * produced from those values would be 'Subject:
+ * test'.
+ *
+ * @param string $body The full text of the message body, including any
+ * MIME parts, etc.
+ *
+ * @return mixed Returns true on success, or a PEAR_Error
+ * containing a descriptive error message on
+ * failure.
+ * @access public
+ */
+ function send($recipients, $headers, $body)
+ {
+ /* If we don't already have an SMTP object, create one. */
+ $result = &$this->getSMTPObject();
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ if (!is_array($headers)) {
+ return PEAR::raiseError('$headers must be an array');
+ }
+
+ $this->_sanitizeHeaders($headers);
+
+ $headerElements = $this->prepareHeaders($headers);
+ if (is_a($headerElements, 'PEAR_Error')) {
+ $this->_smtp->rset();
+ return $headerElements;
+ }
+ list($from, $textHeaders) = $headerElements;
+
+ /* Since few MTAs are going to allow this header to be forged
+ * unless it's in the MAIL FROM: exchange, we'll use
+ * Return-Path instead of From: if it's set. */
+ if (!empty($headers['Return-Path'])) {
+ $from = $headers['Return-Path'];
+ }
+
+ if (!isset($from)) {
+ $this->_smtp->rset();
+ return PEAR::raiseError('No From: address has been provided',
+ PEAR_MAIL_SMTP_ERROR_FROM);
+ }
+
+ $params = null;
+ if (!empty($this->_extparams)) {
+ foreach ($this->_extparams as $key => $val) {
+ $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val);
+ }
+ }
+ if (PEAR::isError($res = $this->_smtp->mailFrom($from, ltrim($params)))) {
+ $error = $this->_error("Failed to set sender: $from", $res);
+ $this->_smtp->rset();
+ return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_SENDER);
+ }
+
+ $recipients = $this->parseRecipients($recipients);
+ if (is_a($recipients, 'PEAR_Error')) {
+ $this->_smtp->rset();
+ return $recipients;
+ }
+
+ foreach ($recipients as $recipient) {
+ $res = $this->_smtp->rcptTo($recipient);
+ if (is_a($res, 'PEAR_Error')) {
+ $error = $this->_error("Failed to add recipient: $recipient", $res);
+ $this->_smtp->rset();
+ return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_RECIPIENT);
+ }
+ }
+
+ /* Send the message's headers and the body as SMTP data. */
+ $res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body);
+ if (is_a($res, 'PEAR_Error')) {
+ $error = $this->_error('Failed to send data', $res);
+ $this->_smtp->rset();
+ return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_DATA);
+ }
+
+ /* If persistent connections are disabled, destroy our SMTP object. */
+ if ($this->persist === false) {
+ $this->disconnect();
+ }
+
+ return true;
+ }
+
+ /**
+ * Connect to the SMTP server by instantiating a Net_SMTP object.
+ *
+ * @return mixed Returns a reference to the Net_SMTP object on success, or
+ * a PEAR_Error containing a descriptive error message on
+ * failure.
+ *
+ * @since 1.2.0
+ * @access public
+ */
+ function &getSMTPObject()
+ {
+ if (is_object($this->_smtp) !== false) {
+ return $this->_smtp;
+ }
+
+ include_once 'Net/SMTP.php';
+ $this->_smtp = &new Net_SMTP($this->host,
+ $this->port,
+ $this->localhost);
+
+ /* If we still don't have an SMTP object at this point, fail. */
+ if (is_object($this->_smtp) === false) {
+ return PEAR::raiseError('Failed to create a Net_SMTP object',
+ PEAR_MAIL_SMTP_ERROR_CREATE);
+ }
+
+ /* Configure the SMTP connection. */
+ if ($this->debug) {
+ $this->_smtp->setDebug(true);
+ }
+
+ /* Attempt to connect to the configured SMTP server. */
+ if (PEAR::isError($res = $this->_smtp->connect($this->timeout))) {
+ $error = $this->_error('Failed to connect to ' .
+ $this->host . ':' . $this->port,
+ $res);
+ return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_CONNECT);
+ }
+
+ /* Attempt to authenticate if authentication has been enabled. */
+ if ($this->auth) {
+ $method = is_string($this->auth) ? $this->auth : '';
+
+ if (PEAR::isError($res = $this->_smtp->auth($this->username,
+ $this->password,
+ $method))) {
+ $error = $this->_error("$method authentication failure",
+ $res);
+ $this->_smtp->rset();
+ return PEAR::raiseError($error, PEAR_MAIL_SMTP_ERROR_AUTH);
+ }
+ }
+
+ return $this->_smtp;
+ }
+
+ /**
+ * Add parameter associated with a SMTP service extension.
+ *
+ * @param string Extension keyword.
+ * @param string Any value the keyword needs.
+ *
+ * @since 1.2.0
+ * @access public
+ */
+ function addServiceExtensionParameter($keyword, $value = null)
+ {
+ $this->_extparams[$keyword] = $value;
+ }
+
+ /**
+ * Disconnect and destroy the current SMTP connection.
+ *
+ * @return boolean True if the SMTP connection no longer exists.
+ *
+ * @since 1.1.9
+ * @access public
+ */
+ function disconnect()
+ {
+ /* If we have an SMTP object, disconnect and destroy it. */
+ if (is_object($this->_smtp) && $this->_smtp->disconnect()) {
+ $this->_smtp = null;
+ }
+
+ /* We are disconnected if we no longer have an SMTP object. */
+ return ($this->_smtp === null);
+ }
+
+ /**
+ * Build a standardized string describing the current SMTP error.
+ *
+ * @param string $text Custom string describing the error context.
+ * @param object $error Reference to the current PEAR_Error object.
+ *
+ * @return string A string describing the current SMTP error.
+ *
+ * @since 1.1.7
+ * @access private
+ */
+ function _error($text, &$error)
+ {
+ /* Split the SMTP response into a code and a response string. */
+ list($code, $response) = $this->_smtp->getResponse();
+
+ /* Build our standardized error string. */
+ return $text
+ . ' [SMTP: ' . $error->getMessage()
+ . " (code: $code, response: $response)]";
+ }
+
+}
diff --git a/extlib/Mail/smtpmx.php b/extlib/Mail/smtpmx.php
new file mode 100644
index 000000000..9d2dccfb1
--- /dev/null
+++ b/extlib/Mail/smtpmx.php
@@ -0,0 +1,478 @@
+<?PHP
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * SMTP MX
+ *
+ * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Mail
+ * @package Mail_smtpmx
+ * @author gERD Schaufelberger <gerd@php-tools.net>
+ * @copyright 1997-2005 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: smtpmx.php,v 1.2 2007/10/06 17:00:00 chagenbu Exp $
+ * @see Mail
+ */
+
+require_once 'Net/SMTP.php';
+
+/**
+ * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
+ *
+ *
+ * @access public
+ * @author gERD Schaufelberger <gerd@php-tools.net>
+ * @package Mail
+ * @version $Revision: 1.2 $
+ */
+class Mail_smtpmx extends Mail {
+
+ /**
+ * SMTP connection object.
+ *
+ * @var object
+ * @access private
+ */
+ var $_smtp = null;
+
+ /**
+ * The port the SMTP server is on.
+ * @var integer
+ * @see getservicebyname()
+ */
+ var $port = 25;
+
+ /**
+ * Hostname or domain that will be sent to the remote SMTP server in the
+ * HELO / EHLO message.
+ *
+ * @var string
+ * @see posix_uname()
+ */
+ var $mailname = 'localhost';
+
+ /**
+ * SMTP connection timeout value. NULL indicates no timeout.
+ *
+ * @var integer
+ */
+ var $timeout = 10;
+
+ /**
+ * use either PEAR:Net_DNS or getmxrr
+ *
+ * @var boolean
+ */
+ var $withNetDns = true;
+
+ /**
+ * PEAR:Net_DNS_Resolver
+ *
+ * @var object
+ */
+ var $resolver;
+
+ /**
+ * Whether to use VERP or not. If not a boolean, the string value
+ * will be used as the VERP separators.
+ *
+ * @var mixed boolean or string
+ */
+ var $verp = false;
+
+ /**
+ * Whether to use VRFY or not.
+ *
+ * @var boolean $vrfy
+ */
+ var $vrfy = false;
+
+ /**
+ * Switch to test mode - don't send emails for real
+ *
+ * @var boolean $debug
+ */
+ var $test = false;
+
+ /**
+ * Turn on Net_SMTP debugging?
+ *
+ * @var boolean $peardebug
+ */
+ var $debug = false;
+
+ /**
+ * internal error codes
+ *
+ * translate internal error identifier to PEAR-Error codes and human
+ * readable messages.
+ *
+ * @var boolean $debug
+ * @todo as I need unique error-codes to identify what exactly went wrond
+ * I did not use intergers as it should be. Instead I added a "namespace"
+ * for each code. This avoids conflicts with error codes from different
+ * classes. How can I use unique error codes and stay conform with PEAR?
+ */
+ var $errorCode = array(
+ 'not_connected' => array(
+ 'code' => 1,
+ 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
+ ),
+ 'failed_vrfy_rcpt' => array(
+ 'code' => 2,
+ 'msg' => 'Recipient "{RCPT}" could not be veryfied.'
+ ),
+ 'failed_set_from' => array(
+ 'code' => 3,
+ 'msg' => 'Failed to set sender: {FROM}.'
+ ),
+ 'failed_set_rcpt' => array(
+ 'code' => 4,
+ 'msg' => 'Failed to set recipient: {RCPT}.'
+ ),
+ 'failed_send_data' => array(
+ 'code' => 5,
+ 'msg' => 'Failed to send mail to: {RCPT}.'
+ ),
+ 'no_from' => array(
+ 'code' => 5,
+ 'msg' => 'No from address has be provided.'
+ ),
+ 'send_data' => array(
+ 'code' => 7,
+ 'msg' => 'Failed to create Net_SMTP object.'
+ ),
+ 'no_mx' => array(
+ 'code' => 8,
+ 'msg' => 'No MX-record for {RCPT} found.'
+ ),
+ 'no_resolver' => array(
+ 'code' => 9,
+ 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
+ ),
+ 'failed_rset' => array(
+ 'code' => 10,
+ 'msg' => 'RSET command failed, SMTP-connection corrupt.'
+ ),
+ );
+
+ /**
+ * Constructor.
+ *
+ * Instantiates a new Mail_smtp:: object based on the parameters
+ * passed in. It looks for the following parameters:
+ * mailname The name of the local mail system (a valid hostname which matches the reverse lookup)
+ * port smtp-port - the default comes from getservicebyname() and should work fine
+ * timeout The SMTP connection timeout. Defaults to 30 seconds.
+ * vrfy Whether to use VRFY or not. Defaults to false.
+ * verp Whether to use VERP or not. Defaults to false.
+ * test Activate test mode? Defaults to false.
+ * debug Activate SMTP and Net_DNS debug mode? Defaults to false.
+ * netdns whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
+ *
+ * If a parameter is present in the $params array, it replaces the
+ * default.
+ *
+ * @access public
+ * @param array Hash containing any parameters different from the
+ * defaults.
+ * @see _Mail_smtpmx()
+ */
+ function __construct($params)
+ {
+ if (isset($params['mailname'])) {
+ $this->mailname = $params['mailname'];
+ } else {
+ // try to find a valid mailname
+ if (function_exists('posix_uname')) {
+ $uname = posix_uname();
+ $this->mailname = $uname['nodename'];
+ }
+ }
+
+ // port number
+ if (isset($params['port'])) {
+ $this->_port = $params['port'];
+ } else {
+ $this->_port = getservbyname('smtp', 'tcp');
+ }
+
+ if (isset($params['timeout'])) $this->timeout = $params['timeout'];
+ if (isset($params['verp'])) $this->verp = $params['verp'];
+ if (isset($params['test'])) $this->test = $params['test'];
+ if (isset($params['peardebug'])) $this->test = $params['peardebug'];
+ if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
+ }
+
+ /**
+ * Constructor wrapper for PHP4
+ *
+ * @access public
+ * @param array Hash containing any parameters different from the defaults
+ * @see __construct()
+ */
+ function Mail_smtpmx($params)
+ {
+ $this->__construct($params);
+ register_shutdown_function(array(&$this, '__destruct'));
+ }
+
+ /**
+ * Destructor implementation to ensure that we disconnect from any
+ * potentially-alive persistent SMTP connections.
+ */
+ function __destruct()
+ {
+ if (is_object($this->_smtp)) {
+ $this->_smtp->disconnect();
+ $this->_smtp = null;
+ }
+ }
+
+ /**
+ * Implements Mail::send() function using SMTP direct delivery
+ *
+ * @access public
+ * @param mixed $recipients in RFC822 style or array
+ * @param array $headers The array of headers to send with the mail.
+ * @param string $body The full text of the message body,
+ * @return mixed Returns true on success, or a PEAR_Error
+ */
+ function send($recipients, $headers, $body)
+ {
+ if (!is_array($headers)) {
+ return PEAR::raiseError('$headers must be an array');
+ }
+
+ $result = $this->_sanitizeHeaders($headers);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ // Prepare headers
+ $headerElements = $this->prepareHeaders($headers);
+ if (is_a($headerElements, 'PEAR_Error')) {
+ return $headerElements;
+ }
+ list($from, $textHeaders) = $headerElements;
+
+ // use 'Return-Path' if possible
+ if (!empty($headers['Return-Path'])) {
+ $from = $headers['Return-Path'];
+ }
+ if (!isset($from)) {
+ return $this->_raiseError('no_from');
+ }
+
+ // Prepare recipients
+ $recipients = $this->parseRecipients($recipients);
+ if (is_a($recipients, 'PEAR_Error')) {
+ return $recipients;
+ }
+
+ foreach ($recipients as $rcpt) {
+ list($user, $host) = explode('@', $rcpt);
+
+ $mx = $this->_getMx($host);
+ if (is_a($mx, 'PEAR_Error')) {
+ return $mx;
+ }
+
+ if (empty($mx)) {
+ $info = array('rcpt' => $rcpt);
+ return $this->_raiseError('no_mx', $info);
+ }
+
+ $connected = false;
+ foreach ($mx as $mserver => $mpriority) {
+ $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
+
+ // configure the SMTP connection.
+ if ($this->debug) {
+ $this->_smtp->setDebug(true);
+ }
+
+ // attempt to connect to the configured SMTP server.
+ $res = $this->_smtp->connect($this->timeout);
+ if (is_a($res, 'PEAR_Error')) {
+ $this->_smtp = null;
+ continue;
+ }
+
+ // connection established
+ if ($res) {
+ $connected = true;
+ break;
+ }
+ }
+
+ if (!$connected) {
+ $info = array(
+ 'host' => implode(', ', array_keys($mx)),
+ 'port' => $this->port,
+ 'rcpt' => $rcpt,
+ );
+ return $this->_raiseError('not_connected', $info);
+ }
+
+ // Verify recipient
+ if ($this->vrfy) {
+ $res = $this->_smtp->vrfy($rcpt);
+ if (is_a($res, 'PEAR_Error')) {
+ $info = array('rcpt' => $rcpt);
+ return $this->_raiseError('failed_vrfy_rcpt', $info);
+ }
+ }
+
+ // mail from:
+ $args['verp'] = $this->verp;
+ $res = $this->_smtp->mailFrom($from, $args);
+ if (is_a($res, 'PEAR_Error')) {
+ $info = array('from' => $from);
+ return $this->_raiseError('failed_set_from', $info);
+ }
+
+ // rcpt to:
+ $res = $this->_smtp->rcptTo($rcpt);
+ if (is_a($res, 'PEAR_Error')) {
+ $info = array('rcpt' => $rcpt);
+ return $this->_raiseError('failed_set_rcpt', $info);
+ }
+
+ // Don't send anything in test mode
+ if ($this->test) {
+ $result = $this->_smtp->rset();
+ $res = $this->_smtp->rset();
+ if (is_a($res, 'PEAR_Error')) {
+ return $this->_raiseError('failed_rset');
+ }
+
+ $this->_smtp->disconnect();
+ $this->_smtp = null;
+ return true;
+ }
+
+ // Send data
+ $res = $this->_smtp->data("$textHeaders\r\n$body");
+ if (is_a($res, 'PEAR_Error')) {
+ $info = array('rcpt' => $rcpt);
+ return $this->_raiseError('failed_send_data', $info);
+ }
+
+ $this->_smtp->disconnect();
+ $this->_smtp = null;
+ }
+
+ return true;
+ }
+
+ /**
+ * Recieve mx rexords for a spciefied host
+ *
+ * The MX records
+ *
+ * @access private
+ * @param string $host mail host
+ * @return mixed sorted
+ */
+ function _getMx($host)
+ {
+ $mx = array();
+
+ if ($this->withNetDns) {
+ $res = $this->_loadNetDns();
+ if (is_a($res, 'PEAR_Error')) {
+ return $res;
+ }
+
+ $response = $this->resolver->query($host, 'MX');
+ if (!$response) {
+ return false;
+ }
+
+ foreach ($response->answer as $rr) {
+ if ($rr->type == 'MX') {
+ $mx[$rr->exchange] = $rr->preference;
+ }
+ }
+ } else {
+ $mxHost = array();
+ $mxWeight = array();
+
+ if (!getmxrr($host, $mxHost, $mxWeight)) {
+ return false;
+ }
+ for ($i = 0; $i < count($mxHost); ++$i) {
+ $mx[$mxHost[$i]] = $mxWeight[$i];
+ }
+ }
+
+ asort($mx);
+ return $mx;
+ }
+
+ /**
+ * initialize PEAR:Net_DNS_Resolver
+ *
+ * @access private
+ * @return boolean true on success
+ */
+ function _loadNetDns()
+ {
+ if (is_object($this->resolver)) {
+ return true;
+ }
+
+ if (!include_once 'Net/DNS.php') {
+ return $this->_raiseError('no_resolver');
+ }
+
+ $this->resolver = new Net_DNS_Resolver();
+ if ($this->debug) {
+ $this->resolver->test = 1;
+ }
+
+ return true;
+ }
+
+ /**
+ * raise standardized error
+ *
+ * include additional information in error message
+ *
+ * @access private
+ * @param string $id maps error ids to codes and message
+ * @param array $info optional information in associative array
+ * @see _errorCode
+ */
+ function _raiseError($id, $info = array())
+ {
+ $code = $this->errorCode[$id]['code'];
+ $msg = $this->errorCode[$id]['msg'];
+
+ // include info to messages
+ if (!empty($info)) {
+ $search = array();
+ $replace = array();
+
+ foreach ($info as $key => $value) {
+ array_push($search, '{' . strtoupper($key) . '}');
+ array_push($replace, $value);
+ }
+
+ $msg = str_replace($search, $replace, $msg);
+ }
+
+ return PEAR::raiseError($msg, $code);
+ }
+
+}
diff --git a/extlib/Net/SMTP.php b/extlib/Net/SMTP.php
new file mode 100644
index 000000000..d632258d6
--- /dev/null
+++ b/extlib/Net/SMTP.php
@@ -0,0 +1,1082 @@
+<?php
+/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Chuck Hagenbuch <chuck@horde.org> |
+// | Jon Parise <jon@php.net> |
+// | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
+// +----------------------------------------------------------------------+
+//
+// $Id: SMTP.php,v 1.63 2008/06/10 05:39:12 jon Exp $
+
+require_once 'PEAR.php';
+require_once 'Net/Socket.php';
+
+/**
+ * Provides an implementation of the SMTP protocol using PEAR's
+ * Net_Socket:: class.
+ *
+ * @package Net_SMTP
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Jon Parise <jon@php.net>
+ * @author Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
+ *
+ * @example basic.php A basic implementation of the Net_SMTP package.
+ */
+class Net_SMTP
+{
+ /**
+ * The server to connect to.
+ * @var string
+ * @access public
+ */
+ var $host = 'localhost';
+
+ /**
+ * The port to connect to.
+ * @var int
+ * @access public
+ */
+ var $port = 25;
+
+ /**
+ * The value to give when sending EHLO or HELO.
+ * @var string
+ * @access public
+ */
+ var $localhost = 'localhost';
+
+ /**
+ * List of supported authentication methods, in preferential order.
+ * @var array
+ * @access public
+ */
+ var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
+
+ /**
+ * Use SMTP command pipelining (specified in RFC 2920) if the SMTP
+ * server supports it.
+ *
+ * When pipeling is enabled, rcptTo(), mailFrom(), sendFrom(),
+ * somlFrom() and samlFrom() do not wait for a response from the
+ * SMTP server but return immediately.
+ *
+ * @var bool
+ * @access public
+ */
+ var $pipelining = false;
+
+ /**
+ * Number of pipelined commands.
+ * @var int
+ * @access private
+ */
+ var $_pipelined_commands = 0;
+
+ /**
+ * Should debugging output be enabled?
+ * @var boolean
+ * @access private
+ */
+ var $_debug = false;
+
+ /**
+ * The socket resource being used to connect to the SMTP server.
+ * @var resource
+ * @access private
+ */
+ var $_socket = null;
+
+ /**
+ * The most recent server response code.
+ * @var int
+ * @access private
+ */
+ var $_code = -1;
+
+ /**
+ * The most recent server response arguments.
+ * @var array
+ * @access private
+ */
+ var $_arguments = array();
+
+ /**
+ * Stores detected features of the SMTP server.
+ * @var array
+ * @access private
+ */
+ var $_esmtp = array();
+
+ /**
+ * Instantiates a new Net_SMTP object, overriding any defaults
+ * with parameters that are passed in.
+ *
+ * If you have SSL support in PHP, you can connect to a server
+ * over SSL using an 'ssl://' prefix:
+ *
+ * // 465 is a common smtps port.
+ * $smtp = new Net_SMTP('ssl://mail.host.com', 465);
+ * $smtp->connect();
+ *
+ * @param string $host The server to connect to.
+ * @param integer $port The port to connect to.
+ * @param string $localhost The value to give when sending EHLO or HELO.
+ * @param boolean $pipeling Use SMTP command pipelining
+ *
+ * @access public
+ * @since 1.0
+ */
+ function Net_SMTP($host = null, $port = null, $localhost = null, $pipelining = false)
+ {
+ if (isset($host)) {
+ $this->host = $host;
+ }
+ if (isset($port)) {
+ $this->port = $port;
+ }
+ if (isset($localhost)) {
+ $this->localhost = $localhost;
+ }
+ $this->pipelining = $pipelining;
+
+ $this->_socket = new Net_Socket();
+
+ /* Include the Auth_SASL package. If the package is not
+ * available, we disable the authentication methods that
+ * depend upon it. */
+ if ((@include_once 'Auth/SASL.php') === false) {
+ $pos = array_search('DIGEST-MD5', $this->auth_methods);
+ unset($this->auth_methods[$pos]);
+ $pos = array_search('CRAM-MD5', $this->auth_methods);
+ unset($this->auth_methods[$pos]);
+ }
+ }
+
+ /**
+ * Set the value of the debugging flag.
+ *
+ * @param boolean $debug New value for the debugging flag.
+ *
+ * @access public
+ * @since 1.1.0
+ */
+ function setDebug($debug)
+ {
+ $this->_debug = $debug;
+ }
+
+ /**
+ * Send the given string of data to the server.
+ *
+ * @param string $data The string of data to send.
+ *
+ * @return mixed True on success or a PEAR_Error object on failure.
+ *
+ * @access private
+ * @since 1.1.0
+ */
+ function _send($data)
+ {
+ if ($this->_debug) {
+ echo "DEBUG: Send: $data\n";
+ }
+
+ if (PEAR::isError($error = $this->_socket->write($data))) {
+ return PEAR::raiseError('Failed to write to socket: ' .
+ $error->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Send a command to the server with an optional string of
+ * arguments. A carriage return / linefeed (CRLF) sequence will
+ * be appended to each command string before it is sent to the
+ * SMTP server - an error will be thrown if the command string
+ * already contains any newline characters. Use _send() for
+ * commands that must contain newlines.
+ *
+ * @param string $command The SMTP command to send to the server.
+ * @param string $args A string of optional arguments to append
+ * to the command.
+ *
+ * @return mixed The result of the _send() call.
+ *
+ * @access private
+ * @since 1.1.0
+ */
+ function _put($command, $args = '')
+ {
+ if (!empty($args)) {
+ $command .= ' ' . $args;
+ }
+
+ if (strcspn($command, "\r\n") !== strlen($command)) {
+ return PEAR::raiseError('Commands cannot contain newlines');
+ }
+
+ return $this->_send($command . "\r\n");
+ }
+
+ /**
+ * Read a reply from the SMTP server. The reply consists of a response
+ * code and a response message.
+ *
+ * @param mixed $valid The set of valid response codes. These
+ * may be specified as an array of integer
+ * values or as a single integer value.
+ * @param bool $later Do not parse the response now, but wait
+ * until the last command in the pipelined
+ * command group
+ *
+ * @return mixed True if the server returned a valid response code or
+ * a PEAR_Error object is an error condition is reached.
+ *
+ * @access private
+ * @since 1.1.0
+ *
+ * @see getResponse
+ */
+ function _parseResponse($valid, $later = false)
+ {
+ $this->_code = -1;
+ $this->_arguments = array();
+
+ if ($later) {
+ $this->_pipelined_commands++;
+ return true;
+ }
+
+ for ($i = 0; $i <= $this->_pipelined_commands; $i++) {
+ while ($line = $this->_socket->readLine()) {
+ if ($this->_debug) {
+ echo "DEBUG: Recv: $line\n";
+ }
+
+ /* If we receive an empty line, the connection has been closed. */
+ if (empty($line)) {
+ $this->disconnect();
+ return PEAR::raiseError('Connection was unexpectedly closed');
+ }
+
+ /* Read the code and store the rest in the arguments array. */
+ $code = substr($line, 0, 3);
+ $this->_arguments[] = trim(substr($line, 4));
+
+ /* Check the syntax of the response code. */
+ if (is_numeric($code)) {
+ $this->_code = (int)$code;
+ } else {
+ $this->_code = -1;
+ break;
+ }
+
+ /* If this is not a multiline response, we're done. */
+ if (substr($line, 3, 1) != '-') {
+ break;
+ }
+ }
+ }
+
+ $this->_pipelined_commands = 0;
+
+ /* Compare the server's response code with the valid code/codes. */
+ if (is_int($valid) && ($this->_code === $valid)) {
+ return true;
+ } elseif (is_array($valid) && in_array($this->_code, $valid, true)) {
+ return true;
+ }
+
+ return PEAR::raiseError('Invalid response code received from server',
+ $this->_code);
+ }
+
+ /**
+ * Return a 2-tuple containing the last response from the SMTP server.
+ *
+ * @return array A two-element array: the first element contains the
+ * response code as an integer and the second element
+ * contains the response's arguments as a string.
+ *
+ * @access public
+ * @since 1.1.0
+ */
+ function getResponse()
+ {
+ return array($this->_code, join("\n", $this->_arguments));
+ }
+
+ /**
+ * Attempt to connect to the SMTP server.
+ *
+ * @param int $timeout The timeout value (in seconds) for the
+ * socket connection.
+ * @param bool $persistent Should a persistent socket connection
+ * be used?
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function connect($timeout = null, $persistent = false)
+ {
+ $result = $this->_socket->connect($this->host, $this->port,
+ $persistent, $timeout);
+ if (PEAR::isError($result)) {
+ return PEAR::raiseError('Failed to connect socket: ' .
+ $result->getMessage());
+ }
+
+ if (PEAR::isError($error = $this->_parseResponse(220))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_negotiate())) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempt to disconnect from the SMTP server.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function disconnect()
+ {
+ if (PEAR::isError($error = $this->_put('QUIT'))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(221))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_socket->disconnect())) {
+ return PEAR::raiseError('Failed to disconnect socket: ' .
+ $error->getMessage());
+ }
+
+ return true;
+ }
+
+ /**
+ * Attempt to send the EHLO command and obtain a list of ESMTP
+ * extensions available, and failing that just send HELO.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ *
+ * @access private
+ * @since 1.1.0
+ */
+ function _negotiate()
+ {
+ if (PEAR::isError($error = $this->_put('EHLO', $this->localhost))) {
+ return $error;
+ }
+
+ if (PEAR::isError($this->_parseResponse(250))) {
+ /* If we receive a 503 response, we're already authenticated. */
+ if ($this->_code === 503) {
+ return true;
+ }
+
+ /* If the EHLO failed, try the simpler HELO command. */
+ if (PEAR::isError($error = $this->_put('HELO', $this->localhost))) {
+ return $error;
+ }
+ if (PEAR::isError($this->_parseResponse(250))) {
+ return PEAR::raiseError('HELO was not accepted: ', $this->_code);
+ }
+
+ return true;
+ }
+
+ foreach ($this->_arguments as $argument) {
+ $verb = strtok($argument, ' ');
+ $arguments = substr($argument, strlen($verb) + 1,
+ strlen($argument) - strlen($verb) - 1);
+ $this->_esmtp[$verb] = $arguments;
+ }
+
+ if (!isset($this->_esmtp['PIPELINING'])) {
+ $this->pipelining = false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the name of the best authentication method that the server
+ * has advertised.
+ *
+ * @return mixed Returns a string containing the name of the best
+ * supported authentication method or a PEAR_Error object
+ * if a failure condition is encountered.
+ * @access private
+ * @since 1.1.0
+ */
+ function _getBestAuthMethod()
+ {
+ $available_methods = explode(' ', $this->_esmtp['AUTH']);
+
+ foreach ($this->auth_methods as $method) {
+ if (in_array($method, $available_methods)) {
+ return $method;
+ }
+ }
+
+ return PEAR::raiseError('No supported authentication methods');
+ }
+
+ /**
+ * Attempt to do SMTP authentication.
+ *
+ * @param string The userid to authenticate as.
+ * @param string The password to authenticate with.
+ * @param string The requested authentication method. If none is
+ * specified, the best supported method will be used.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function auth($uid, $pwd , $method = '')
+ {
+ if (empty($this->_esmtp['AUTH'])) {
+ if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
+ if (!isset($this->_esmtp['STARTTLS'])) {
+ return PEAR::raiseError('SMTP server does not support authentication');
+ }
+ if (PEAR::isError($result = $this->_put('STARTTLS'))) {
+ return $result;
+ }
+ if (PEAR::isError($result = $this->_parseResponse(220))) {
+ return $result;
+ }
+ if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) {
+ return $result;
+ } elseif ($result !== true) {
+ return PEAR::raiseError('STARTTLS failed');
+ }
+
+ /* Send EHLO again to recieve the AUTH string from the
+ * SMTP server. */
+ $this->_negotiate();
+ if (empty($this->_esmtp['AUTH'])) {
+ return PEAR::raiseError('SMTP server does not support authentication');
+ }
+ } else {
+ return PEAR::raiseError('SMTP server does not support authentication');
+ }
+ }
+
+ /* If no method has been specified, get the name of the best
+ * supported method advertised by the SMTP server. */
+ if (empty($method)) {
+ if (PEAR::isError($method = $this->_getBestAuthMethod())) {
+ /* Return the PEAR_Error object from _getBestAuthMethod(). */
+ return $method;
+ }
+ } else {
+ $method = strtoupper($method);
+ if (!in_array($method, $this->auth_methods)) {
+ return PEAR::raiseError("$method is not a supported authentication method");
+ }
+ }
+
+ switch ($method) {
+ case 'DIGEST-MD5':
+ $result = $this->_authDigest_MD5($uid, $pwd);
+ break;
+
+ case 'CRAM-MD5':
+ $result = $this->_authCRAM_MD5($uid, $pwd);
+ break;
+
+ case 'LOGIN':
+ $result = $this->_authLogin($uid, $pwd);
+ break;
+
+ case 'PLAIN':
+ $result = $this->_authPlain($uid, $pwd);
+ break;
+
+ default:
+ $result = PEAR::raiseError("$method is not a supported authentication method");
+ break;
+ }
+
+ /* If an error was encountered, return the PEAR_Error object. */
+ if (PEAR::isError($result)) {
+ return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Authenticates the user using the DIGEST-MD5 method.
+ *
+ * @param string The userid to authenticate as.
+ * @param string The password to authenticate with.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access private
+ * @since 1.1.0
+ */
+ function _authDigest_MD5($uid, $pwd)
+ {
+ if (PEAR::isError($error = $this->_put('AUTH', 'DIGEST-MD5'))) {
+ return $error;
+ }
+ /* 334: Continue authentication request */
+ if (PEAR::isError($error = $this->_parseResponse(334))) {
+ /* 503: Error: already authenticated */
+ if ($this->_code === 503) {
+ return true;
+ }
+ return $error;
+ }
+
+ $challenge = base64_decode($this->_arguments[0]);
+ $digest = &Auth_SASL::factory('digestmd5');
+ $auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
+ $this->host, "smtp"));
+
+ if (PEAR::isError($error = $this->_put($auth_str))) {
+ return $error;
+ }
+ /* 334: Continue authentication request */
+ if (PEAR::isError($error = $this->_parseResponse(334))) {
+ return $error;
+ }
+
+ /* We don't use the protocol's third step because SMTP doesn't
+ * allow subsequent authentication, so we just silently ignore
+ * it. */
+ if (PEAR::isError($error = $this->_put(''))) {
+ return $error;
+ }
+ /* 235: Authentication successful */
+ if (PEAR::isError($error = $this->_parseResponse(235))) {
+ return $error;
+ }
+ }
+
+ /**
+ * Authenticates the user using the CRAM-MD5 method.
+ *
+ * @param string The userid to authenticate as.
+ * @param string The password to authenticate with.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access private
+ * @since 1.1.0
+ */
+ function _authCRAM_MD5($uid, $pwd)
+ {
+ if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
+ return $error;
+ }
+ /* 334: Continue authentication request */
+ if (PEAR::isError($error = $this->_parseResponse(334))) {
+ /* 503: Error: already authenticated */
+ if ($this->_code === 503) {
+ return true;
+ }
+ return $error;
+ }
+
+ $challenge = base64_decode($this->_arguments[0]);
+ $cram = &Auth_SASL::factory('crammd5');
+ $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
+
+ if (PEAR::isError($error = $this->_put($auth_str))) {
+ return $error;
+ }
+
+ /* 235: Authentication successful */
+ if (PEAR::isError($error = $this->_parseResponse(235))) {
+ return $error;
+ }
+ }
+
+ /**
+ * Authenticates the user using the LOGIN method.
+ *
+ * @param string The userid to authenticate as.
+ * @param string The password to authenticate with.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access private
+ * @since 1.1.0
+ */
+ function _authLogin($uid, $pwd)
+ {
+ if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
+ return $error;
+ }
+ /* 334: Continue authentication request */
+ if (PEAR::isError($error = $this->_parseResponse(334))) {
+ /* 503: Error: already authenticated */
+ if ($this->_code === 503) {
+ return true;
+ }
+ return $error;
+ }
+
+ if (PEAR::isError($error = $this->_put(base64_encode($uid)))) {
+ return $error;
+ }
+ /* 334: Continue authentication request */
+ if (PEAR::isError($error = $this->_parseResponse(334))) {
+ return $error;
+ }
+
+ if (PEAR::isError($error = $this->_put(base64_encode($pwd)))) {
+ return $error;
+ }
+
+ /* 235: Authentication successful */
+ if (PEAR::isError($error = $this->_parseResponse(235))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Authenticates the user using the PLAIN method.
+ *
+ * @param string The userid to authenticate as.
+ * @param string The password to authenticate with.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access private
+ * @since 1.1.0
+ */
+ function _authPlain($uid, $pwd)
+ {
+ if (PEAR::isError($error = $this->_put('AUTH', 'PLAIN'))) {
+ return $error;
+ }
+ /* 334: Continue authentication request */
+ if (PEAR::isError($error = $this->_parseResponse(334))) {
+ /* 503: Error: already authenticated */
+ if ($this->_code === 503) {
+ return true;
+ }
+ return $error;
+ }
+
+ $auth_str = base64_encode(chr(0) . $uid . chr(0) . $pwd);
+
+ if (PEAR::isError($error = $this->_put($auth_str))) {
+ return $error;
+ }
+
+ /* 235: Authentication successful */
+ if (PEAR::isError($error = $this->_parseResponse(235))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Send the HELO command.
+ *
+ * @param string The domain name to say we are.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function helo($domain)
+ {
+ if (PEAR::isError($error = $this->_put('HELO', $domain))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return the list of SMTP service extensions advertised by the server.
+ *
+ * @return array The list of SMTP service extensions.
+ * @access public
+ * @since 1.3
+ */
+ function getServiceExtensions()
+ {
+ return $this->_esmtp;
+ }
+
+ /**
+ * Send the MAIL FROM: command.
+ *
+ * @param string $sender The sender (reverse path) to set.
+ * @param string $params String containing additional MAIL parameters,
+ * such as the NOTIFY flags defined by RFC 1891
+ * or the VERP protocol.
+ *
+ * If $params is an array, only the 'verp' option
+ * is supported. If 'verp' is true, the XVERP
+ * parameter is appended to the MAIL command. If
+ * the 'verp' value is a string, the full
+ * XVERP=value parameter is appended.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function mailFrom($sender, $params = null)
+ {
+ $args = "FROM:<$sender>";
+
+ /* Support the deprecated array form of $params. */
+ if (is_array($params) && isset($params['verp'])) {
+ /* XVERP */
+ if ($params['verp'] === true) {
+ $args .= ' XVERP';
+
+ /* XVERP=something */
+ } elseif (trim($params['verp'])) {
+ $args .= ' XVERP=' . $params['verp'];
+ }
+ } elseif (is_string($params)) {
+ $args .= ' ' . $params;
+ }
+
+ if (PEAR::isError($error = $this->_put('MAIL', $args))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Send the RCPT TO: command.
+ *
+ * @param string $recipient The recipient (forward path) to add.
+ * @param string $params String containing additional RCPT parameters,
+ * such as the NOTIFY flags defined by RFC 1891.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ *
+ * @access public
+ * @since 1.0
+ */
+ function rcptTo($recipient, $params = null)
+ {
+ $args = "TO:<$recipient>";
+ if (is_string($params)) {
+ $args .= ' ' . $params;
+ }
+
+ if (PEAR::isError($error = $this->_put('RCPT', $args))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(array(250, 251), $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Quote the data so that it meets SMTP standards.
+ *
+ * This is provided as a separate public function to facilitate
+ * easier overloading for the cases where it is desirable to
+ * customize the quoting behavior.
+ *
+ * @param string $data The message text to quote. The string must be passed
+ * by reference, and the text will be modified in place.
+ *
+ * @access public
+ * @since 1.2
+ */
+ function quotedata(&$data)
+ {
+ /* Change Unix (\n) and Mac (\r) linefeeds into
+ * Internet-standard CRLF (\r\n) linefeeds. */
+ $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
+
+ /* Because a single leading period (.) signifies an end to the
+ * data, legitimate leading periods need to be "doubled"
+ * (e.g. '..'). */
+ $data = str_replace("\n.", "\n..", $data);
+ }
+
+ /**
+ * Send the DATA command.
+ *
+ * @param string $data The message body to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function data($data)
+ {
+ /* RFC 1870, section 3, subsection 3 states "a value of zero
+ * indicates that no fixed maximum message size is in force".
+ * Furthermore, it says that if "the parameter is omitted no
+ * information is conveyed about the server's fixed maximum
+ * message size". */
+ if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) {
+ if (strlen($data) >= $this->_esmtp['SIZE']) {
+ $this->disconnect();
+ return PEAR::raiseError('Message size excedes the server limit');
+ }
+ }
+
+ /* Quote the data based on the SMTP standards. */
+ $this->quotedata($data);
+
+ if (PEAR::isError($error = $this->_put('DATA'))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(354))) {
+ return $error;
+ }
+
+ if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) {
+ return $result;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Send the SEND FROM: command.
+ *
+ * @param string The reverse path to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.2.6
+ */
+ function sendFrom($path)
+ {
+ if (PEAR::isError($error = $this->_put('SEND', "FROM:<$path>"))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Backwards-compatibility wrapper for sendFrom().
+ *
+ * @param string The reverse path to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ *
+ * @access public
+ * @since 1.0
+ * @deprecated 1.2.6
+ */
+ function send_from($path)
+ {
+ return sendFrom($path);
+ }
+
+ /**
+ * Send the SOML FROM: command.
+ *
+ * @param string The reverse path to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.2.6
+ */
+ function somlFrom($path)
+ {
+ if (PEAR::isError($error = $this->_put('SOML', "FROM:<$path>"))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Backwards-compatibility wrapper for somlFrom().
+ *
+ * @param string The reverse path to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ *
+ * @access public
+ * @since 1.0
+ * @deprecated 1.2.6
+ */
+ function soml_from($path)
+ {
+ return somlFrom($path);
+ }
+
+ /**
+ * Send the SAML FROM: command.
+ *
+ * @param string The reverse path to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.2.6
+ */
+ function samlFrom($path)
+ {
+ if (PEAR::isError($error = $this->_put('SAML', "FROM:<$path>"))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Backwards-compatibility wrapper for samlFrom().
+ *
+ * @param string The reverse path to send.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ *
+ * @access public
+ * @since 1.0
+ * @deprecated 1.2.6
+ */
+ function saml_from($path)
+ {
+ return samlFrom($path);
+ }
+
+ /**
+ * Send the RSET command.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function rset()
+ {
+ if (PEAR::isError($error = $this->_put('RSET'))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Send the VRFY command.
+ *
+ * @param string The string to verify
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function vrfy($string)
+ {
+ /* Note: 251 is also a valid response code */
+ if (PEAR::isError($error = $this->_put('VRFY', $string))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(array(250, 252)))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Send the NOOP command.
+ *
+ * @return mixed Returns a PEAR_Error with an error message on any
+ * kind of failure, or true on success.
+ * @access public
+ * @since 1.0
+ */
+ function noop()
+ {
+ if (PEAR::isError($error = $this->_put('NOOP'))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse(250))) {
+ return $error;
+ }
+
+ return true;
+ }
+
+ /**
+ * Backwards-compatibility method. identifySender()'s functionality is
+ * now handled internally.
+ *
+ * @return boolean This method always return true.
+ *
+ * @access public
+ * @since 1.0
+ */
+ function identifySender()
+ {
+ return true;
+ }
+
+}
diff --git a/extlib/Net/Socket.php b/extlib/Net/Socket.php
new file mode 100644
index 000000000..73bb4dd11
--- /dev/null
+++ b/extlib/Net/Socket.php
@@ -0,0 +1,592 @@
+<?php
+//
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2003 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.0 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Authors: Stig Bakken <ssb@php.net> |
+// | Chuck Hagenbuch <chuck@horde.org> |
+// +----------------------------------------------------------------------+
+//
+// $Id: Socket.php,v 1.38 2008/02/15 18:24:17 chagenbu Exp $
+
+require_once 'PEAR.php';
+
+define('NET_SOCKET_READ', 1);
+define('NET_SOCKET_WRITE', 2);
+define('NET_SOCKET_ERROR', 4);
+
+/**
+ * Generalized Socket class.
+ *
+ * @version 1.1
+ * @author Stig Bakken <ssb@php.net>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ */
+class Net_Socket extends PEAR {
+
+ /**
+ * Socket file pointer.
+ * @var resource $fp
+ */
+ var $fp = null;
+
+ /**
+ * Whether the socket is blocking. Defaults to true.
+ * @var boolean $blocking
+ */
+ var $blocking = true;
+
+ /**
+ * Whether the socket is persistent. Defaults to false.
+ * @var boolean $persistent
+ */
+ var $persistent = false;
+
+ /**
+ * The IP address to connect to.
+ * @var string $addr
+ */
+ var $addr = '';
+
+ /**
+ * The port number to connect to.
+ * @var integer $port
+ */
+ var $port = 0;
+
+ /**
+ * Number of seconds to wait on socket connections before assuming
+ * there's no more data. Defaults to no timeout.
+ * @var integer $timeout
+ */
+ var $timeout = false;
+
+ /**
+ * Number of bytes to read at a time in readLine() and
+ * readAll(). Defaults to 2048.
+ * @var integer $lineLength
+ */
+ var $lineLength = 2048;
+
+ /**
+ * Connect to the specified port. If called when the socket is
+ * already connected, it disconnects and connects again.
+ *
+ * @param string $addr IP address or host name.
+ * @param integer $port TCP port number.
+ * @param boolean $persistent (optional) Whether the connection is
+ * persistent (kept open between requests
+ * by the web server).
+ * @param integer $timeout (optional) How long to wait for data.
+ * @param array $options See options for stream_context_create.
+ *
+ * @access public
+ *
+ * @return boolean | PEAR_Error True on success or a PEAR_Error on failure.
+ */
+ function connect($addr, $port = 0, $persistent = null, $timeout = null, $options = null)
+ {
+ if (is_resource($this->fp)) {
+ @fclose($this->fp);
+ $this->fp = null;
+ }
+
+ if (!$addr) {
+ return $this->raiseError('$addr cannot be empty');
+ } elseif (strspn($addr, '.0123456789') == strlen($addr) ||
+ strstr($addr, '/') !== false) {
+ $this->addr = $addr;
+ } else {
+ $this->addr = @gethostbyname($addr);
+ }
+
+ $this->port = $port % 65536;
+
+ if ($persistent !== null) {
+ $this->persistent = $persistent;
+ }
+
+ if ($timeout !== null) {
+ $this->timeout = $timeout;
+ }
+
+ $openfunc = $this->persistent ? 'pfsockopen' : 'fsockopen';
+ $errno = 0;
+ $errstr = '';
+ $old_track_errors = @ini_set('track_errors', 1);
+ if ($options && function_exists('stream_context_create')) {
+ if ($this->timeout) {
+ $timeout = $this->timeout;
+ } else {
+ $timeout = 0;
+ }
+ $context = stream_context_create($options);
+
+ // Since PHP 5 fsockopen doesn't allow context specification
+ if (function_exists('stream_socket_client')) {
+ $flags = $this->persistent ? STREAM_CLIENT_PERSISTENT : STREAM_CLIENT_CONNECT;
+ $addr = $this->addr . ':' . $this->port;
+ $fp = stream_socket_client($addr, $errno, $errstr, $timeout, $flags, $context);
+ } else {
+ $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $timeout, $context);
+ }
+ } else {
+ if ($this->timeout) {
+ $fp = @$openfunc($this->addr, $this->port, $errno, $errstr, $this->timeout);
+ } else {
+ $fp = @$openfunc($this->addr, $this->port, $errno, $errstr);
+ }
+ }
+
+ if (!$fp) {
+ if ($errno == 0 && isset($php_errormsg)) {
+ $errstr = $php_errormsg;
+ }
+ @ini_set('track_errors', $old_track_errors);
+ return $this->raiseError($errstr, $errno);
+ }
+
+ @ini_set('track_errors', $old_track_errors);
+ $this->fp = $fp;
+
+ return $this->setBlocking($this->blocking);
+ }
+
+ /**
+ * Disconnects from the peer, closes the socket.
+ *
+ * @access public
+ * @return mixed true on success or a PEAR_Error instance otherwise
+ */
+ function disconnect()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ @fclose($this->fp);
+ $this->fp = null;
+ return true;
+ }
+
+ /**
+ * Find out if the socket is in blocking mode.
+ *
+ * @access public
+ * @return boolean The current blocking mode.
+ */
+ function isBlocking()
+ {
+ return $this->blocking;
+ }
+
+ /**
+ * Sets whether the socket connection should be blocking or
+ * not. A read call to a non-blocking socket will return immediately
+ * if there is no data available, whereas it will block until there
+ * is data for blocking sockets.
+ *
+ * @param boolean $mode True for blocking sockets, false for nonblocking.
+ * @access public
+ * @return mixed true on success or a PEAR_Error instance otherwise
+ */
+ function setBlocking($mode)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $this->blocking = $mode;
+ socket_set_blocking($this->fp, $this->blocking);
+ return true;
+ }
+
+ /**
+ * Sets the timeout value on socket descriptor,
+ * expressed in the sum of seconds and microseconds
+ *
+ * @param integer $seconds Seconds.
+ * @param integer $microseconds Microseconds.
+ * @access public
+ * @return mixed true on success or a PEAR_Error instance otherwise
+ */
+ function setTimeout($seconds, $microseconds)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return socket_set_timeout($this->fp, $seconds, $microseconds);
+ }
+
+ /**
+ * Sets the file buffering size on the stream.
+ * See php's stream_set_write_buffer for more information.
+ *
+ * @param integer $size Write buffer size.
+ * @access public
+ * @return mixed on success or an PEAR_Error object otherwise
+ */
+ function setWriteBuffer($size)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $returned = stream_set_write_buffer($this->fp, $size);
+ if ($returned == 0) {
+ return true;
+ }
+ return $this->raiseError('Cannot set write buffer.');
+ }
+
+ /**
+ * Returns information about an existing socket resource.
+ * Currently returns four entries in the result array:
+ *
+ * <p>
+ * timed_out (bool) - The socket timed out waiting for data<br>
+ * blocked (bool) - The socket was blocked<br>
+ * eof (bool) - Indicates EOF event<br>
+ * unread_bytes (int) - Number of bytes left in the socket buffer<br>
+ * </p>
+ *
+ * @access public
+ * @return mixed Array containing information about existing socket resource or a PEAR_Error instance otherwise
+ */
+ function getStatus()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return socket_get_status($this->fp);
+ }
+
+ /**
+ * Get a specified line of data
+ *
+ * @access public
+ * @return $size bytes of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function gets($size)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return @fgets($this->fp, $size);
+ }
+
+ /**
+ * Read a specified amount of data. This is guaranteed to return,
+ * and has the added benefit of getting everything in one fread()
+ * chunk; if you know the size of the data you're getting
+ * beforehand, this is definitely the way to go.
+ *
+ * @param integer $size The number of bytes to read from the socket.
+ * @access public
+ * @return $size bytes of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function read($size)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return @fread($this->fp, $size);
+ }
+
+ /**
+ * Write a specified amount of data.
+ *
+ * @param string $data Data to write.
+ * @param integer $blocksize Amount of data to write at once.
+ * NULL means all at once.
+ *
+ * @access public
+ * @return mixed If the socket is not connected, returns an instance of PEAR_Error
+ * If the write succeeds, returns the number of bytes written
+ * If the write fails, returns false.
+ */
+ function write($data, $blocksize = null)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ if (is_null($blocksize) && !OS_WINDOWS) {
+ return @fwrite($this->fp, $data);
+ } else {
+ if (is_null($blocksize)) {
+ $blocksize = 1024;
+ }
+
+ $pos = 0;
+ $size = strlen($data);
+ while ($pos < $size) {
+ $written = @fwrite($this->fp, substr($data, $pos, $blocksize));
+ if ($written === false) {
+ return false;
+ }
+ $pos += $written;
+ }
+
+ return $pos;
+ }
+ }
+
+ /**
+ * Write a line of data to the socket, followed by a trailing "\r\n".
+ *
+ * @access public
+ * @return mixed fputs result, or an error
+ */
+ function writeLine($data)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return fwrite($this->fp, $data . "\r\n");
+ }
+
+ /**
+ * Tests for end-of-file on a socket descriptor.
+ *
+ * Also returns true if the socket is disconnected.
+ *
+ * @access public
+ * @return bool
+ */
+ function eof()
+ {
+ return (!is_resource($this->fp) || feof($this->fp));
+ }
+
+ /**
+ * Reads a byte of data
+ *
+ * @access public
+ * @return 1 byte of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readByte()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ return ord(@fread($this->fp, 1));
+ }
+
+ /**
+ * Reads a word of data
+ *
+ * @access public
+ * @return 1 word of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readWord()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $buf = @fread($this->fp, 2);
+ return (ord($buf[0]) + (ord($buf[1]) << 8));
+ }
+
+ /**
+ * Reads an int of data
+ *
+ * @access public
+ * @return integer 1 int of data from the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readInt()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $buf = @fread($this->fp, 4);
+ return (ord($buf[0]) + (ord($buf[1]) << 8) +
+ (ord($buf[2]) << 16) + (ord($buf[3]) << 24));
+ }
+
+ /**
+ * Reads a zero-terminated string of data
+ *
+ * @access public
+ * @return string, or a PEAR_Error if
+ * not connected.
+ */
+ function readString()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $string = '';
+ while (($char = @fread($this->fp, 1)) != "\x00") {
+ $string .= $char;
+ }
+ return $string;
+ }
+
+ /**
+ * Reads an IP Address and returns it in a dot formatted string
+ *
+ * @access public
+ * @return Dot formatted string, or a PEAR_Error if
+ * not connected.
+ */
+ function readIPAddress()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $buf = @fread($this->fp, 4);
+ return sprintf('%d.%d.%d.%d', ord($buf[0]), ord($buf[1]),
+ ord($buf[2]), ord($buf[3]));
+ }
+
+ /**
+ * Read until either the end of the socket or a newline, whichever
+ * comes first. Strips the trailing newline from the returned data.
+ *
+ * @access public
+ * @return All available data up to a newline, without that
+ * newline, or until the end of the socket, or a PEAR_Error if
+ * not connected.
+ */
+ function readLine()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $line = '';
+ $timeout = time() + $this->timeout;
+ while (!feof($this->fp) && (!$this->timeout || time() < $timeout)) {
+ $line .= @fgets($this->fp, $this->lineLength);
+ if (substr($line, -1) == "\n") {
+ return rtrim($line, "\r\n");
+ }
+ }
+ return $line;
+ }
+
+ /**
+ * Read until the socket closes, or until there is no more data in
+ * the inner PHP buffer. If the inner buffer is empty, in blocking
+ * mode we wait for at least 1 byte of data. Therefore, in
+ * blocking mode, if there is no data at all to be read, this
+ * function will never exit (unless the socket is closed on the
+ * remote end).
+ *
+ * @access public
+ *
+ * @return string All data until the socket closes, or a PEAR_Error if
+ * not connected.
+ */
+ function readAll()
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $data = '';
+ while (!feof($this->fp)) {
+ $data .= @fread($this->fp, $this->lineLength);
+ }
+ return $data;
+ }
+
+ /**
+ * Runs the equivalent of the select() system call on the socket
+ * with a timeout specified by tv_sec and tv_usec.
+ *
+ * @param integer $state Which of read/write/error to check for.
+ * @param integer $tv_sec Number of seconds for timeout.
+ * @param integer $tv_usec Number of microseconds for timeout.
+ *
+ * @access public
+ * @return False if select fails, integer describing which of read/write/error
+ * are ready, or PEAR_Error if not connected.
+ */
+ function select($state, $tv_sec, $tv_usec = 0)
+ {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+
+ $read = null;
+ $write = null;
+ $except = null;
+ if ($state & NET_SOCKET_READ) {
+ $read[] = $this->fp;
+ }
+ if ($state & NET_SOCKET_WRITE) {
+ $write[] = $this->fp;
+ }
+ if ($state & NET_SOCKET_ERROR) {
+ $except[] = $this->fp;
+ }
+ if (false === ($sr = stream_select($read, $write, $except, $tv_sec, $tv_usec))) {
+ return false;
+ }
+
+ $result = 0;
+ if (count($read)) {
+ $result |= NET_SOCKET_READ;
+ }
+ if (count($write)) {
+ $result |= NET_SOCKET_WRITE;
+ }
+ if (count($except)) {
+ $result |= NET_SOCKET_ERROR;
+ }
+ return $result;
+ }
+
+ /**
+ * Turns encryption on/off on a connected socket.
+ *
+ * @param bool $enabled Set this parameter to true to enable encryption
+ * and false to disable encryption.
+ * @param integer $type Type of encryption. See
+ * http://se.php.net/manual/en/function.stream-socket-enable-crypto.php for values.
+ *
+ * @access public
+ * @return false on error, true on success and 0 if there isn't enough data and the
+ * user should try again (non-blocking sockets only). A PEAR_Error object
+ * is returned if the socket is not connected
+ */
+ function enableCrypto($enabled, $type)
+ {
+ if (version_compare(phpversion(), "5.1.0", ">=")) {
+ if (!is_resource($this->fp)) {
+ return $this->raiseError('not connected');
+ }
+ return @stream_socket_enable_crypto($this->fp, $enabled, $type);
+ } else {
+ return $this->raiseError('Net_Socket::enableCrypto() requires php version >= 5.1.0');
+ }
+ }
+
+}
diff --git a/extlib/OAuth.php b/extlib/OAuth.php
new file mode 100644
index 000000000..6dc6b3f35
--- /dev/null
+++ b/extlib/OAuth.php
@@ -0,0 +1,755 @@
+<?php
+// vim: foldmethod=marker
+
+/* Generic exception class
+ */
+class OAuthException extends Exception {/*{{{*/
+ // pass
+}/*}}}*/
+
+class OAuthConsumer {/*{{{*/
+ public $key;
+ public $secret;
+
+ function __construct($key, $secret, $callback_url=NULL) {/*{{{*/
+ $this->key = $key;
+ $this->secret = $secret;
+ $this->callback_url = $callback_url;
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthToken {/*{{{*/
+ // access tokens and request tokens
+ public $key;
+ public $secret;
+
+ /**
+ * key = the token
+ * secret = the token secret
+ */
+ function __construct($key, $secret) {/*{{{*/
+ $this->key = $key;
+ $this->secret = $secret;
+ }/*}}}*/
+
+ /**
+ * generates the basic string serialization of a token that a server
+ * would respond to request_token and access_token calls with
+ */
+ function to_string() {/*{{{*/
+ return "oauth_token=" . OAuthUtil::urlencodeRFC3986($this->key) .
+ "&oauth_token_secret=" . OAuthUtil::urlencodeRFC3986($this->secret);
+ }/*}}}*/
+
+ function __toString() {/*{{{*/
+ return $this->to_string();
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthSignatureMethod {/*{{{*/
+ public function check_signature(&$request, $consumer, $token, $signature) {
+ $built = $this->build_signature($request, $consumer, $token);
+ return $built == $signature;
+ }
+}/*}}}*/
+
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {/*{{{*/
+ function get_name() {/*{{{*/
+ return "HMAC-SHA1";
+ }/*}}}*/
+
+ public function build_signature($request, $consumer, $token) {/*{{{*/
+ $base_string = $request->get_signature_base_string();
+ $request->base_string = $base_string;
+
+ $key_parts = array(
+ $consumer->secret,
+ ($token) ? $token->secret : ""
+ );
+
+ $key_parts = array_map(array('OAuthUtil','urlencodeRFC3986'), $key_parts);
+ $key = implode('&', $key_parts);
+
+ return base64_encode( hash_hmac('sha1', $base_string, $key, true));
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {/*{{{*/
+ public function get_name() {/*{{{*/
+ return "PLAINTEXT";
+ }/*}}}*/
+
+ public function build_signature($request, $consumer, $token) {/*{{{*/
+ $sig = array(
+ OAuthUtil::urlencodeRFC3986($consumer->secret)
+ );
+
+ if ($token) {
+ array_push($sig, OAuthUtil::urlencodeRFC3986($token->secret));
+ } else {
+ array_push($sig, '');
+ }
+
+ $raw = implode("&", $sig);
+ // for debug purposes
+ $request->base_string = $raw;
+
+ return OAuthUtil::urlencodeRFC3986($raw);
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {/*{{{*/
+ public function get_name() {/*{{{*/
+ return "RSA-SHA1";
+ }/*}}}*/
+
+ protected function fetch_public_cert(&$request) {/*{{{*/
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ // (2) fetch via http using a url provided by the requester
+ // (3) some sort of specific discovery code based on request
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_public_cert not implemented");
+ }/*}}}*/
+
+ protected function fetch_private_cert(&$request) {/*{{{*/
+ // not implemented yet, ideas are:
+ // (1) do a lookup in a table of trusted certs keyed off of consumer
+ //
+ // either way should return a string representation of the certificate
+ throw Exception("fetch_private_cert not implemented");
+ }/*}}}*/
+
+ public function build_signature(&$request, $consumer, $token) {/*{{{*/
+ $base_string = $request->get_signature_base_string();
+ $request->base_string = $base_string;
+
+ // Fetch the private key cert based on the request
+ $cert = $this->fetch_private_cert($request);
+
+ // Pull the private key ID from the certificate
+ $privatekeyid = openssl_get_privatekey($cert);
+
+ // Sign using the key
+ $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+ // Release the key resource
+ openssl_free_key($privatekeyid);
+
+ return base64_encode($signature);
+ } /*}}}*/
+
+ public function check_signature(&$request, $consumer, $token, $signature) {/*{{{*/
+ $decoded_sig = base64_decode($signature);
+
+ $base_string = $request->get_signature_base_string();
+
+ // Fetch the public key cert based on the request
+ $cert = $this->fetch_public_cert($request);
+
+ // Pull the public key ID from the certificate
+ $publickeyid = openssl_get_publickey($cert);
+
+ // Check the computed signature against the one passed in the query
+ $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+ // Release the key resource
+ openssl_free_key($publickeyid);
+
+ return $ok == 1;
+ } /*}}}*/
+}/*}}}*/
+
+class OAuthRequest {/*{{{*/
+ private $parameters;
+ private $http_method;
+ private $http_url;
+ // for debug purposes
+ public $base_string;
+ public static $version = '1.0';
+
+ function __construct($http_method, $http_url, $parameters=NULL) {/*{{{*/
+ @$parameters or $parameters = array();
+ $this->parameters = $parameters;
+ $this->http_method = $http_method;
+ $this->http_url = $http_url;
+ }/*}}}*/
+
+
+ /**
+ * attempt to build up a request from what was passed to the server
+ */
+ public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {/*{{{*/
+ $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https';
+ @$http_url or $http_url = $scheme . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
+
+ $request_headers = OAuthRequest::get_headers();
+
+ // let the library user override things however they'd like, if they know
+ // which parameters to use then go for it, for example XMLRPC might want to
+ // do this
+ if ($parameters) {
+ $req = new OAuthRequest($http_method, $http_url, $parameters);
+ }
+ // next check for the auth header, we need to do some extra stuff
+ // if that is the case, namely suck in the parameters from GET or POST
+ // so that we can include them in the signature
+ else if (@substr($request_headers['Authorization'], 0, 5) == "OAuth") {
+ $header_parameters = OAuthRequest::split_header($request_headers['Authorization']);
+ if ($http_method == "GET") {
+ $req_parameters = $_GET;
+ }
+ else if ($http_method == "POST") {
+ $req_parameters = $_POST;
+ }
+ $parameters = array_merge($header_parameters, $req_parameters);
+ $req = new OAuthRequest($http_method, $http_url, $parameters);
+ }
+ else if ($http_method == "GET") {
+ $req = new OAuthRequest($http_method, $http_url, $_GET);
+ }
+ else if ($http_method == "POST") {
+ $req = new OAuthRequest($http_method, $http_url, $_POST);
+ }
+ return $req;
+ }/*}}}*/
+
+ /**
+ * pretty much a helper function to set up the request
+ */
+ public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {/*{{{*/
+ @$parameters or $parameters = array();
+ $defaults = array("oauth_version" => OAuthRequest::$version,
+ "oauth_nonce" => OAuthRequest::generate_nonce(),
+ "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+ "oauth_consumer_key" => $consumer->key);
+ $parameters = array_merge($defaults, $parameters);
+
+ if ($token) {
+ $parameters['oauth_token'] = $token->key;
+ }
+ return new OAuthRequest($http_method, $http_url, $parameters);
+ }/*}}}*/
+
+ public function set_parameter($name, $value) {/*{{{*/
+ $this->parameters[$name] = $value;
+ }/*}}}*/
+
+ public function get_parameter($name) {/*{{{*/
+ return $this->parameters[$name];
+ }/*}}}*/
+
+ public function get_parameters() {/*{{{*/
+ return $this->parameters;
+ }/*}}}*/
+
+ /**
+ * Returns the normalized parameters of the request
+ *
+ * This will be all (except oauth_signature) parameters,
+ * sorted first by key, and if duplicate keys, then by
+ * value.
+ *
+ * The returned string will be all the key=value pairs
+ * concated by &.
+ *
+ * @return string
+ */
+ public function get_signable_parameters() {/*{{{*/
+ // Grab all parameters
+ $params = $this->parameters;
+
+ // Remove oauth_signature if present
+ if (isset($params['oauth_signature'])) {
+ unset($params['oauth_signature']);
+ }
+
+ // Urlencode both keys and values
+ $keys = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_keys($params));
+ $values = array_map(array('OAuthUtil', 'urlencodeRFC3986'), array_values($params));
+ $params = array_combine($keys, $values);
+
+ // Sort by keys (natsort)
+ uksort($params, 'strnatcmp');
+
+ // Generate key=value pairs
+ $pairs = array();
+ foreach ($params as $key=>$value ) {
+ if (is_array($value)) {
+ // If the value is an array, it's because there are multiple
+ // with the same key, sort them, then add all the pairs
+ natsort($value);
+ foreach ($value as $v2) {
+ $pairs[] = $key . '=' . $v2;
+ }
+ } else {
+ $pairs[] = $key . '=' . $value;
+ }
+ }
+
+ // Return the pairs, concated with &
+ return implode('&', $pairs);
+ }/*}}}*/
+
+ /**
+ * Returns the base string of this request
+ *
+ * The base string defined as the method, the url
+ * and the parameters (normalized), each urlencoded
+ * and the concated with &.
+ */
+ public function get_signature_base_string() {/*{{{*/
+ $parts = array(
+ $this->get_normalized_http_method(),
+ $this->get_normalized_http_url(),
+ $this->get_signable_parameters()
+ );
+
+ $parts = array_map(array('OAuthUtil', 'urlencodeRFC3986'), $parts);
+
+ return implode('&', $parts);
+ }/*}}}*/
+
+ /**
+ * just uppercases the http method
+ */
+ public function get_normalized_http_method() {/*{{{*/
+ return strtoupper($this->http_method);
+ }/*}}}*/
+
+ /**
+ * parses the url and rebuilds it to be
+ * scheme://host/path
+ */
+ public function get_normalized_http_url() {/*{{{*/
+ $parts = parse_url($this->http_url);
+
+ $port = @$parts['port'];
+ $scheme = $parts['scheme'];
+ $host = $parts['host'];
+ $path = @$parts['path'];
+
+ $port or $port = ($scheme == 'https') ? '443' : '80';
+
+ if (($scheme == 'https' && $port != '443')
+ || ($scheme == 'http' && $port != '80')) {
+ $host = "$host:$port";
+ }
+ return "$scheme://$host$path";
+ }/*}}}*/
+
+ /**
+ * builds a url usable for a GET request
+ */
+ public function to_url() {/*{{{*/
+ $out = $this->get_normalized_http_url() . "?";
+ $out .= $this->to_postdata();
+ return $out;
+ }/*}}}*/
+
+ /**
+ * builds the data one would send in a POST request
+ */
+ public function to_postdata() {/*{{{*/
+ $total = array();
+ foreach ($this->parameters as $k => $v) {
+ $total[] = OAuthUtil::urlencodeRFC3986($k) . "=" . OAuthUtil::urlencodeRFC3986($v);
+ }
+ $out = implode("&", $total);
+ return $out;
+ }/*}}}*/
+
+ /**
+ * builds the Authorization: header
+ */
+ public function to_header($realm="") {/*{{{*/
+ $out ='"Authorization: OAuth realm="' . $realm . '",';
+ $total = array();
+ foreach ($this->parameters as $k => $v) {
+ if (substr($k, 0, 5) != "oauth") continue;
+ $out .= ',' . OAuthUtil::urlencodeRFC3986($k) . '="' . OAuthUtil::urlencodeRFC3986($v) . '"';
+ }
+ return $out;
+ }/*}}}*/
+
+ public function __toString() {/*{{{*/
+ return $this->to_url();
+ }/*}}}*/
+
+
+ public function sign_request($signature_method, $consumer, $token) {/*{{{*/
+ $this->set_parameter("oauth_signature_method", $signature_method->get_name());
+ $signature = $this->build_signature($signature_method, $consumer, $token);
+ $this->set_parameter("oauth_signature", $signature);
+ }/*}}}*/
+
+ public function build_signature($signature_method, $consumer, $token) {/*{{{*/
+ $signature = $signature_method->build_signature($this, $consumer, $token);
+ return $signature;
+ }/*}}}*/
+
+ /**
+ * util function: current timestamp
+ */
+ private static function generate_timestamp() {/*{{{*/
+ return time();
+ }/*}}}*/
+
+ /**
+ * util function: current nonce
+ */
+ private static function generate_nonce() {/*{{{*/
+ $mt = microtime();
+ $rand = mt_rand();
+
+ return md5($mt . $rand); // md5s look nicer than numbers
+ }/*}}}*/
+
+ /**
+ * util function for turning the Authorization: header into
+ * parameters, has to do some unescaping
+ */
+ private static function split_header($header) {/*{{{*/
+ // remove 'OAuth ' at the start of a header
+ $header = substr($header, 6);
+
+ // error cases: commas in parameter values?
+ $parts = explode(",", $header);
+ $out = array();
+ foreach ($parts as $param) {
+ $param = ltrim($param);
+ // skip the "realm" param, nobody ever uses it anyway
+ if (substr($param, 0, 5) != "oauth") continue;
+
+ $param_parts = explode("=", $param);
+
+ // rawurldecode() used because urldecode() will turn a "+" in the
+ // value into a space
+ $out[$param_parts[0]] = rawurldecode(substr($param_parts[1], 1, -1));
+ }
+ return $out;
+ }/*}}}*/
+
+ /**
+ * helper to try to sort out headers for people who aren't running apache
+ */
+ private static function get_headers() {/*{{{*/
+ if (function_exists('apache_request_headers')) {
+ // we need this to get the actual Authorization: header
+ // because apache tends to tell us it doesn't exist
+ return apache_request_headers();
+ }
+ // otherwise we don't have apache and are just going to have to hope
+ // that $_SERVER actually contains what we need
+ $out = array();
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) == "HTTP_") {
+ // this is chaos, basically it is just there to capitalize the first
+ // letter of every word that is not an initial HTTP and strip HTTP
+ // code from przemek
+ $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5)))));
+ $out[$key] = $value;
+ }
+ }
+ return $out;
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthServer {/*{{{*/
+ protected $timestamp_threshold = 300; // in seconds, five minutes
+ protected $version = 1.0; // hi blaine
+ protected $signature_methods = array();
+
+ protected $data_store;
+
+ function __construct($data_store) {/*{{{*/
+ $this->data_store = $data_store;
+ }/*}}}*/
+
+ public function add_signature_method($signature_method) {/*{{{*/
+ $this->signature_methods[$signature_method->get_name()] =
+ $signature_method;
+ }/*}}}*/
+
+ // high level functions
+
+ /**
+ * process a request_token request
+ * returns the request token on success
+ */
+ public function fetch_request_token(&$request) {/*{{{*/
+ $this->get_version($request);
+
+ $consumer = $this->get_consumer($request);
+
+ // no token required for the initial token request
+ $token = NULL;
+
+ $this->check_signature($request, $consumer, $token);
+
+ $new_token = $this->data_store->new_request_token($consumer);
+
+ return $new_token;
+ }/*}}}*/
+
+ /**
+ * process an access_token request
+ * returns the access token on success
+ */
+ public function fetch_access_token(&$request) {/*{{{*/
+ $this->get_version($request);
+
+ $consumer = $this->get_consumer($request);
+
+ // requires authorized request token
+ $token = $this->get_token($request, $consumer, "request");
+
+ $this->check_signature($request, $consumer, $token);
+
+ $new_token = $this->data_store->new_access_token($token, $consumer);
+
+ return $new_token;
+ }/*}}}*/
+
+ /**
+ * verify an api call, checks all the parameters
+ */
+ public function verify_request(&$request) {/*{{{*/
+ $this->get_version($request);
+ $consumer = $this->get_consumer($request);
+ $token = $this->get_token($request, $consumer, "access");
+ $this->check_signature($request, $consumer, $token);
+ return array($consumer, $token);
+ }/*}}}*/
+
+ // Internals from here
+ /**
+ * version 1
+ */
+ private function get_version(&$request) {/*{{{*/
+ $version = $request->get_parameter("oauth_version");
+ if (!$version) {
+ $version = 1.0;
+ }
+ if ($version && $version != $this->version) {
+ throw new OAuthException("OAuth version '$version' not supported");
+ }
+ return $version;
+ }/*}}}*/
+
+ /**
+ * figure out the signature with some defaults
+ */
+ private function get_signature_method(&$request) {/*{{{*/
+ $signature_method =
+ @$request->get_parameter("oauth_signature_method");
+ if (!$signature_method) {
+ $signature_method = "PLAINTEXT";
+ }
+ if (!in_array($signature_method,
+ array_keys($this->signature_methods))) {
+ throw new OAuthException(
+ "Signature method '$signature_method' not supported try one of the following: " . implode(", ", array_keys($this->signature_methods))
+ );
+ }
+ return $this->signature_methods[$signature_method];
+ }/*}}}*/
+
+ /**
+ * try to find the consumer for the provided request's consumer key
+ */
+ private function get_consumer(&$request) {/*{{{*/
+ $consumer_key = @$request->get_parameter("oauth_consumer_key");
+ if (!$consumer_key) {
+ throw new OAuthException("Invalid consumer key");
+ }
+
+ $consumer = $this->data_store->lookup_consumer($consumer_key);
+ if (!$consumer) {
+ throw new OAuthException("Invalid consumer");
+ }
+
+ return $consumer;
+ }/*}}}*/
+
+ /**
+ * try to find the token for the provided request's token key
+ */
+ private function get_token(&$request, $consumer, $token_type="access") {/*{{{*/
+ $token_field = @$request->get_parameter('oauth_token');
+ $token = $this->data_store->lookup_token(
+ $consumer, $token_type, $token_field
+ );
+ if (!$token) {
+ throw new OAuthException("Invalid $token_type token: $token_field");
+ }
+ return $token;
+ }/*}}}*/
+
+ /**
+ * all-in-one function to check the signature on a request
+ * should guess the signature method appropriately
+ */
+ private function check_signature(&$request, $consumer, $token) {/*{{{*/
+ // this should probably be in a different method
+ $timestamp = @$request->get_parameter('oauth_timestamp');
+ $nonce = @$request->get_parameter('oauth_nonce');
+
+ $this->check_timestamp($timestamp);
+ $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+ $signature_method = $this->get_signature_method($request);
+
+ $signature = $request->get_parameter('oauth_signature');
+ $valid_sig = $signature_method->check_signature(
+ $request,
+ $consumer,
+ $token,
+ $signature
+ );
+
+ if (!$valid_sig) {
+ throw new OAuthException("Invalid signature");
+ }
+ }/*}}}*/
+
+ /**
+ * check that the timestamp is new enough
+ */
+ private function check_timestamp($timestamp) {/*{{{*/
+ // verify that timestamp is recentish
+ $now = time();
+ if ($now - $timestamp > $this->timestamp_threshold) {
+ throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
+ }
+ }/*}}}*/
+
+ /**
+ * check that the nonce is not repeated
+ */
+ private function check_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
+ // verify that the nonce is uniqueish
+ $found = $this->data_store->lookup_nonce($consumer, $token, $nonce, $timestamp);
+ if ($found) {
+ throw new OAuthException("Nonce already used: $nonce");
+ }
+ }/*}}}*/
+
+
+
+}/*}}}*/
+
+class OAuthDataStore {/*{{{*/
+ function lookup_consumer($consumer_key) {/*{{{*/
+ // implement me
+ }/*}}}*/
+
+ function lookup_token($consumer, $token_type, $token) {/*{{{*/
+ // implement me
+ }/*}}}*/
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
+ // implement me
+ }/*}}}*/
+
+ function fetch_request_token($consumer) {/*{{{*/
+ // return a new token attached to this consumer
+ }/*}}}*/
+
+ function fetch_access_token($token, $consumer) {/*{{{*/
+ // return a new access token attached to this consumer
+ // for the user associated with this token if the request token
+ // is authorized
+ // should also invalidate the request token
+ }/*}}}*/
+
+}/*}}}*/
+
+
+/* A very naive dbm-based oauth storage
+ */
+class SimpleOAuthDataStore extends OAuthDataStore {/*{{{*/
+ private $dbh;
+
+ function __construct($path = "oauth.gdbm") {/*{{{*/
+ $this->dbh = dba_popen($path, 'c', 'gdbm');
+ }/*}}}*/
+
+ function __destruct() {/*{{{*/
+ dba_close($this->dbh);
+ }/*}}}*/
+
+ function lookup_consumer($consumer_key) {/*{{{*/
+ $rv = dba_fetch("consumer_$consumer_key", $this->dbh);
+ if ($rv === FALSE) {
+ return NULL;
+ }
+ $obj = unserialize($rv);
+ if (!($obj instanceof OAuthConsumer)) {
+ return NULL;
+ }
+ return $obj;
+ }/*}}}*/
+
+ function lookup_token($consumer, $token_type, $token) {/*{{{*/
+ $rv = dba_fetch("${token_type}_${token}", $this->dbh);
+ if ($rv === FALSE) {
+ return NULL;
+ }
+ $obj = unserialize($rv);
+ if (!($obj instanceof OAuthToken)) {
+ return NULL;
+ }
+ return $obj;
+ }/*}}}*/
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {/*{{{*/
+ if (dba_exists("nonce_$nonce", $this->dbh)) {
+ return TRUE;
+ } else {
+ dba_insert("nonce_$nonce", "1", $this->dbh);
+ return FALSE;
+ }
+ }/*}}}*/
+
+ function new_token($consumer, $type="request") {/*{{{*/
+ $key = md5(time());
+ $secret = time() + time();
+ $token = new OAuthToken($key, md5(md5($secret)));
+ if (!dba_insert("${type}_$key", serialize($token), $this->dbh)) {
+ throw new OAuthException("doooom!");
+ }
+ return $token;
+ }/*}}}*/
+
+ function new_request_token($consumer) {/*{{{*/
+ return $this->new_token($consumer, "request");
+ }/*}}}*/
+
+ function new_access_token($token, $consumer) {/*{{{*/
+
+ $token = $this->new_token($consumer, 'access');
+ dba_delete("request_" . $token->key, $this->dbh);
+ return $token;
+ }/*}}}*/
+}/*}}}*/
+
+class OAuthUtil {/*{{{*/
+ public static function urlencodeRFC3986($string) {/*{{{*/
+ return str_replace('+', ' ',
+ str_replace('%7E', '~', rawurlencode($string)));
+
+ }/*}}}*/
+
+
+ // This decode function isn't taking into consideration the above
+ // modifications to the encoding process. However, this method doesn't
+ // seem to be used anywhere so leaving it as is.
+ public static function urldecodeRFC3986($string) {/*{{{*/
+ return rawurldecode($string);
+ }/*}}}*/
+}/*}}}*/
+
+?>
diff --git a/extlib/OAuth_LICENSE.txt b/extlib/OAuth_LICENSE.txt
new file mode 100644
index 000000000..89f059169
--- /dev/null
+++ b/extlib/OAuth_LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License
+
+Copyright (c) 2007 Andy Smith
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/extlib/PEAR.php b/extlib/PEAR.php
new file mode 100644
index 000000000..4c24c6006
--- /dev/null
+++ b/extlib/PEAR.php
@@ -0,0 +1,1118 @@
+<?php
+/**
+ * PEAR, the PHP Extension and Application Repository
+ *
+ * PEAR class and PEAR_Error class
+ *
+ * PHP versions 4 and 5
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category pear
+ * @package PEAR
+ * @author Sterling Hughes <sterling@php.net>
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2008 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: PEAR.php,v 1.104 2008/01/03 20:26:34 cellog Exp $
+ * @link http://pear.php.net/package/PEAR
+ * @since File available since Release 0.1
+ */
+
+/**#@+
+ * ERROR constants
+ */
+define('PEAR_ERROR_RETURN', 1);
+define('PEAR_ERROR_PRINT', 2);
+define('PEAR_ERROR_TRIGGER', 4);
+define('PEAR_ERROR_DIE', 8);
+define('PEAR_ERROR_CALLBACK', 16);
+/**
+ * WARNING: obsolete
+ * @deprecated
+ */
+define('PEAR_ERROR_EXCEPTION', 32);
+/**#@-*/
+define('PEAR_ZE2', (function_exists('version_compare') &&
+ version_compare(zend_version(), "2-dev", "ge")));
+
+if (substr(PHP_OS, 0, 3) == 'WIN') {
+ define('OS_WINDOWS', true);
+ define('OS_UNIX', false);
+ define('PEAR_OS', 'Windows');
+} else {
+ define('OS_WINDOWS', false);
+ define('OS_UNIX', true);
+ define('PEAR_OS', 'Unix'); // blatant assumption
+}
+
+// instant backwards compatibility
+if (!defined('PATH_SEPARATOR')) {
+ if (OS_WINDOWS) {
+ define('PATH_SEPARATOR', ';');
+ } else {
+ define('PATH_SEPARATOR', ':');
+ }
+}
+
+$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
+$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
+$GLOBALS['_PEAR_destructor_object_list'] = array();
+$GLOBALS['_PEAR_shutdown_funcs'] = array();
+$GLOBALS['_PEAR_error_handler_stack'] = array();
+
+@ini_set('track_errors', true);
+
+/**
+ * Base class for other PEAR classes. Provides rudimentary
+ * emulation of destructors.
+ *
+ * If you want a destructor in your class, inherit PEAR and make a
+ * destructor method called _yourclassname (same name as the
+ * constructor, but with a "_" prefix). Also, in your constructor you
+ * have to call the PEAR constructor: $this->PEAR();.
+ * The destructor method will be called without parameters. Note that
+ * at in some SAPI implementations (such as Apache), any output during
+ * the request shutdown (in which destructors are called) seems to be
+ * discarded. If you need to get any debug information from your
+ * destructor, use error_log(), syslog() or something similar.
+ *
+ * IMPORTANT! To use the emulated destructors you need to create the
+ * objects by reference: $obj =& new PEAR_child;
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Greg Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.2
+ * @link http://pear.php.net/package/PEAR
+ * @see PEAR_Error
+ * @since Class available since PHP 4.0.2
+ * @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
+ */
+class PEAR
+{
+ // {{{ properties
+
+ /**
+ * Whether to enable internal debug messages.
+ *
+ * @var bool
+ * @access private
+ */
+ var $_debug = false;
+
+ /**
+ * Default error mode for this object.
+ *
+ * @var int
+ * @access private
+ */
+ var $_default_error_mode = null;
+
+ /**
+ * Default error options used for this object when error mode
+ * is PEAR_ERROR_TRIGGER.
+ *
+ * @var int
+ * @access private
+ */
+ var $_default_error_options = null;
+
+ /**
+ * Default error handler (callback) for this object, if error mode is
+ * PEAR_ERROR_CALLBACK.
+ *
+ * @var string
+ * @access private
+ */
+ var $_default_error_handler = '';
+
+ /**
+ * Which class to use for error objects.
+ *
+ * @var string
+ * @access private
+ */
+ var $_error_class = 'PEAR_Error';
+
+ /**
+ * An array of expected errors.
+ *
+ * @var array
+ * @access private
+ */
+ var $_expected_errors = array();
+
+ // }}}
+
+ // {{{ constructor
+
+ /**
+ * Constructor. Registers this object in
+ * $_PEAR_destructor_object_list for destructor emulation if a
+ * destructor object exists.
+ *
+ * @param string $error_class (optional) which class to use for
+ * error objects, defaults to PEAR_Error.
+ * @access public
+ * @return void
+ */
+ function PEAR($error_class = null)
+ {
+ $classname = strtolower(get_class($this));
+ if ($this->_debug) {
+ print "PEAR constructor called, class=$classname\n";
+ }
+ if ($error_class !== null) {
+ $this->_error_class = $error_class;
+ }
+ while ($classname && strcasecmp($classname, "pear")) {
+ $destructor = "_$classname";
+ if (method_exists($this, $destructor)) {
+ global $_PEAR_destructor_object_list;
+ $_PEAR_destructor_object_list[] = &$this;
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+
+ // }}}
+ // {{{ destructor
+
+ /**
+ * Destructor (the emulated type of...). Does nothing right now,
+ * but is included for forward compatibility, so subclass
+ * destructors should always call it.
+ *
+ * See the note in the class desciption about output from
+ * destructors.
+ *
+ * @access public
+ * @return void
+ */
+ function _PEAR() {
+ if ($this->_debug) {
+ printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
+ }
+ }
+
+ // }}}
+ // {{{ getStaticProperty()
+
+ /**
+ * If you have a class that's mostly/entirely static, and you need static
+ * properties, you can use this method to simulate them. Eg. in your method(s)
+ * do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
+ * You MUST use a reference, or they will not persist!
+ *
+ * @access public
+ * @param string $class The calling classname, to prevent clashes
+ * @param string $var The variable to retrieve.
+ * @return mixed A reference to the variable. If not set it will be
+ * auto initialised to NULL.
+ */
+ function &getStaticProperty($class, $var)
+ {
+ static $properties;
+ if (!isset($properties[$class])) {
+ $properties[$class] = array();
+ }
+ if (!array_key_exists($var, $properties[$class])) {
+ $properties[$class][$var] = null;
+ }
+ return $properties[$class][$var];
+ }
+
+ // }}}
+ // {{{ registerShutdownFunc()
+
+ /**
+ * Use this function to register a shutdown method for static
+ * classes.
+ *
+ * @access public
+ * @param mixed $func The function name (or array of class/method) to call
+ * @param mixed $args The arguments to pass to the function
+ * @return void
+ */
+ function registerShutdownFunc($func, $args = array())
+ {
+ // if we are called statically, there is a potential
+ // that no shutdown func is registered. Bug #6445
+ if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
+ register_shutdown_function("_PEAR_call_destructors");
+ $GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
+ }
+ $GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
+ }
+
+ // }}}
+ // {{{ isError()
+
+ /**
+ * Tell whether a value is a PEAR error.
+ *
+ * @param mixed $data the value to test
+ * @param int $code if $data is an error object, return true
+ * only if $code is a string and
+ * $obj->getMessage() == $code or
+ * $code is an integer and $obj->getCode() == $code
+ * @access public
+ * @return bool true if parameter is an error
+ */
+ function isError($data, $code = null)
+ {
+ if (is_a($data, 'PEAR_Error')) {
+ if (is_null($code)) {
+ return true;
+ } elseif (is_string($code)) {
+ return $data->getMessage() == $code;
+ } else {
+ return $data->getCode() == $code;
+ }
+ }
+ return false;
+ }
+
+ // }}}
+ // {{{ setErrorHandling()
+
+ /**
+ * Sets how errors generated by this object should be handled.
+ * Can be invoked both in objects and statically. If called
+ * statically, setErrorHandling sets the default behaviour for all
+ * PEAR objects. If called in an object, setErrorHandling sets
+ * the default behaviour for that object.
+ *
+ * @param int $mode
+ * One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options
+ * When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
+ * of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ *
+ * When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
+ * to be the callback function or method. A callback
+ * function is a string with the name of the function, a
+ * callback method is an array of two elements: the element
+ * at index 0 is the object, and the element at index 1 is
+ * the name of the method to call in the object.
+ *
+ * When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
+ * a printf format string used when printing the error
+ * message.
+ *
+ * @access public
+ * @return void
+ * @see PEAR_ERROR_RETURN
+ * @see PEAR_ERROR_PRINT
+ * @see PEAR_ERROR_TRIGGER
+ * @see PEAR_ERROR_DIE
+ * @see PEAR_ERROR_CALLBACK
+ * @see PEAR_ERROR_EXCEPTION
+ *
+ * @since PHP 4.0.5
+ */
+
+ function setErrorHandling($mode = null, $options = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $setmode = &$this->_default_error_mode;
+ $setoptions = &$this->_default_error_options;
+ } else {
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ }
+
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ }
+
+ // }}}
+ // {{{ expectError()
+
+ /**
+ * This method is used to tell which errors you expect to get.
+ * Expected errors are always returned with error mode
+ * PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
+ * and this method pushes a new element onto it. The list of
+ * expected errors are in effect until they are popped off the
+ * stack with the popExpect() method.
+ *
+ * Note that this method can not be called statically
+ *
+ * @param mixed $code a single error code or an array of error codes to expect
+ *
+ * @return int the new depth of the "expected errors" stack
+ * @access public
+ */
+ function expectError($code = '*')
+ {
+ if (is_array($code)) {
+ array_push($this->_expected_errors, $code);
+ } else {
+ array_push($this->_expected_errors, array($code));
+ }
+ return sizeof($this->_expected_errors);
+ }
+
+ // }}}
+ // {{{ popExpect()
+
+ /**
+ * This method pops one element off the expected error codes
+ * stack.
+ *
+ * @return array the list of error codes that were popped
+ */
+ function popExpect()
+ {
+ return array_pop($this->_expected_errors);
+ }
+
+ // }}}
+ // {{{ _checkDelExpect()
+
+ /**
+ * This method checks unsets an error code if available
+ *
+ * @param mixed error code
+ * @return bool true if the error code was unset, false otherwise
+ * @access private
+ * @since PHP 4.3.0
+ */
+ function _checkDelExpect($error_code)
+ {
+ $deleted = false;
+
+ foreach ($this->_expected_errors AS $key => $error_array) {
+ if (in_array($error_code, $error_array)) {
+ unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
+ $deleted = true;
+ }
+
+ // clean up empty arrays
+ if (0 == count($this->_expected_errors[$key])) {
+ unset($this->_expected_errors[$key]);
+ }
+ }
+ return $deleted;
+ }
+
+ // }}}
+ // {{{ delExpect()
+
+ /**
+ * This method deletes all occurences of the specified element from
+ * the expected error codes stack.
+ *
+ * @param mixed $error_code error code that should be deleted
+ * @return mixed list of error codes that were deleted or error
+ * @access public
+ * @since PHP 4.3.0
+ */
+ function delExpect($error_code)
+ {
+ $deleted = false;
+
+ if ((is_array($error_code) && (0 != count($error_code)))) {
+ // $error_code is a non-empty array here;
+ // we walk through it trying to unset all
+ // values
+ foreach($error_code as $key => $error) {
+ if ($this->_checkDelExpect($error)) {
+ $deleted = true;
+ } else {
+ $deleted = false;
+ }
+ }
+ return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ } elseif (!empty($error_code)) {
+ // $error_code comes alone, trying to unset it
+ if ($this->_checkDelExpect($error_code)) {
+ return true;
+ } else {
+ return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
+ }
+ } else {
+ // $error_code is empty
+ return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
+ }
+ }
+
+ // }}}
+ // {{{ raiseError()
+
+ /**
+ * This method is a wrapper that returns an instance of the
+ * configured error class with this object's default error
+ * handling applied. If the $mode and $options parameters are not
+ * specified, the object's defaults are used.
+ *
+ * @param mixed $message a text error message or a PEAR error object
+ *
+ * @param int $code a numeric error code (it is up to your class
+ * to define these if you want to use codes)
+ *
+ * @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
+ * PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
+ * PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
+ *
+ * @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
+ * specifies the PHP-internal error level (one of
+ * E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
+ * If $mode is PEAR_ERROR_CALLBACK, this
+ * parameter specifies the callback function or
+ * method. In other error modes this parameter
+ * is ignored.
+ *
+ * @param string $userinfo If you need to pass along for example debug
+ * information, this parameter is meant for that.
+ *
+ * @param string $error_class The returned error object will be
+ * instantiated from this class, if specified.
+ *
+ * @param bool $skipmsg If true, raiseError will only pass error codes,
+ * the error message parameter will be dropped.
+ *
+ * @access public
+ * @return object a PEAR error object
+ * @see PEAR::setErrorHandling
+ * @since PHP 4.0.5
+ */
+ function &raiseError($message = null,
+ $code = null,
+ $mode = null,
+ $options = null,
+ $userinfo = null,
+ $error_class = null,
+ $skipmsg = false)
+ {
+ // The error is yet a PEAR error object
+ if (is_object($message)) {
+ $code = $message->getCode();
+ $userinfo = $message->getUserInfo();
+ $error_class = $message->getType();
+ $message->error_message_prefix = '';
+ $message = $message->getMessage();
+ }
+
+ if (isset($this) && isset($this->_expected_errors) && sizeof($this->_expected_errors) > 0 && sizeof($exp = end($this->_expected_errors))) {
+ if ($exp[0] == "*" ||
+ (is_int(reset($exp)) && in_array($code, $exp)) ||
+ (is_string(reset($exp)) && in_array($message, $exp))) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ }
+ // No mode given, try global ones
+ if ($mode === null) {
+ // Class error handler
+ if (isset($this) && isset($this->_default_error_mode)) {
+ $mode = $this->_default_error_mode;
+ $options = $this->_default_error_options;
+ // Global error handler
+ } elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
+ $mode = $GLOBALS['_PEAR_default_error_mode'];
+ $options = $GLOBALS['_PEAR_default_error_options'];
+ }
+ }
+
+ if ($error_class !== null) {
+ $ec = $error_class;
+ } elseif (isset($this) && isset($this->_error_class)) {
+ $ec = $this->_error_class;
+ } else {
+ $ec = 'PEAR_Error';
+ }
+ if (intval(PHP_VERSION) < 5) {
+ // little non-eval hack to fix bug #12147
+ include 'PEAR/FixPHP5PEARWarnings.php';
+ return $a;
+ }
+ if ($skipmsg) {
+ $a = new $ec($code, $mode, $options, $userinfo);
+ } else {
+ $a = new $ec($message, $code, $mode, $options, $userinfo);
+ }
+ return $a;
+ }
+
+ // }}}
+ // {{{ throwError()
+
+ /**
+ * Simpler form of raiseError with fewer options. In most cases
+ * message, code and userinfo are enough.
+ *
+ * @param string $message
+ *
+ */
+ function &throwError($message = null,
+ $code = null,
+ $userinfo = null)
+ {
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $a = &$this->raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ } else {
+ $a = &PEAR::raiseError($message, $code, null, null, $userinfo);
+ return $a;
+ }
+ }
+
+ // }}}
+ function staticPushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ $stack[] = array($def_mode, $def_options);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $def_mode = $mode;
+ $def_options = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $def_mode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $def_options = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ function staticPopErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ $setmode = &$GLOBALS['_PEAR_default_error_mode'];
+ $setoptions = &$GLOBALS['_PEAR_default_error_options'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ switch ($mode) {
+ case PEAR_ERROR_EXCEPTION:
+ case PEAR_ERROR_RETURN:
+ case PEAR_ERROR_PRINT:
+ case PEAR_ERROR_TRIGGER:
+ case PEAR_ERROR_DIE:
+ case null:
+ $setmode = $mode;
+ $setoptions = $options;
+ break;
+
+ case PEAR_ERROR_CALLBACK:
+ $setmode = $mode;
+ // class/object method callback
+ if (is_callable($options)) {
+ $setoptions = $options;
+ } else {
+ trigger_error("invalid error callback", E_USER_WARNING);
+ }
+ break;
+
+ default:
+ trigger_error("invalid error mode", E_USER_WARNING);
+ break;
+ }
+ return true;
+ }
+
+ // {{{ pushErrorHandling()
+
+ /**
+ * Push a new error handler on top of the error handler options stack. With this
+ * you can easily override the actual error handler for some code and restore
+ * it later with popErrorHandling.
+ *
+ * @param mixed $mode (same as setErrorHandling)
+ * @param mixed $options (same as setErrorHandling)
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::setErrorHandling
+ */
+ function pushErrorHandling($mode, $options = null)
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $def_mode = &$this->_default_error_mode;
+ $def_options = &$this->_default_error_options;
+ } else {
+ $def_mode = &$GLOBALS['_PEAR_default_error_mode'];
+ $def_options = &$GLOBALS['_PEAR_default_error_options'];
+ }
+ $stack[] = array($def_mode, $def_options);
+
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ $stack[] = array($mode, $options);
+ return true;
+ }
+
+ // }}}
+ // {{{ popErrorHandling()
+
+ /**
+ * Pop the last error handler used
+ *
+ * @return bool Always true
+ *
+ * @see PEAR::pushErrorHandling
+ */
+ function popErrorHandling()
+ {
+ $stack = &$GLOBALS['_PEAR_error_handler_stack'];
+ array_pop($stack);
+ list($mode, $options) = $stack[sizeof($stack) - 1];
+ array_pop($stack);
+ if (isset($this) && is_a($this, 'PEAR')) {
+ $this->setErrorHandling($mode, $options);
+ } else {
+ PEAR::setErrorHandling($mode, $options);
+ }
+ return true;
+ }
+
+ // }}}
+ // {{{ loadExtension()
+
+ /**
+ * OS independant PHP extension load. Remember to take care
+ * on the correct extension name for case sensitive OSes.
+ *
+ * @param string $ext The extension name
+ * @return bool Success or not on the dl() call
+ */
+ function loadExtension($ext)
+ {
+ if (!extension_loaded($ext)) {
+ // if either returns true dl() will produce a FATAL error, stop that
+ if ((ini_get('enable_dl') != 1) || (ini_get('safe_mode') == 1)) {
+ return false;
+ }
+ if (OS_WINDOWS) {
+ $suffix = '.dll';
+ } elseif (PHP_OS == 'HP-UX') {
+ $suffix = '.sl';
+ } elseif (PHP_OS == 'AIX') {
+ $suffix = '.a';
+ } elseif (PHP_OS == 'OSX') {
+ $suffix = '.bundle';
+ } else {
+ $suffix = '.so';
+ }
+ return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
+ }
+ return true;
+ }
+
+ // }}}
+}
+
+// {{{ _PEAR_call_destructors()
+
+function _PEAR_call_destructors()
+{
+ global $_PEAR_destructor_object_list;
+ if (is_array($_PEAR_destructor_object_list) &&
+ sizeof($_PEAR_destructor_object_list))
+ {
+ reset($_PEAR_destructor_object_list);
+ if (PEAR::getStaticProperty('PEAR', 'destructlifo')) {
+ $_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
+ }
+ while (list($k, $objref) = each($_PEAR_destructor_object_list)) {
+ $classname = get_class($objref);
+ while ($classname) {
+ $destructor = "_$classname";
+ if (method_exists($objref, $destructor)) {
+ $objref->$destructor();
+ break;
+ } else {
+ $classname = get_parent_class($classname);
+ }
+ }
+ }
+ // Empty the object list to ensure that destructors are
+ // not called more than once.
+ $_PEAR_destructor_object_list = array();
+ }
+
+ // Now call the shutdown functions
+ if (is_array($GLOBALS['_PEAR_shutdown_funcs']) AND !empty($GLOBALS['_PEAR_shutdown_funcs'])) {
+ foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
+ call_user_func_array($value[0], $value[1]);
+ }
+ }
+}
+
+// }}}
+/**
+ * Standard PEAR error class for PHP 4
+ *
+ * This class is supserseded by {@link PEAR_Exception} in PHP 5
+ *
+ * @category pear
+ * @package PEAR
+ * @author Stig Bakken <ssb@php.net>
+ * @author Tomas V.V. Cox <cox@idecnet.com>
+ * @author Gregory Beaver <cellog@php.net>
+ * @copyright 1997-2006 The PHP Group
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version Release: 1.7.2
+ * @link http://pear.php.net/manual/en/core.pear.pear-error.php
+ * @see PEAR::raiseError(), PEAR::throwError()
+ * @since Class available since PHP 4.0.2
+ */
+class PEAR_Error
+{
+ // {{{ properties
+
+ var $error_message_prefix = '';
+ var $mode = PEAR_ERROR_RETURN;
+ var $level = E_USER_NOTICE;
+ var $code = -1;
+ var $message = '';
+ var $userinfo = '';
+ var $backtrace = null;
+
+ // }}}
+ // {{{ constructor
+
+ /**
+ * PEAR_Error constructor
+ *
+ * @param string $message message
+ *
+ * @param int $code (optional) error code
+ *
+ * @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
+ * PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
+ * PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
+ *
+ * @param mixed $options (optional) error level, _OR_ in the case of
+ * PEAR_ERROR_CALLBACK, the callback function or object/method
+ * tuple.
+ *
+ * @param string $userinfo (optional) additional user/debug info
+ *
+ * @access public
+ *
+ */
+ function PEAR_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ if ($mode === null) {
+ $mode = PEAR_ERROR_RETURN;
+ }
+ $this->message = $message;
+ $this->code = $code;
+ $this->mode = $mode;
+ $this->userinfo = $userinfo;
+ if (!PEAR::getStaticProperty('PEAR_Error', 'skiptrace')) {
+ $this->backtrace = debug_backtrace();
+ if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
+ unset($this->backtrace[0]['object']);
+ }
+ }
+ if ($mode & PEAR_ERROR_CALLBACK) {
+ $this->level = E_USER_NOTICE;
+ $this->callback = $options;
+ } else {
+ if ($options === null) {
+ $options = E_USER_NOTICE;
+ }
+ $this->level = $options;
+ $this->callback = null;
+ }
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ } else {
+ $format = $options;
+ }
+ printf($format, $this->getMessage());
+ }
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ trigger_error($this->getMessage(), $this->level);
+ }
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $msg = $this->getMessage();
+ if (is_null($options) || is_int($options)) {
+ $format = "%s";
+ if (substr($msg, -1) != "\n") {
+ $msg .= "\n";
+ }
+ } else {
+ $format = $options;
+ }
+ die(sprintf($format, $msg));
+ }
+ if ($this->mode & PEAR_ERROR_CALLBACK) {
+ if (is_callable($this->callback)) {
+ call_user_func($this->callback, $this);
+ }
+ }
+ if ($this->mode & PEAR_ERROR_EXCEPTION) {
+ trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
+ eval('$e = new Exception($this->message, $this->code);throw($e);');
+ }
+ }
+
+ // }}}
+ // {{{ getMode()
+
+ /**
+ * Get the error mode from an error object.
+ *
+ * @return int error mode
+ * @access public
+ */
+ function getMode() {
+ return $this->mode;
+ }
+
+ // }}}
+ // {{{ getCallback()
+
+ /**
+ * Get the callback function/method from an error object.
+ *
+ * @return mixed callback function or object/method array
+ * @access public
+ */
+ function getCallback() {
+ return $this->callback;
+ }
+
+ // }}}
+ // {{{ getMessage()
+
+
+ /**
+ * Get the error message from an error object.
+ *
+ * @return string full error message
+ * @access public
+ */
+ function getMessage()
+ {
+ return ($this->error_message_prefix . $this->message);
+ }
+
+
+ // }}}
+ // {{{ getCode()
+
+ /**
+ * Get error code from an error object
+ *
+ * @return int error code
+ * @access public
+ */
+ function getCode()
+ {
+ return $this->code;
+ }
+
+ // }}}
+ // {{{ getType()
+
+ /**
+ * Get the name of this error/exception.
+ *
+ * @return string error/exception name (type)
+ * @access public
+ */
+ function getType()
+ {
+ return get_class($this);
+ }
+
+ // }}}
+ // {{{ getUserInfo()
+
+ /**
+ * Get additional user-supplied information.
+ *
+ * @return string user-supplied information
+ * @access public
+ */
+ function getUserInfo()
+ {
+ return $this->userinfo;
+ }
+
+ // }}}
+ // {{{ getDebugInfo()
+
+ /**
+ * Get additional debug information supplied by the application.
+ *
+ * @return string debug information
+ * @access public
+ */
+ function getDebugInfo()
+ {
+ return $this->getUserInfo();
+ }
+
+ // }}}
+ // {{{ getBacktrace()
+
+ /**
+ * Get the call backtrace from where the error was generated.
+ * Supported with PHP 4.3.0 or newer.
+ *
+ * @param int $frame (optional) what frame to fetch
+ * @return array Backtrace, or NULL if not available.
+ * @access public
+ */
+ function getBacktrace($frame = null)
+ {
+ if (defined('PEAR_IGNORE_BACKTRACE')) {
+ return null;
+ }
+ if ($frame === null) {
+ return $this->backtrace;
+ }
+ return $this->backtrace[$frame];
+ }
+
+ // }}}
+ // {{{ addUserInfo()
+
+ function addUserInfo($info)
+ {
+ if (empty($this->userinfo)) {
+ $this->userinfo = $info;
+ } else {
+ $this->userinfo .= " ** $info";
+ }
+ }
+
+ // }}}
+ // {{{ toString()
+ function __toString()
+ {
+ return $this->getMessage();
+ }
+ // }}}
+ // {{{ toString()
+
+ /**
+ * Make a string representation of this object.
+ *
+ * @return string a string with an object summary
+ * @access public
+ */
+ function toString() {
+ $modes = array();
+ $levels = array(E_USER_NOTICE => 'notice',
+ E_USER_WARNING => 'warning',
+ E_USER_ERROR => 'error');
+ if ($this->mode & PEAR_ERROR_CALLBACK) {
+ if (is_array($this->callback)) {
+ $callback = (is_object($this->callback[0]) ?
+ strtolower(get_class($this->callback[0])) :
+ $this->callback[0]) . '::' .
+ $this->callback[1];
+ } else {
+ $callback = $this->callback;
+ }
+ return sprintf('[%s: message="%s" code=%d mode=callback '.
+ 'callback=%s prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ $callback, $this->error_message_prefix,
+ $this->userinfo);
+ }
+ if ($this->mode & PEAR_ERROR_PRINT) {
+ $modes[] = 'print';
+ }
+ if ($this->mode & PEAR_ERROR_TRIGGER) {
+ $modes[] = 'trigger';
+ }
+ if ($this->mode & PEAR_ERROR_DIE) {
+ $modes[] = 'die';
+ }
+ if ($this->mode & PEAR_ERROR_RETURN) {
+ $modes[] = 'return';
+ }
+ return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
+ 'prefix="%s" info="%s"]',
+ strtolower(get_class($this)), $this->message, $this->code,
+ implode("|", $modes), $levels[$this->level],
+ $this->error_message_prefix,
+ $this->userinfo);
+ }
+
+ // }}}
+}
+
+/*
+ * Local Variables:
+ * mode: php
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */
+?>
diff --git a/extlib/PHP_License_2_02.txt b/extlib/PHP_License_2_02.txt
new file mode 100644
index 000000000..af5b01c49
--- /dev/null
+++ b/extlib/PHP_License_2_02.txt
@@ -0,0 +1,75 @@
+--------------------------------------------------------------------
+ The PHP License, version 2.02
+Copyright (c) 1999 - 2002 The PHP Group. All rights reserved.
+--------------------------------------------------------------------
+
+Redistribution and use in source and binary forms, with or without
+modification, is permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ 3. The name "PHP" must not be used to endorse or promote products
+ derived from this software without prior permission from the
+ PHP Group. This does not apply to add-on libraries or tools
+ that work in conjunction with PHP. In such a case the PHP
+ name may be used to indicate that the product supports PHP.
+
+ 4. The PHP Group may publish revised and/or new versions of the
+ license from time to time. Each version will be given a
+ distinguishing version number.
+ Once covered code has been published under a particular version
+ of the license, you may always continue to use it under the
+ terms of that version. You may also choose to use such covered
+ code under the terms of any subsequent version of the license
+ published by the PHP Group. No one other than the PHP Group has
+ the right to modify the terms applicable to covered code created
+ under this License.
+
+ 5. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes PHP, freely available from
+ http://www.php.net/".
+
+ 6. The software incorporates the Zend Engine, a product of Zend
+ Technologies, Ltd. ("Zend"). The Zend Engine is licensed to the
+ PHP Association (pursuant to a grant from Zend that can be
+ found at http://www.php.net/license/ZendGrant/) for
+ distribution to you under this license agreement, only as a
+ part of PHP. In the event that you separate the Zend Engine
+ (or any portion thereof) from the rest of the software, or
+ modify the Zend Engine, or any portion thereof, your use of the
+ separated or modified Zend Engine software shall not be governed
+ by this license, and instead shall be governed by the license
+ set forth at http://www.zend.com/license/ZendLicense/.
+
+
+
+THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
+ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
+DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------
+
+This software consists of voluntary contributions made by many
+individuals on behalf of the PHP Group.
+
+The PHP Group can be contacted via Email at group@php.net.
+
+For more information on the PHP Group and the PHP project,
+please see <http://www.php.net>.
diff --git a/extlib/PHP_License_3.01.txt b/extlib/PHP_License_3.01.txt
new file mode 100644
index 000000000..25662b2b9
--- /dev/null
+++ b/extlib/PHP_License_3.01.txt
@@ -0,0 +1,68 @@
+--------------------------------------------------------------------
+ The PHP License, version 3.01
+Copyright (c) 1999 - 2008 The PHP Group. All rights reserved.
+--------------------------------------------------------------------
+
+Redistribution and use in source and binary forms, with or without
+modification, is permitted provided that the following conditions
+are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ 3. The name "PHP" must not be used to endorse or promote products
+ derived from this software without prior written permission. For
+ written permission, please contact group@php.net.
+
+ 4. Products derived from this software may not be called "PHP", nor
+ may "PHP" appear in their name, without prior written permission
+ from group@php.net. You may indicate that your software works in
+ conjunction with PHP by saying "Foo for PHP" instead of calling
+ it "PHP Foo" or "phpfoo"
+
+ 5. The PHP Group may publish revised and/or new versions of the
+ license from time to time. Each version will be given a
+ distinguishing version number.
+ Once covered code has been published under a particular version
+ of the license, you may always continue to use it under the terms
+ of that version. You may also choose to use such covered code
+ under the terms of any subsequent version of the license
+ published by the PHP Group. No one other than the PHP Group has
+ the right to modify the terms applicable to covered code created
+ under this License.
+
+ 6. Redistributions of any form whatsoever must retain the following
+ acknowledgment:
+ "This product includes PHP software, freely available from
+ <http://www.php.net/software/>".
+
+THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
+ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
+DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------
+
+This software consists of voluntary contributions made by many
+individuals on behalf of the PHP Group.
+
+The PHP Group can be contacted via Email at group@php.net.
+
+For more information on the PHP Group and the PHP project,
+please see <http://www.php.net>.
+
+PHP includes the Zend Engine, freely available at
+<http://www.zend.com>.
diff --git a/extlib/PHP_Markdown_License.text b/extlib/PHP_Markdown_License.text
new file mode 100644
index 000000000..fbde2c04d
--- /dev/null
+++ b/extlib/PHP_Markdown_License.text
@@ -0,0 +1,36 @@
+PHP Markdown
+Copyright (c) 2004-2008 Michel Fortin
+<http://www.michelf.com/>
+All rights reserved.
+
+Based on Markdown
+Copyright (c) 2003-2006 John Gruber
+<http://daringfireball.net/>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name "Markdown" nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
diff --git a/extlib/Validate.php b/extlib/Validate.php
new file mode 100644
index 000000000..4c05506b3
--- /dev/null
+++ b/extlib/Validate.php
@@ -0,0 +1,1051 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox, Amir Saied |
+// +----------------------------------------------------------------------+
+// | This source file is subject to the New BSD license, That is bundled |
+// | with this package in the file LICENSE, and is available through |
+// | the world-wide-web at |
+// | http://www.opensource.org/licenses/bsd-license.php |
+// | If you did not receive a copy of the new BSDlicense and are unable |
+// | to obtain it through the world-wide-web, please send a note to |
+// | pajoye@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Tomas V.V.Cox <cox@idecnet.com> |
+// | Pierre-Alain Joye <pajoye@php.net> |
+// | Amir Mohammad Saied <amir@php.net> |
+// +----------------------------------------------------------------------+
+//
+/**
+ * Validation class
+ *
+ * Package to validate various datas. It includes :
+ * - numbers (min/max, decimal or not)
+ * - email (syntax, domain check)
+ * - string (predifined type alpha upper and/or lowercase, numeric,...)
+ * - date (min, max, rfc822 compliant)
+ * - uri (RFC2396)
+ * - possibility valid multiple data with a single method call (::multiple)
+ *
+ * @category Validate
+ * @package Validate
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Pierre-Alain Joye <pajoye@php.net>
+ * @author Amir Mohammad Saied <amir@php.net>
+ * @copyright 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version CVS: $Id: Validate.php,v 1.123 2007/12/12 16:45:51 davidc Exp $
+ * @link http://pear.php.net/package/Validate
+ */
+
+/**
+ * Methods for common data validations
+ */
+define('VALIDATE_NUM', '0-9');
+define('VALIDATE_SPACE', '\s');
+define('VALIDATE_ALPHA_LOWER', 'a-z');
+define('VALIDATE_ALPHA_UPPER', 'A-Z');
+define('VALIDATE_ALPHA', VALIDATE_ALPHA_LOWER . VALIDATE_ALPHA_UPPER);
+define('VALIDATE_EALPHA_LOWER', VALIDATE_ALPHA_LOWER . 'áéíóúýàèìòùäëïöüÿâêîôûãñõ¨åæç½ðøþß');
+define('VALIDATE_EALPHA_UPPER', VALIDATE_ALPHA_UPPER . 'ÁÉÍÓÚÝÀÈÌÒÙÄËÏÖܾÂÊÎÔÛÃÑÕ¦ÅÆǼÐØÞ');
+define('VALIDATE_EALPHA', VALIDATE_EALPHA_LOWER . VALIDATE_EALPHA_UPPER);
+define('VALIDATE_PUNCTUATION', VALIDATE_SPACE . '\.,;\:&"\'\?\!\(\)');
+define('VALIDATE_NAME', VALIDATE_EALPHA . VALIDATE_SPACE . "'" . "-");
+define('VALIDATE_STREET', VALIDATE_NUM . VALIDATE_NAME . "/\\ºª\.");
+
+define('VALIDATE_ITLD_EMAILS', 1);
+define('VALIDATE_GTLD_EMAILS', 2);
+define('VALIDATE_CCTLD_EMAILS', 4);
+define('VALIDATE_ALL_EMAILS', 8);
+
+/**
+ * Validation class
+ *
+ * Package to validate various datas. It includes :
+ * - numbers (min/max, decimal or not)
+ * - email (syntax, domain check)
+ * - string (predifined type alpha upper and/or lowercase, numeric,...)
+ * - date (min, max)
+ * - uri (RFC2396)
+ * - possibility valid multiple data with a single method call (::multiple)
+ *
+ * @category Validate
+ * @package Validate
+ * @author Tomas V.V.Cox <cox@idecnet.com>
+ * @author Pierre-Alain Joye <pajoye@php.net>
+ * @author Amir Mohammad Saied <amir@php.net>
+ * @copyright 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Validate
+ */
+class Validate
+{
+ /**
+ * International Top-Level Domain
+ *
+ * This is an array of the known international
+ * top-level domain names.
+ *
+ * @access protected
+ * @var array $_iTld (International top-level domains)
+ */
+ var $_itld = array(
+ 'arpa',
+ 'root',
+ );
+
+ /**
+ * Generic top-level domain
+ *
+ * This is an array of the official
+ * generic top-level domains.
+ *
+ * @access protected
+ * @var array $_gTld (Generic top-level domains)
+ */
+ var $_gtld = array(
+ 'aero',
+ 'biz',
+ 'cat',
+ 'com',
+ 'coop',
+ 'edu',
+ 'gov',
+ 'info',
+ 'int',
+ 'jobs',
+ 'mil',
+ 'mobi',
+ 'museum',
+ 'name',
+ 'net',
+ 'org',
+ 'pro',
+ 'travel',
+ 'asia',
+ 'post',
+ 'tel',
+ 'geo',
+ );
+
+ /**
+ * Country code top-level domains
+ *
+ * This is an array of the official country
+ * codes top-level domains
+ *
+ * @access protected
+ * @var array $_ccTld (Country Code Top-Level Domain)
+ */
+ var $_cctld = array(
+ 'ac',
+ 'ad','ae','af','ag',
+ 'ai','al','am','an',
+ 'ao','aq','ar','as',
+ 'at','au','aw','ax',
+ 'az','ba','bb','bd',
+ 'be','bf','bg','bh',
+ 'bi','bj','bm','bn',
+ 'bo','br','bs','bt',
+ 'bu','bv','bw','by',
+ 'bz','ca','cc','cd',
+ 'cf','cg','ch','ci',
+ 'ck','cl','cm','cn',
+ 'co','cr','cs','cu',
+ 'cv','cx','cy','cz',
+ 'de','dj','dk','dm',
+ 'do','dz','ec','ee',
+ 'eg','eh','er','es',
+ 'et','eu','fi','fj',
+ 'fk','fm','fo','fr',
+ 'ga','gb','gd','ge',
+ 'gf','gg','gh','gi',
+ 'gl','gm','gn','gp',
+ 'gq','gr','gs','gt',
+ 'gu','gw','gy','hk',
+ 'hm','hn','hr','ht',
+ 'hu','id','ie','il',
+ 'im','in','io','iq',
+ 'ir','is','it','je',
+ 'jm','jo','jp','ke',
+ 'kg','kh','ki','km',
+ 'kn','kp','kr','kw',
+ 'ky','kz','la','lb',
+ 'lc','li','lk','lr',
+ 'ls','lt','lu','lv',
+ 'ly','ma','mc','md',
+ 'me','mg','mh','mk',
+ 'ml','mm','mn','mo',
+ 'mp','mq','mr','ms',
+ 'mt','mu','mv','mw',
+ 'mx','my','mz','na',
+ 'nc','ne','nf','ng',
+ 'ni','nl','no','np',
+ 'nr','nu','nz','om',
+ 'pa','pe','pf','pg',
+ 'ph','pk','pl','pm',
+ 'pn','pr','ps','pt',
+ 'pw','py','qa','re',
+ 'ro','rs','ru','rw',
+ 'sa','sb','sc','sd',
+ 'se','sg','sh','si',
+ 'sj','sk','sl','sm',
+ 'sn','so','sr','st',
+ 'su','sv','sy','sz',
+ 'tc','td','tf','tg',
+ 'th','tj','tk','tl',
+ 'tm','tn','to','tp',
+ 'tr','tt','tv','tw',
+ 'tz','ua','ug','uk',
+ 'us','uy','uz','va',
+ 'vc','ve','vg','vi',
+ 'vn','vu','wf','ws',
+ 'ye','yt','yu','za',
+ 'zm','zw',
+ );
+
+
+ /**
+ * Validate a number
+ *
+ * @param string $number Number to validate
+ * @param array $options array where:
+ * 'decimal' is the decimal char or false when decimal not allowed
+ * i.e. ',.' to allow both ',' and '.'
+ * 'dec_prec' Number of allowed decimals
+ * 'min' minimum value
+ * 'max' maximum value
+ *
+ * @return boolean true if valid number, false if not
+ *
+ * @access public
+ */
+ function number($number, $options = array())
+ {
+ $decimal = $dec_prec = $min = $max = null;
+ if (is_array($options)) {
+ extract($options);
+ }
+
+ $dec_prec = $dec_prec ? "{1,$dec_prec}" : '+';
+ $dec_regex = $decimal ? "[$decimal][0-9]$dec_prec" : '';
+
+ if (!preg_match("|^[-+]?\s*[0-9]+($dec_regex)?\$|", $number)) {
+ return false;
+ }
+
+ if ($decimal != '.') {
+ $number = strtr($number, $decimal, '.');
+ }
+
+ $number = (float)str_replace(' ', '', $number);
+ if ($min !== null && $min > $number) {
+ return false;
+ }
+
+ if ($max !== null && $max < $number) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Converting a string to UTF-7 (RFC 2152)
+ *
+ * @param $string string to be converted
+ *
+ * @return string converted string
+ *
+ * @access private
+ */
+ function __stringToUtf7($string) {
+ $return = '';
+ $utf7 = array(
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
+ 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
+ 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
+ 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
+ '3', '4', '5', '6', '7', '8', '9', '+', ','
+ );
+
+ $state = 0;
+ if (!empty($string)) {
+ $i = 0;
+ while ($i <= strlen($string)) {
+ $char = substr($string, $i, 1);
+ if ($state == 0) {
+ if ((ord($char) >= 0x7F) || (ord($char) <= 0x1F)) {
+ if ($char) {
+ $return .= '&';
+ }
+ $state = 1;
+ } elseif ($char == '&') {
+ $return .= '&-';
+ } else {
+ $return .= $char;
+ }
+ } elseif (($i == strlen($string) ||
+ !((ord($char) >= 0x7F)) || (ord($char) <= 0x1F))) {
+ if ($state != 1) {
+ if (ord($char) > 64) {
+ $return .= '';
+ } else {
+ $return .= $utf7[ord($char)];
+ }
+ }
+ $return .= '-';
+ $state = 0;
+ } else {
+ switch($state) {
+ case 1:
+ $return .= $utf7[ord($char) >> 2];
+ $residue = (ord($char) & 0x03) << 4;
+ $state = 2;
+ break;
+ case 2:
+ $return .= $utf7[$residue | (ord($char) >> 4)];
+ $residue = (ord($char) & 0x0F) << 2;
+ $state = 3;
+ break;
+ case 3:
+ $return .= $utf7[$residue | (ord($char) >> 6)];
+ $return .= $utf7[ord($char) & 0x3F];
+ $state = 1;
+ break;
+ }
+ }
+ $i++;
+ }
+ return $return;
+ }
+ return '';
+ }
+
+ /**
+ * Validate an email according to full RFC822 (inclusive human readable part)
+ *
+ * @param string $email email to validate,
+ * will return the address for optional dns validation
+ * @param array $options email() options
+ *
+ * @return boolean true if valid email, false if not
+ *
+ * @access private
+ */
+ function __emailRFC822(&$email, &$options)
+ {
+ if (Validate::__stringToUtf7($email) != $email) {
+ return false;
+ }
+ static $address = null;
+ static $uncomment = null;
+ if (!$address) {
+ // atom = 1*<any CHAR except specials, SPACE and CTLs>
+ $atom = '[^][()<>@,;:\\".\s\000-\037\177-\377]+\s*';
+ // qtext = <any CHAR excepting <">, ; => may be folded
+ // "\" & CR, and including linear-white-space>
+ $qtext = '[^"\\\\\r]';
+ // quoted-pair = "\" CHAR ; may quote any char
+ $quoted_pair = '\\\\.';
+ // quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or
+ // ; quoted chars.
+ $quoted_string = '"(?:' . $qtext . '|' . $quoted_pair . ')*"\s*';
+ // word = atom / quoted-string
+ $word = '(?:' . $atom . '|' . $quoted_string . ')';
+ // local-part = word *("." word) ; uninterpreted
+ // ; case-preserved
+ $local_part = $word . '(?:\.\s*' . $word . ')*';
+ // dtext = <any CHAR excluding "[", ; => may be folded
+ // "]", "\" & CR, & including linear-white-space>
+ $dtext = '[^][\\\\\r]';
+ // domain-literal = "[" *(dtext / quoted-pair) "]"
+ $domain_literal = '\[(?:' . $dtext . '|' . $quoted_pair . ')*\]\s*';
+ // sub-domain = domain-ref / domain-literal
+ // domain-ref = atom ; symbolic reference
+ $sub_domain = '(?:' . $atom . '|' . $domain_literal . ')';
+ // domain = sub-domain *("." sub-domain)
+ $domain = $sub_domain . '(?:\.\s*' . $sub_domain . ')*';
+ // addr-spec = local-part "@" domain ; global address
+ $addr_spec = $local_part . '@\s*' . $domain;
+ // route = 1#("@" domain) ":" ; path-relative
+ $route = '@' . $domain . '(?:,@\s*' . $domain . ')*:\s*';
+ // route-addr = "<" [route] addr-spec ">"
+ $route_addr = '<\s*(?:' . $route . ')?' . $addr_spec . '>\s*';
+ // phrase = 1*word ; Sequence of words
+ $phrase = $word . '+';
+ // mailbox = addr-spec ; simple address
+ // / phrase route-addr ; name & addr-spec
+ $mailbox = '(?:' . $addr_spec . '|' . $phrase . $route_addr . ')';
+ // group = phrase ":" [#mailbox] ";"
+ $group = $phrase . ':\s*(?:' . $mailbox . '(?:,\s*' . $mailbox . ')*)?;\s*';
+ // address = mailbox ; one addressee
+ // / group ; named list
+ $address = '/^\s*(?:' . $mailbox . '|' . $group . ')$/';
+ $uncomment =
+ '/((?:(?:\\\\"|[^("])*(?:' . $quoted_string .
+ ')?)*)((?<!\\\\)\((?:(?2)|.)*?(?<!\\\\)\))/';
+ }
+ // strip comments
+ $email = preg_replace($uncomment, '$1 ', $email);
+ return preg_match($address, $email);
+ }
+
+ /**
+ * Full TLD Validation function
+ *
+ * This function is used to make a much more proficient validation
+ * against all types of official domain names.
+ *
+ * @access protected
+ * @param string $email The email address to check.
+ * @param array $options The options for validation
+ * @return bool True if validating succeeds
+ */
+ function _fullTLDValidation($email, $options)
+ {
+ $validate = array();
+
+ switch ($options) {
+ /** 1 */
+ case VALIDATE_ITLD_EMAILS:
+ array_push($validate, 'itld');
+ break;
+
+ /** 2 */
+ case VALIDATE_GTLD_EMAILS:
+ array_push($validate, 'gtld');
+ break;
+
+ /** 3 */
+ case VALIDATE_ITLD_EMAILS | VALIDATE_GTLD_EMAILS:
+ array_push($validate, 'itld');
+ array_push($validate, 'gtld');
+ break;
+
+ /** 4 */
+ case VALIDATE_CCTLD_EMAILS:
+ array_push($validate, 'cctld');
+ break;
+
+ /** 5 */
+ case VALIDATE_CCTLD_EMAILS | VALIDATE_ITLD_EMAILS:
+ array_push($validate, 'cctld');
+ array_push($validate, 'itld');
+ break;
+
+ /** 6 */
+ case VALIDATE_CCTLD_EMAILS ^ VALIDATE_ITLD_EMAILS:
+ array_push($validate, 'cctld');
+ array_push($validate, 'itld');
+ break;
+
+ /** 7 - 8 */
+ case VALIDATE_CCTLD_EMAILS | VALIDATE_ITLD_EMAILS | VALIDATE_GTLD_EMAILS:
+ case VALIDATE_ALL_EMAILS:
+ array_push($validate, 'cctld');
+ array_push($validate, 'itld');
+ array_push($validate, 'gtld');
+ break;
+ }
+
+ /**
+ * Debugging still, not implemented but code is somewhat here.
+ */
+
+ $self = new Validate;
+
+ $toValidate = array();
+
+ foreach ($validate as $valid) {
+ $tmpVar = '_' . (string)$valid;
+ $toValidate[$valid] = $self->{$tmpVar};
+ }
+
+ $e = $self->executeFullEmailValidation($email, $toValidate);
+
+ return $e;
+ }
+ // {{{ protected function executeFullEmailValidation
+ /**
+ * Execute the validation
+ *
+ * This function will execute the full email vs tld
+ * validation using an array of tlds passed to it.
+ *
+ * @access public
+ * @param string $email The email to validate.
+ * @param array $arrayOfTLDs The array of the TLDs to validate
+ * @return true or false (Depending on if it validates or if it does not)
+ */
+ function executeFullEmailValidation($email, $arrayOfTLDs)
+ {
+ $emailEnding = explode('.', $email);
+ $emailEnding = $emailEnding[count($emailEnding)-1];
+
+ foreach ($arrayOfTLDs as $validator => $keys) {
+ if (in_array($emailEnding, $keys)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // }}}
+
+ /**
+ * Validate an email
+ *
+ * @param string $email email to validate
+ * @param mixed boolean (BC) $check_domain Check or not if the domain exists
+ * array $options associative array of options
+ * 'check_domain' boolean Check or not if the domain exists
+ * 'use_rfc822' boolean Apply the full RFC822 grammar
+ *
+ * @return boolean true if valid email, false if not
+ *
+ * @access public
+ */
+ function email($email, $options = null)
+ {
+ $check_domain = false;
+ $use_rfc822 = false;
+ if (is_bool($options)) {
+ $check_domain = $options;
+ } elseif (is_array($options)) {
+ extract($options);
+ }
+
+ /**
+ * @todo Fix bug here.. even if it passes this, it won't be passing
+ * The regular expression below
+ */
+ if (isset($fullTLDValidation)) {
+ $valid = Validate::_fullTLDValidation($email, $fullTLDValidation);
+
+ if (!$valid) {
+ return false;
+ }
+ }
+
+ // the base regexp for address
+ $regex = '&^(?: # recipient:
+ ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
+ ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*)) #2 OR dot-atom
+ @(((\[)? #3 domain, 4 as IPv4, 5 optionally bracketed
+ (?:(?:(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.){3}
+ (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))))(?(5)\])|
+ ((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z0-9](?:[-a-z0-9]*[a-z0-9])?) #6 domain as hostname
+ \.((?:([^- ])[-a-z]*[-a-z]))) #7 TLD
+ $&xi';
+
+ if ($use_rfc822? Validate::__emailRFC822($email, $options) :
+ preg_match($regex, $email)) {
+ if ($check_domain && function_exists('checkdnsrr')) {
+ list (, $domain) = explode('@', $email);
+ if (checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A')) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Validate a string using the given format 'format'
+ *
+ * @param string $string String to validate
+ * @param array $options Options array where:
+ * 'format' is the format of the string
+ * Ex: VALIDATE_NUM . VALIDATE_ALPHA (see constants)
+ * 'min_length' minimum length
+ * 'max_length' maximum length
+ *
+ * @return boolean true if valid string, false if not
+ *
+ * @access public
+ */
+ function string($string, $options)
+ {
+ $format = null;
+ $min_length = $max_length = 0;
+ if (is_array($options)) {
+ extract($options);
+ }
+ if ($format && !preg_match("|^[$format]*\$|s", $string)) {
+ return false;
+ }
+ if ($min_length && strlen($string) < $min_length) {
+ return false;
+ }
+ if ($max_length && strlen($string) > $max_length) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Validate an URI (RFC2396)
+ * This function will validate 'foobarstring' by default, to get it to validate
+ * only http, https, ftp and such you have to pass it in the allowed_schemes
+ * option, like this:
+ * <code>
+ * $options = array('allowed_schemes' => array('http', 'https', 'ftp'))
+ * var_dump(Validate::uri('http://www.example.org', $options));
+ * </code>
+ *
+ * NOTE 1: The rfc2396 normally allows middle '-' in the top domain
+ * e.g. http://example.co-m should be valid
+ * However, as '-' is not used in any known TLD, it is invalid
+ * NOTE 2: As double shlashes // are allowed in the path part, only full URIs
+ * including an authority can be valid, no relative URIs
+ * the // are mandatory (optionally preceeded by the 'sheme:' )
+ * NOTE 3: the full complience to rfc2396 is not achieved by default
+ * the characters ';/?:@$,' will not be accepted in the query part
+ * if not urlencoded, refer to the option "strict'"
+ *
+ * @param string $url URI to validate
+ * @param array $options Options used by the validation method.
+ * key => type
+ * 'domain_check' => boolean
+ * Whether to check the DNS entry or not
+ * 'allowed_schemes' => array, list of protocols
+ * List of allowed schemes ('http',
+ * 'ssh+svn', 'mms')
+ * 'strict' => string the refused chars
+ * in query and fragment parts
+ * default: ';/?:@$,'
+ * empty: accept all rfc2396 foreseen chars
+ *
+ * @return boolean true if valid uri, false if not
+ *
+ * @access public
+ */
+ function uri($url, $options = null)
+ {
+ $strict = ';/?:@$,';
+ $domain_check = false;
+ $allowed_schemes = null;
+ if (is_array($options)) {
+ extract($options);
+ }
+ if (preg_match(
+ '&^(?:([a-z][-+.a-z0-9]*):)? # 1. scheme
+ (?:// # authority start
+ (?:((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();:\&=+$,])*)@)? # 2. authority-userinfo
+ (?:((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z](?:[a-z0-9]+)?\.?) # 3. authority-hostname OR
+ |([0-9]{1,3}(?:\.[0-9]{1,3}){3})) # 4. authority-ipv4
+ (?::([0-9]*))?) # 5. authority-port
+ ((?:/(?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'():@\&=+$,;])*)*/?)? # 6. path
+ (?:\?([^#]*))? # 7. query
+ (?:\#((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();/?:@\&=+$,])*))? # 8. fragment
+ $&xi', $url, $matches)) {
+ $scheme = isset($matches[1]) ? $matches[1] : '';
+ $authority = isset($matches[3]) ? $matches[3] : '' ;
+ if (is_array($allowed_schemes) &&
+ !in_array($scheme,$allowed_schemes)
+ ) {
+ return false;
+ }
+ if (!empty($matches[4])) {
+ $parts = explode('.', $matches[4]);
+ foreach ($parts as $part) {
+ if ($part > 255) {
+ return false;
+ }
+ }
+ } elseif ($domain_check && function_exists('checkdnsrr')) {
+ if (!checkdnsrr($authority, 'A')) {
+ return false;
+ }
+ }
+ if ($strict) {
+ $strict = '#[' . preg_quote($strict, '#') . ']#';
+ if ((!empty($matches[7]) && preg_match($strict, $matches[7]))
+ || (!empty($matches[8]) && preg_match($strict, $matches[8]))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Validate date and times. Note that this method need the Date_Calc class
+ *
+ * @param string $date Date to validate
+ * @param array $options array options where :
+ * 'format' The format of the date (%d-%m-%Y)
+ * or rfc822_compliant
+ * 'min' The date has to be greater
+ * than this array($day, $month, $year)
+ * or PEAR::Date object
+ * 'max' The date has to be smaller than
+ * this array($day, $month, $year)
+ * or PEAR::Date object
+ *
+ * @return boolean true if valid date/time, false if not
+ *
+ * @access public
+ */
+ function date($date, $options)
+ {
+ $max = $min = false;
+ $format = '';
+ if (is_array($options)) {
+ extract($options);
+ }
+
+ if (strtolower($format) == 'rfc822_compliant') {
+ $preg = '&^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),) \s+
+ (?:(\d{2})?) \s+
+ (?:(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?) \s+
+ (?:(\d{2}(\d{2})?)?) \s+
+ (?:(\d{2}?)):(?:(\d{2}?))(:(?:(\d{2}?)))? \s+
+ (?:[+-]\d{4}|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Za-ik-z])$&xi';
+
+ if (!preg_match($preg, $date, $matches)) {
+ return false;
+ }
+
+ $year = (int)$matches[4];
+ $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
+ $month = array_keys($months, $matches[3]);
+ $month = (int)$month[0]+1;
+ $day = (int)$matches[2];
+ $weekday= $matches[1];
+ $hour = (int)$matches[6];
+ $minute = (int)$matches[7];
+ isset($matches[9]) ? $second = (int)$matches[9] : $second = 0;
+
+ if ((strlen($year) != 4) ||
+ ($day > 31 || $day < 1)||
+ ($hour > 23) ||
+ ($minute > 59) ||
+ ($second > 59)) {
+ return false;
+ }
+ } else {
+ $date_len = strlen($format);
+ for ($i = 0; $i < $date_len; $i++) {
+ $c = $format{$i};
+ if ($c == '%') {
+ $next = $format{$i + 1};
+ switch ($next) {
+ case 'j':
+ case 'd':
+ if ($next == 'j') {
+ $day = (int)Validate::_substr($date, 1, 2);
+ } else {
+ $day = (int)Validate::_substr($date, 0, 2);
+ }
+ if ($day < 1 || $day > 31) {
+ return false;
+ }
+ break;
+ case 'm':
+ case 'n':
+ if ($next == 'm') {
+ $month = (int)Validate::_substr($date, 0, 2);
+ } else {
+ $month = (int)Validate::_substr($date, 1, 2);
+ }
+ if ($month < 1 || $month > 12) {
+ return false;
+ }
+ break;
+ case 'Y':
+ case 'y':
+ if ($next == 'Y') {
+ $year = Validate::_substr($date, 4);
+ $year = (int)$year?$year:'';
+ } else {
+ $year = (int)(substr(date('Y'), 0, 2) .
+ Validate::_substr($date, 2));
+ }
+ if (strlen($year) != 4 || $year < 0 || $year > 9999) {
+ return false;
+ }
+ break;
+ case 'g':
+ case 'h':
+ if ($next == 'g') {
+ $hour = Validate::_substr($date, 1, 2);
+ } else {
+ $hour = Validate::_substr($date, 2);
+ }
+ if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 12) {
+ return false;
+ }
+ break;
+ case 'G':
+ case 'H':
+ if ($next == 'G') {
+ $hour = Validate::_substr($date, 1, 2);
+ } else {
+ $hour = Validate::_substr($date, 2);
+ }
+ if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 24) {
+ return false;
+ }
+ break;
+ case 's':
+ case 'i':
+ $t = Validate::_substr($date, 2);
+ if (!preg_match('/^\d+$/', $t) || $t < 0 || $t > 59) {
+ return false;
+ }
+ break;
+ default:
+ trigger_error("Not supported char `$next' after % in offset " . ($i+2), E_USER_WARNING);
+ }
+ $i++;
+ } else {
+ //literal
+ if (Validate::_substr($date, 1) != $c) {
+ return false;
+ }
+ }
+ }
+ }
+ // there is remaing data, we don't want it
+ if (strlen($date) && (strtolower($format) != 'rfc822_compliant')) {
+ return false;
+ }
+
+ if (isset($day) && isset($month) && isset($year)) {
+ if (!checkdate($month, $day, $year)) {
+ return false;
+ }
+
+ if (strtolower($format) == 'rfc822_compliant') {
+ if ($weekday != date("D", mktime(0, 0, 0, $month, $day, $year))) {
+ return false;
+ }
+ }
+
+ if ($min) {
+ include_once 'Date/Calc.php';
+ if (is_a($min, 'Date') &&
+ (Date_Calc::compareDates($day, $month, $year,
+ $min->getDay(), $min->getMonth(), $min->getYear()) < 0))
+ {
+ return false;
+ } elseif (is_array($min) &&
+ (Date_Calc::compareDates($day, $month, $year,
+ $min[0], $min[1], $min[2]) < 0))
+ {
+ return false;
+ }
+ }
+
+ if ($max) {
+ include_once 'Date/Calc.php';
+ if (is_a($max, 'Date') &&
+ (Date_Calc::compareDates($day, $month, $year,
+ $max->getDay(), $max->getMonth(), $max->getYear()) > 0))
+ {
+ return false;
+ } elseif (is_array($max) &&
+ (Date_Calc::compareDates($day, $month, $year,
+ $max[0], $max[1], $max[2]) > 0))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function _substr(&$date, $num, $opt = false)
+ {
+ if ($opt && strlen($date) >= $opt && preg_match('/^[0-9]{'.$opt.'}/', $date, $m)) {
+ $ret = $m[0];
+ } else {
+ $ret = substr($date, 0, $num);
+ }
+ $date = substr($date, strlen($ret));
+ return $ret;
+ }
+
+ function _modf($val, $div) {
+ if (function_exists('bcmod')) {
+ return bcmod($val, $div);
+ } elseif (function_exists('fmod')) {
+ return fmod($val, $div);
+ }
+ $r = $val / $div;
+ $i = intval($r);
+ return intval($val - $i * $div + .1);
+ }
+
+ /**
+ * Calculates sum of product of number digits with weights
+ *
+ * @param string $number number string
+ * @param array $weights reference to array of weights
+ *
+ * @returns int returns product of number digits with weights
+ *
+ * @access protected
+ */
+ function _multWeights($number, &$weights) {
+ if (!is_array($weights)) {
+ return -1;
+ }
+ $sum = 0;
+
+ $count = min(count($weights), strlen($number));
+ if ($count == 0) { // empty string or weights array
+ return -1;
+ }
+ for ($i = 0; $i < $count; ++$i) {
+ $sum += intval(substr($number, $i, 1)) * $weights[$i];
+ }
+
+ return $sum;
+ }
+
+ /**
+ * Calculates control digit for a given number
+ *
+ * @param string $number number string
+ * @param array $weights reference to array of weights
+ * @param int $modulo (optionsl) number
+ * @param int $subtract (optional) number
+ * @param bool $allow_high (optional) true if function can return number higher than 10
+ *
+ * @returns int -1 calculated control number is returned
+ *
+ * @access protected
+ */
+ function _getControlNumber($number, &$weights, $modulo = 10, $subtract = 0, $allow_high = false) {
+ // calc sum
+ $sum = Validate::_multWeights($number, $weights);
+ if ($sum == -1) {
+ return -1;
+ }
+ $mod = Validate::_modf($sum, $modulo); // calculate control digit
+
+ if ($subtract > $mod && $mod > 0) {
+ $mod = $subtract - $mod;
+ }
+ if ($allow_high === false) {
+ $mod %= 10; // change 10 to zero
+ }
+ return $mod;
+ }
+
+ /**
+ * Validates a number
+ *
+ * @param string $number number to validate
+ * @param array $weights reference to array of weights
+ * @param int $modulo (optionsl) number
+ * @param int $subtract (optional) numbier
+ *
+ * @returns bool true if valid, false if not
+ *
+ * @access protected
+ */
+ function _checkControlNumber($number, &$weights, $modulo = 10, $subtract = 0) {
+ if (strlen($number) < count($weights)) {
+ return false;
+ }
+ $target_digit = substr($number, count($weights), 1);
+ $control_digit = Validate::_getControlNumber($number, $weights, $modulo, $subtract, $modulo > 10);
+
+ if ($control_digit == -1) {
+ return false;
+ }
+ if ($target_digit === 'X' && $control_digit == 10) {
+ return true;
+ }
+ if ($control_digit != $target_digit) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Bulk data validation for data introduced in the form of an
+ * assoc array in the form $var_name => $value.
+ * Can be used on any of Validate subpackages
+ *
+ * @param array $data Ex: array('name' => 'toto', 'email' => 'toto@thing.info');
+ * @param array $val_type Contains the validation type and all parameters used in.
+ * 'val_type' is not optional
+ * others validations properties must have the same name as the function
+ * parameters.
+ * Ex: array('toto'=>array('type'=>'string','format'='toto@thing.info','min_length'=>5));
+ * @param boolean $remove if set, the elements not listed in data will be removed
+ *
+ * @return array value name => true|false the value name comes from the data key
+ *
+ * @access public
+ */
+ function multiple(&$data, &$val_type, $remove = false)
+ {
+ $keys = array_keys($data);
+ $valid = array();
+ foreach ($keys as $var_name) {
+ if (!isset($val_type[$var_name])) {
+ if ($remove) {
+ unset($data[$var_name]);
+ }
+ continue;
+ }
+ $opt = $val_type[$var_name];
+ $methods = get_class_methods('Validate');
+ $val2check = $data[$var_name];
+ // core validation method
+ if (in_array(strtolower($opt['type']), $methods)) {
+ //$opt[$opt['type']] = $data[$var_name];
+ $method = $opt['type'];
+ unset($opt['type']);
+
+ if (sizeof($opt) == 1 && is_array(reset($opt))) {
+ $opt = array_pop($opt);
+ }
+ $valid[$var_name] = call_user_func(array('Validate', $method), $val2check, $opt);
+
+ /**
+ * external validation method in the form:
+ * "<class name><underscore><method name>"
+ * Ex: us_ssn will include class Validate/US.php and call method ssn()
+ */
+ } elseif (strpos($opt['type'], '_') !== false) {
+ $validateType = explode('_', $opt['type']);
+ $method = array_pop($validateType);
+ $class = implode('_', $validateType);
+ $classPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
+ $class = 'Validate_' . $class;
+ if (!@include_once "Validate/$classPath.php") {
+ trigger_error("$class isn't installed or you may have some permissoin issues", E_USER_ERROR);
+ }
+
+ $ce = substr(phpversion(), 0, 1) > 4 ? class_exists($class, false) : class_exists($class);
+ if (!$ce ||
+ !in_array($method, get_class_methods($class)))
+ {
+ trigger_error("Invalid validation type $class::$method", E_USER_WARNING);
+ continue;
+ }
+ unset($opt['type']);
+ if (sizeof($opt) == 1) {
+ $opt = array_pop($opt);
+ }
+ $valid[$var_name] = call_user_func(array($class, $method), $data[$var_name], $opt);
+ } else {
+ trigger_error("Invalid validation type {$opt['type']}", E_USER_WARNING);
+ }
+ }
+ return $valid;
+ }
+}
+
diff --git a/extlib/XMPPHP/BOSH.php b/extlib/XMPPHP/BOSH.php
new file mode 100644
index 000000000..b147443d7
--- /dev/null
+++ b/extlib/XMPPHP/BOSH.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/** XMPPHP_XMLStream */
+require_once "XMPP.php";
+
+/**
+ * XMPPHP Main Class
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+class XMPPHP_BOSH extends XMPPHP_XMPP {
+
+ protected $rid;
+ protected $sid;
+ protected $http_server;
+ protected $http_buffer = Array();
+ protected $session = false;
+
+ public function connect($server, $wait='1', $session=false) {
+ $this->http_server = $server;
+ $this->use_encryption = false;
+ $this->session = $session;
+
+ $this->rid = 3001;
+ $this->sid = null;
+ if($session)
+ {
+ $this->loadSession();
+ }
+ if(!$this->sid) {
+ $body = $this->__buildBody();
+ $body->addAttribute('hold','1');
+ $body->addAttribute('to', $this->host);
+ $body->addAttribute('route', "xmpp:{$this->host}:{$this->port}");
+ $body->addAttribute('secure','true');
+ $body->addAttribute('xmpp:version','1.6', 'urn:xmpp:xbosh');
+ $body->addAttribute('wait', strval($wait));
+ $body->addAttribute('ack','1');
+ $body->addAttribute('xmlns:xmpp','urn:xmpp:xbosh');
+ $buff = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+ xml_parse($this->parser, $buff, false);
+ $response = $this->__sendBody($body);
+ $rxml = new SimpleXMLElement($response);
+ $this->sid = $rxml['sid'];
+
+ } else {
+ $buff = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+ xml_parse($this->parser, $buff, false);
+ }
+ }
+
+ public function __sendBody($body=null, $recv=true) {
+ if(!$body) {
+ $body = $this->__buildBody();
+ }
+ $ch = curl_init($this->http_server);
+ curl_setopt($ch, CURLOPT_HEADER, 0);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $body->asXML());
+ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ $header = array('Accept-Encoding: gzip, deflate','Content-Type: text/xml; charset=utf-8');
+ curl_setopt($ch, CURLOPT_HTTPHEADER, $header );
+ curl_setopt($ch, CURLOPT_VERBOSE, 0);
+ $output = '';
+ if($recv) {
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ $output = curl_exec($ch);
+ $this->http_buffer[] = $output;
+ }
+ curl_close($ch);
+ return $output;
+ }
+
+ public function __buildBody($sub=null) {
+ $xml = new SimpleXMLElement("<body xmlns='http://jabber.org/protocol/httpbind' xmlns:xmpp='urn:xmpp:xbosh' />");
+ $xml->addAttribute('content', 'text/xml; charset=utf-8');
+ $xml->addAttribute('rid', $this->rid);
+ $this->rid += 1;
+ if($this->sid) $xml->addAttribute('sid', $this->sid);
+ #if($this->sid) $xml->addAttribute('xmlns', 'http://jabber.org/protocol/httpbind');
+ $xml->addAttribute('xml:lang', 'en');
+ if($sub) { // ok, so simplexml is lame
+ $p = dom_import_simplexml($xml);
+ $c = dom_import_simplexml($sub);
+ $cn = $p->ownerDocument->importNode($c, true);
+ $p->appendChild($cn);
+ $xml = simplexml_import_dom($p);
+ }
+ return $xml;
+ }
+
+ public function __process() {
+ if($this->http_buffer) {
+ $this->__parseBuffer();
+ } else {
+ $this->__sendBody();
+ $this->__parseBuffer();
+ }
+ }
+
+ public function __parseBuffer() {
+ while ($this->http_buffer) {
+ $idx = key($this->http_buffer);
+ $buffer = $this->http_buffer[$idx];
+ unset($this->http_buffer[$idx]);
+ if($buffer) {
+ $xml = new SimpleXMLElement($buffer);
+ $children = $xml->xpath('child::node()');
+ foreach ($children as $child) {
+ $buff = $child->asXML();
+ $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE);
+ xml_parse($this->parser, $buff, false);
+ }
+ }
+ }
+ }
+
+ public function send($msg) {
+ $this->log->log("SEND: $msg", XMPPHP_Log::LEVEL_VERBOSE);
+ $msg = new SimpleXMLElement($msg);
+ #$msg->addAttribute('xmlns', 'jabber:client');
+ $this->__sendBody($this->__buildBody($msg), true);
+ #$this->__parseBuffer();
+ }
+
+ public function reset() {
+ $this->xml_depth = 0;
+ unset($this->xmlobj);
+ $this->xmlobj = array();
+ $this->setupParser();
+ #$this->send($this->stream_start);
+ $body = $this->__buildBody();
+ $body->addAttribute('to', $this->host);
+ $body->addAttribute('xmpp:restart', 'true', 'urn:xmpp:xbosh');
+ $buff = "<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>";
+ $response = $this->__sendBody($body);
+ $this->been_reset = true;
+ xml_parse($this->parser, $buff, false);
+ }
+
+ public function loadSession() {
+ if(isset($_SESSION['XMPPHP_BOSH_RID'])) $this->rid = $_SESSION['XMPPHP_BOSH_RID'];
+ if(isset($_SESSION['XMPPHP_BOSH_SID'])) $this->sid = $_SESSION['XMPPHP_BOSH_SID'];
+ if(isset($_SESSION['XMPPHP_BOSH_authed'])) $this->authed = $_SESSION['XMPPHP_BOSH_authed'];
+ if(isset($_SESSION['XMPPHP_BOSH_jid'])) $this->jid = $_SESSION['XMPPHP_BOSH_jid'];
+ if(isset($_SESSION['XMPPHP_BOSH_fulljid'])) $this->fulljid = $_SESSION['XMPPHP_BOSH_fulljid'];
+ }
+
+ public function saveSession() {
+ $_SESSION['XMPPHP_BOSH_RID'] = (string) $this->rid;
+ $_SESSION['XMPPHP_BOSH_SID'] = (string) $this->sid;
+ $_SESSION['XMPPHP_BOSH_authed'] = (boolean) $this->authed;
+ $_SESSION['XMPPHP_BOSH_jid'] = (string) $this->jid;
+ $_SESSION['XMPPHP_BOSH_fulljid'] = (string) $this->fulljid;
+ }
+}
diff --git a/extlib/XMPPHP/Exception.php b/extlib/XMPPHP/Exception.php
new file mode 100644
index 000000000..da59bc791
--- /dev/null
+++ b/extlib/XMPPHP/Exception.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP Exception
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+class XMPPHP_Exception extends Exception {
+}
diff --git a/extlib/XMPPHP/Log.php b/extlib/XMPPHP/Log.php
new file mode 100644
index 000000000..a9bce3d84
--- /dev/null
+++ b/extlib/XMPPHP/Log.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP Log
+ *
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+class XMPPHP_Log {
+
+ const LEVEL_ERROR = 0;
+ const LEVEL_WARNING = 1;
+ const LEVEL_INFO = 2;
+ const LEVEL_DEBUG = 3;
+ const LEVEL_VERBOSE = 4;
+
+ /**
+ * @var array
+ */
+ protected $data = array();
+
+ /**
+ * @var array
+ */
+ protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE');
+
+ /**
+ * @var integer
+ */
+ protected $runlevel;
+
+ /**
+ * @var boolean
+ */
+ protected $printout;
+
+ /**
+ * Constructor
+ *
+ * @param boolean $printout
+ * @param string $runlevel
+ */
+ public function __construct($printout = false, $runlevel = self::LEVEL_INFO) {
+ $this->printout = (boolean)$printout;
+ $this->runlevel = (int)$runlevel;
+ }
+
+ /**
+ * Add a message to the log data array
+ * If printout in this instance is set to true, directly output the message
+ *
+ * @param string $msg
+ * @param integer $runlevel
+ */
+ public function log($msg, $runlevel = self::LEVEL_INFO) {
+ $time = time();
+ #$this->data[] = array($this->runlevel, $msg, $time);
+ if($this->printout and $runlevel <= $this->runlevel) {
+ $this->writeLine($msg, $runlevel, $time);
+ }
+ }
+
+ /**
+ * Output the complete log.
+ * Log will be cleared if $clear = true
+ *
+ * @param boolean $clear
+ * @param integer $runlevel
+ */
+ public function printout($clear = true, $runlevel = null) {
+ if($runlevel === null) {
+ $runlevel = $this->runlevel;
+ }
+ foreach($this->data as $data) {
+ if($runlevel <= $data[0]) {
+ $this->writeLine($data[1], $runlevel, $data[2]);
+ }
+ }
+ if($clear) {
+ $this->data = array();
+ }
+ }
+
+ protected function writeLine($msg, $runlevel, $time) {
+ //echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n";
+ echo $time." [".$this->names[$runlevel]."]: ".$msg."\n";
+ flush();
+ }
+}
diff --git a/extlib/XMPPHP/Roster.php b/extlib/XMPPHP/Roster.php
new file mode 100644
index 000000000..2e459e2a2
--- /dev/null
+++ b/extlib/XMPPHP/Roster.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP Roster Object
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+
+class Roster {
+ /**
+ * Roster array, handles contacts and presence. Indexed by jid.
+ * Contains array with potentially two indexes 'contact' and 'presence'
+ * @var array
+ */
+ protected $roster_array = array();
+ /**
+ * Constructor
+ *
+ */
+ public function __construct($roster_array = array()) {
+ if ($this->verifyRoster($roster_array)) {
+ $this->roster_array = $roster_array; //Allow for prepopulation with existing roster
+ } else {
+ $this->roster_array = array();
+ }
+ }
+
+ /**
+ *
+ * Check that a given roster array is of a valid structure (empty is still valid)
+ *
+ * @param array $roster_array
+ */
+ protected function verifyRoster($roster_array) {
+ #TODO once we know *what* a valid roster array looks like
+ return True;
+ }
+
+ /**
+ *
+ * Add given contact to roster
+ *
+ * @param string $jid
+ * @param string $subscription
+ * @param string $name
+ * @param array $groups
+ */
+ public function addContact($jid, $subscription, $name='', $groups=array()) {
+ $contact = array('jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups);
+ if ($this->isContact($jid)) {
+ $this->roster_array[$jid]['contact'] = $contact;
+ } else {
+ $this->roster_array[$jid] = array('contact' => $contact);
+ }
+ }
+
+ /**
+ *
+ * Retrieve contact via jid
+ *
+ * @param string $jid
+ */
+ public function getContact($jid) {
+ if ($this->isContact($jid)) {
+ return $this->roster_array[$jid]['contact'];
+ }
+ }
+
+ /**
+ *
+ * Discover if a contact exists in the roster via jid
+ *
+ * @param string $jid
+ */
+ public function isContact($jid) {
+ return (array_key_exists($jid, $this->roster_array));
+ }
+
+ /**
+ *
+ * Set presence
+ *
+ * @param string $presence
+ * @param integer $priority
+ * @param string $show
+ * @param string $status
+ */
+ public function setPresence($presence, $priority, $show, $status) {
+ list($jid, $resource) = split("/", $presence);
+ if ($show != 'unavailable') {
+ if (!$this->isContact($jid)) {
+ $this->addContact($jid, 'not-in-roster');
+ }
+ $resource = $resource ? $resource : '';
+ $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status);
+ } else { //Nuke unavailable resources to save memory
+ unset($this->roster_array[$jid]['resource'][$resource]);
+ }
+ }
+
+ /*
+ *
+ * Return best presence for jid
+ *
+ * @param string $jid
+ */
+ public function getPresence($jid) {
+ $split = split("/", $jid);
+ $jid = $split[0];
+ if($this->isContact($jid)) {
+ $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127
+ foreach($this->roster_array[$jid]['presence'] as $resource => $presence) {
+ //Highest available priority or just highest priority
+ if ($presence['priority'] > $current['priority'] and (($presence['show'] == "chat" or $presence['show'] == "available") or ($current['show'] != "chat" or $current['show'] != "available"))) {
+ $current = $presence;
+ $current['resource'] = $resource;
+ }
+ }
+ return $current;
+ }
+ }
+ /**
+ *
+ * Get roster
+ *
+ */
+ public function getRoster() {
+ return $this->roster_array;
+ }
+}
+?>
diff --git a/extlib/XMPPHP/XMLObj.php b/extlib/XMPPHP/XMLObj.php
new file mode 100644
index 000000000..0d3e21991
--- /dev/null
+++ b/extlib/XMPPHP/XMLObj.php
@@ -0,0 +1,158 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/**
+ * XMPPHP XML Object
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+class XMPPHP_XMLObj {
+ /**
+ * Tag name
+ *
+ * @var string
+ */
+ public $name;
+
+ /**
+ * Namespace
+ *
+ * @var string
+ */
+ public $ns;
+
+ /**
+ * Attributes
+ *
+ * @var array
+ */
+ public $attrs = array();
+
+ /**
+ * Subs?
+ *
+ * @var array
+ */
+ public $subs = array();
+
+ /**
+ * Node data
+ *
+ * @var string
+ */
+ public $data = '';
+
+ /**
+ * Constructor
+ *
+ * @param string $name
+ * @param string $ns
+ * @param array $attrs
+ * @param string $data
+ */
+ public function __construct($name, $ns = '', $attrs = array(), $data = '') {
+ $this->name = strtolower($name);
+ $this->ns = $ns;
+ if(is_array($attrs) && count($attrs)) {
+ foreach($attrs as $key => $value) {
+ $this->attrs[strtolower($key)] = $value;
+ }
+ }
+ $this->data = $data;
+ }
+
+ /**
+ * Dump this XML Object to output.
+ *
+ * @param integer $depth
+ */
+ public function printObj($depth = 0) {
+ print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data;
+ print "\n";
+ foreach($this->subs as $sub) {
+ $sub->printObj($depth + 1);
+ }
+ }
+
+ /**
+ * Return this XML Object in xml notation
+ *
+ * @param string $str
+ */
+ public function toString($str = '') {
+ $str .= "<{$this->name} xmlns='{$this->ns}' ";
+ foreach($this->attrs as $key => $value) {
+ if($key != 'xmlns') {
+ $value = htmlspecialchars($value);
+ $str .= "$key='$value' ";
+ }
+ }
+ $str .= ">";
+ foreach($this->subs as $sub) {
+ $str .= $sub->toString();
+ }
+ $body = htmlspecialchars($this->data);
+ $str .= "$body</{$this->name}>";
+ return $str;
+ }
+
+ /**
+ * Has this XML Object the given sub?
+ *
+ * @param string $name
+ * @return boolean
+ */
+ public function hasSub($name, $ns = null) {
+ foreach($this->subs as $sub) {
+ if(($name == "*" or $sub->name == $name) and ($ns == null or $sub->ns == $ns)) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return a sub
+ *
+ * @param string $name
+ * @param string $attrs
+ * @param string $ns
+ */
+ public function sub($name, $attrs = null, $ns = null) {
+ #TODO attrs is ignored
+ foreach($this->subs as $sub) {
+ if($sub->name == $name and ($ns == null or $sub->ns == $ns)) {
+ return $sub;
+ }
+ }
+ }
+}
diff --git a/extlib/XMPPHP/XMLStream.php b/extlib/XMPPHP/XMLStream.php
new file mode 100644
index 000000000..0fcfea375
--- /dev/null
+++ b/extlib/XMPPHP/XMLStream.php
@@ -0,0 +1,763 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/** XMPPHP_Exception */
+require_once 'Exception.php';
+
+/** XMPPHP_XMLObj */
+require_once 'XMLObj.php';
+
+/** XMPPHP_Log */
+require_once 'Log.php';
+
+/**
+ * XMPPHP XML Stream
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+class XMPPHP_XMLStream {
+ /**
+ * @var resource
+ */
+ protected $socket;
+ /**
+ * @var resource
+ */
+ protected $parser;
+ /**
+ * @var string
+ */
+ protected $buffer;
+ /**
+ * @var integer
+ */
+ protected $xml_depth = 0;
+ /**
+ * @var string
+ */
+ protected $host;
+ /**
+ * @var integer
+ */
+ protected $port;
+ /**
+ * @var string
+ */
+ protected $stream_start = '<stream>';
+ /**
+ * @var string
+ */
+ protected $stream_end = '</stream>';
+ /**
+ * @var boolean
+ */
+ protected $disconnected = false;
+ /**
+ * @var boolean
+ */
+ protected $sent_disconnect = false;
+ /**
+ * @var array
+ */
+ protected $ns_map = array();
+ /**
+ * @var array
+ */
+ protected $current_ns = array();
+ /**
+ * @var array
+ */
+ protected $xmlobj = null;
+ /**
+ * @var array
+ */
+ protected $nshandlers = array();
+ /**
+ * @var array
+ */
+ protected $xpathhandlers = array();
+ /**
+ * @var array
+ */
+ protected $idhandlers = array();
+ /**
+ * @var array
+ */
+ protected $eventhandlers = array();
+ /**
+ * @var integer
+ */
+ protected $lastid = 0;
+ /**
+ * @var string
+ */
+ protected $default_ns;
+ /**
+ * @var string
+ */
+ protected $until = '';
+ /**
+ * @var string
+ */
+ protected $until_count = '';
+ /**
+ * @var array
+ */
+ protected $until_happened = false;
+ /**
+ * @var array
+ */
+ protected $until_payload = array();
+ /**
+ * @var XMPPHP_Log
+ */
+ protected $log;
+ /**
+ * @var boolean
+ */
+ protected $reconnect = true;
+ /**
+ * @var boolean
+ */
+ protected $been_reset = false;
+ /**
+ * @var boolean
+ */
+ protected $is_server;
+ /**
+ * @var float
+ */
+ protected $last_send = 0;
+ /**
+ * @var boolean
+ */
+ protected $use_ssl = false;
+ /**
+ * @var integer
+ */
+ protected $reconnectTimeout = 30;
+
+ /**
+ * Constructor
+ *
+ * @param string $host
+ * @param string $port
+ * @param boolean $printlog
+ * @param string $loglevel
+ * @param boolean $is_server
+ */
+ public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) {
+ $this->reconnect = !$is_server;
+ $this->is_server = $is_server;
+ $this->host = $host;
+ $this->port = $port;
+ $this->setupParser();
+ $this->log = new XMPPHP_Log($printlog, $loglevel);
+ }
+
+ /**
+ * Destructor
+ * Cleanup connection
+ */
+ public function __destruct() {
+ if(!$this->disconnected && $this->socket) {
+ $this->disconnect();
+ }
+ }
+
+ /**
+ * Return the log instance
+ *
+ * @return XMPPHP_Log
+ */
+ public function getLog() {
+ return $this->log;
+ }
+
+ /**
+ * Get next ID
+ *
+ * @return integer
+ */
+ public function getId() {
+ $this->lastid++;
+ return $this->lastid;
+ }
+
+ /**
+ * Set SSL
+ *
+ * @return integer
+ */
+ public function useSSL($use=true) {
+ $this->use_ssl = $use;
+ }
+
+ /**
+ * Add ID Handler
+ *
+ * @param integer $id
+ * @param string $pointer
+ * @param string $obj
+ */
+ public function addIdHandler($id, $pointer, $obj = null) {
+ $this->idhandlers[$id] = array($pointer, $obj);
+ }
+
+ /**
+ * Add Handler
+ *
+ * @param string $name
+ * @param string $ns
+ * @param string $pointer
+ * @param string $obj
+ * @param integer $depth
+ */
+ public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) {
+ #TODO deprication warning
+ $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth);
+ }
+
+ /**
+ * Add XPath Handler
+ *
+ * @param string $xpath
+ * @param string $pointer
+ * @param
+ */
+ public function addXPathHandler($xpath, $pointer, $obj = null) {
+ if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) {
+ $ns_tags = $regs[0];
+ } else {
+ $ns_tags = array($xpath);
+ }
+ foreach($ns_tags as $ns_tag) {
+ list($l, $r) = split("}", $ns_tag);
+ if ($r != null) {
+ $xpart = array(substr($l, 1), $r);
+ } else {
+ $xpart = array(null, $l);
+ }
+ $xpath_array[] = $xpart;
+ }
+ $this->xpathhandlers[] = array($xpath_array, $pointer, $obj);
+ }
+
+ /**
+ * Add Event Handler
+ *
+ * @param integer $id
+ * @param string $pointer
+ * @param string $obj
+ */
+ public function addEventHandler($name, $pointer, $obj) {
+ $this->eventhandlers[] = array($name, $pointer, $obj);
+ }
+
+ /**
+ * Connect to XMPP Host
+ *
+ * @param integer $timeout
+ * @param boolean $persistent
+ * @param boolean $sendinit
+ */
+ public function connect($timeout = 30, $persistent = false, $sendinit = true) {
+ $this->sent_disconnect = false;
+ $starttime = time();
+
+ do {
+ $this->disconnected = false;
+ $this->sent_disconnect = false;
+ if($persistent) {
+ $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
+ } else {
+ $conflag = STREAM_CLIENT_CONNECT;
+ }
+ $conntype = 'tcp';
+ if($this->use_ssl) $conntype = 'ssl';
+ $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}");
+ try {
+ $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag);
+ } catch (Exception $e) {
+ throw new XMPPHP_Exception($e->getMessage());
+ }
+ if(!$this->socket) {
+ $this->log->log("Could not connect.", XMPPHP_Log::LEVEL_ERROR);
+ $this->disconnected = true;
+ # Take it easy for a few seconds
+ sleep(min($timeout, 5));
+ }
+ } while (!$this->socket && (time() - $starttime) < $timeout);
+
+ if ($this->socket) {
+ stream_set_blocking($this->socket, 1);
+ if($sendinit) $this->send($this->stream_start);
+ } else {
+ throw new XMPPHP_Exception("Could not connect before timeout.");
+ }
+ }
+
+ /**
+ * Reconnect XMPP Host
+ */
+ public function doReconnect() {
+ if(!$this->is_server) {
+ $this->log->log("Reconnecting ($this->reconnectTimeout)...", XMPPHP_Log::LEVEL_WARNING);
+ $this->connect($this->reconnectTimeout, false, false);
+ $this->reset();
+ $this->event('reconnect');
+ }
+ }
+
+ public function setReconnectTimeout($timeout) {
+ $this->reconnectTimeout = $timeout;
+ }
+
+ /**
+ * Disconnect from XMPP Host
+ */
+ public function disconnect() {
+ $this->log->log("Disconnecting...", XMPPHP_Log::LEVEL_VERBOSE);
+ if(false == (bool) $this->socket) {
+ return;
+ }
+ $this->reconnect = false;
+ $this->send($this->stream_end);
+ $this->sent_disconnect = true;
+ $this->processUntil('end_stream', 5);
+ $this->disconnected = true;
+ }
+
+ /**
+ * Are we are disconnected?
+ *
+ * @return boolean
+ */
+ public function isDisconnected() {
+ return $this->disconnected;
+ }
+
+ /**
+ * Core reading tool
+ * 0 -> only read if data is immediately ready
+ * NULL -> wait forever and ever
+ * integer -> process for this amount of time
+ */
+
+ private function __process($maximum=0) {
+
+ $remaining = $maximum;
+
+ do {
+ $starttime = (microtime(true) * 1000000);
+ $read = array($this->socket);
+ $write = array();
+ $except = array();
+ if (is_null($maximum)) {
+ $secs = NULL;
+ $usecs = NULL;
+ } else if ($maximum == 0) {
+ $secs = 0;
+ $usecs = 0;
+ } else {
+ $usecs = $remaining % 1000000;
+ $secs = floor(($remaining - $usecs) / 1000000);
+ }
+ $updated = @stream_select($read, $write, $except, $secs, $usecs);
+ if ($updated === false) {
+ $this->log->log("Error on stream_select()", XMPPHP_Log::LEVEL_VERBOSE);
+ if ($this->reconnect) {
+ $this->doReconnect();
+ } else {
+ fclose($this->socket);
+ $this->socket = NULL;
+ return false;
+ }
+ } else if ($updated > 0) {
+ # XXX: Is this big enough?
+ $buff = @fread($this->socket, 4096);
+ if(!$buff) {
+ if($this->reconnect) {
+ $this->doReconnect();
+ } else {
+ fclose($this->socket);
+ $this->socket = NULL;
+ return false;
+ }
+ }
+ $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE);
+ xml_parse($this->parser, $buff, false);
+ } else {
+ # $updated == 0 means no changes during timeout.
+ }
+ $endtime = (microtime(true)*1000000);
+ $time_past = $endtime - $starttime;
+ $remaining = $remaining - $time_past;
+ } while (is_null($maximum) || $remaining > 0);
+ return true;
+ }
+
+ /**
+ * Process
+ *
+ * @return string
+ */
+ public function process() {
+ $this->__process(NULL);
+ }
+
+ /**
+ * Process until a timeout occurs
+ *
+ * @param integer $timeout
+ * @return string
+ */
+ public function processTime($timeout=NULL) {
+ if (is_null($timeout)) {
+ return $this->__process(NULL);
+ } else {
+ return $this->__process($timeout * 1000000);
+ }
+ }
+
+ /**
+ * Process until a specified event or a timeout occurs
+ *
+ * @param string|array $event
+ * @param integer $timeout
+ * @return string
+ */
+ public function processUntil($event, $timeout=-1) {
+ $start = time();
+ if(!is_array($event)) $event = array($event);
+ $this->until[] = $event;
+ end($this->until);
+ $event_key = key($this->until);
+ reset($this->until);
+ $this->until_count[$event_key] = 0;
+ $updated = '';
+ while(!$this->disconnected and $this->until_count[$event_key] < 1 and (time() - $start < $timeout or $timeout == -1)) {
+ $this->__process();
+ }
+ if(array_key_exists($event_key, $this->until_payload)) {
+ $payload = $this->until_payload[$event_key];
+ unset($this->until_payload[$event_key]);
+ unset($this->until_count[$event_key]);
+ unset($this->until[$event_key]);
+ } else {
+ $payload = array();
+ }
+ return $payload;
+ }
+
+ /**
+ * Obsolete?
+ */
+ public function Xapply_socket($socket) {
+ $this->socket = $socket;
+ }
+
+ /**
+ * XML start callback
+ *
+ * @see xml_set_element_handler
+ *
+ * @param resource $parser
+ * @param string $name
+ */
+ public function startXML($parser, $name, $attr) {
+ if($this->been_reset) {
+ $this->been_reset = false;
+ $this->xml_depth = 0;
+ }
+ $this->xml_depth++;
+ if(array_key_exists('XMLNS', $attr)) {
+ $this->current_ns[$this->xml_depth] = $attr['XMLNS'];
+ } else {
+ $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1];
+ if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns;
+ }
+ $ns = $this->current_ns[$this->xml_depth];
+ foreach($attr as $key => $value) {
+ if(strstr($key, ":")) {
+ $key = explode(':', $key);
+ $key = $key[1];
+ $this->ns_map[$key] = $value;
+ }
+ }
+ if(!strstr($name, ":") === false)
+ {
+ $name = explode(':', $name);
+ $ns = $this->ns_map[$name[0]];
+ $name = $name[1];
+ }
+ $obj = new XMPPHP_XMLObj($name, $ns, $attr);
+ if($this->xml_depth > 1) {
+ $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj;
+ }
+ $this->xmlobj[$this->xml_depth] = $obj;
+ }
+
+ /**
+ * XML end callback
+ *
+ * @see xml_set_element_handler
+ *
+ * @param resource $parser
+ * @param string $name
+ */
+ public function endXML($parser, $name) {
+ #$this->log->log("Ending $name", XMPPHP_Log::LEVEL_DEBUG);
+ #print "$name\n";
+ if($this->been_reset) {
+ $this->been_reset = false;
+ $this->xml_depth = 0;
+ }
+ $this->xml_depth--;
+ if($this->xml_depth == 1) {
+ #clean-up old objects
+ #$found = false; #FIXME This didn't appear to be in use --Gar
+ foreach($this->xpathhandlers as $handler) {
+ if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) {
+ $searchxml = $this->xmlobj[2];
+ $nstag = array_shift($handler[0]);
+ if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) {
+ foreach($handler[0] as $nstag) {
+ if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) {
+ $searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]);
+ } else {
+ $searchxml = null;
+ break;
+ }
+ }
+ if ($searchxml !== null) {
+ if($handler[2] === null) $handler[2] = $this;
+ $this->log->log("Calling {$handler[1]}", XMPPHP_Log::LEVEL_DEBUG);
+ $handler[2]->$handler[1]($this->xmlobj[2]);
+ }
+ }
+ }
+ }
+ foreach($this->nshandlers as $handler) {
+ if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and $this->xmlobj[2]->hasSub($handler[0])) {
+ $searchxml = $this->xmlobj[2]->sub($handler[0]);
+ } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) {
+ $searchxml = $this->xmlobj[2];
+ }
+ if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) {
+ if($handler[3] === null) $handler[3] = $this;
+ $this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG);
+ $handler[3]->$handler[2]($this->xmlobj[2]);
+ }
+ }
+ foreach($this->idhandlers as $id => $handler) {
+ if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) {
+ if($handler[1] === null) $handler[1] = $this;
+ $handler[1]->$handler[0]($this->xmlobj[2]);
+ #id handlers are only used once
+ unset($this->idhandlers[$id]);
+ break;
+ }
+ }
+ if(is_array($this->xmlobj)) {
+ $this->xmlobj = array_slice($this->xmlobj, 0, 1);
+ if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) {
+ $this->xmlobj[0]->subs = null;
+ }
+ }
+ unset($this->xmlobj[2]);
+ }
+ if($this->xml_depth == 0 and !$this->been_reset) {
+ if(!$this->disconnected) {
+ if(!$this->sent_disconnect) {
+ $this->send($this->stream_end);
+ }
+ $this->disconnected = true;
+ $this->sent_disconnect = true;
+ fclose($this->socket);
+ if($this->reconnect) {
+ $this->doReconnect();
+ }
+ }
+ $this->event('end_stream');
+ }
+ }
+
+ /**
+ * XML character callback
+ * @see xml_set_character_data_handler
+ *
+ * @param resource $parser
+ * @param string $data
+ */
+ public function charXML($parser, $data) {
+ if(array_key_exists($this->xml_depth, $this->xmlobj)) {
+ $this->xmlobj[$this->xml_depth]->data .= $data;
+ }
+ }
+
+ /**
+ * Event?
+ *
+ * @param string $name
+ * @param string $payload
+ */
+ public function event($name, $payload = null) {
+ $this->log->log("EVENT: $name", XMPPHP_Log::LEVEL_DEBUG);
+ foreach($this->eventhandlers as $handler) {
+ if($name == $handler[0]) {
+ if($handler[2] === null) {
+ $handler[2] = $this;
+ }
+ $handler[2]->$handler[1]($payload);
+ }
+ }
+ foreach($this->until as $key => $until) {
+ if(is_array($until)) {
+ if(in_array($name, $until)) {
+ $this->until_payload[$key][] = array($name, $payload);
+ if(!isset($this->until_count[$key])) {
+ $this->until_count[$key] = 0;
+ }
+ $this->until_count[$key] += 1;
+ #$this->until[$key] = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Read from socket
+ */
+ public function read() {
+ $buff = @fread($this->socket, 1024);
+ if(!$buff) {
+ if($this->reconnect) {
+ $this->doReconnect();
+ } else {
+ fclose($this->socket);
+ return false;
+ }
+ }
+ $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE);
+ xml_parse($this->parser, $buff, false);
+ }
+
+ /**
+ * Send to socket
+ *
+ * @param string $msg
+ */
+ public function send($msg, $timeout=NULL) {
+
+ if (is_null($timeout)) {
+ $secs = NULL;
+ $usecs = NULL;
+ } else if ($timeout == 0) {
+ $secs = 0;
+ $usecs = 0;
+ } else {
+ $maximum = $timeout * 1000000;
+ $usecs = $maximum % 1000000;
+ $secs = floor(($maximum - $usecs) / 1000000);
+ }
+
+ $read = array();
+ $write = array($this->socket);
+ $except = array();
+
+ $select = @stream_select($read, $write, $except, $secs, $usecs);
+
+ if($select === False) {
+ $this->log->log("ERROR sending message; reconnecting.");
+ $this->doReconnect();
+ # TODO: retry send here
+ return false;
+ } elseif ($select > 0) {
+ $this->log->log("Socket is ready; send it.", XMPPHP_Log::LEVEL_VERBOSE);
+ } else {
+ $this->log->log("Socket is not ready; break.", XMPPHP_Log::LEVEL_ERROR);
+ return false;
+ }
+
+ $sentbytes = @fwrite($this->socket, $msg);
+ $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), XMPPHP_Log::LEVEL_VERBOSE);
+ if($sentbytes === FALSE) {
+ $this->log->log("ERROR sending message; reconnecting.", XMPPHP_Log::LEVEL_ERROR);
+ $this->doReconnect();
+ return false;
+ }
+ $this->log->log("Successfully sent $sentbytes bytes.", XMPPHP_Log::LEVEL_VERBOSE);
+ return $sentbytes;
+ }
+
+ public function time() {
+ list($usec, $sec) = explode(" ", microtime());
+ return (float)$sec + (float)$usec;
+ }
+
+ /**
+ * Reset connection
+ */
+ public function reset() {
+ $this->xml_depth = 0;
+ unset($this->xmlobj);
+ $this->xmlobj = array();
+ $this->setupParser();
+ if(!$this->is_server) {
+ $this->send($this->stream_start);
+ }
+ $this->been_reset = true;
+ }
+
+ /**
+ * Setup the XML parser
+ */
+ public function setupParser() {
+ $this->parser = xml_parser_create('UTF-8');
+ xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
+ xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
+ xml_set_object($this->parser, $this);
+ xml_set_element_handler($this->parser, 'startXML', 'endXML');
+ xml_set_character_data_handler($this->parser, 'charXML');
+ }
+
+ public function readyToProcess() {
+ $read = array($this->socket);
+ $write = array();
+ $except = array();
+ $updated = @stream_select($read, $write, $except, 0);
+ return (($updated !== false) && ($updated > 0));
+ }
+}
diff --git a/extlib/XMPPHP/XMPP.php b/extlib/XMPPHP/XMPP.php
new file mode 100644
index 000000000..73fbd2658
--- /dev/null
+++ b/extlib/XMPPHP/XMPP.php
@@ -0,0 +1,423 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/** XMPPHP_XMLStream */
+require_once "XMLStream.php";
+require_once "Roster.php";
+
+/**
+ * XMPPHP Main Class
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ * @version $Id$
+ */
+class XMPPHP_XMPP extends XMPPHP_XMLStream {
+ /**
+ * @var string
+ */
+ public $server;
+
+ /**
+ * @var string
+ */
+ public $user;
+
+ /**
+ * @var string
+ */
+ protected $password;
+
+ /**
+ * @var string
+ */
+ protected $resource;
+
+ /**
+ * @var string
+ */
+ protected $fulljid;
+
+ /**
+ * @var string
+ */
+ protected $basejid;
+
+ /**
+ * @var boolean
+ */
+ protected $authed = false;
+ protected $session_started = false;
+
+ /**
+ * @var boolean
+ */
+ protected $auto_subscribe = false;
+
+ /**
+ * @var boolean
+ */
+ protected $use_encryption = true;
+
+ /**
+ * @var boolean
+ */
+ public $track_presence = true;
+
+ /**
+ * @var object
+ */
+ public $roster;
+
+ /**
+ * Constructor
+ *
+ * @param string $host
+ * @param integer $port
+ * @param string $user
+ * @param string $password
+ * @param string $resource
+ * @param string $server
+ * @param boolean $printlog
+ * @param string $loglevel
+ */
+ public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) {
+ parent::__construct($host, $port, $printlog, $loglevel);
+
+ $this->user = $user;
+ $this->password = $password;
+ $this->resource = $resource;
+ if(!$server) $server = $host;
+ $this->basejid = $this->user . '@' . $this->host;
+
+ $this->roster = new Roster();
+ $this->track_presence = true;
+
+ $this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" version="1.0">';
+ $this->stream_end = '</stream:stream>';
+ $this->default_ns = 'jabber:client';
+
+ $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler');
+ $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler');
+ $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler');
+ $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler');
+ $this->addXPathHandler('{jabber:client}message', 'message_handler');
+ $this->addXPathHandler('{jabber:client}presence', 'presence_handler');
+ $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler');
+ }
+
+ /**
+ * Turn encryption on/ff
+ *
+ * @param boolean $useEncryption
+ */
+ public function useEncryption($useEncryption = true) {
+ $this->use_encryption = $useEncryption;
+ }
+
+ /**
+ * Turn on auto-authorization of subscription requests.
+ *
+ * @param boolean $autoSubscribe
+ */
+ public function autoSubscribe($autoSubscribe = true) {
+ $this->auto_subscribe = $autoSubscribe;
+ }
+
+ /**
+ * Send XMPP Message
+ *
+ * @param string $to
+ * @param string $body
+ * @param string $type
+ * @param string $subject
+ */
+ public function message($to, $body, $type = 'chat', $subject = null, $payload = null) {
+ if(is_null($type))
+ {
+ $type = 'chat';
+ }
+
+ $to = htmlspecialchars($to);
+ $body = htmlspecialchars($body);
+ $subject = htmlspecialchars($subject);
+
+ $out = "<message from='{$this->fulljid}' to='$to' type='$type'>";
+ if($subject) $out .= "<subject>$subject</subject>";
+ $out .= "<body>$body</body>";
+ if($payload) $out .= $payload;
+ $out .= "</message>";
+
+ $this->send($out);
+ }
+
+ /**
+ * Set Presence
+ *
+ * @param string $status
+ * @param string $show
+ * @param string $to
+ */
+ public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) {
+ if($type == 'available') $type = '';
+ $to = htmlspecialchars($to);
+ $status = htmlspecialchars($status);
+ if($show == 'unavailable') $type = 'unavailable';
+
+ $out = "<presence";
+ if($to) $out .= " to='$to'";
+ if($type) $out .= " type='$type'";
+ if($show == 'available' and !$status) {
+ $out .= "/>";
+ } else {
+ $out .= ">";
+ if($show != 'available') $out .= "<show>$show</show>";
+ if($status) $out .= "<status>$status</status>";
+ if($priority) $out .= "<priority>$priority</priority>";
+ $out .= "</presence>";
+ }
+
+ $this->send($out);
+ }
+
+ /**
+ * Message handler
+ *
+ * @param string $xml
+ */
+ public function message_handler($xml) {
+ if(isset($xml->attrs['type'])) {
+ $payload['type'] = $xml->attrs['type'];
+ } else {
+ $payload['type'] = 'chat';
+ }
+ $payload['from'] = $xml->attrs['from'];
+ $payload['body'] = $xml->sub('body')->data;
+ $payload['xml'] = $xml;
+ $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG);
+ $this->event('message', $payload);
+ }
+
+ /**
+ * Presence handler
+ *
+ * @param string $xml
+ */
+ public function presence_handler($xml) {
+ $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available';
+ $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type'];
+ $payload['from'] = $xml->attrs['from'];
+ $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : '';
+ $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0;
+ $payload['xml'] = $xml;
+ if($this->track_presence) {
+ $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']);
+ }
+ $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG);
+ if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') {
+ if($this->auto_subscribe) {
+ $this->send("<presence type='subscribed' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
+ $this->send("<presence type='subscribe' to='{$xml->attrs['from']}' from='{$this->fulljid}' />");
+ }
+ $this->event('subscription_requested', $payload);
+ } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') {
+ $this->event('subscription_accepted', $payload);
+ } else {
+ $this->event('presence', $payload);
+ }
+ }
+
+ /**
+ * Features handler
+ *
+ * @param string $xml
+ */
+ protected function features_handler($xml) {
+ if($xml->hasSub('starttls') and $this->use_encryption) {
+ $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
+ } elseif($xml->hasSub('bind') and $this->authed) {
+ $id = $this->getId();
+ $this->addIdHandler($id, 'resource_bind_handler');
+ $this->send("<iq xmlns=\"jabber:client\" type=\"set\" id=\"$id\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>{$this->resource}</resource></bind></iq>");
+ } else {
+ $this->log->log("Attempting Auth...");
+ if ($this->password) {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . "</auth>");
+ } else {
+ $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
+ }
+ }
+ }
+
+ /**
+ * SASL success handler
+ *
+ * @param string $xml
+ */
+ protected function sasl_success_handler($xml) {
+ $this->log->log("Auth success!");
+ $this->authed = true;
+ $this->reset();
+ }
+
+ /**
+ * SASL feature handler
+ *
+ * @param string $xml
+ */
+ protected function sasl_failure_handler($xml) {
+ $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR);
+ $this->disconnect();
+
+ throw new XMPPHP_Exception('Auth failed!');
+ }
+
+ /**
+ * Resource bind handler
+ *
+ * @param string $xml
+ */
+ protected function resource_bind_handler($xml) {
+ if($xml->attrs['type'] == 'result') {
+ $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data);
+ $this->fulljid = $xml->sub('bind')->sub('jid')->data;
+ $jidarray = explode('/',$this->fulljid);
+ $this->jid = $jidarray[0];
+ }
+ $id = $this->getId();
+ $this->addIdHandler($id, 'session_start_handler');
+ $this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
+ }
+
+ /**
+ * Retrieves the roster
+ *
+ */
+ public function getRoster() {
+ $id = $this->getID();
+ $this->send("<iq xmlns='jabber:client' type='get' id='$id'><query xmlns='jabber:iq:roster' /></iq>");
+ }
+
+ /**
+ * Roster iq handler
+ * Gets all packets matching XPath "iq/{jabber:iq:roster}query'
+ *
+ * @param string $xml
+ */
+ protected function roster_iq_handler($xml) {
+ $status = "result";
+ $xmlroster = $xml->sub('query');
+ foreach($xmlroster->subs as $item) {
+ $groups = array();
+ if ($item->name == 'item') {
+ $jid = $item->attrs['jid']; //REQUIRED
+ $name = $item->attrs['name']; //MAY
+ $subscription = $item->attrs['subscription'];
+ foreach($item->subs as $subitem) {
+ if ($subitem->name == 'group') {
+ $groups[] = $subitem->data;
+ }
+ }
+ $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen
+ } else {
+ $status = "error";
+ }
+ }
+ if ($status == "result") { //No errors, add contacts
+ foreach($contacts as $contact) {
+ $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]);
+ }
+ }
+ if ($xml->attrs['type'] == 'set') {
+ $this->send("<iq type=\"reply\" id=\"{$xml->attrs['id']}\" to=\"{$xml->attrs['from']}\" />");
+ }
+ }
+
+ /**
+ * Session start handler
+ *
+ * @param string $xml
+ */
+ protected function session_start_handler($xml) {
+ $this->log->log("Session started");
+ $this->session_started = true;
+ $this->event('session_start');
+ }
+
+ /**
+ * TLS proceed handler
+ *
+ * @param string $xml
+ */
+ protected function tls_proceed_handler($xml) {
+ $this->log->log("Starting TLS encryption");
+ stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
+ $this->reset();
+ }
+
+ /**
+ * Retrieves the vcard
+ *
+ */
+ public function getVCard($jid = Null) {
+ $id = $this->getID();
+ $this->addIdHandler($id, 'vcard_get_handler');
+ if($jid) {
+ $this->send("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
+ } else {
+ $this->send("<iq type='get' id='$id'><vCard xmlns='vcard-temp' /></iq>");
+ }
+ }
+
+ /**
+ * VCard retrieval handler
+ *
+ * @param XML Object $xml
+ */
+ protected function vcard_get_handler($xml) {
+ $vcard_array = array();
+ $vcard = $xml->sub('vcard');
+ // go through all of the sub elements and add them to the vcard array
+ foreach ($vcard->subs as $sub) {
+ if ($sub->subs) {
+ $vcard_array[$sub->name] = array();
+ foreach ($sub->subs as $sub_child) {
+ $vcard_array[$sub->name][$sub_child->name] = $sub_child->data;
+ }
+ } else {
+ $vcard_array[$sub->name] = $sub->data;
+ }
+ }
+ $vcard_array['from'] = $xml->attrs['from'];
+ $this->event('vcard', $vcard_array);
+ }
+}
diff --git a/extlib/XMPPHP/XMPP_Old.php b/extlib/XMPPHP/XMPP_Old.php
new file mode 100644
index 000000000..43f56b154
--- /dev/null
+++ b/extlib/XMPPHP/XMPP_Old.php
@@ -0,0 +1,114 @@
+<?php
+/**
+ * XMPPHP: The PHP XMPP Library
+ * Copyright (C) 2008 Nathanael C. Fritz
+ * This file is part of SleekXMPP.
+ *
+ * XMPPHP 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * XMPPHP 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 XMPPHP; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * @category xmpphp
+ * @package XMPPHP
+ * @author Nathanael C. Fritz <JID: fritzy@netflint.net>
+ * @author Stephan Wentz <JID: stephan@jabber.wentz.it>
+ * @author Michael Garvin <JID: gar@netflint.net>
+ * @copyright 2008 Nathanael C. Fritz
+ */
+
+/** XMPPHP_XMPP
+ *
+ * This file is unnecessary unless you need to connect to older, non-XMPP-compliant servers like Dreamhost's.
+ * In this case, use instead of XMPPHP_XMPP, otherwise feel free to delete it.
+ * The old Jabber protocol wasn't standardized, so use at your own risk.
+ *
+ */
+require_once "XMPP.php";
+
+ class XMPPHP_XMPPOld extends XMPPHP_XMPP {
+ /**
+ *
+ * @var string
+ */
+ protected $session_id;
+
+ public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) {
+ parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel);
+ if(!$server) $server = $host;
+ $this->stream_start = '<stream:stream to="' . $server . '" xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client">';
+ $this->fulljid = "{$user}@{$server}/{$resource}";
+ }
+
+ /**
+ * Override XMLStream's startXML
+ *
+ * @param parser $parser
+ * @param string $name
+ * @param array $attr
+ */
+ public function startXML($parser, $name, $attr) {
+ if($this->xml_depth == 0) {
+ $this->session_id = $attr['ID'];
+ $this->authenticate();
+ }
+ parent::startXML($parser, $name, $attr);
+ }
+
+ /**
+ * Send Authenticate Info Request
+ *
+ */
+ public function authenticate() {
+ $id = $this->getId();
+ $this->addidhandler($id, 'authfieldshandler');
+ $this->send("<iq type='get' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username></query></iq>");
+ }
+
+ /**
+ * Retrieve auth fields and send auth attempt
+ *
+ * @param XMLObj $xml
+ */
+ public function authFieldsHandler($xml) {
+ $id = $this->getId();
+ $this->addidhandler($id, 'oldAuthResultHandler');
+ if($xml->sub('query')->hasSub('digest')) {
+ $hash = sha1($this->session_id . $this->password);
+ print "{$this->session_id} {$this->password}\n";
+ $out = "<iq type='set' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username><digest>{$hash}</digest><resource>{$this->resource}</resource></query></iq>";
+ } else {
+ $out = "<iq type='set' id='$id'><query xmlns='jabber:iq:auth'><username>{$this->user}</username><password>{$this->password}</password><resource>{$this->resource}</resource></query></iq>";
+ }
+ $this->send($out);
+
+ }
+
+ /**
+ * Determine authenticated or failure
+ *
+ * @param XMLObj $xml
+ */
+ public function oldAuthResultHandler($xml) {
+ if($xml->attrs['type'] != 'result') {
+ $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR);
+ $this->disconnect();
+ throw new XMPPHP_Exception('Auth failed!');
+ } else {
+ $this->log->log("Session started");
+ $this->event('session_start');
+ }
+ }
+ }
+
+
+?>
diff --git a/extlib/facebook/facebook.php b/extlib/facebook/facebook.php
new file mode 100644
index 000000000..35de6be50
--- /dev/null
+++ b/extlib/facebook/facebook.php
@@ -0,0 +1,499 @@
+<?php
+// Copyright 2004-2008 Facebook. All Rights Reserved.
+//
+// +---------------------------------------------------------------------------+
+// | Facebook Platform PHP5 client |
+// +---------------------------------------------------------------------------+
+// | Copyright (c) 2007 Facebook, Inc. |
+// | All rights reserved. |
+// | |
+// | Redistribution and use in source and binary forms, with or without |
+// | modification, are permitted provided that the following conditions |
+// | are met: |
+// | |
+// | 1. Redistributions of source code must retain the above copyright |
+// | notice, this list of conditions and the following disclaimer. |
+// | 2. Redistributions in binary form must reproduce the above copyright |
+// | notice, this list of conditions and the following disclaimer in the |
+// | documentation and/or other materials provided with the distribution. |
+// | |
+// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
+// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
+// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
+// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
+// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+// +---------------------------------------------------------------------------+
+// | For help with this library, contact developers-help@facebook.com |
+// +---------------------------------------------------------------------------+
+//
+include_once 'facebookapi_php5_restlib.php';
+
+define('FACEBOOK_API_VALIDATION_ERROR', 1);
+class Facebook {
+ public $api_client;
+
+ public $api_key;
+ public $secret;
+ public $generate_session_secret;
+ public $session_expires;
+
+ public $fb_params;
+ public $user;
+ public $profile_user;
+ public $canvas_user;
+ protected $base_domain;
+ /*
+ * Create a Facebook client like this:
+ *
+ * $fb = new Facebook(API_KEY, SECRET);
+ *
+ * This will automatically pull in any parameters, validate them against the
+ * session signature, and chuck them in the public $fb_params member variable.
+ *
+ * @param api_key your Developer API key
+ * @param secret your Developer API secret
+ * @param generate_session_secret whether to automatically generate a session
+ * if the user doesn't have one, but
+ * there is an auth token present in the url,
+ */
+ public function __construct($api_key, $secret, $generate_session_secret=false) {
+ $this->api_key = $api_key;
+ $this->secret = $secret;
+ $this->generate_session_secret = $generate_session_secret;
+ $this->api_client = new FacebookRestClient($api_key, $secret, null);
+ $this->validate_fb_params();
+
+ // Set the default user id for methods that allow the caller to
+ // pass an explicit uid instead of using a session key.
+ $defaultUser = null;
+ if ($this->user) {
+ $defaultUser = $this->user;
+ } else if ($this->profile_user) {
+ $defaultUser = $this->profile_user;
+ } else if ($this->canvas_user) {
+ $defaultUser = $this->canvas_user;
+ }
+
+ $this->api_client->set_user($defaultUser);
+
+
+ if (isset($this->fb_params['friends'])) {
+ $this->api_client->friends_list = explode(',', $this->fb_params['friends']);
+ }
+ if (isset($this->fb_params['added'])) {
+ $this->api_client->added = $this->fb_params['added'];
+ }
+ if (isset($this->fb_params['canvas_user'])) {
+ $this->api_client->canvas_user = $this->fb_params['canvas_user'];
+ }
+ }
+
+ /*
+ * Validates that the parameters passed in were sent from Facebook. It does so
+ * by validating that the signature matches one that could only be generated
+ * by using your application's secret key.
+ *
+ * Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE,
+ * in that order. $_POST and $_GET are always more up-to-date than cookies,
+ * so we prefer those if they are available.
+ *
+ * For nitty-gritty details of when each of these is used, check out
+ * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
+ *
+ * @param bool resolve_auth_token convert an auth token into a session
+ */
+ public function validate_fb_params($resolve_auth_token=true) {
+ $this->fb_params = $this->get_valid_fb_params($_POST, 48*3600, 'fb_sig');
+
+ // note that with preload FQL, it's possible to receive POST params in
+ // addition to GET, so use a different prefix to differentiate them
+ if (!$this->fb_params) {
+ $fb_params = $this->get_valid_fb_params($_GET, 48*3600, 'fb_sig');
+ $fb_post_params = $this->get_valid_fb_params($_POST, 48*3600, 'fb_post_sig');
+ $this->fb_params = array_merge($fb_params, $fb_post_params);
+ }
+
+ // Okay, something came in via POST or GET
+ if ($this->fb_params) {
+ $user = isset($this->fb_params['user']) ?
+ $this->fb_params['user'] : null;
+ $this->profile_user = isset($this->fb_params['profile_user']) ?
+ $this->fb_params['profile_user'] : null;
+ $this->canvas_user = isset($this->fb_params['canvas_user']) ?
+ $this->fb_params['canvas_user'] : null;
+ $this->base_domain = isset($this->fb_params['base_domain']) ?
+ $this->fb_params['base_domain'] : null;
+
+ if (isset($this->fb_params['session_key'])) {
+ $session_key = $this->fb_params['session_key'];
+ } else if (isset($this->fb_params['profile_session_key'])) {
+ $session_key = $this->fb_params['profile_session_key'];
+ } else {
+ $session_key = null;
+ }
+ $expires = isset($this->fb_params['expires']) ?
+ $this->fb_params['expires'] : null;
+ $this->set_user($user,
+ $session_key,
+ $expires);
+ }
+ // if no Facebook parameters were found in the GET or POST variables,
+ // then fall back to cookies, which may have cached user information
+ // Cookies are also used to receive session data via the Javascript API
+ else if ($cookies =
+ $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
+
+ $base_domain_cookie = 'base_domain_' . $this->api_key;
+ if (isset($_COOKIE[$base_domain_cookie])) {
+ $this->base_domain = $_COOKIE[$base_domain_cookie];
+ }
+
+ // use $api_key . '_' as a prefix for the cookies in case there are
+ // multiple facebook clients on the same domain.
+ $expires = isset($cookies['expires']) ? $cookies['expires'] : null;
+ $this->set_user($cookies['user'],
+ $cookies['session_key'],
+ $expires);
+ }
+ // finally, if we received no parameters, but the 'auth_token' GET var
+ // is present, then we are in the middle of auth handshake,
+ // so go ahead and create the session
+ else if ($resolve_auth_token && isset($_GET['auth_token']) &&
+ $session = $this->do_get_session($_GET['auth_token'])) {
+ if ($this->generate_session_secret &&
+ !empty($session['secret'])) {
+ $session_secret = $session['secret'];
+ }
+
+ if (isset($session['base_domain'])) {
+ $this->base_domain = $session['base_domain'];
+ }
+
+ $this->set_user($session['uid'],
+ $session['session_key'],
+ $session['expires'],
+ isset($session_secret) ? $session_secret : null);
+ }
+
+ return !empty($this->fb_params);
+ }
+
+ // Store a temporary session secret for the current session
+ // for use with the JS client library
+ public function promote_session() {
+ try {
+ $session_secret = $this->api_client->auth_promoteSession();
+ if (!$this->in_fb_canvas()) {
+ $this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret);
+ }
+ return $session_secret;
+ } catch (FacebookRestClientException $e) {
+ // API_EC_PARAM means we don't have a logged in user, otherwise who
+ // knows what it means, so just throw it.
+ if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
+ throw $e;
+ }
+ }
+ }
+
+ public function do_get_session($auth_token) {
+ try {
+ return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret);
+ } catch (FacebookRestClientException $e) {
+ // API_EC_PARAM means we don't have a logged in user, otherwise who
+ // knows what it means, so just throw it.
+ if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
+ throw $e;
+ }
+ }
+ }
+
+ // Invalidate the session currently being used, and clear any state associated with it
+ public function expire_session() {
+ if ($this->api_client->auth_expireSession()) {
+ if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
+ $cookies = array('user', 'session_key', 'expires', 'ss');
+ foreach ($cookies as $name) {
+ setcookie($this->api_key . '_' . $name, false, time() - 3600);
+ unset($_COOKIE[$this->api_key . '_' . $name]);
+ }
+ setcookie($this->api_key, false, time() - 3600);
+ unset($_COOKIE[$this->api_key]);
+ }
+
+ // now, clear the rest of the stored state
+ $this->user = 0;
+ $this->api_client->session_key = 0;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public function redirect($url) {
+ if ($this->in_fb_canvas()) {
+ echo '<fb:redirect url="' . $url . '"/>';
+ } else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) {
+ // make sure facebook.com url's load in the full frame so that we don't
+ // get a frame within a frame.
+ echo "<script type=\"text/javascript\">\ntop.location.href = \"$url\";\n</script>";
+ } else {
+ header('Location: ' . $url);
+ }
+ exit;
+ }
+
+ public function in_frame() {
+ return isset($this->fb_params['in_canvas']) || isset($this->fb_params['in_iframe']);
+ }
+ public function in_fb_canvas() {
+ return isset($this->fb_params['in_canvas']);
+ }
+
+ public function get_loggedin_user() {
+ return $this->user;
+ }
+
+ public function get_canvas_user() {
+ return $this->canvas_user;
+ }
+
+ public function get_profile_user() {
+ return $this->profile_user;
+ }
+
+ public static function current_url() {
+ return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+ }
+
+ // require_add and require_install have been removed.
+ // see http://developer.facebook.com/news.php?blog=1&story=116 for more details
+ public function require_login() {
+ if ($user = $this->get_loggedin_user()) {
+ return $user;
+ }
+ $this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
+ }
+
+ public function require_frame() {
+ if (!$this->in_frame()) {
+ $this->redirect($this->get_login_url(self::current_url(), true));
+ }
+ }
+
+ public static function get_facebook_url($subdomain='www') {
+ return 'http://' . $subdomain . '.facebook.com';
+ }
+
+ public function get_install_url($next=null) {
+ // this was renamed, keeping for compatibility's sake
+ return $this->get_add_url($next);
+ }
+
+ public function get_add_url($next=null) {
+ return self::get_facebook_url().'/add.php?api_key='.$this->api_key .
+ ($next ? '&next=' . urlencode($next) : '');
+ }
+
+ public function get_login_url($next, $canvas) {
+ return self::get_facebook_url().'/login.php?v=1.0&api_key=' . $this->api_key .
+ ($next ? '&next=' . urlencode($next) : '') .
+ ($canvas ? '&canvas' : '');
+ }
+
+ public function set_user($user, $session_key, $expires=null, $session_secret=null) {
+ if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user'])
+ || $_COOKIE[$this->api_key . '_user'] != $user)) {
+ $this->set_cookies($user, $session_key, $expires, $session_secret);
+ }
+ $this->user = $user;
+ $this->api_client->session_key = $session_key;
+ $this->session_expires = $expires;
+ }
+
+ public function set_cookies($user, $session_key, $expires=null, $session_secret=null) {
+ $cookies = array();
+ $cookies['user'] = $user;
+ $cookies['session_key'] = $session_key;
+ if ($expires != null) {
+ $cookies['expires'] = $expires;
+ }
+ if ($session_secret != null) {
+ $cookies['ss'] = $session_secret;
+ }
+
+ foreach ($cookies as $name => $val) {
+ setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain);
+ $_COOKIE[$this->api_key . '_' . $name] = $val;
+ }
+ $sig = self::generate_sig($cookies, $this->secret);
+ setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain);
+ $_COOKIE[$this->api_key] = $sig;
+
+ if ($this->base_domain != null) {
+ $base_domain_cookie = 'base_domain_' . $this->api_key;
+ setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain);
+ $_COOKIE[$base_domain_cookie] = $this->base_domain;
+ }
+ }
+
+ /**
+ * Tries to undo the badness of magic quotes as best we can
+ * @param string $val Should come directly from $_GET, $_POST, etc.
+ * @return string val without added slashes
+ */
+ public static function no_magic_quotes($val) {
+ if (get_magic_quotes_gpc()) {
+ return stripslashes($val);
+ } else {
+ return $val;
+ }
+ }
+
+ /*
+ * Get the signed parameters that were sent from Facebook. Validates the set
+ * of parameters against the included signature.
+ *
+ * Since Facebook sends data to your callback URL via unsecured means, the
+ * signature is the only way to make sure that the data actually came from
+ * Facebook. So if an app receives a request at the callback URL, it should
+ * always verify the signature that comes with against your own secret key.
+ * Otherwise, it's possible for someone to spoof a request by
+ * pretending to be someone else, i.e.:
+ * www.your-callback-url.com/?fb_user=10101
+ *
+ * This is done automatically by verify_fb_params.
+ *
+ * @param assoc $params a full array of external parameters.
+ * presumed $_GET, $_POST, or $_COOKIE
+ * @param int $timeout number of seconds that the args are good for.
+ * Specifically good for forcing cookies to expire.
+ * @param string $namespace prefix string for the set of parameters we want
+ * to verify. i.e., fb_sig or fb_post_sig
+ *
+ * @return assoc the subset of parameters containing the given prefix,
+ * and also matching the signature associated with them.
+ * OR an empty array if the params do not validate
+ */
+ public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') {
+ $prefix = $namespace . '_';
+ $prefix_len = strlen($prefix);
+ $fb_params = array();
+ if (empty($params)) {
+ return array();
+ }
+
+ foreach ($params as $name => $val) {
+ // pull out only those parameters that match the prefix
+ // note that the signature itself ($params[$namespace]) is not in the list
+ if (strpos($name, $prefix) === 0) {
+ $fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val);
+ }
+ }
+
+ // validate that the request hasn't expired. this is most likely
+ // for params that come from $_COOKIE
+ if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) {
+ return array();
+ }
+
+ // validate that the params match the signature
+ $signature = isset($params[$namespace]) ? $params[$namespace] : null;
+ if (!$signature || (!$this->verify_signature($fb_params, $signature))) {
+ return array();
+ }
+ return $fb_params;
+ }
+
+ /*
+ * Validates that a given set of parameters match their signature.
+ * Parameters all match a given input prefix, such as "fb_sig".
+ *
+ * @param $fb_params an array of all Facebook-sent parameters,
+ * not including the signature itself
+ * @param $expected_sig the expected result to check against
+ */
+ public function verify_signature($fb_params, $expected_sig) {
+ return self::generate_sig($fb_params, $this->secret) == $expected_sig;
+ }
+
+ /*
+ * Generate a signature using the application secret key.
+ *
+ * The only two entities that know your secret key are you and Facebook,
+ * according to the Terms of Service. Since nobody else can generate
+ * the signature, you can rely on it to verify that the information
+ * came from Facebook.
+ *
+ * @param $params_array an array of all Facebook-sent parameters,
+ * NOT INCLUDING the signature itself
+ * @param $secret your app's secret key
+ *
+ * @return a hash to be checked against the signature provided by Facebook
+ */
+ public static function generate_sig($params_array, $secret) {
+ $str = '';
+
+ ksort($params_array);
+ // Note: make sure that the signature parameter is not already included in
+ // $params_array.
+ foreach ($params_array as $k=>$v) {
+ $str .= "$k=$v";
+ }
+ $str .= $secret;
+
+ return md5($str);
+ }
+
+ public function encode_validationError($summary, $message) {
+ return json_encode(
+ array('errorCode' => FACEBOOK_API_VALIDATION_ERROR,
+ 'errorTitle' => $summary,
+ 'errorMessage' => $message));
+ }
+
+ public function encode_multiFeedStory($feed, $next) {
+ return json_encode(
+ array('method' => 'multiFeedStory',
+ 'content' =>
+ array('next' => $next,
+ 'feed' => $feed)));
+ }
+
+ public function encode_feedStory($feed, $next) {
+ return json_encode(
+ array('method' => 'feedStory',
+ 'content' =>
+ array('next' => $next,
+ 'feed' => $feed)));
+ }
+
+ public function create_templatizedFeedStory($title_template, $title_data=array(),
+ $body_template='', $body_data = array(), $body_general=null,
+ $image_1=null, $image_1_link=null,
+ $image_2=null, $image_2_link=null,
+ $image_3=null, $image_3_link=null,
+ $image_4=null, $image_4_link=null) {
+ return array('title_template'=> $title_template,
+ 'title_data' => $title_data,
+ 'body_template'=> $body_template,
+ 'body_data' => $body_data,
+ 'body_general' => $body_general,
+ 'image_1' => $image_1,
+ 'image_1_link' => $image_1_link,
+ 'image_2' => $image_2,
+ 'image_2_link' => $image_2_link,
+ 'image_3' => $image_3,
+ 'image_3_link' => $image_3_link,
+ 'image_4' => $image_4,
+ 'image_4_link' => $image_4_link);
+ }
+
+
+}
+
diff --git a/extlib/facebook/facebook_desktop.php b/extlib/facebook/facebook_desktop.php
new file mode 100644
index 000000000..90cdf66bd
--- /dev/null
+++ b/extlib/facebook/facebook_desktop.php
@@ -0,0 +1,104 @@
+<?php
+// Copyright 2004-2008 Facebook. All Rights Reserved.
+//
+// +---------------------------------------------------------------------------+
+// | Facebook Platform PHP5 client |
+// +---------------------------------------------------------------------------+
+// | Copyright (c) 2007 Facebook, Inc. |
+// | All rights reserved. |
+// | |
+// | Redistribution and use in source and binary forms, with or without |
+// | modification, are permitted provided that the following conditions |
+// | are met: |
+// | |
+// | 1. Redistributions of source code must retain the above copyright |
+// | notice, this list of conditions and the following disclaimer. |
+// | 2. Redistributions in binary form must reproduce the above copyright |
+// | notice, this list of conditions and the following disclaimer in the |
+// | documentation and/or other materials provided with the distribution. |
+// | |
+// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
+// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
+// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
+// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
+// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+// +---------------------------------------------------------------------------+
+// | For help with this library, contact developers-help@facebook.com |
+// +---------------------------------------------------------------------------+
+//
+
+/**
+ * This class extends and modifies the "Facebook" class to better
+ * suit desktop apps.
+ */
+class FacebookDesktop extends Facebook {
+ // the application secret, which differs from the session secret
+ public $app_secret;
+ public $verify_sig;
+
+ public function __construct($api_key, $secret) {
+ $this->app_secret = $secret;
+ $this->verify_sig = false;
+ parent::__construct($api_key, $secret);
+ }
+
+ public function do_get_session($auth_token) {
+ $this->api_client->secret = $this->app_secret;
+ $this->api_client->session_key = null;
+ $session_info = parent::do_get_session($auth_token);
+ if (!empty($session_info['secret'])) {
+ // store the session secret
+ $this->set_session_secret($session_info['secret']);
+ }
+ return $session_info;
+ }
+
+ public function set_session_secret($session_secret) {
+ $this->secret = $session_secret;
+ $this->api_client->secret = $session_secret;
+ }
+
+ public function require_login() {
+ if ($this->get_loggedin_user()) {
+ try {
+ // try a session-based API call to ensure that we have the correct
+ // session secret
+ $user = $this->api_client->users_getLoggedInUser();
+
+ // now that we have a valid session secret, verify the signature
+ $this->verify_sig = true;
+ if ($this->validate_fb_params(false)) {
+ return $user;
+ } else {
+ // validation failed
+ return null;
+ }
+ } catch (FacebookRestClientException $ex) {
+ if (isset($_GET['auth_token'])) {
+ // if we have an auth_token, use it to establish a session
+ $session_info = $this->do_get_session($_GET['auth_token']);
+ if ($session_info) {
+ return $session_info['uid'];
+ }
+ }
+ }
+ }
+ // if we get here, we need to redirect the user to log in
+ $this->redirect($this->get_login_url(self::current_url(), $this->in_fb_canvas()));
+ }
+
+ public function verify_signature($fb_params, $expected_sig) {
+ // we don't want to verify the signature until we have a valid
+ // session secret
+ if ($this->verify_sig) {
+ return parent::verify_signature($fb_params, $expected_sig);
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/extlib/facebook/facebookapi_php5_restlib.php b/extlib/facebook/facebookapi_php5_restlib.php
new file mode 100644
index 000000000..389f40a9d
--- /dev/null
+++ b/extlib/facebook/facebookapi_php5_restlib.php
@@ -0,0 +1,2632 @@
+<?php
+//
+// +---------------------------------------------------------------------------+
+// | Facebook Platform PHP5 client |
+// +---------------------------------------------------------------------------+
+// | Copyright (c) 2007-2008 Facebook, Inc. |
+// | All rights reserved. |
+// | |
+// | Redistribution and use in source and binary forms, with or without |
+// | modification, are permitted provided that the following conditions |
+// | are met: |
+// | |
+// | 1. Redistributions of source code must retain the above copyright |
+// | notice, this list of conditions and the following disclaimer. |
+// | 2. Redistributions in binary form must reproduce the above copyright |
+// | notice, this list of conditions and the following disclaimer in the |
+// | documentation and/or other materials provided with the distribution. |
+// | |
+// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
+// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
+// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
+// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
+// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+// +---------------------------------------------------------------------------+
+// | For help with this library, contact developers-help@facebook.com |
+// +---------------------------------------------------------------------------+
+//
+
+include_once 'jsonwrapper/jsonwrapper.php';
+class FacebookRestClient {
+ public $secret;
+ public $session_key;
+ public $api_key;
+ // to save making the friends.get api call, this will get prepopulated on
+ // canvas pages
+ public $friends_list;
+ public $user;
+ // to save making the pages.isAppAdded api call, this will get prepopulated
+ // on canvas pages
+ public $added;
+ public $is_user;
+ // we don't pass friends list to iframes, but we want to make
+ // friends_get really simple in the canvas_user (non-logged in) case.
+ // So we use the canvas_user as default arg to friends_get
+ public $canvas_user;
+ public $batch_mode;
+ private $batch_queue;
+ private $call_as_apikey;
+
+ const BATCH_MODE_DEFAULT = 0;
+ const BATCH_MODE_SERVER_PARALLEL = 0;
+ const BATCH_MODE_SERIAL_ONLY = 2;
+
+ /**
+ * Create the client.
+ * @param string $session_key if you haven't gotten a session key yet, leave
+ * this as null and then set it later by just
+ * directly accessing the $session_key member
+ * variable.
+ */
+ public function __construct($api_key, $secret, $session_key=null) {
+ $this->secret = $secret;
+ $this->session_key = $session_key;
+ $this->api_key = $api_key;
+ $this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT;
+ $this->last_call_id = 0;
+ $this->call_as_apikey = '';
+ $this->server_addr = Facebook::get_facebook_url('api') . '/restserver.php';
+
+ if (!empty($GLOBALS['facebook_config']['debug'])) {
+ $this->cur_id = 0;
+ ?>
+<script type="text/javascript">
+var types = ['params', 'xml', 'php', 'sxml'];
+function getStyle(elem, style) {
+ if (elem.getStyle) {
+ return elem.getStyle(style);
+ } else {
+ return elem.style[style];
+ }
+}
+function setStyle(elem, style, value) {
+ if (elem.setStyle) {
+ elem.setStyle(style, value);
+ } else {
+ elem.style[style] = value;
+ }
+}
+function toggleDisplay(id, type) {
+ for (var i = 0; i < types.length; i++) {
+ var t = types[i];
+ var pre = document.getElementById(t + id);
+ if (pre) {
+ if (t != type || getStyle(pre, 'display') == 'block') {
+ setStyle(pre, 'display', 'none');
+ } else {
+ setStyle(pre, 'display', 'block');
+ }
+ }
+ }
+ return false;
+}
+</script>
+<?php
+ }
+ }
+
+ /**
+ * Set the default user id for methods that allow the caller
+ * to pass an uid parameter to identify the target user
+ * instead of a session key. This currently applies to
+ * the user preferences methods.
+ *
+ * @param $uid int the user id
+ */
+ public function set_user($uid) {
+ $this->user = $uid;
+ }
+
+ /**
+ * Start a batch operation.
+ */
+ public function begin_batch() {
+ if($this->batch_queue !== null) {
+ $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
+ throw new FacebookRestClientException($code,
+ FacebookAPIErrorCodes::$api_error_descriptions[$code]);
+ }
+
+ $this->batch_queue = array();
+ }
+
+ /*
+ * End current batch operation
+ */
+ public function end_batch() {
+ if($this->batch_queue === null) {
+ $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
+ throw new FacebookRestClientException($code,
+ FacebookAPIErrorCodes::$api_error_descriptions[$code]);
+ }
+
+ $this->execute_server_side_batch();
+
+ $this->batch_queue = null;
+ }
+
+ private function execute_server_side_batch() {
+ $item_count = count($this->batch_queue);
+ $method_feed = array();
+ foreach($this->batch_queue as $batch_item) {
+ $method_feed[] = $this->create_post_string($batch_item['m'],
+ $batch_item['p']);
+ }
+
+ $method_feed_json = json_encode($method_feed);
+
+ $serial_only =
+ ($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
+ $params = array('method_feed' => $method_feed_json,
+ 'serial_only' => $serial_only);
+ if ($this->call_as_apikey) {
+ $params['call_as_apikey'] = $this->call_as_apikey;
+ }
+
+ $xml = $this->post_request('batch.run', $params);
+
+ $result = $this->convert_xml_to_result($xml, 'batch.run', $params);
+
+
+ if (is_array($result) && isset($result['error_code'])) {
+ throw new FacebookRestClientException($result['error_msg'],
+ $result['error_code']);
+ }
+
+ for($i = 0; $i < $item_count; $i++) {
+ $batch_item = $this->batch_queue[$i];
+ $batch_item_result_xml = $result[$i];
+ $batch_item_result = $this->convert_xml_to_result($batch_item_result_xml,
+ $batch_item['m'],
+ $batch_item['p']);
+
+ if (is_array($batch_item_result) &&
+ isset($batch_item_result['error_code'])) {
+ throw new FacebookRestClientException($batch_item_result['error_msg'],
+ $batch_item_result['error_code']);
+ }
+ $batch_item['r'] = $batch_item_result;
+ }
+ }
+
+ public function begin_permissions_mode($permissions_apikey) {
+ $this->call_as_apikey = $permissions_apikey;
+ }
+
+ public function end_permissions_mode() {
+ $this->call_as_apikey = '';
+ }
+
+ /**
+ * Returns public information for an application (as shown in the application
+ * directory) by either application ID, API key, or canvas page name.
+ *
+ * @param int $application_id (Optional) app id
+ * @param string $application_api_key (Optional) api key
+ * @param string $application_canvas_name (Optional) canvas name
+ *
+ * Exactly one argument must be specified, otherwise it is an error.
+ *
+ * @return array An array of public information about the application.
+ */
+ public function application_getPublicInfo($application_id=null,
+ $application_api_key=null,
+ $application_canvas_name=null) {
+ return $this->call_method('facebook.application.getPublicInfo',
+ array('application_id' => $application_id,
+ 'application_api_key' => $application_api_key,
+ 'application_canvas_name' => $application_canvas_name));
+ }
+
+ /**
+ * Creates an authentication token to be used as part of the desktop login
+ * flow. For more information, please see
+ * http://wiki.developers.facebook.com/index.php/Auth.createToken.
+ *
+ * @return string An authentication token.
+ */
+ public function auth_createToken() {
+ return $this->call_method('facebook.auth.createToken', array());
+ }
+
+ /**
+ * Returns the session information available after current user logs in.
+ *
+ * @param string $auth_token the token returned by
+ * auth_createToken or passed back to
+ * your callback_url.
+ * @param bool $generate_session_secret whether the session returned should
+ * include a session secret
+ *
+ * @return array An assoc array containing session_key, uid
+ */
+ public function auth_getSession($auth_token, $generate_session_secret=false) {
+ //Check if we are in batch mode
+ if($this->batch_queue === null) {
+ $result = $this->call_method('facebook.auth.getSession',
+ array('auth_token' => $auth_token,
+ 'generate_session_secret' => $generate_session_secret));
+ $this->session_key = $result['session_key'];
+
+ if (!empty($result['secret']) && !$generate_session_secret) {
+ // desktop apps have a special secret
+ $this->secret = $result['secret'];
+ }
+ return $result;
+ }
+ }
+
+ /**
+ * Generates a session-specific secret. This is for integration with
+ * client-side API calls, such as the JS library.
+ *
+ * @return array A session secret for the current promoted session
+ *
+ * @error API_EC_PARAM_SESSION_KEY
+ * API_EC_PARAM_UNKNOWN
+ */
+ public function auth_promoteSession() {
+ return $this->call_method('facebook.auth.promoteSession', array());
+ }
+
+ /**
+ * Expires the session that is currently being used. If this call is
+ * successful, no further calls to the API (which require a session) can be
+ * made until a valid session is created.
+ *
+ * @return bool true if session expiration was successful, false otherwise
+ */
+ public function auth_expireSession() {
+ return $this->call_method('facebook.auth.expireSession', array());
+ }
+
+ /**
+ * Revokes the user's agreement to the Facebook Terms of Service for your
+ * application. If you call this method for one of your users, you will no
+ * longer be able to make API requests on their behalf until they again
+ * authorize your application. Use with care. Note that if this method is
+ * called without a user parameter, then it will revoke access for the
+ * current session's user.
+ *
+ * @param int $uid (Optional) User to revoke
+ *
+ * @return bool true if revocation succeeds, false otherwise
+ */
+ public function auth_revokeAuthorization($uid=null) {
+ return $this->call_method('facebook.auth.revokeAuthorization',
+ array('uid' => $uid));
+ }
+
+ /**
+ * Returns the number of unconnected friends that exist in this application.
+ * This number is determined based on the accounts registered through
+ * connect.registerUsers() (see below).
+ */
+ public function connect_getUnconnectedFriendsCount() {
+ return $this->call_method('facebook.connect.getUnconnectedFriendsCount',
+ array());
+ }
+
+ /**
+ * This method is used to create an association between an external user
+ * account and a Facebook user account, as per Facebook Connect.
+ *
+ * This method takes an array of account data, including a required email_hash
+ * and optional account data. For each connected account, if the user exists,
+ * the information is added to the set of the user's connected accounts.
+ * If the user has already authorized the site, the connected account is added
+ * in the confirmed state. If the user has not yet authorized the site, the
+ * connected account is added in the pending state.
+ *
+ * This is designed to help Facebook Connect recognize when two Facebook
+ * friends are both members of a external site, but perhaps are not aware of
+ * it. The Connect dialog (see fb:connect-form) is used when friends can be
+ * identified through these email hashes. See the following url for details:
+ *
+ * http://wiki.developers.facebook.com/index.php/Connect.registerUsers
+ *
+ * @param mixed $accounts A (JSON-encoded) array of arrays, where each array
+ * has three properties:
+ * 'email_hash' (req) - public email hash of account
+ * 'account_id' (opt) - remote account id;
+ * 'account_url' (opt) - url to remote account;
+ *
+ * @return array The list of email hashes for the successfully registered
+ * accounts.
+ */
+ public function connect_registerUsers($accounts) {
+ return $this->call_method('facebook.connect.registerUsers',
+ array('accounts' => $accounts));
+ }
+
+ /**
+ * Unregisters a set of accounts registered using connect.registerUsers.
+ *
+ * @param array $email_hashes The (JSON-encoded) list of email hashes to be
+ * unregistered.
+ *
+ * @return array The list of email hashes which have been successfully
+ * unregistered.
+ */
+ public function connect_unregisterUsers($email_hashes) {
+ return $this->call_method('facebook.connect.unregisterUsers',
+ array('email_hashes' => $email_hashes));
+ }
+
+ /**
+ * Returns events according to the filters specified.
+ *
+ * @param int $uid (Optional) User associated with events. A null
+ * parameter will default to the session user.
+ * @param array $eids (Optional) Filter by these event ids. A null
+ * parameter will get all events for the user.
+ * @param int $start_time (Optional) Filter with this unix time as lower
+ * bound. A null or zero parameter indicates no
+ * lower bound.
+ * @param int $end_time (Optional) Filter with this UTC as upper bound.
+ * A null or zero parameter indicates no upper
+ * bound.
+ * @param string $rsvp_status (Optional) Only show events where the given uid
+ * has this rsvp status. This only works if you
+ * have specified a value for $uid. Values are as
+ * in events.getMembers. Null indicates to ignore
+ * rsvp status when filtering.
+ *
+ * @return array The events matching the query.
+ */
+ public function &events_get($uid=null,
+ $eids=null,
+ $start_time=null,
+ $end_time=null,
+ $rsvp_status=null) {
+ return $this->call_method('facebook.events.get',
+ array('uid' => $uid,
+ 'eids' => $eids,
+ 'start_time' => $start_time,
+ 'end_time' => $end_time,
+ 'rsvp_status' => $rsvp_status));
+ }
+
+ /**
+ * Returns membership list data associated with an event.
+ *
+ * @param int $eid event id
+ *
+ * @return array An assoc array of four membership lists, with keys
+ * 'attending', 'unsure', 'declined', and 'not_replied'
+ */
+ public function &events_getMembers($eid) {
+ return $this->call_method('facebook.events.getMembers',
+ array('eid' => $eid));
+ }
+
+ /**
+ * RSVPs the current user to this event.
+ *
+ * @param int $eid event id
+ * @param string $rsvp_status 'attending', 'unsure', or 'declined'
+ *
+ * @return bool true if successful
+ */
+ public function &events_rsvp($eid, $rsvp_status) {
+ return $this->call_method('facebook.events.rsvp',
+ array(
+ 'eid' => $eid,
+ 'rsvp_status' => $rsvp_status));
+ }
+
+ /**
+ * Cancels an event. Only works for events where application is the admin.
+ *
+ * @param int $eid event id
+ * @param string $cancel_message (Optional) message to send to members of
+ * the event about why it is cancelled
+ *
+ * @return bool true if successful
+ */
+ public function &events_cancel($eid, $cancel_message='') {
+ return $this->call_method('facebook.events.cancel',
+ array('eid' => $eid,
+ 'cancel_message' => $cancel_message));
+ }
+
+ /**
+ * Creates an event on behalf of the user is there is a session, otherwise on
+ * behalf of app. Successful creation guarantees app will be admin.
+ *
+ * @param assoc array $event_info json encoded event information
+ *
+ * @return int event id
+ */
+ public function &events_create($event_info) {
+ return $this->call_method('facebook.events.create',
+ array('event_info' => $event_info));
+ }
+
+ /**
+ * Edits an existing event. Only works for events where application is admin.
+ *
+ * @param int $eid event id
+ * @param assoc array $event_info json encoded event information
+ *
+ * @return bool true if successful
+ */
+ public function &events_edit($eid, $event_info) {
+ return $this->call_method('facebook.events.edit',
+ array('eid' => $eid,
+ 'event_info' => $event_info));
+ }
+
+ /**
+ * Fetches and re-caches the image stored at the given URL, for use in images
+ * published to non-canvas pages via the API (for example, to user profiles
+ * via profile.setFBML, or to News Feed via feed.publishUserAction).
+ *
+ * @param string $url The absolute URL from which to refresh the image.
+ *
+ * @return bool true on success
+ */
+ public function &fbml_refreshImgSrc($url) {
+ return $this->call_method('facebook.fbml.refreshImgSrc',
+ array('url' => $url));
+ }
+
+ /**
+ * Fetches and re-caches the content stored at the given URL, for use in an
+ * fb:ref FBML tag.
+ *
+ * @param string $url The absolute URL from which to fetch content. This URL
+ * should be used in a fb:ref FBML tag.
+ *
+ * @return bool true on success
+ */
+ public function &fbml_refreshRefUrl($url) {
+ return $this->call_method('facebook.fbml.refreshRefUrl',
+ array('url' => $url));
+ }
+
+ /**
+ * Lets you insert text strings in their native language into the Facebook
+ * Translations database so they can be translated.
+ *
+ * @param array $native_strings An array of maps, where each map has a 'text'
+ * field and a 'description' field.
+ *
+ * @return int Number of strings uploaded.
+ */
+ public function &fbml_uploadNativeStrings($native_strings) {
+ return $this->call_method('facebook.fbml.uploadNativeStrings',
+ array('native_strings' => json_encode($native_strings)));
+ }
+
+ /**
+ * Associates a given "handle" with FBML markup so that the handle can be
+ * used within the fb:ref FBML tag. A handle is unique within an application
+ * and allows an application to publish identical FBML to many user profiles
+ * and do subsequent updates without having to republish FBML on behalf of
+ * each user.
+ *
+ * @param string $handle The handle to associate with the given FBML.
+ * @param string $fbml The FBML to associate with the given handle.
+ *
+ * @return bool true on success
+ */
+ public function &fbml_setRefHandle($handle, $fbml) {
+ return $this->call_method('facebook.fbml.setRefHandle',
+ array('handle' => $handle, 'fbml' => $fbml));
+ }
+
+ /**
+ * Register custom tags for the application. Custom tags can be used
+ * to extend the set of tags available to applications in FBML
+ * markup.
+ *
+ * Before you call this function,
+ * make sure you read the full documentation at
+ *
+ * http://wiki.developers.facebook.com/index.php/Fbml.RegisterCustomTags
+ *
+ * IMPORTANT: This function overwrites the values of
+ * existing tags if the names match. Use this function with care because
+ * it may break the FBML of any application that is using the
+ * existing version of the tags.
+ *
+ * @param mixed $tags an array of tag objects (the full description is on the
+ * wiki page)
+ *
+ * @return int the number of tags that were registered
+ */
+ public function &fbml_registerCustomTags($tags) {
+ $tags = json_encode($tags);
+ return $this->call_method('facebook.fbml.registerCustomTags',
+ array('tags' => $tags));
+ }
+
+ /**
+ * Get the custom tags for an application. If $app_id
+ * is not specified, the calling app's tags are returned.
+ * If $app_id is different from the id of the calling app,
+ * only the app's public tags are returned.
+ * The return value is an array of the same type as
+ * the $tags parameter of fbml_registerCustomTags().
+ *
+ * @param int $app_id the application's id (optional)
+ *
+ * @return mixed an array containing the custom tag objects
+ */
+ public function &fbml_getCustomTags($app_id = null) {
+ return $this->call_method('facebook.fbml.getCustomTags',
+ array('app_id' => $app_id));
+ }
+
+
+ /**
+ * Delete custom tags the application has registered. If
+ * $tag_names is null, all the application's custom tags will be
+ * deleted.
+ *
+ * IMPORTANT: If your application has registered public tags
+ * that other applications may be using, don't delete those tags!
+ * Doing so can break the FBML ofapplications that are using them.
+ *
+ * @param array $tag_names the names of the tags to delete (optinal)
+ * @return bool true on success
+ */
+ public function &fbml_deleteCustomTags($tag_names = null) {
+ return $this->call_method('facebook.fbml.deleteCustomTags',
+ array('tag_names' => json_encode($tag_names)));
+ }
+
+
+
+ /**
+ * This method is deprecated for calls made on behalf of users. This method
+ * works only for publishing stories on a Facebook Page that has installed
+ * your application. To publish stories to a user's profile, use
+ * feed.publishUserAction instead.
+ *
+ * For more details on this call, please visit the wiki page:
+ *
+ * http://wiki.developers.facebook.com/index.php/Feed.publishTemplatizedAction
+ */
+ public function &feed_publishTemplatizedAction($title_template,
+ $title_data,
+ $body_template,
+ $body_data,
+ $body_general,
+ $image_1=null,
+ $image_1_link=null,
+ $image_2=null,
+ $image_2_link=null,
+ $image_3=null,
+ $image_3_link=null,
+ $image_4=null,
+ $image_4_link=null,
+ $target_ids='',
+ $page_actor_id=null) {
+ return $this->call_method('facebook.feed.publishTemplatizedAction',
+ array('title_template' => $title_template,
+ 'title_data' => $title_data,
+ 'body_template' => $body_template,
+ 'body_data' => $body_data,
+ 'body_general' => $body_general,
+ 'image_1' => $image_1,
+ 'image_1_link' => $image_1_link,
+ 'image_2' => $image_2,
+ 'image_2_link' => $image_2_link,
+ 'image_3' => $image_3,
+ 'image_3_link' => $image_3_link,
+ 'image_4' => $image_4,
+ 'image_4_link' => $image_4_link,
+ 'target_ids' => $target_ids,
+ 'page_actor_id' => $page_actor_id));
+ }
+
+ /**
+ * Registers a template bundle. Template bundles are somewhat involved, so
+ * it's recommended you check out the wiki for more details:
+ *
+ * http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle
+ *
+ * @return string A template bundle id
+ */
+ public function &feed_registerTemplateBundle($one_line_story_templates,
+ $short_story_templates = array(),
+ $full_story_template = null,
+ $action_links = array()) {
+
+ $one_line_story_templates = json_encode($one_line_story_templates);
+
+ if (!empty($short_story_templates)) {
+ $short_story_templates = json_encode($short_story_templates);
+ }
+
+ if (isset($full_story_template)) {
+ $full_story_template = json_encode($full_story_template);
+ }
+
+ if (isset($action_links)) {
+ $action_links = json_encode($action_links);
+ }
+
+ return $this->call_method('facebook.feed.registerTemplateBundle',
+ array('one_line_story_templates' => $one_line_story_templates,
+ 'short_story_templates' => $short_story_templates,
+ 'full_story_template' => $full_story_template,
+ 'action_links' => $action_links));
+ }
+
+ /**
+ * Retrieves the full list of active template bundles registered by the
+ * requesting application.
+ *
+ * @return array An array of template bundles
+ */
+ public function &feed_getRegisteredTemplateBundles() {
+ return $this->call_method('facebook.feed.getRegisteredTemplateBundles',
+ array());
+ }
+
+ /**
+ * Retrieves information about a specified template bundle previously
+ * registered by the requesting application.
+ *
+ * @param string $template_bundle_id The template bundle id
+ *
+ * @return array Template bundle
+ */
+ public function &feed_getRegisteredTemplateBundleByID($template_bundle_id) {
+ return $this->call_method('facebook.feed.getRegisteredTemplateBundleByID',
+ array('template_bundle_id' => $template_bundle_id));
+ }
+
+ /**
+ * Deactivates a previously registered template bundle.
+ *
+ * @param string $template_bundle_id The template bundle id
+ *
+ * @return bool true on success
+ */
+ public function &feed_deactivateTemplateBundleByID($template_bundle_id) {
+ return $this->call_method('facebook.feed.deactivateTemplateBundleByID',
+ array('template_bundle_id' => $template_bundle_id));
+ }
+
+ const STORY_SIZE_ONE_LINE = 1;
+ const STORY_SIZE_SHORT = 2;
+ const STORY_SIZE_FULL = 4;
+
+ /**
+ * Publishes a story on behalf of the user owning the session, using the
+ * specified template bundle. This method requires an active session key in
+ * order to be called.
+ *
+ * The parameters to this method ($templata_data in particular) are somewhat
+ * involved. It's recommended you visit the wiki for details:
+ *
+ * http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
+ *
+ * @param int $template_bundle_id A template bundle id previously registered
+ * @param array $template_data See wiki article for syntax
+ * @param array $target_ids (Optional) An array of friend uids of the
+ * user who shared in this action.
+ * @param string $body_general (Optional) Additional markup that extends
+ * the body of a short story.
+ * @param int $story_size (Optional) A story size (see above)
+ *
+ * @return bool true on success
+ */
+ public function &feed_publishUserAction(
+ $template_bundle_id, $template_data, $target_ids='', $body_general='',
+ $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE) {
+
+ if (is_array($template_data)) {
+ $template_data = json_encode($template_data);
+ } // allow client to either pass in JSON or an assoc that we JSON for them
+
+ if (is_array($target_ids)) {
+ $target_ids = json_encode($target_ids);
+ $target_ids = trim($target_ids, "[]"); // we don't want square brackets
+ }
+
+ return $this->call_method('facebook.feed.publishUserAction',
+ array('template_bundle_id' => $template_bundle_id,
+ 'template_data' => $template_data,
+ 'target_ids' => $target_ids,
+ 'body_general' => $body_general,
+ 'story_size' => $story_size));
+ }
+
+ /**
+ * For the current user, retrieves stories generated by the user's friends
+ * while using this application. This can be used to easily create a
+ * "News Feed" like experience.
+ *
+ * @return array An array of feed story objects.
+ */
+ public function &feed_getAppFriendStories() {
+ return $this->call_method('facebook.feed.getAppFriendStories', array());
+ }
+
+ /**
+ * Makes an FQL query. This is a generalized way of accessing all the data
+ * in the API, as an alternative to most of the other method calls. More
+ * info at http://developers.facebook.com/documentation.php?v=1.0&doc=fql
+ *
+ * @param string $query the query to evaluate
+ *
+ * @return array generalized array representing the results
+ */
+ public function &fql_query($query) {
+ return $this->call_method('facebook.fql.query',
+ array('query' => $query));
+ }
+
+ /**
+ * Returns whether or not pairs of users are friends.
+ * Note that the Facebook friend relationship is symmetric.
+ *
+ * @param array $uids1 array of ids (id_1, id_2,...) of some length X
+ * @param array $uids2 array of ids (id_A, id_B,...) of SAME length X
+ *
+ * @return array An array with uid1, uid2, and bool if friends, e.g.:
+ * array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1),
+ * 1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0)
+ * ...)
+ */
+ public function &friends_areFriends($uids1, $uids2) {
+ return $this->call_method('facebook.friends.areFriends',
+ array('uids1' => $uids1, 'uids2' => $uids2));
+ }
+
+ /**
+ * Returns the friends of the current session user.
+ *
+ * @param int $flid (Optional) Only return friends on this friend list.
+ *
+ * @return array An array of friends
+ */
+ public function &friends_get($flid=null) {
+ if (isset($this->friends_list)) {
+ return $this->friends_list;
+ }
+ $params = array();
+ if (isset($this->canvas_user)) {
+ $params['uid'] = $this->canvas_user;
+ }
+ if ($flid) {
+ $params['flid'] = $flid;
+ }
+ return $this->call_method('facebook.friends.get', $params);
+
+ }
+
+ /**
+ * Returns the set of friend lists for the current session user.
+ *
+ * @return array An array of friend list objects
+ */
+ public function &friends_getLists() {
+ return $this->call_method('facebook.friends.getLists', array());
+ }
+
+ /**
+ * Returns the friends of the session user, who are also users
+ * of the calling application.
+ *
+ * @return array An array of friends also using the app
+ */
+ public function &friends_getAppUsers() {
+ return $this->call_method('facebook.friends.getAppUsers', array());
+ }
+
+ /**
+ * Returns groups according to the filters specified.
+ *
+ * @param int $uid (Optional) User associated with groups. A null
+ * parameter will default to the session user.
+ * @param array $gids (Optional) Group ids to query. A null parameter will
+ * get all groups for the user.
+ *
+ * @return array An array of group objects
+ */
+ public function &groups_get($uid, $gids) {
+ return $this->call_method('facebook.groups.get',
+ array('uid' => $uid,
+ 'gids' => $gids));
+ }
+
+ /**
+ * Returns the membership list of a group.
+ *
+ * @param int $gid Group id
+ *
+ * @return array An array with four membership lists, with keys 'members',
+ * 'admins', 'officers', and 'not_replied'
+ */
+ public function &groups_getMembers($gid) {
+ return $this->call_method('facebook.groups.getMembers',
+ array('gid' => $gid));
+ }
+
+ /**
+ * Returns cookies according to the filters specified.
+ *
+ * @param int $uid User for which the cookies are needed.
+ * @param string $name (Optional) A null parameter will get all cookies
+ * for the user.
+ *
+ * @return array Cookies! Nom nom nom nom nom.
+ */
+ public function data_getCookies($uid, $name) {
+ return $this->call_method('facebook.data.getCookies',
+ array('uid' => $uid,
+ 'name' => $name));
+ }
+
+ /**
+ * Sets cookies according to the params specified.
+ *
+ * @param int $uid User for which the cookies are needed.
+ * @param string $name Name of the cookie
+ * @param string $value (Optional) if expires specified and is in the past
+ * @param int $expires (Optional) Expiry time
+ * @param string $path (Optional) Url path to associate with (default is /)
+ *
+ * @return bool true on success
+ */
+ public function data_setCookie($uid, $name, $value, $expires, $path) {
+ return $this->call_method('facebook.data.setCookie',
+ array('uid' => $uid,
+ 'name' => $name,
+ 'value' => $value,
+ 'expires' => $expires,
+ 'path' => $path));
+ }
+
+ /**
+ * Permissions API
+ */
+
+ /**
+ * Checks API-access granted by self to the specified application.
+ *
+ * @param string $permissions_apikey Other application key
+ *
+ * @return array API methods/namespaces which are allowed access
+ */
+ public function permissions_checkGrantedApiAccess($permissions_apikey) {
+ return $this->call_method('facebook.permissions.checkGrantedApiAccess',
+ array('permissions_apikey' => $permissions_apikey));
+ }
+
+ /**
+ * Checks API-access granted to self by the specified application.
+ *
+ * @param string $permissions_apikey Other application key
+ *
+ * @return array API methods/namespaces which are allowed access
+ */
+ public function permissions_checkAvailableApiAccess($permissions_apikey) {
+ return $this->call_method('facebook.permissions.checkAvailableApiAccess',
+ array('permissions_apikey' => $permissions_apikey));
+ }
+
+ /**
+ * Grant API-access to the specified methods/namespaces to the specified
+ * application.
+ *
+ * @param string $permissions_apikey Other application key
+ * @param array(string) $method_arr (Optional) API methods/namespaces
+ * allowed
+ *
+ * @return array API methods/namespaces which are allowed access
+ */
+ public function permissions_grantApiAccess($permissions_apikey, $method_arr) {
+ return $this->call_method('facebook.permissions.grantApiAccess',
+ array('permissions_apikey' => $permissions_apikey,
+ 'method_arr' => $method_arr));
+ }
+
+ /**
+ * Revoke API-access granted to the specified application.
+ *
+ * @param string $permissions_apikey Other application key
+ *
+ * @return bool true on success
+ */
+ public function permissions_revokeApiAccess($permissions_apikey) {
+ return $this->call_method('facebook.permissions.revokeApiAccess',
+ array('permissions_apikey' => $permissions_apikey));
+ }
+
+ /**
+ * Returns the outstanding notifications for the session user.
+ *
+ * @return array An assoc array of notification count objects for
+ * 'messages', 'pokes' and 'shares', a uid list of
+ * 'friend_requests', a gid list of 'group_invites',
+ * and an eid list of 'event_invites'
+ */
+ public function &notifications_get() {
+ return $this->call_method('facebook.notifications.get', array());
+ }
+
+ /**
+ * Sends a notification to the specified users.
+ *
+ * @return A comma separated list of successful recipients
+ */
+ public function &notifications_send($to_ids, $notification, $type) {
+ return $this->call_method('facebook.notifications.send',
+ array('to_ids' => $to_ids,
+ 'notification' => $notification,
+ 'type' => $type));
+ }
+
+ /**
+ * Sends an email to the specified user of the application.
+ *
+ * @param array $recipients id of the recipients
+ * @param string $subject subject of the email
+ * @param string $text (plain text) body of the email
+ * @param string $fbml fbml markup for an html version of the email
+ *
+ * @return string A comma separated list of successful recipients
+ */
+ public function &notifications_sendEmail($recipients,
+ $subject,
+ $text,
+ $fbml) {
+ return $this->call_method('facebook.notifications.sendEmail',
+ array('recipients' => $recipients,
+ 'subject' => $subject,
+ 'text' => $text,
+ 'fbml' => $fbml));
+ }
+
+ /**
+ * Returns the requested info fields for the requested set of pages.
+ *
+ * @param array $page_ids an array of page ids
+ * @param array $fields an array of strings describing the info fields
+ * desired
+ * @param int $uid (Optional) limit results to pages of which this
+ * user is a fan.
+ * @param string type limits results to a particular type of page.
+ *
+ * @return array An array of pages
+ */
+ public function &pages_getInfo($page_ids, $fields, $uid, $type) {
+ return $this->call_method('facebook.pages.getInfo',
+ array('page_ids' => $page_ids,
+ 'fields' => $fields,
+ 'uid' => $uid,
+ 'type' => $type));
+ }
+
+ /**
+ * Returns true if the given user is an admin for the passed page.
+ *
+ * @param int $page_id target page id
+ * @param int $uid (Optional) user id (defaults to the logged-in user)
+ *
+ * @return bool true on success
+ */
+ public function &pages_isAdmin($page_id, $uid = null) {
+ return $this->call_method('facebook.pages.isAdmin',
+ array('page_id' => $page_id,
+ 'uid' => $uid));
+ }
+
+ /**
+ * Returns whether or not the given page has added the application.
+ *
+ * @param int $page_id target page id
+ *
+ * @return bool true on success
+ */
+ public function &pages_isAppAdded($page_id) {
+ return $this->call_method('facebook.pages.isAppAdded',
+ array('page_id' => $page_id));
+ }
+
+ /**
+ * Returns true if logged in user is a fan for the passed page.
+ *
+ * @param int $page_id target page id
+ * @param int $uid user to compare. If empty, the logged in user.
+ *
+ * @return bool true on success
+ */
+ public function &pages_isFan($page_id, $uid = null) {
+ return $this->call_method('facebook.pages.isFan',
+ array('page_id' => $page_id,
+ 'uid' => $uid));
+ }
+
+ /**
+ * Adds a tag with the given information to a photo. See the wiki for details:
+ *
+ * http://wiki.developers.facebook.com/index.php/Photos.addTag
+ *
+ * @param int $pid The ID of the photo to be tagged
+ * @param int $tag_uid The ID of the user being tagged. You must specify
+ * either the $tag_uid or the $tag_text parameter
+ * (unless $tags is specified).
+ * @param string $tag_text Some text identifying the person being tagged.
+ * You must specify either the $tag_uid or $tag_text
+ * parameter (unless $tags is specified).
+ * @param float $x The horizontal position of the tag, as a
+ * percentage from 0 to 100, from the left of the
+ * photo.
+ * @param float $y The vertical position of the tag, as a percentage
+ * from 0 to 100, from the top of the photo.
+ * @param array $tags (Optional) An array of maps, where each map
+ * can contain the tag_uid, tag_text, x, and y
+ * parameters defined above. If specified, the
+ * individual arguments are ignored.
+ * @param int $owner_uid (Optional) The user ID of the user whose photo
+ * you are tagging. If this parameter is not
+ * specified, then it defaults to the session user.
+ *
+ * @return bool true on success
+ */
+ public function &photos_addTag($pid,
+ $tag_uid,
+ $tag_text,
+ $x,
+ $y,
+ $tags,
+ $owner_uid=0) {
+ return $this->call_method('facebook.photos.addTag',
+ array('pid' => $pid,
+ 'tag_uid' => $tag_uid,
+ 'tag_text' => $tag_text,
+ 'x' => $x,
+ 'y' => $y,
+ 'tags' => json_encode($tags),
+ 'owner_uid' => $this->get_uid($owner_uid)));
+ }
+
+ /**
+ * Creates and returns a new album owned by the specified user or the current
+ * session user.
+ *
+ * @param string $name The name of the album.
+ * @param string $description (Optional) A description of the album.
+ * @param string $location (Optional) A description of the location.
+ * @param string $visible (Optional) A privacy setting for the album.
+ * One of 'friends', 'friends-of-friends',
+ * 'networks', or 'everyone'. Default 'everyone'.
+ * @param int $uid (Optional) User id for creating the album; if
+ * not specified, the session user is used.
+ *
+ * @return array An album object
+ */
+ public function &photos_createAlbum($name,
+ $description='',
+ $location='',
+ $visible='',
+ $uid=0) {
+ return $this->call_method('facebook.photos.createAlbum',
+ array('name' => $name,
+ 'description' => $description,
+ 'location' => $location,
+ 'visible' => $visible,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Returns photos according to the filters specified.
+ *
+ * @param int $subj_id (Optional) Filter by uid of user tagged in the photos.
+ * @param int $aid (Optional) Filter by an album, as returned by
+ * photos_getAlbums.
+ * @param array $pids (Optional) Restrict to a list of pids
+ *
+ * Note that at least one of these parameters needs to be specified, or an
+ * error is returned.
+ *
+ * @return array An array of photo objects.
+ */
+ public function &photos_get($subj_id, $aid, $pids) {
+ return $this->call_method('facebook.photos.get',
+ array('subj_id' => $subj_id, 'aid' => $aid, 'pids' => $pids));
+ }
+
+ /**
+ * Returns the albums created by the given user.
+ *
+ * @param int $uid (Optional) The uid of the user whose albums you want.
+ * A null will return the albums of the session user.
+ * @param array $aids (Optional) A list of aids to restrict the query.
+ *
+ * Note that at least one of the (uid, aids) parameters must be specified.
+ *
+ * @returns an array of album objects.
+ */
+ public function &photos_getAlbums($uid, $aids) {
+ return $this->call_method('facebook.photos.getAlbums',
+ array('uid' => $uid,
+ 'aids' => $aids));
+ }
+
+ /**
+ * Returns the tags on all photos specified.
+ *
+ * @param string $pids A list of pids to query
+ *
+ * @return array An array of photo tag objects, which include pid,
+ * subject uid, and two floating-point numbers (xcoord, ycoord)
+ * for tag pixel location.
+ */
+ public function &photos_getTags($pids) {
+ return $this->call_method('facebook.photos.getTags',
+ array('pids' => $pids));
+ }
+
+ /**
+ * Returns the requested info fields for the requested set of users.
+ *
+ * @param array $uids An array of user ids
+ * @param array $fields An array of info field names desired
+ *
+ * @return array An array of user objects
+ */
+ public function &users_getInfo($uids, $fields) {
+ return $this->call_method('facebook.users.getInfo',
+ array('uids' => $uids, 'fields' => $fields));
+ }
+
+ /**
+ * Returns the requested info fields for the requested set of users. A
+ * session key must not be specified. Only data about users that have
+ * authorized your application will be returned.
+ *
+ * Check the wiki for fields that can be queried through this API call.
+ * Data returned from here should not be used for rendering to application
+ * users, use users.getInfo instead, so that proper privacy rules will be
+ * applied.
+ *
+ * @param array $uids An array of user ids
+ * @param array $fields An array of info field names desired
+ *
+ * @return array An array of user objects
+ */
+ public function &users_getStandardInfo($uids, $fields) {
+ return $this->call_method('facebook.users.getStandardInfo',
+ array('uids' => $uids, 'fields' => $fields));
+ }
+
+ /**
+ * Returns the user corresponding to the current session object.
+ *
+ * @return integer User id
+ */
+ public function &users_getLoggedInUser() {
+ return $this->call_method('facebook.users.getLoggedInUser', array());
+ }
+
+ /**
+ * Returns 1 if the user has the specified permission, 0 otherwise.
+ * http://wiki.developers.facebook.com/index.php/Users.hasAppPermission
+ *
+ * @return integer 1 or 0
+ */
+ public function &users_hasAppPermission($ext_perm, $uid=null) {
+ return $this->call_method('facebook.users.hasAppPermission',
+ array('ext_perm' => $ext_perm, 'uid' => $uid));
+ }
+
+ /**
+ * Returns whether or not the user corresponding to the current
+ * session object has the give the app basic authorization.
+ *
+ * @return boolean true if the user has authorized the app
+ */
+ public function &users_isAppUser($uid=null) {
+ if ($uid === null && isset($this->is_user)) {
+ return $this->is_user;
+ }
+
+ return $this->call_method('facebook.users.isAppUser', array('uid' => $uid));
+ }
+
+ /**
+ * Sets the users' current status message. Message does NOT contain the
+ * word "is" , so make sure to include a verb.
+ *
+ * Example: setStatus("is loving the API!")
+ * will produce the status "Luke is loving the API!"
+ *
+ * @param string $status text-only message to set
+ * @param int $uid user to set for (defaults to the
+ * logged-in user)
+ * @param bool $clear whether or not to clear the status,
+ * instead of setting it
+ * @param bool $status_includes_verb if true, the word "is" will *not* be
+ * prepended to the status message
+ *
+ * @return boolean
+ */
+ public function &users_setStatus($status,
+ $uid = null,
+ $clear = false,
+ $status_includes_verb = true) {
+ $args = array(
+ 'status' => $status,
+ 'uid' => $uid,
+ 'clear' => $clear,
+ 'status_includes_verb' => $status_includes_verb,
+ );
+ return $this->call_method('facebook.users.setStatus', $args);
+ }
+
+ /**
+ * Sets the FBML for the profile of the user attached to this session.
+ *
+ * @param string $markup The FBML that describes the profile
+ * presence of this app for the user
+ * @param int $uid The user
+ * @param string $profile Profile FBML
+ * @param string $profile_action Profile action FBML (deprecated)
+ * @param string $mobile_profile Mobile profile FBML
+ * @param string $profile_main Main Tab profile FBML
+ *
+ * @return array A list of strings describing any compile errors for the
+ * submitted FBML
+ */
+ function profile_setFBML($markup,
+ $uid=null,
+ $profile='',
+ $profile_action='',
+ $mobile_profile='',
+ $profile_main='') {
+ return $this->call_method('facebook.profile.setFBML',
+ array('markup' => $markup,
+ 'uid' => $uid,
+ 'profile' => $profile,
+ 'profile_action' => $profile_action,
+ 'mobile_profile' => $mobile_profile,
+ 'profile_main' => $profile_main));
+ }
+
+ /**
+ * Gets the FBML for the profile box that is currently set for a user's
+ * profile (your application set the FBML previously by calling the
+ * profile.setFBML method).
+ *
+ * @param int $uid (Optional) User id to lookup; defaults to session.
+ * @param int $type (Optional) 1 for original style, 2 for profile_main boxes
+ *
+ * @return string The FBML
+ */
+ public function &profile_getFBML($uid=null, $type=null) {
+ return $this->call_method('facebook.profile.getFBML',
+ array('uid' => $uid,
+ 'type' => $type));
+ }
+
+ /**
+ * Returns the specified user's application info section for the calling
+ * application. These info sections have either been set via a previous
+ * profile.setInfo call or by the user editing them directly.
+ *
+ * @param int $uid (Optional) User id to lookup; defaults to session.
+ *
+ * @return array Info fields for the current user. See wiki for structure:
+ *
+ * http://wiki.developers.facebook.com/index.php/Profile.getInfo
+ *
+ */
+ public function &profile_getInfo($uid=null) {
+ return $this->call_method('facebook.profile.getInfo',
+ array('uid' => $uid));
+ }
+
+ /**
+ * Returns the options associated with the specified info field for an
+ * application info section.
+ *
+ * @param string $field The title of the field
+ *
+ * @return array An array of info options.
+ */
+ public function &profile_getInfoOptions($field) {
+ return $this->call_method('facebook.profile.getInfoOptions',
+ array('field' => $field));
+ }
+
+ /**
+ * Configures an application info section that the specified user can install
+ * on the Info tab of her profile. For details on the structure of an info
+ * field, please see:
+ *
+ * http://wiki.developers.facebook.com/index.php/Profile.setInfo
+ *
+ * @param string $title Title / header of the info section
+ * @param int $type 1 for text-only, 5 for thumbnail views
+ * @param array $info_fields An array of info fields. See wiki for details.
+ * @param int $uid (Optional)
+ *
+ * @return bool true on success
+ */
+ public function &profile_setInfo($title, $type, $info_fields, $uid=null) {
+ return $this->call_method('facebook.profile.setInfo',
+ array('uid' => $uid,
+ 'type' => $type,
+ 'title' => $title,
+ 'info_fields' => json_encode($info_fields)));
+ }
+
+ /**
+ * Specifies the objects for a field for an application info section. These
+ * options populate the typeahead for a thumbnail.
+ *
+ * @param string $field The title of the field
+ * @param array $options An array of items for a thumbnail, including
+ * 'label', 'link', and optionally 'image',
+ * 'description' and 'sublabel'
+ *
+ * @return bool true on success
+ */
+ public function profile_setInfoOptions($field, $options) {
+ return $this->call_method('facebook.profile.setInfoOptions',
+ array('field' => $field,
+ 'options' => json_encode($options)));
+ }
+
+ /**
+ * Get all the marketplace categories.
+ *
+ * @return array A list of category names
+ */
+ function marketplace_getCategories() {
+ return $this->call_method('facebook.marketplace.getCategories',
+ array());
+ }
+
+ /**
+ * Get all the marketplace subcategories for a particular category.
+ *
+ * @param category The category for which we are pulling subcategories
+ *
+ * @return array A list of subcategory names
+ */
+ function marketplace_getSubCategories($category) {
+ return $this->call_method('facebook.marketplace.getSubCategories',
+ array('category' => $category));
+ }
+
+ /**
+ * Get listings by either listing_id or user.
+ *
+ * @param listing_ids An array of listing_ids (optional)
+ * @param uids An array of user ids (optional)
+ *
+ * @return array The data for matched listings
+ */
+ function marketplace_getListings($listing_ids, $uids) {
+ return $this->call_method('facebook.marketplace.getListings',
+ array('listing_ids' => $listing_ids, 'uids' => $uids));
+ }
+
+ /**
+ * Search for Marketplace listings. All arguments are optional, though at
+ * least one must be filled out to retrieve results.
+ *
+ * @param category The category in which to search (optional)
+ * @param subcategory The subcategory in which to search (optional)
+ * @param query A query string (optional)
+ *
+ * @return array The data for matched listings
+ */
+ function marketplace_search($category, $subcategory, $query) {
+ return $this->call_method('facebook.marketplace.search',
+ array('category' => $category,
+ 'subcategory' => $subcategory,
+ 'query' => $query));
+ }
+
+ /**
+ * Remove a listing from Marketplace.
+ *
+ * @param listing_id The id of the listing to be removed
+ * @param status 'SUCCESS', 'NOT_SUCCESS', or 'DEFAULT'
+ *
+ * @return bool True on success
+ */
+ function marketplace_removeListing($listing_id,
+ $status='DEFAULT',
+ $uid=null) {
+ return $this->call_method('facebook.marketplace.removeListing',
+ array('listing_id' => $listing_id,
+ 'status' => $status,
+ 'uid' => $uid));
+ }
+
+ /**
+ * Create/modify a Marketplace listing for the loggedinuser.
+ *
+ * @param int listing_id The id of a listing to be modified, 0
+ * for a new listing.
+ * @param show_on_profile bool Should we show this listing on the
+ * user's profile
+ * @param listing_attrs array An array of the listing data
+ *
+ * @return int The listing_id (unchanged if modifying an existing listing).
+ */
+ function marketplace_createListing($listing_id,
+ $show_on_profile,
+ $attrs,
+ $uid=null) {
+ return $this->call_method('facebook.marketplace.createListing',
+ array('listing_id' => $listing_id,
+ 'show_on_profile' => $show_on_profile,
+ 'listing_attrs' => json_encode($attrs),
+ 'uid' => $uid));
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Data Store API
+
+ /**
+ * Set a user preference.
+ *
+ * @param pref_id preference identifier (0-200)
+ * @param value preferece's value
+ * @param uid the user id (defaults to current session user)
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ * API_EC_PERMISSION_OTHER_USER
+ */
+ public function &data_setUserPreference($pref_id, $value, $uid = null) {
+ return $this->call_method('facebook.data.setUserPreference',
+ array('pref_id' => $pref_id,
+ 'value' => $value,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Set a user's all preferences for this application.
+ *
+ * @param values preferece values in an associative arrays
+ * @param replace whether to replace all existing preferences or
+ * merge into them.
+ * @param uid the user id (defaults to current session user)
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ * API_EC_PERMISSION_OTHER_USER
+ */
+ public function &data_setUserPreferences($values,
+ $replace = false,
+ $uid = null) {
+ return $this->call_method('facebook.data.setUserPreferences',
+ array('values' => json_encode($values),
+ 'replace' => $replace,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Get a user preference.
+ *
+ * @param pref_id preference identifier (0-200)
+ * @param uid the user id (defaults to current session user)
+ * @return preference's value
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ * API_EC_PERMISSION_OTHER_USER
+ */
+ public function &data_getUserPreference($pref_id, $uid = null) {
+ return $this->call_method('facebook.data.getUserPreference',
+ array('pref_id' => $pref_id,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Get a user preference.
+ *
+ * @param uid the user id (defaults to current session user)
+ * @return preference values
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ * API_EC_PERMISSION_OTHER_USER
+ */
+ public function &data_getUserPreferences($uid = null) {
+ return $this->call_method('facebook.data.getUserPreferences',
+ array('uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Create a new object type.
+ *
+ * @param name object type's name
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_ALREADY_EXISTS
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_createObjectType($name) {
+ return $this->call_method('facebook.data.createObjectType',
+ array('name' => $name));
+ }
+
+ /**
+ * Delete an object type.
+ *
+ * @param obj_type object type's name
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_dropObjectType($obj_type) {
+ return $this->call_method('facebook.data.dropObjectType',
+ array('obj_type' => $obj_type));
+ }
+
+ /**
+ * Rename an object type.
+ *
+ * @param obj_type object type's name
+ * @param new_name new object type's name
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_DATA_OBJECT_ALREADY_EXISTS
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_renameObjectType($obj_type, $new_name) {
+ return $this->call_method('facebook.data.renameObjectType',
+ array('obj_type' => $obj_type,
+ 'new_name' => $new_name));
+ }
+
+ /**
+ * Add a new property to an object type.
+ *
+ * @param obj_type object type's name
+ * @param prop_name name of the property to add
+ * @param prop_type 1: integer; 2: string; 3: text blob
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_ALREADY_EXISTS
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_defineObjectProperty($obj_type,
+ $prop_name,
+ $prop_type) {
+ return $this->call_method('facebook.data.defineObjectProperty',
+ array('obj_type' => $obj_type,
+ 'prop_name' => $prop_name,
+ 'prop_type' => $prop_type));
+ }
+
+ /**
+ * Remove a previously defined property from an object type.
+ *
+ * @param obj_type object type's name
+ * @param prop_name name of the property to remove
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_undefineObjectProperty($obj_type, $prop_name) {
+ return $this->call_method('facebook.data.undefineObjectProperty',
+ array('obj_type' => $obj_type,
+ 'prop_name' => $prop_name));
+ }
+
+ /**
+ * Rename a previously defined property of an object type.
+ *
+ * @param obj_type object type's name
+ * @param prop_name name of the property to rename
+ * @param new_name new name to use
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_DATA_OBJECT_ALREADY_EXISTS
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_renameObjectProperty($obj_type, $prop_name,
+ $new_name) {
+ return $this->call_method('facebook.data.renameObjectProperty',
+ array('obj_type' => $obj_type,
+ 'prop_name' => $prop_name,
+ 'new_name' => $new_name));
+ }
+
+ /**
+ * Retrieve a list of all object types that have defined for the application.
+ *
+ * @return a list of object type names
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PERMISSION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getObjectTypes() {
+ return $this->call_method('facebook.data.getObjectTypes', array());
+ }
+
+ /**
+ * Get definitions of all properties of an object type.
+ *
+ * @param obj_type object type's name
+ * @return pairs of property name and property types
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getObjectType($obj_type) {
+ return $this->call_method('facebook.data.getObjectType',
+ array('obj_type' => $obj_type));
+ }
+
+ /**
+ * Create a new object.
+ *
+ * @param obj_type object type's name
+ * @param properties (optional) properties to set initially
+ * @return newly created object's id
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_createObject($obj_type, $properties = null) {
+ return $this->call_method('facebook.data.createObject',
+ array('obj_type' => $obj_type,
+ 'properties' => json_encode($properties)));
+ }
+
+ /**
+ * Update an existing object.
+ *
+ * @param obj_id object's id
+ * @param properties new properties
+ * @param replace true for replacing existing properties;
+ * false for merging
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_updateObject($obj_id, $properties, $replace = false) {
+ return $this->call_method('facebook.data.updateObject',
+ array('obj_id' => $obj_id,
+ 'properties' => json_encode($properties),
+ 'replace' => $replace));
+ }
+
+ /**
+ * Delete an existing object.
+ *
+ * @param obj_id object's id
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_deleteObject($obj_id) {
+ return $this->call_method('facebook.data.deleteObject',
+ array('obj_id' => $obj_id));
+ }
+
+ /**
+ * Delete a list of objects.
+ *
+ * @param obj_ids objects to delete
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_deleteObjects($obj_ids) {
+ return $this->call_method('facebook.data.deleteObjects',
+ array('obj_ids' => json_encode($obj_ids)));
+ }
+
+ /**
+ * Get a single property value of an object.
+ *
+ * @param obj_id object's id
+ * @param prop_name individual property's name
+ * @return individual property's value
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getObjectProperty($obj_id, $prop_name) {
+ return $this->call_method('facebook.data.getObjectProperty',
+ array('obj_id' => $obj_id,
+ 'prop_name' => $prop_name));
+ }
+
+ /**
+ * Get properties of an object.
+ *
+ * @param obj_id object's id
+ * @param prop_names (optional) properties to return; null for all.
+ * @return specified properties of an object
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getObject($obj_id, $prop_names = null) {
+ return $this->call_method('facebook.data.getObject',
+ array('obj_id' => $obj_id,
+ 'prop_names' => json_encode($prop_names)));
+ }
+
+ /**
+ * Get properties of a list of objects.
+ *
+ * @param obj_ids object ids
+ * @param prop_names (optional) properties to return; null for all.
+ * @return specified properties of an object
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getObjects($obj_ids, $prop_names = null) {
+ return $this->call_method('facebook.data.getObjects',
+ array('obj_ids' => json_encode($obj_ids),
+ 'prop_names' => json_encode($prop_names)));
+ }
+
+ /**
+ * Set a single property value of an object.
+ *
+ * @param obj_id object's id
+ * @param prop_name individual property's name
+ * @param prop_value new value to set
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_setObjectProperty($obj_id, $prop_name,
+ $prop_value) {
+ return $this->call_method('facebook.data.setObjectProperty',
+ array('obj_id' => $obj_id,
+ 'prop_name' => $prop_name,
+ 'prop_value' => $prop_value));
+ }
+
+ /**
+ * Read hash value by key.
+ *
+ * @param obj_type object type's name
+ * @param key hash key
+ * @param prop_name (optional) individual property's name
+ * @return hash value
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getHashValue($obj_type, $key, $prop_name = null) {
+ return $this->call_method('facebook.data.getHashValue',
+ array('obj_type' => $obj_type,
+ 'key' => $key,
+ 'prop_name' => $prop_name));
+ }
+
+ /**
+ * Write hash value by key.
+ *
+ * @param obj_type object type's name
+ * @param key hash key
+ * @param value hash value
+ * @param prop_name (optional) individual property's name
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_setHashValue($obj_type,
+ $key,
+ $value,
+ $prop_name = null) {
+ return $this->call_method('facebook.data.setHashValue',
+ array('obj_type' => $obj_type,
+ 'key' => $key,
+ 'value' => $value,
+ 'prop_name' => $prop_name));
+ }
+
+ /**
+ * Increase a hash value by specified increment atomically.
+ *
+ * @param obj_type object type's name
+ * @param key hash key
+ * @param prop_name individual property's name
+ * @param increment (optional) default is 1
+ * @return incremented hash value
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_incHashValue($obj_type,
+ $key,
+ $prop_name,
+ $increment = 1) {
+ return $this->call_method('facebook.data.incHashValue',
+ array('obj_type' => $obj_type,
+ 'key' => $key,
+ 'prop_name' => $prop_name,
+ 'increment' => $increment));
+ }
+
+ /**
+ * Remove a hash key and its values.
+ *
+ * @param obj_type object type's name
+ * @param key hash key
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_removeHashKey($obj_type, $key) {
+ return $this->call_method('facebook.data.removeHashKey',
+ array('obj_type' => $obj_type,
+ 'key' => $key));
+ }
+
+ /**
+ * Remove hash keys and their values.
+ *
+ * @param obj_type object type's name
+ * @param keys hash keys
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_removeHashKeys($obj_type, $keys) {
+ return $this->call_method('facebook.data.removeHashKeys',
+ array('obj_type' => $obj_type,
+ 'keys' => json_encode($keys)));
+ }
+
+ /**
+ * Define an object association.
+ *
+ * @param name name of this association
+ * @param assoc_type 1: one-way 2: two-way symmetric 3: two-way asymmetric
+ * @param assoc_info1 needed info about first object type
+ * @param assoc_info2 needed info about second object type
+ * @param inverse (optional) name of reverse association
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_ALREADY_EXISTS
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_defineAssociation($name, $assoc_type, $assoc_info1,
+ $assoc_info2, $inverse = null) {
+ return $this->call_method('facebook.data.defineAssociation',
+ array('name' => $name,
+ 'assoc_type' => $assoc_type,
+ 'assoc_info1' => json_encode($assoc_info1),
+ 'assoc_info2' => json_encode($assoc_info2),
+ 'inverse' => $inverse));
+ }
+
+ /**
+ * Undefine an object association.
+ *
+ * @param name name of this association
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_undefineAssociation($name) {
+ return $this->call_method('facebook.data.undefineAssociation',
+ array('name' => $name));
+ }
+
+ /**
+ * Rename an object association or aliases.
+ *
+ * @param name name of this association
+ * @param new_name (optional) new name of this association
+ * @param new_alias1 (optional) new alias for object type 1
+ * @param new_alias2 (optional) new alias for object type 2
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_ALREADY_EXISTS
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_renameAssociation($name, $new_name, $new_alias1 = null,
+ $new_alias2 = null) {
+ return $this->call_method('facebook.data.renameAssociation',
+ array('name' => $name,
+ 'new_name' => $new_name,
+ 'new_alias1' => $new_alias1,
+ 'new_alias2' => $new_alias2));
+ }
+
+ /**
+ * Get definition of an object association.
+ *
+ * @param name name of this association
+ * @return specified association
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getAssociationDefinition($name) {
+ return $this->call_method('facebook.data.getAssociationDefinition',
+ array('name' => $name));
+ }
+
+ /**
+ * Get definition of all associations.
+ *
+ * @return all defined associations
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PERMISSION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getAssociationDefinitions() {
+ return $this->call_method('facebook.data.getAssociationDefinitions',
+ array());
+ }
+
+ /**
+ * Create or modify an association between two objects.
+ *
+ * @param name name of association
+ * @param obj_id1 id of first object
+ * @param obj_id2 id of second object
+ * @param data (optional) extra string data to store
+ * @param assoc_time (optional) extra time data; default to creation time
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_setAssociation($name, $obj_id1, $obj_id2, $data = null,
+ $assoc_time = null) {
+ return $this->call_method('facebook.data.setAssociation',
+ array('name' => $name,
+ 'obj_id1' => $obj_id1,
+ 'obj_id2' => $obj_id2,
+ 'data' => $data,
+ 'assoc_time' => $assoc_time));
+ }
+
+ /**
+ * Create or modify associations between objects.
+ *
+ * @param assocs associations to set
+ * @param name (optional) name of association
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_setAssociations($assocs, $name = null) {
+ return $this->call_method('facebook.data.setAssociations',
+ array('assocs' => json_encode($assocs),
+ 'name' => $name));
+ }
+
+ /**
+ * Remove an association between two objects.
+ *
+ * @param name name of association
+ * @param obj_id1 id of first object
+ * @param obj_id2 id of second object
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_removeAssociation($name, $obj_id1, $obj_id2) {
+ return $this->call_method('facebook.data.removeAssociation',
+ array('name' => $name,
+ 'obj_id1' => $obj_id1,
+ 'obj_id2' => $obj_id2));
+ }
+
+ /**
+ * Remove associations between objects by specifying pairs of object ids.
+ *
+ * @param assocs associations to remove
+ * @param name (optional) name of association
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_removeAssociations($assocs, $name = null) {
+ return $this->call_method('facebook.data.removeAssociations',
+ array('assocs' => json_encode($assocs),
+ 'name' => $name));
+ }
+
+ /**
+ * Remove associations between objects by specifying one object id.
+ *
+ * @param name name of association
+ * @param obj_id who's association to remove
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_removeAssociatedObjects($name, $obj_id) {
+ return $this->call_method('facebook.data.removeAssociatedObjects',
+ array('name' => $name,
+ 'obj_id' => $obj_id));
+ }
+
+ /**
+ * Retrieve a list of associated objects.
+ *
+ * @param name name of association
+ * @param obj_id who's association to retrieve
+ * @param no_data only return object ids
+ * @return associated objects
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getAssociatedObjects($name, $obj_id, $no_data = true) {
+ return $this->call_method('facebook.data.getAssociatedObjects',
+ array('name' => $name,
+ 'obj_id' => $obj_id,
+ 'no_data' => $no_data));
+ }
+
+ /**
+ * Count associated objects.
+ *
+ * @param name name of association
+ * @param obj_id who's association to retrieve
+ * @return associated object's count
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getAssociatedObjectCount($name, $obj_id) {
+ return $this->call_method('facebook.data.getAssociatedObjectCount',
+ array('name' => $name,
+ 'obj_id' => $obj_id));
+ }
+
+ /**
+ * Get a list of associated object counts.
+ *
+ * @param name name of association
+ * @param obj_ids whose association to retrieve
+ * @return associated object counts
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_DATA_OBJECT_NOT_FOUND
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_INVALID_OPERATION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getAssociatedObjectCounts($name, $obj_ids) {
+ return $this->call_method('facebook.data.getAssociatedObjectCounts',
+ array('name' => $name,
+ 'obj_ids' => json_encode($obj_ids)));
+ }
+
+ /**
+ * Find all associations between two objects.
+ *
+ * @param obj_id1 id of first object
+ * @param obj_id2 id of second object
+ * @param no_data only return association names without data
+ * @return all associations between objects
+ * @error
+ * API_EC_DATA_DATABASE_ERROR
+ * API_EC_PARAM
+ * API_EC_PERMISSION
+ * API_EC_DATA_QUOTA_EXCEEDED
+ * API_EC_DATA_UNKNOWN_ERROR
+ */
+ public function &data_getAssociations($obj_id1, $obj_id2, $no_data = true) {
+ return $this->call_method('facebook.data.getAssociations',
+ array('obj_id1' => $obj_id1,
+ 'obj_id2' => $obj_id2,
+ 'no_data' => $no_data));
+ }
+
+ /**
+ * Get the properties that you have set for an app.
+ *
+ * @param properties List of properties names to fetch
+ *
+ * @return array A map from property name to value
+ */
+ public function admin_getAppProperties($properties) {
+ return json_decode(
+ $this->call_method('facebook.admin.getAppProperties',
+ array('properties' => json_encode($properties))), true);
+ }
+
+ /**
+ * Set properties for an app.
+ *
+ * @param properties A map from property names to values
+ *
+ * @return bool true on success
+ */
+ public function admin_setAppProperties($properties) {
+ return $this->call_method('facebook.admin.setAppProperties',
+ array('properties' => json_encode($properties)));
+ }
+
+ /**
+ * Returns the allocation limit value for a specified integration point name
+ * Integration point names are defined in lib/api/karma/constants.php in the
+ * limit_map.
+ *
+ * @param string $integration_point_name Name of an integration point
+ * (see developer wiki for list).
+ *
+ * @return int Integration point allocation value
+ */
+ public function &admin_getAllocation($integration_point_name) {
+ return $this->call_method('facebook.admin.getAllocation',
+ array('integration_point_name' => $integration_point_name));
+ }
+
+ /**
+ * Returns values for the specified metrics for the current application, in
+ * the given time range. The metrics are collected for fixed-length periods,
+ * and the times represent midnight at the end of each period.
+ *
+ * @param start_time unix time for the start of the range
+ * @param end_time unix time for the end of the range
+ * @param period number of seconds in the desired period
+ * @param metrics list of metrics to look up
+ *
+ * @return array A map of the names and values for those metrics
+ */
+ public function &admin_getMetrics($start_time, $end_time, $period, $metrics) {
+ return $this->call_method('admin.getMetrics',
+ array('start_time' => $start_time,
+ 'end_time' => $end_time,
+ 'period' => $period,
+ 'metrics' => json_encode($metrics)));
+ }
+
+ /**
+ * Sets application restriction info.
+ *
+ * Applications can restrict themselves to only a limited user demographic
+ * based on users' age and/or location or based on static predefined types
+ * specified by facebook for specifying diff age restriction for diff
+ * locations.
+ *
+ * @param array $restriction_info The age restriction settings to set.
+ *
+ * @return bool true on success
+ */
+ public function admin_setRestrictionInfo($restriction_info = null) {
+ $restriction_str = null;
+ if (!empty($restriction_info)) {
+ $restriction_str = json_encode($restriction_info);
+ }
+ return $this->call_method('admin.setRestrictionInfo',
+ array('restriction_str' => $restriction_str));
+ }
+
+ /**
+ * Gets application restriction info.
+ *
+ * Applications can restrict themselves to only a limited user demographic
+ * based on users' age and/or location or based on static predefined types
+ * specified by facebook for specifying diff age restriction for diff
+ * locations.
+ *
+ * @return array The age restriction settings for this application.
+ */
+ public function admin_getRestrictionInfo() {
+ return json_decode(
+ $this->call_method('admin.getRestrictionInfo', array()),
+ true);
+ }
+
+ /* UTILITY FUNCTIONS */
+
+ /**
+ * Calls the specified method with the specified parameters.
+ *
+ * @param string $method Name of the Facebook method to invoke
+ * @param array $params A map of param names => param values
+ *
+ * @return mixed Result of method call
+ */
+ public function & call_method($method, $params) {
+ //Check if we are in batch mode
+ if($this->batch_queue === null) {
+ if ($this->call_as_apikey) {
+ $params['call_as_apikey'] = $this->call_as_apikey;
+ }
+ $xml = $this->post_request($method, $params);
+ $result = $this->convert_xml_to_result($xml, $method, $params);
+
+ if (is_array($result) && isset($result['error_code'])) {
+ throw new FacebookRestClientException($result['error_msg'],
+ $result['error_code']);
+ }
+ }
+ else {
+ $result = null;
+ $batch_item = array('m' => $method, 'p' => $params, 'r' => & $result);
+ $this->batch_queue[] = $batch_item;
+ }
+
+ return $result;
+ }
+
+ private function convert_xml_to_result($xml, $method, $params) {
+ $sxml = simplexml_load_string($xml);
+ $result = self::convert_simplexml_to_array($sxml);
+
+
+ if (!empty($GLOBALS['facebook_config']['debug'])) {
+ // output the raw xml and its corresponding php object, for debugging:
+ print '<div style="margin: 10px 30px; padding: 5px; border: 2px solid black; background: gray; color: white; font-size: 12px; font-weight: bold;">';
+ $this->cur_id++;
+ print $this->cur_id . ': Called ' . $method . ', show ' .
+ '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'params\');">Params</a> | '.
+ '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'xml\');">XML</a> | '.
+ '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'sxml\');">SXML</a> | '.
+ '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'php\');">PHP</a>';
+ print '<pre id="params'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($params, true).'</pre>';
+ print '<pre id="xml'.$this->cur_id.'" style="display: none; overflow: auto;">'.htmlspecialchars($xml).'</pre>';
+ print '<pre id="php'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($result, true).'</pre>';
+ print '<pre id="sxml'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($sxml, true).'</pre>';
+ print '</div>';
+ }
+ return $result;
+ }
+
+ private function create_post_string($method, $params) {
+ $params['method'] = $method;
+ $params['session_key'] = $this->session_key;
+ $params['api_key'] = $this->api_key;
+ $params['call_id'] = microtime(true);
+ if ($params['call_id'] <= $this->last_call_id) {
+ $params['call_id'] = $this->last_call_id + 0.001;
+ }
+ $this->last_call_id = $params['call_id'];
+ if (!isset($params['v'])) {
+ $params['v'] = '1.0';
+ }
+ $post_params = array();
+ foreach ($params as $key => &$val) {
+ if (is_array($val)) $val = implode(',', $val);
+ $post_params[] = $key.'='.urlencode($val);
+ }
+ $secret = $this->secret;
+ $post_params[] = 'sig='.Facebook::generate_sig($params, $secret);
+ return implode('&', $post_params);
+ }
+
+ public function post_request($method, $params) {
+
+ $post_string = $this->create_post_string($method, $params);
+
+ if (function_exists('curl_init')) {
+ // Use CURL if installed...
+ $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $this->server_addr);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
+ $result = curl_exec($ch);
+ curl_close($ch);
+ } else {
+ // Non-CURL based version...
+ $content_type = 'application/x-www-form-urlencoded';
+ $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) '.phpversion();
+ $context =
+ array('http' =>
+ array('method' => 'POST',
+ 'header' => 'Content-type: '.$content_type."\r\n".
+ 'User-Agent: '.$user_agent."\r\n".
+ 'Content-length: ' . strlen($post_string),
+ 'content' => $post_string));
+ $contextid=stream_context_create($context);
+ $sock=fopen($this->server_addr, 'r', false, $contextid);
+ if ($sock) {
+ $result='';
+ while (!feof($sock))
+ $result.=fgets($sock, 4096);
+
+ fclose($sock);
+ }
+ }
+ return $result;
+ }
+
+ public static function convert_simplexml_to_array($sxml) {
+ $arr = array();
+ if ($sxml) {
+ foreach ($sxml as $k => $v) {
+ if ($sxml['list']) {
+ $arr[] = self::convert_simplexml_to_array($v);
+ } else {
+ $arr[$k] = self::convert_simplexml_to_array($v);
+ }
+ }
+ }
+ if (sizeof($arr) > 0) {
+ return $arr;
+ } else {
+ return (string)$sxml;
+ }
+ }
+
+ private function get_uid($uid) {
+ return $uid ? $uid : $this->user;
+ }
+}
+
+
+class FacebookRestClientException extends Exception {
+}
+
+// Supporting methods and values------
+
+/**
+ * Error codes and descriptions for the Facebook API.
+ */
+
+class FacebookAPIErrorCodes {
+
+ const API_EC_SUCCESS = 0;
+
+ /*
+ * GENERAL ERRORS
+ */
+ const API_EC_UNKNOWN = 1;
+ const API_EC_SERVICE = 2;
+ const API_EC_METHOD = 3;
+ const API_EC_TOO_MANY_CALLS = 4;
+ const API_EC_BAD_IP = 5;
+
+ /*
+ * PARAMETER ERRORS
+ */
+ const API_EC_PARAM = 100;
+ const API_EC_PARAM_API_KEY = 101;
+ const API_EC_PARAM_SESSION_KEY = 102;
+ const API_EC_PARAM_CALL_ID = 103;
+ const API_EC_PARAM_SIGNATURE = 104;
+ const API_EC_PARAM_USER_ID = 110;
+ const API_EC_PARAM_USER_FIELD = 111;
+ const API_EC_PARAM_SOCIAL_FIELD = 112;
+ const API_EC_PARAM_ALBUM_ID = 120;
+ const API_EC_PARAM_BAD_EID = 150;
+ const API_EC_PARAM_UNKNOWN_CITY = 151;
+
+ /*
+ * USER PERMISSIONS ERRORS
+ */
+ const API_EC_PERMISSION = 200;
+ const API_EC_PERMISSION_USER = 210;
+ const API_EC_PERMISSION_ALBUM = 220;
+ const API_EC_PERMISSION_PHOTO = 221;
+ const API_EC_PERMISSION_EVENT = 290;
+ const API_EC_PERMISSION_RSVP_EVENT = 299;
+
+ const FQL_EC_PARSER = 601;
+ const FQL_EC_UNKNOWN_FIELD = 602;
+ const FQL_EC_UNKNOWN_TABLE = 603;
+ const FQL_EC_NOT_INDEXABLE = 604;
+
+ /**
+ * DATA STORE API ERRORS
+ */
+ const API_EC_DATA_UNKNOWN_ERROR = 800;
+ const API_EC_DATA_INVALID_OPERATION = 801;
+ const API_EC_DATA_QUOTA_EXCEEDED = 802;
+ const API_EC_DATA_OBJECT_NOT_FOUND = 803;
+ const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804;
+ const API_EC_DATA_DATABASE_ERROR = 805;
+
+ /*
+ * Batch ERROR
+ */
+ const API_EC_BATCH_ALREADY_STARTED = 900;
+ const API_EC_BATCH_NOT_STARTED = 901;
+ const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 902;
+
+ public static $api_error_descriptions = array(
+ API_EC_SUCCESS => 'Success',
+ API_EC_UNKNOWN => 'An unknown error occurred',
+ API_EC_SERVICE => 'Service temporarily unavailable',
+ API_EC_METHOD => 'Unknown method',
+ API_EC_TOO_MANY_CALLS => 'Application request limit reached',
+ API_EC_BAD_IP => 'Unauthorized source IP address',
+ API_EC_PARAM => 'Invalid parameter',
+ API_EC_PARAM_API_KEY => 'Invalid API key',
+ API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
+ API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous',
+ API_EC_PARAM_SIGNATURE => 'Incorrect signature',
+ API_EC_PARAM_USER_ID => 'Invalid user id',
+ API_EC_PARAM_USER_FIELD => 'Invalid user info field',
+ API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
+ API_EC_PARAM_ALBUM_ID => 'Invalid album id',
+ API_EC_PARAM_BAD_EID => 'Invalid eid',
+ API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
+ API_EC_PERMISSION => 'Permissions error',
+ API_EC_PERMISSION_USER => 'User not visible',
+ API_EC_PERMISSION_ALBUM => 'Album not visible',
+ API_EC_PERMISSION_PHOTO => 'Photo not visible',
+ API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event',
+ API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
+ FQL_EC_PARSER => 'FQL: Parser Error',
+ FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field',
+ FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table',
+ FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable',
+ FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function',
+ FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in',
+ API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
+ API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
+ API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
+ API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
+ API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
+ API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
+ API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
+ API_EC_BATCH_NOT_STARTED => 'end_batch called before start_batch',
+ API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode',
+ );
+}
diff --git a/extlib/facebook/jsonwrapper/JSON/JSON.php b/extlib/facebook/jsonwrapper/JSON/JSON.php
new file mode 100644
index 000000000..0cddbddb4
--- /dev/null
+++ b/extlib/facebook/jsonwrapper/JSON/JSON.php
@@ -0,0 +1,806 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package Services_JSON
+ * @author Michal Migurski <mike-json@teczno.com>
+ * @author Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright 2005 Michal Migurski
+ * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE', 1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR', 2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR', 3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ', 4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+ /**
+ * constructs a new JSON instance
+ *
+ * @param int $use object behavior flags; combine with boolean-OR
+ *
+ * possible values:
+ * - SERVICES_JSON_LOOSE_TYPE: loose typing.
+ * "{...}" syntax creates associative arrays
+ * instead of objects in decode().
+ * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
+ * Values which can't be encoded (e.g. resources)
+ * appear as NULL instead of throwing errors.
+ * By default, a deeply-nested resource will
+ * bubble up with an error, so all return values
+ * from encode() should be checked with isError()
+ */
+ function Services_JSON($use = 0)
+ {
+ $this->use = $use;
+ }
+
+ /**
+ * convert a string from one UTF-16 char to one UTF-8 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf16 UTF-16 character
+ * @return string UTF-8 character
+ * @access private
+ */
+ function utf162utf8($utf16)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+ }
+
+ $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+ switch(true) {
+ case ((0x7F & $bytes) == $bytes):
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x7F & $bytes);
+
+ case (0x07FF & $bytes) == $bytes:
+ // return a 2-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xC0 | (($bytes >> 6) & 0x1F))
+ . chr(0x80 | ($bytes & 0x3F));
+
+ case (0xFFFF & $bytes) == $bytes:
+ // return a 3-byte UTF-8 character
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0xE0 | (($bytes >> 12) & 0x0F))
+ . chr(0x80 | (($bytes >> 6) & 0x3F))
+ . chr(0x80 | ($bytes & 0x3F));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * convert a string from one UTF-8 char to one UTF-16 char
+ *
+ * Normally should be handled by mb_convert_encoding, but
+ * provides a slower PHP-only method for installations
+ * that lack the multibye string extension.
+ *
+ * @param string $utf8 UTF-8 character
+ * @return string UTF-16 character
+ * @access private
+ */
+ function utf82utf16($utf8)
+ {
+ // oh please oh please oh please oh please oh please
+ if(function_exists('mb_convert_encoding')) {
+ return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+ }
+
+ switch(strlen($utf8)) {
+ case 1:
+ // this case should never be reached, because we are in ASCII range
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return $utf8;
+
+ case 2:
+ // return a UTF-16 character from a 2-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr(0x07 & (ord($utf8{0}) >> 2))
+ . chr((0xC0 & (ord($utf8{0}) << 6))
+ | (0x3F & ord($utf8{1})));
+
+ case 3:
+ // return a UTF-16 character from a 3-byte UTF-8 char
+ // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ return chr((0xF0 & (ord($utf8{0}) << 4))
+ | (0x0F & (ord($utf8{1}) >> 2)))
+ . chr((0xC0 & (ord($utf8{1}) << 6))
+ | (0x7F & ord($utf8{2})));
+ }
+
+ // ignoring UTF-32 for now, sorry
+ return '';
+ }
+
+ /**
+ * encodes an arbitrary variable into JSON format
+ *
+ * @param mixed $var any number, boolean, string, array, or object to be encoded.
+ * see argument 1 to Services_JSON() above for array-parsing behavior.
+ * if var is a strng, note that encode() always expects it
+ * to be in ASCII or UTF-8 format!
+ *
+ * @return mixed JSON string representation of input var or an error if a problem occurs
+ * @access public
+ */
+ function encode($var)
+ {
+ switch (gettype($var)) {
+ case 'boolean':
+ return $var ? 'true' : 'false';
+
+ case 'NULL':
+ return 'null';
+
+ case 'integer':
+ return (int) $var;
+
+ case 'double':
+ case 'float':
+ return (float) $var;
+
+ case 'string':
+ // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+ $ascii = '';
+ $strlen_var = strlen($var);
+
+ /*
+ * Iterate over every character in the string,
+ * escaping with a slash or encoding to UTF-8 where necessary
+ */
+ for ($c = 0; $c < $strlen_var; ++$c) {
+
+ $ord_var_c = ord($var{$c});
+
+ switch (true) {
+ case $ord_var_c == 0x08:
+ $ascii .= '\b';
+ break;
+ case $ord_var_c == 0x09:
+ $ascii .= '\t';
+ break;
+ case $ord_var_c == 0x0A:
+ $ascii .= '\n';
+ break;
+ case $ord_var_c == 0x0C:
+ $ascii .= '\f';
+ break;
+ case $ord_var_c == 0x0D:
+ $ascii .= '\r';
+ break;
+
+ case $ord_var_c == 0x22:
+ case $ord_var_c == 0x2F:
+ case $ord_var_c == 0x5C:
+ // double quote, slash, slosh
+ $ascii .= '\\'.$var{$c};
+ break;
+
+ case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+ // characters U-00000000 - U-0000007F (same as ASCII)
+ $ascii .= $var{$c};
+ break;
+
+ case (($ord_var_c & 0xE0) == 0xC0):
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+ $c += 1;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF0) == 0xE0):
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}));
+ $c += 2;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xF8) == 0xF0):
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}));
+ $c += 3;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFC) == 0xF8):
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}));
+ $c += 4;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+
+ case (($ord_var_c & 0xFE) == 0xFC):
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $char = pack('C*', $ord_var_c,
+ ord($var{$c + 1}),
+ ord($var{$c + 2}),
+ ord($var{$c + 3}),
+ ord($var{$c + 4}),
+ ord($var{$c + 5}));
+ $c += 5;
+ $utf16 = $this->utf82utf16($char);
+ $ascii .= sprintf('\u%04s', bin2hex($utf16));
+ break;
+ }
+ }
+
+ return '"'.$ascii.'"';
+
+ case 'array':
+ /*
+ * As per JSON spec if any array key is not an integer
+ * we must treat the the whole array as an object. We
+ * also try to catch a sparsely populated associative
+ * array with numeric keys here because some JS engines
+ * will create an array with empty indexes up to
+ * max_index which can cause memory issues and because
+ * the keys, which may be relevant, will be remapped
+ * otherwise.
+ *
+ * As per the ECMA and JSON specification an object may
+ * have any string as a property. Unfortunately due to
+ * a hole in the ECMA specification if the key is a
+ * ECMA reserved word or starts with a digit the
+ * parameter is only accessible using ECMAScript's
+ * bracket notation.
+ */
+
+ // treat as a JSON object
+ if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($var),
+ array_values($var));
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . join(',', $properties) . '}';
+ }
+
+ // treat it like a regular array
+ $elements = array_map(array($this, 'encode'), $var);
+
+ foreach($elements as $element) {
+ if(Services_JSON::isError($element)) {
+ return $element;
+ }
+ }
+
+ return '[' . join(',', $elements) . ']';
+
+ case 'object':
+ $vars = get_object_vars($var);
+
+ $properties = array_map(array($this, 'name_value'),
+ array_keys($vars),
+ array_values($vars));
+
+ foreach($properties as $property) {
+ if(Services_JSON::isError($property)) {
+ return $property;
+ }
+ }
+
+ return '{' . join(',', $properties) . '}';
+
+ default:
+ return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+ ? 'null'
+ : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+ }
+ }
+
+ /**
+ * array-walking function for use in generating JSON-formatted name-value pairs
+ *
+ * @param string $name name of key to use
+ * @param mixed $value reference to an array element to be encoded
+ *
+ * @return string JSON-formatted name-value pair, like '"name":value'
+ * @access private
+ */
+ function name_value($name, $value)
+ {
+ $encoded_value = $this->encode($value);
+
+ if(Services_JSON::isError($encoded_value)) {
+ return $encoded_value;
+ }
+
+ return $this->encode(strval($name)) . ':' . $encoded_value;
+ }
+
+ /**
+ * reduce a string by removing leading and trailing comments and whitespace
+ *
+ * @param $str string string value to strip of comments and whitespace
+ *
+ * @return string string value stripped of comments and whitespace
+ * @access private
+ */
+ function reduce_string($str)
+ {
+ $str = preg_replace(array(
+
+ // eliminate single line comments in '// ...' form
+ '#^\s*//(.+)$#m',
+
+ // eliminate multi-line comments in '/* ... */' form, at start of string
+ '#^\s*/\*(.+)\*/#Us',
+
+ // eliminate multi-line comments in '/* ... */' form, at end of string
+ '#/\*(.+)\*/\s*$#Us'
+
+ ), '', $str);
+
+ // eliminate extraneous space
+ return trim($str);
+ }
+
+ /**
+ * decodes a JSON string into appropriate variable
+ *
+ * @param string $str JSON-formatted string
+ *
+ * @return mixed number, boolean, string, array, or object
+ * corresponding to given JSON input string.
+ * See argument 1 to Services_JSON() above for object-output behavior.
+ * Note that decode() always returns strings
+ * in ASCII or UTF-8 format!
+ * @access public
+ */
+ function decode($str)
+ {
+ $str = $this->reduce_string($str);
+
+ switch (strtolower($str)) {
+ case 'true':
+ return true;
+
+ case 'false':
+ return false;
+
+ case 'null':
+ return null;
+
+ default:
+ $m = array();
+
+ if (is_numeric($str)) {
+ // Lookie-loo, it's a number
+
+ // This would work on its own, but I'm trying to be
+ // good about returning integers where appropriate:
+ // return (float)$str;
+
+ // Return float or int, as appropriate
+ return ((float)$str == (integer)$str)
+ ? (integer)$str
+ : (float)$str;
+
+ } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+ // STRINGS RETURNED IN UTF-8 FORMAT
+ $delim = substr($str, 0, 1);
+ $chrs = substr($str, 1, -1);
+ $utf8 = '';
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+ $ord_chrs_c = ord($chrs{$c});
+
+ switch (true) {
+ case $substr_chrs_c_2 == '\b':
+ $utf8 .= chr(0x08);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\t':
+ $utf8 .= chr(0x09);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\n':
+ $utf8 .= chr(0x0A);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\f':
+ $utf8 .= chr(0x0C);
+ ++$c;
+ break;
+ case $substr_chrs_c_2 == '\r':
+ $utf8 .= chr(0x0D);
+ ++$c;
+ break;
+
+ case $substr_chrs_c_2 == '\\"':
+ case $substr_chrs_c_2 == '\\\'':
+ case $substr_chrs_c_2 == '\\\\':
+ case $substr_chrs_c_2 == '\\/':
+ if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+ ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+ $utf8 .= $chrs{++$c};
+ }
+ break;
+
+ case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+ // single, escaped unicode character
+ $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+ . chr(hexdec(substr($chrs, ($c + 4), 2)));
+ $utf8 .= $this->utf162utf8($utf16);
+ $c += 5;
+ break;
+
+ case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+ $utf8 .= $chrs{$c};
+ break;
+
+ case ($ord_chrs_c & 0xE0) == 0xC0:
+ // characters U-00000080 - U-000007FF, mask 110XXXXX
+ //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 2);
+ ++$c;
+ break;
+
+ case ($ord_chrs_c & 0xF0) == 0xE0:
+ // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 3);
+ $c += 2;
+ break;
+
+ case ($ord_chrs_c & 0xF8) == 0xF0:
+ // characters U-00010000 - U-001FFFFF, mask 11110XXX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 4);
+ $c += 3;
+ break;
+
+ case ($ord_chrs_c & 0xFC) == 0xF8:
+ // characters U-00200000 - U-03FFFFFF, mask 111110XX
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 5);
+ $c += 4;
+ break;
+
+ case ($ord_chrs_c & 0xFE) == 0xFC:
+ // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+ // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+ $utf8 .= substr($chrs, $c, 6);
+ $c += 5;
+ break;
+
+ }
+
+ }
+
+ return $utf8;
+
+ } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+ // array, or object notation
+
+ if ($str{0} == '[') {
+ $stk = array(SERVICES_JSON_IN_ARR);
+ $arr = array();
+ } else {
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = array();
+ } else {
+ $stk = array(SERVICES_JSON_IN_OBJ);
+ $obj = new stdClass();
+ }
+ }
+
+ array_push($stk, array('what' => SERVICES_JSON_SLICE,
+ 'where' => 0,
+ 'delim' => false));
+
+ $chrs = substr($str, 1, -1);
+ $chrs = $this->reduce_string($chrs);
+
+ if ($chrs == '') {
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } else {
+ return $obj;
+
+ }
+ }
+
+ //print("\nparsing {$chrs}\n");
+
+ $strlen_chrs = strlen($chrs);
+
+ for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+ $top = end($stk);
+ $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+ if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+ // found a comma that is not inside a string, array, etc.,
+ // OR we've reached the end of the character list
+ $slice = substr($chrs, $top['where'], ($c - $top['where']));
+ array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+ //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ // we are in an array, so just push an element onto the stack
+ array_push($arr, $this->decode($slice));
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ // we are in an object, so figure
+ // out the property name and set an
+ // element in an associative array,
+ // for now
+ $parts = array();
+
+ if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // "name":value pair
+ $key = $this->decode($parts[1]);
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+ // name:value pair, where name is unquoted
+ $key = $parts[1];
+ $val = $this->decode($parts[2]);
+
+ if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+ $obj[$key] = $val;
+ } else {
+ $obj->$key = $val;
+ }
+ }
+
+ }
+
+ } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+ // found a quote, and we are not inside a string
+ array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+ //print("Found start of string at {$c}\n");
+
+ } elseif (($chrs{$c} == $top['delim']) &&
+ ($top['what'] == SERVICES_JSON_IN_STR) &&
+ ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+ // found a quote, we're in a string, and it's not escaped
+ // we know that it's not escaped becase there is _not_ an
+ // odd number of backslashes at the end of the string so far
+ array_pop($stk);
+ //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '[') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-bracket, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+ //print("Found start of array at {$c}\n");
+
+ } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+ // found a right-bracket, and we're in an array
+ array_pop($stk);
+ //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($chrs{$c} == '{') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a left-brace, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+ //print("Found start of object at {$c}\n");
+
+ } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+ // found a right-brace, and we're in an object
+ array_pop($stk);
+ //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ } elseif (($substr_chrs_c_2 == '/*') &&
+ in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+ // found a comment start, and we are in an array, object, or slice
+ array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+ $c++;
+ //print("Found start of comment at {$c}\n");
+
+ } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+ // found a comment end, and we're in one now
+ array_pop($stk);
+ $c++;
+
+ for ($i = $top['where']; $i <= $c; ++$i)
+ $chrs = substr_replace($chrs, ' ', $i, 1);
+
+ //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+ }
+
+ }
+
+ if (reset($stk) == SERVICES_JSON_IN_ARR) {
+ return $arr;
+
+ } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+ return $obj;
+
+ }
+
+ }
+ }
+ }
+
+ /**
+ * @todo Ultimately, this should just call PEAR::isError()
+ */
+ function isError($data, $code = null)
+ {
+ if (class_exists('pear')) {
+ return PEAR::isError($data, $code);
+ } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+ is_subclass_of($data, 'services_json_error'))) {
+ return true;
+ }
+
+ return false;
+ }
+}
+
+if (class_exists('PEAR_Error')) {
+
+ class Services_JSON_Error extends PEAR_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+ parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+ }
+ }
+
+} else {
+
+ /**
+ * @todo Ultimately, this class shall be descended from PEAR_Error
+ */
+ class Services_JSON_Error
+ {
+ function Services_JSON_Error($message = 'unknown error', $code = null,
+ $mode = null, $options = null, $userinfo = null)
+ {
+
+ }
+ }
+
+}
+
+?>
diff --git a/extlib/facebook/jsonwrapper/JSON/LICENSE b/extlib/facebook/jsonwrapper/JSON/LICENSE
new file mode 100644
index 000000000..4ae6bef55
--- /dev/null
+++ b/extlib/facebook/jsonwrapper/JSON/LICENSE
@@ -0,0 +1,21 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/extlib/facebook/jsonwrapper/jsonwrapper.php b/extlib/facebook/jsonwrapper/jsonwrapper.php
new file mode 100644
index 000000000..29509deba
--- /dev/null
+++ b/extlib/facebook/jsonwrapper/jsonwrapper.php
@@ -0,0 +1,6 @@
+<?php
+# In PHP 5.2 or higher we don't need to bring this in
+if (!function_exists('json_encode')) {
+ require_once 'jsonwrapper_inner.php';
+}
+?>
diff --git a/extlib/facebook/jsonwrapper/jsonwrapper_inner.php b/extlib/facebook/jsonwrapper/jsonwrapper_inner.php
new file mode 100644
index 000000000..36a3f2863
--- /dev/null
+++ b/extlib/facebook/jsonwrapper/jsonwrapper_inner.php
@@ -0,0 +1,23 @@
+<?php
+
+require_once 'JSON/JSON.php';
+
+function json_encode($arg)
+{
+ global $services_json;
+ if (!isset($services_json)) {
+ $services_json = new Services_JSON();
+ }
+ return $services_json->encode($arg);
+}
+
+function json_decode($arg)
+{
+ global $services_json;
+ if (!isset($services_json)) {
+ $services_json = new Services_JSON();
+ }
+ return $services_json->decode($arg);
+}
+
+?>
diff --git a/extlib/get_temp_dir.php b/extlib/get_temp_dir.php
new file mode 100644
index 000000000..4ec96e522
--- /dev/null
+++ b/extlib/get_temp_dir.php
@@ -0,0 +1,14 @@
+<?php
+if ( !function_exists('sys_get_temp_dir')) {
+ function sys_get_temp_dir() {
+ if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); }
+ if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); }
+ if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); }
+ $tempfile=tempnam(uniqid(rand(),TRUE),'');
+ if (file_exists($tempfile)) {
+ unlink($tempfile);
+ }
+ return realpath(dirname($tempfile));
+ }
+}
+?>
diff --git a/extlib/gpl-2.0.txt b/extlib/gpl-2.0.txt
new file mode 100644
index 000000000..d511905c1
--- /dev/null
+++ b/extlib/gpl-2.0.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/extlib/markdown.php b/extlib/markdown.php
new file mode 100644
index 000000000..8179b568b
--- /dev/null
+++ b/extlib/markdown.php
@@ -0,0 +1,1710 @@
+<?php
+#
+# Markdown - A text-to-HTML conversion tool for web writers
+#
+# PHP Markdown
+# Copyright (c) 2004-2008 Michel Fortin
+# <http://www.michelf.com/projects/php-markdown/>
+#
+# Original Markdown
+# Copyright (c) 2004-2006 John Gruber
+# <http://daringfireball.net/projects/markdown/>
+#
+
+
+define( 'MARKDOWN_VERSION', "1.0.1m" ); # Sat 21 Jun 2008
+
+
+#
+# Global default settings:
+#
+
+# Change to ">" for HTML output
+@define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");
+
+# Define the width of a tab for code blocks.
+@define( 'MARKDOWN_TAB_WIDTH', 4 );
+
+
+#
+# WordPress settings:
+#
+
+# Change to false to remove Markdown from posts and/or comments.
+@define( 'MARKDOWN_WP_POSTS', true );
+@define( 'MARKDOWN_WP_COMMENTS', true );
+
+
+
+### Standard Function Interface ###
+
+@define( 'MARKDOWN_PARSER_CLASS', 'Markdown_Parser' );
+
+function Markdown($text) {
+#
+# Initialize the parser and return the result of its transform method.
+#
+ # Setup static parser variable.
+ static $parser;
+ if (!isset($parser)) {
+ $parser_class = MARKDOWN_PARSER_CLASS;
+ $parser = new $parser_class;
+ }
+
+ # Transform text using parser.
+ return $parser->transform($text);
+}
+
+
+### WordPress Plugin Interface ###
+
+/*
+Plugin Name: Markdown
+Plugin URI: http://www.michelf.com/projects/php-markdown/
+Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>
+Version: 1.0.1m
+Author: Michel Fortin
+Author URI: http://www.michelf.com/
+*/
+
+if (isset($wp_version)) {
+ # More details about how it works here:
+ # <http://www.michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/>
+
+ # Post content and excerpts
+ # - Remove WordPress paragraph generator.
+ # - Run Markdown on excerpt, then remove all tags.
+ # - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
+ if (MARKDOWN_WP_POSTS) {
+ remove_filter('the_content', 'wpautop');
+ remove_filter('the_content_rss', 'wpautop');
+ remove_filter('the_excerpt', 'wpautop');
+ add_filter('the_content', 'Markdown', 6);
+ add_filter('the_content_rss', 'Markdown', 6);
+ add_filter('get_the_excerpt', 'Markdown', 6);
+ add_filter('get_the_excerpt', 'trim', 7);
+ add_filter('the_excerpt', 'mdwp_add_p');
+ add_filter('the_excerpt_rss', 'mdwp_strip_p');
+
+ remove_filter('content_save_pre', 'balanceTags', 50);
+ remove_filter('excerpt_save_pre', 'balanceTags', 50);
+ add_filter('the_content', 'balanceTags', 50);
+ add_filter('get_the_excerpt', 'balanceTags', 9);
+ }
+
+ # Comments
+ # - Remove WordPress paragraph generator.
+ # - Remove WordPress auto-link generator.
+ # - Scramble important tags before passing them to the kses filter.
+ # - Run Markdown on excerpt then remove paragraph tags.
+ if (MARKDOWN_WP_COMMENTS) {
+ remove_filter('comment_text', 'wpautop', 30);
+ remove_filter('comment_text', 'make_clickable');
+ add_filter('pre_comment_content', 'Markdown', 6);
+ add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
+ add_filter('pre_comment_content', 'mdwp_show_tags', 12);
+ add_filter('get_comment_text', 'Markdown', 6);
+ add_filter('get_comment_excerpt', 'Markdown', 6);
+ add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
+
+ global $mdwp_hidden_tags, $mdwp_placeholders;
+ $mdwp_hidden_tags = explode(' ',
+ '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
+ $mdwp_placeholders = explode(' ', str_rot13(
+ 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
+ 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
+ }
+
+ function mdwp_add_p($text) {
+ if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
+ $text = '<p>'.$text.'</p>';
+ $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
+ }
+ return $text;
+ }
+
+ function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
+
+ function mdwp_hide_tags($text) {
+ global $mdwp_hidden_tags, $mdwp_placeholders;
+ return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
+ }
+ function mdwp_show_tags($text) {
+ global $mdwp_hidden_tags, $mdwp_placeholders;
+ return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
+ }
+}
+
+
+### bBlog Plugin Info ###
+
+function identify_modifier_markdown() {
+ return array(
+ 'name' => 'markdown',
+ 'type' => 'modifier',
+ 'nicename' => 'Markdown',
+ 'description' => 'A text-to-HTML conversion tool for web writers',
+ 'authors' => 'Michel Fortin and John Gruber',
+ 'licence' => 'BSD-like',
+ 'version' => MARKDOWN_VERSION,
+ 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>'
+ );
+}
+
+
+### Smarty Modifier Interface ###
+
+function smarty_modifier_markdown($text) {
+ return Markdown($text);
+}
+
+
+### Textile Compatibility Mode ###
+
+# Rename this file to "classTextile.php" and it can replace Textile everywhere.
+
+if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
+ # Try to include PHP SmartyPants. Should be in the same directory.
+ @include_once 'smartypants.php';
+ # Fake Textile class. It calls Markdown instead.
+ class Textile {
+ function TextileThis($text, $lite='', $encode='') {
+ if ($lite == '' && $encode == '') $text = Markdown($text);
+ if (function_exists('SmartyPants')) $text = SmartyPants($text);
+ return $text;
+ }
+ # Fake restricted version: restrictions are not supported for now.
+ function TextileRestricted($text, $lite='', $noimage='') {
+ return $this->TextileThis($text, $lite);
+ }
+ # Workaround to ensure compatibility with TextPattern 4.0.3.
+ function blockLite($text) { return $text; }
+ }
+}
+
+
+
+#
+# Markdown Parser Class
+#
+
+class Markdown_Parser {
+
+ # Regex to match balanced [brackets].
+ # Needed to insert a maximum bracked depth while converting to PHP.
+ var $nested_brackets_depth = 6;
+ var $nested_brackets_re;
+
+ var $nested_url_parenthesis_depth = 4;
+ var $nested_url_parenthesis_re;
+
+ # Table of hash values for escaped characters:
+ var $escape_chars = '\`*_{}[]()>#+-.!';
+ var $escape_chars_re;
+
+ # Change to ">" for HTML output.
+ var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
+ var $tab_width = MARKDOWN_TAB_WIDTH;
+
+ # Change to `true` to disallow markup or entities.
+ var $no_markup = false;
+ var $no_entities = false;
+
+ # Predefined urls and titles for reference links and images.
+ var $predef_urls = array();
+ var $predef_titles = array();
+
+
+ function Markdown_Parser() {
+ #
+ # Constructor function. Initialize appropriate member variables.
+ #
+ $this->_initDetab();
+ $this->prepareItalicsAndBold();
+
+ $this->nested_brackets_re =
+ str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
+ str_repeat('\])*', $this->nested_brackets_depth);
+
+ $this->nested_url_parenthesis_re =
+ str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
+ str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
+
+ $this->escape_chars_re = '['.preg_quote($this->escape_chars).']';
+
+ # Sort document, block, and span gamut in ascendent priority order.
+ asort($this->document_gamut);
+ asort($this->block_gamut);
+ asort($this->span_gamut);
+ }
+
+
+ # Internal hashes used during transformation.
+ var $urls = array();
+ var $titles = array();
+ var $html_hashes = array();
+
+ # Status flag to avoid invalid nesting.
+ var $in_anchor = false;
+
+
+ function setup() {
+ #
+ # Called before the transformation process starts to setup parser
+ # states.
+ #
+ # Clear global hashes.
+ $this->urls = $this->predef_urls;
+ $this->titles = $this->predef_titles;
+ $this->html_hashes = array();
+
+ $in_anchor = false;
+ }
+
+ function teardown() {
+ #
+ # Called after the transformation process to clear any variable
+ # which may be taking up memory unnecessarly.
+ #
+ $this->urls = array();
+ $this->titles = array();
+ $this->html_hashes = array();
+ }
+
+
+ function transform($text) {
+ #
+ # Main function. Performs some preprocessing on the input text
+ # and pass it through the document gamut.
+ #
+ $this->setup();
+
+ # Remove UTF-8 BOM and marker character in input, if present.
+ $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
+
+ # Standardize line endings:
+ # DOS to Unix and Mac to Unix
+ $text = preg_replace('{\r\n?}', "\n", $text);
+
+ # Make sure $text ends with a couple of newlines:
+ $text .= "\n\n";
+
+ # Convert all tabs to spaces.
+ $text = $this->detab($text);
+
+ # Turn block-level HTML blocks into hash entries
+ $text = $this->hashHTMLBlocks($text);
+
+ # Strip any lines consisting only of spaces and tabs.
+ # This makes subsequent regexen easier to write, because we can
+ # match consecutive blank lines with /\n+/ instead of something
+ # contorted like /[ ]*\n+/ .
+ $text = preg_replace('/^[ ]+$/m', '', $text);
+
+ # Run document gamut methods.
+ foreach ($this->document_gamut as $method => $priority) {
+ $text = $this->$method($text);
+ }
+
+ $this->teardown();
+
+ return $text . "\n";
+ }
+
+ var $document_gamut = array(
+ # Strip link definitions, store in hashes.
+ "stripLinkDefinitions" => 20,
+
+ "runBasicBlockGamut" => 30,
+ );
+
+
+ function stripLinkDefinitions($text) {
+ #
+ # Strips link definitions from text, stores the URLs and titles in
+ # hash references.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Link defs are in the form: ^[id]: url "optional title"
+ $text = preg_replace_callback('{
+ ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
+ [ ]*
+ \n? # maybe *one* newline
+ [ ]*
+ <?(\S+?)>? # url = $2
+ [ ]*
+ \n? # maybe one newline
+ [ ]*
+ (?:
+ (?<=\s) # lookbehind for whitespace
+ ["(]
+ (.*?) # title = $3
+ [")]
+ [ ]*
+ )? # title is optional
+ (?:\n+|\Z)
+ }xm',
+ array(&$this, '_stripLinkDefinitions_callback'),
+ $text);
+ return $text;
+ }
+ function _stripLinkDefinitions_callback($matches) {
+ $link_id = strtolower($matches[1]);
+ $this->urls[$link_id] = $matches[2];
+ $this->titles[$link_id] =& $matches[3];
+ return ''; # String that will replace the block
+ }
+
+
+ function hashHTMLBlocks($text) {
+ if ($this->no_markup) return $text;
+
+ $less_than_tab = $this->tab_width - 1;
+
+ # Hashify HTML blocks:
+ # We only want to do this for block-level HTML tags, such as headers,
+ # lists, and tables. That's because we still want to wrap <p>s around
+ # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+ # phrase emphasis, and spans. The list of tags we're looking for is
+ # hard-coded:
+ #
+ # * List "a" is made of tags which can be both inline or block-level.
+ # These will be treated block-level when the start tag is alone on
+ # its line, otherwise they're not matched here and will be taken as
+ # inline later.
+ # * List "b" is made of tags which are always block-level;
+ #
+ $block_tags_a_re = 'ins|del';
+ $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
+ 'script|noscript|form|fieldset|iframe|math';
+
+ # Regular expression for the content of a block tag.
+ $nested_tags_level = 4;
+ $attr = '
+ (?> # optional tag attributes
+ \s # starts with whitespace
+ (?>
+ [^>"/]+ # text outside quotes
+ |
+ /+(?!>) # slash not followed by ">"
+ |
+ "[^"]*" # text inside double quotes (tolerate ">")
+ |
+ \'[^\']*\' # text inside single quotes (tolerate ">")
+ )*
+ )?
+ ';
+ $content =
+ str_repeat('
+ (?>
+ [^<]+ # content without tag
+ |
+ <\2 # nested opening tag
+ '.$attr.' # attributes
+ (?>
+ />
+ |
+ >', $nested_tags_level). # end of opening tag
+ '.*?'. # last level nested tag content
+ str_repeat('
+ </\2\s*> # closing nested tag
+ )
+ |
+ <(?!/\2\s*> # other tags with a different name
+ )
+ )*',
+ $nested_tags_level);
+ $content2 = str_replace('\2', '\3', $content);
+
+ # First, look for nested blocks, e.g.:
+ # <div>
+ # <div>
+ # tags for inner block must be indented.
+ # </div>
+ # </div>
+ #
+ # The outermost tags must start at the left margin for this to match, and
+ # the inner nested divs must be indented.
+ # We need to do this before the next, more liberal match, because the next
+ # match will start at the first `<div>` and stop at the first `</div>`.
+ $text = preg_replace_callback('{(?>
+ (?>
+ (?<=\n\n) # Starting after a blank line
+ | # or
+ \A\n? # the beginning of the doc
+ )
+ ( # save in $1
+
+ # Match from `\n<tag>` to `</tag>\n`, handling nested tags
+ # in between.
+
+ [ ]{0,'.$less_than_tab.'}
+ <('.$block_tags_b_re.')# start tag = $2
+ '.$attr.'> # attributes followed by > and \n
+ '.$content.' # content, support nesting
+ </\2> # the matching end tag
+ [ ]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+
+ | # Special version for tags of group a.
+
+ [ ]{0,'.$less_than_tab.'}
+ <('.$block_tags_a_re.')# start tag = $3
+ '.$attr.'>[ ]*\n # attributes followed by >
+ '.$content2.' # content, support nesting
+ </\3> # the matching end tag
+ [ ]* # trailing spaces/tabs
+ (?=\n+|\Z) # followed by a newline or end of document
+
+ | # Special case just for <hr />. It was easier to make a special
+ # case than to make the other regex more complicated.
+
+ [ ]{0,'.$less_than_tab.'}
+ <(hr) # start tag = $2
+ '.$attr.' # attributes
+ /?> # the matching end tag
+ [ ]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+
+ | # Special case for standalone HTML comments:
+
+ [ ]{0,'.$less_than_tab.'}
+ (?s:
+ <!-- .*? -->
+ )
+ [ ]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+
+ | # PHP and ASP-style processor instructions (<? and <%)
+
+ [ ]{0,'.$less_than_tab.'}
+ (?s:
+ <([?%]) # $2
+ .*?
+ \2>
+ )
+ [ ]*
+ (?=\n{2,}|\Z) # followed by a blank line or end of document
+
+ )
+ )}Sxmi',
+ array(&$this, '_hashHTMLBlocks_callback'),
+ $text);
+
+ return $text;
+ }
+ function _hashHTMLBlocks_callback($matches) {
+ $text = $matches[1];
+ $key = $this->hashBlock($text);
+ return "\n\n$key\n\n";
+ }
+
+
+ function hashPart($text, $boundary = 'X') {
+ #
+ # Called whenever a tag must be hashed when a function insert an atomic
+ # element in the text stream. Passing $text to through this function gives
+ # a unique text-token which will be reverted back when calling unhash.
+ #
+ # The $boundary argument specify what character should be used to surround
+ # the token. By convension, "B" is used for block elements that needs not
+ # to be wrapped into paragraph tags at the end, ":" is used for elements
+ # that are word separators and "X" is used in the general case.
+ #
+ # Swap back any tag hash found in $text so we do not have to `unhash`
+ # multiple times at the end.
+ $text = $this->unhash($text);
+
+ # Then hash the block.
+ static $i = 0;
+ $key = "$boundary\x1A" . ++$i . $boundary;
+ $this->html_hashes[$key] = $text;
+ return $key; # String that will replace the tag.
+ }
+
+
+ function hashBlock($text) {
+ #
+ # Shortcut function for hashPart with block-level boundaries.
+ #
+ return $this->hashPart($text, 'B');
+ }
+
+
+ var $block_gamut = array(
+ #
+ # These are all the transformations that form block-level
+ # tags like paragraphs, headers, and list items.
+ #
+ "doHeaders" => 10,
+ "doHorizontalRules" => 20,
+
+ "doLists" => 40,
+ "doCodeBlocks" => 50,
+ "doBlockQuotes" => 60,
+ );
+
+ function runBlockGamut($text) {
+ #
+ # Run block gamut tranformations.
+ #
+ # We need to escape raw HTML in Markdown source before doing anything
+ # else. This need to be done for each block, and not only at the
+ # begining in the Markdown function since hashed blocks can be part of
+ # list items and could have been indented. Indented blocks would have
+ # been seen as a code block in a previous pass of hashHTMLBlocks.
+ $text = $this->hashHTMLBlocks($text);
+
+ return $this->runBasicBlockGamut($text);
+ }
+
+ function runBasicBlockGamut($text) {
+ #
+ # Run block gamut tranformations, without hashing HTML blocks. This is
+ # useful when HTML blocks are known to be already hashed, like in the first
+ # whole-document pass.
+ #
+ foreach ($this->block_gamut as $method => $priority) {
+ $text = $this->$method($text);
+ }
+
+ # Finally form paragraph and restore hashed blocks.
+ $text = $this->formParagraphs($text);
+
+ return $text;
+ }
+
+
+ function doHorizontalRules($text) {
+ # Do Horizontal Rules:
+ return preg_replace(
+ '{
+ ^[ ]{0,3} # Leading space
+ ([-*_]) # $1: First marker
+ (?> # Repeated marker group
+ [ ]{0,2} # Zero, one, or two spaces.
+ \1 # Marker character
+ ){2,} # Group repeated at least twice
+ [ ]* # Tailing spaces
+ $ # End of line.
+ }mx',
+ "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n",
+ $text);
+ }
+
+
+ var $span_gamut = array(
+ #
+ # These are all the transformations that occur *within* block-level
+ # tags like paragraphs, headers, and list items.
+ #
+ # Process character escapes, code spans, and inline HTML
+ # in one shot.
+ "parseSpan" => -30,
+
+ # Process anchor and image tags. Images must come first,
+ # because ![foo][f] looks like an anchor.
+ "doImages" => 10,
+ "doAnchors" => 20,
+
+ # Make links out of things like `<http://example.com/>`
+ # Must come after doAnchors, because you can use < and >
+ # delimiters in inline links like [this](<url>).
+ "doAutoLinks" => 30,
+ "encodeAmpsAndAngles" => 40,
+
+ "doItalicsAndBold" => 50,
+ "doHardBreaks" => 60,
+ );
+
+ function runSpanGamut($text) {
+ #
+ # Run span gamut tranformations.
+ #
+ foreach ($this->span_gamut as $method => $priority) {
+ $text = $this->$method($text);
+ }
+
+ return $text;
+ }
+
+
+ function doHardBreaks($text) {
+ # Do hard breaks:
+ return preg_replace_callback('/ {2,}\n/',
+ array(&$this, '_doHardBreaks_callback'), $text);
+ }
+ function _doHardBreaks_callback($matches) {
+ return $this->hashPart("<br$this->empty_element_suffix\n");
+ }
+
+
+ function doAnchors($text) {
+ #
+ # Turn Markdown link shortcuts into XHTML <a> tags.
+ #
+ if ($this->in_anchor) return $text;
+ $this->in_anchor = true;
+
+ #
+ # First, handle reference-style links: [link text] [id]
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ \[
+ ('.$this->nested_brackets_re.') # link text = $2
+ \]
+
+ [ ]? # one optional space
+ (?:\n[ ]*)? # one optional newline followed by spaces
+
+ \[
+ (.*?) # id = $3
+ \]
+ )
+ }xs',
+ array(&$this, '_doAnchors_reference_callback'), $text);
+
+ #
+ # Next, inline-style links: [link text](url "optional title")
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ \[
+ ('.$this->nested_brackets_re.') # link text = $2
+ \]
+ \( # literal paren
+ [ ]*
+ (?:
+ <(\S*)> # href = $3
+ |
+ ('.$this->nested_url_parenthesis_re.') # href = $4
+ )
+ [ ]*
+ ( # $5
+ ([\'"]) # quote char = $6
+ (.*?) # Title = $7
+ \6 # matching quote
+ [ ]* # ignore any spaces/tabs between closing quote and )
+ )? # title is optional
+ \)
+ )
+ }xs',
+ array(&$this, '_DoAnchors_inline_callback'), $text);
+
+ #
+ # Last, handle reference-style shortcuts: [link text]
+ # These must come last in case you've also got [link test][1]
+ # or [link test](/foo)
+ #
+// $text = preg_replace_callback('{
+// ( # wrap whole match in $1
+// \[
+// ([^\[\]]+) # link text = $2; can\'t contain [ or ]
+// \]
+// )
+// }xs',
+// array(&$this, '_doAnchors_reference_callback'), $text);
+
+ $this->in_anchor = false;
+ return $text;
+ }
+ function _doAnchors_reference_callback($matches) {
+ $whole_match = $matches[1];
+ $link_text = $matches[2];
+ $link_id =& $matches[3];
+
+ if ($link_id == "") {
+ # for shortcut links like [this][] or [this].
+ $link_id = $link_text;
+ }
+
+ # lower-case and turn embedded newlines into spaces
+ $link_id = strtolower($link_id);
+ $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
+
+ if (isset($this->urls[$link_id])) {
+ $url = $this->urls[$link_id];
+ $url = $this->encodeAttribute($url);
+
+ $result = "<a href=\"$url\"";
+ if ( isset( $this->titles[$link_id] ) ) {
+ $title = $this->titles[$link_id];
+ $title = $this->encodeAttribute($title);
+ $result .= " title=\"$title\"";
+ }
+
+ $link_text = $this->runSpanGamut($link_text);
+ $result .= ">$link_text</a>";
+ $result = $this->hashPart($result);
+ }
+ else {
+ $result = $whole_match;
+ }
+ return $result;
+ }
+ function _doAnchors_inline_callback($matches) {
+ $whole_match = $matches[1];
+ $link_text = $this->runSpanGamut($matches[2]);
+ $url = $matches[3] == '' ? $matches[4] : $matches[3];
+ $title =& $matches[7];
+
+ $url = $this->encodeAttribute($url);
+
+ $result = "<a href=\"$url\"";
+ if (isset($title)) {
+ $title = $this->encodeAttribute($title);
+ $result .= " title=\"$title\"";
+ }
+
+ $link_text = $this->runSpanGamut($link_text);
+ $result .= ">$link_text</a>";
+
+ return $this->hashPart($result);
+ }
+
+
+ function doImages($text) {
+ #
+ # Turn Markdown image shortcuts into <img> tags.
+ #
+ #
+ # First, handle reference-style labeled images: ![alt text][id]
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ !\[
+ ('.$this->nested_brackets_re.') # alt text = $2
+ \]
+
+ [ ]? # one optional space
+ (?:\n[ ]*)? # one optional newline followed by spaces
+
+ \[
+ (.*?) # id = $3
+ \]
+
+ )
+ }xs',
+ array(&$this, '_doImages_reference_callback'), $text);
+
+ #
+ # Next, handle inline images: ![alt text](url "optional title")
+ # Don't forget: encode * and _
+ #
+ $text = preg_replace_callback('{
+ ( # wrap whole match in $1
+ !\[
+ ('.$this->nested_brackets_re.') # alt text = $2
+ \]
+ \s? # One optional whitespace character
+ \( # literal paren
+ [ ]*
+ (?:
+ <(\S*)> # src url = $3
+ |
+ ('.$this->nested_url_parenthesis_re.') # src url = $4
+ )
+ [ ]*
+ ( # $5
+ ([\'"]) # quote char = $6
+ (.*?) # title = $7
+ \6 # matching quote
+ [ ]*
+ )? # title is optional
+ \)
+ )
+ }xs',
+ array(&$this, '_doImages_inline_callback'), $text);
+
+ return $text;
+ }
+ function _doImages_reference_callback($matches) {
+ $whole_match = $matches[1];
+ $alt_text = $matches[2];
+ $link_id = strtolower($matches[3]);
+
+ if ($link_id == "") {
+ $link_id = strtolower($alt_text); # for shortcut links like ![this][].
+ }
+
+ $alt_text = $this->encodeAttribute($alt_text);
+ if (isset($this->urls[$link_id])) {
+ $url = $this->encodeAttribute($this->urls[$link_id]);
+ $result = "<img src=\"$url\" alt=\"$alt_text\"";
+ if (isset($this->titles[$link_id])) {
+ $title = $this->titles[$link_id];
+ $title = $this->encodeAttribute($title);
+ $result .= " title=\"$title\"";
+ }
+ $result .= $this->empty_element_suffix;
+ $result = $this->hashPart($result);
+ }
+ else {
+ # If there's no such link ID, leave intact:
+ $result = $whole_match;
+ }
+
+ return $result;
+ }
+ function _doImages_inline_callback($matches) {
+ $whole_match = $matches[1];
+ $alt_text = $matches[2];
+ $url = $matches[3] == '' ? $matches[4] : $matches[3];
+ $title =& $matches[7];
+
+ $alt_text = $this->encodeAttribute($alt_text);
+ $url = $this->encodeAttribute($url);
+ $result = "<img src=\"$url\" alt=\"$alt_text\"";
+ if (isset($title)) {
+ $title = $this->encodeAttribute($title);
+ $result .= " title=\"$title\""; # $title already quoted
+ }
+ $result .= $this->empty_element_suffix;
+
+ return $this->hashPart($result);
+ }
+
+
+ function doHeaders($text) {
+ # Setext-style headers:
+ # Header 1
+ # ========
+ #
+ # Header 2
+ # --------
+ #
+ $text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
+ array(&$this, '_doHeaders_callback_setext'), $text);
+
+ # atx-style headers:
+ # # Header 1
+ # ## Header 2
+ # ## Header 2 with closing hashes ##
+ # ...
+ # ###### Header 6
+ #
+ $text = preg_replace_callback('{
+ ^(\#{1,6}) # $1 = string of #\'s
+ [ ]*
+ (.+?) # $2 = Header text
+ [ ]*
+ \#* # optional closing #\'s (not counted)
+ \n+
+ }xm',
+ array(&$this, '_doHeaders_callback_atx'), $text);
+
+ return $text;
+ }
+ function _doHeaders_callback_setext($matches) {
+ # Terrible hack to check we haven't found an empty list item.
+ if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
+ return $matches[0];
+
+ $level = $matches[2]{0} == '=' ? 1 : 2;
+ $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
+ return "\n" . $this->hashBlock($block) . "\n\n";
+ }
+ function _doHeaders_callback_atx($matches) {
+ $level = strlen($matches[1]);
+ $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
+ return "\n" . $this->hashBlock($block) . "\n\n";
+ }
+
+
+ function doLists($text) {
+ #
+ # Form HTML ordered (numbered) and unordered (bulleted) lists.
+ #
+ $less_than_tab = $this->tab_width - 1;
+
+ # Re-usable patterns to match list item bullets and number markers:
+ $marker_ul_re = '[*+-]';
+ $marker_ol_re = '\d+[.]';
+ $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
+
+ $markers_relist = array($marker_ul_re, $marker_ol_re);
+
+ foreach ($markers_relist as $marker_re) {
+ # Re-usable pattern to match any entirel ul or ol list:
+ $whole_list_re = '
+ ( # $1 = whole list
+ ( # $2
+ [ ]{0,'.$less_than_tab.'}
+ ('.$marker_re.') # $3 = first list item marker
+ [ ]+
+ )
+ (?s:.+?)
+ ( # $4
+ \z
+ |
+ \n{2,}
+ (?=\S)
+ (?! # Negative lookahead for another list item marker
+ [ ]*
+ '.$marker_re.'[ ]+
+ )
+ )
+ )
+ '; // mx
+
+ # We use a different prefix before nested lists than top-level lists.
+ # See extended comment in _ProcessListItems().
+
+ if ($this->list_level) {
+ $text = preg_replace_callback('{
+ ^
+ '.$whole_list_re.'
+ }mx',
+ array(&$this, '_doLists_callback'), $text);
+ }
+ else {
+ $text = preg_replace_callback('{
+ (?:(?<=\n)\n|\A\n?) # Must eat the newline
+ '.$whole_list_re.'
+ }mx',
+ array(&$this, '_doLists_callback'), $text);
+ }
+ }
+
+ return $text;
+ }
+ function _doLists_callback($matches) {
+ # Re-usable patterns to match list item bullets and number markers:
+ $marker_ul_re = '[*+-]';
+ $marker_ol_re = '\d+[.]';
+ $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
+
+ $list = $matches[1];
+ $list_type = preg_match("/$marker_ul_re/", $matches[3]) ? "ul" : "ol";
+
+ $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
+
+ $list .= "\n";
+ $result = $this->processListItems($list, $marker_any_re);
+
+ $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
+ return "\n". $result ."\n\n";
+ }
+
+ var $list_level = 0;
+
+ function processListItems($list_str, $marker_any_re) {
+ #
+ # Process the contents of a single ordered or unordered list, splitting it
+ # into individual list items.
+ #
+ # The $this->list_level global keeps track of when we're inside a list.
+ # Each time we enter a list, we increment it; when we leave a list,
+ # we decrement. If it's zero, we're not in a list anymore.
+ #
+ # We do this because when we're not inside a list, we want to treat
+ # something like this:
+ #
+ # I recommend upgrading to version
+ # 8. Oops, now this line is treated
+ # as a sub-list.
+ #
+ # As a single paragraph, despite the fact that the second line starts
+ # with a digit-period-space sequence.
+ #
+ # Whereas when we're inside a list (or sub-list), that line will be
+ # treated as the start of a sub-list. What a kludge, huh? This is
+ # an aspect of Markdown's syntax that's hard to parse perfectly
+ # without resorting to mind-reading. Perhaps the solution is to
+ # change the syntax rules such that sub-lists must start with a
+ # starting cardinal number; e.g. "1." or "a.".
+
+ $this->list_level++;
+
+ # trim trailing blank lines:
+ $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
+
+ $list_str = preg_replace_callback('{
+ (\n)? # leading line = $1
+ (^[ ]*) # leading whitespace = $2
+ ('.$marker_any_re.' # list marker and space = $3
+ (?:[ ]+|(?=\n)) # space only required if item is not empty
+ )
+ ((?s:.*?)) # list item text = $4
+ (?:(\n+(?=\n))|\n) # tailing blank line = $5
+ (?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
+ }xm',
+ array(&$this, '_processListItems_callback'), $list_str);
+
+ $this->list_level--;
+ return $list_str;
+ }
+ function _processListItems_callback($matches) {
+ $item = $matches[4];
+ $leading_line =& $matches[1];
+ $leading_space =& $matches[2];
+ $marker_space = $matches[3];
+ $tailing_blank_line =& $matches[5];
+
+ if ($leading_line || $tailing_blank_line ||
+ preg_match('/\n{2,}/', $item))
+ {
+ # Replace marker with the appropriate whitespace indentation
+ $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
+ $item = $this->runBlockGamut($this->outdent($item)."\n");
+ }
+ else {
+ # Recursion for sub-lists:
+ $item = $this->doLists($this->outdent($item));
+ $item = preg_replace('/\n+$/', '', $item);
+ $item = $this->runSpanGamut($item);
+ }
+
+ return "<li>" . $item . "</li>\n";
+ }
+
+
+ function doCodeBlocks($text) {
+ #
+ # Process Markdown `<pre><code>` blocks.
+ #
+ $text = preg_replace_callback('{
+ (?:\n\n|\A\n?)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?>
+ [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ }xm',
+ array(&$this, '_doCodeBlocks_callback'), $text);
+
+ return $text;
+ }
+ function _doCodeBlocks_callback($matches) {
+ $codeblock = $matches[1];
+
+ $codeblock = $this->outdent($codeblock);
+ $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
+
+ # trim leading newlines and trailing newlines
+ $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
+
+ $codeblock = "<pre><code>$codeblock\n</code></pre>";
+ return "\n\n".$this->hashBlock($codeblock)."\n\n";
+ }
+
+
+ function makeCodeSpan($code) {
+ #
+ # Create a code span markup for $code. Called from handleSpanToken.
+ #
+ $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
+ return $this->hashPart("<code>$code</code>");
+ }
+
+
+ var $em_relist = array(
+ '' => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?=\S)(?![.,:;]\s)',
+ '*' => '(?<=\S)(?<!\*)\*(?!\*)',
+ '_' => '(?<=\S)(?<!_)_(?!_)',
+ );
+ var $strong_relist = array(
+ '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S)(?![.,:;]\s)',
+ '**' => '(?<=\S)(?<!\*)\*\*(?!\*)',
+ '__' => '(?<=\S)(?<!_)__(?!_)',
+ );
+ var $em_strong_relist = array(
+ '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S)(?![.,:;]\s)',
+ '***' => '(?<=\S)(?<!\*)\*\*\*(?!\*)',
+ '___' => '(?<=\S)(?<!_)___(?!_)',
+ );
+ var $em_strong_prepared_relist;
+
+ function prepareItalicsAndBold() {
+ #
+ # Prepare regular expressions for seraching emphasis tokens in any
+ # context.
+ #
+ foreach ($this->em_relist as $em => $em_re) {
+ foreach ($this->strong_relist as $strong => $strong_re) {
+ # Construct list of allowed token expressions.
+ $token_relist = array();
+ if (isset($this->em_strong_relist["$em$strong"])) {
+ $token_relist[] = $this->em_strong_relist["$em$strong"];
+ }
+ $token_relist[] = $em_re;
+ $token_relist[] = $strong_re;
+
+ # Construct master expression from list.
+ $token_re = '{('. implode('|', $token_relist) .')}';
+ $this->em_strong_prepared_relist["$em$strong"] = $token_re;
+ }
+ }
+ }
+
+ function doItalicsAndBold($text) {
+ $token_stack = array('');
+ $text_stack = array('');
+ $em = '';
+ $strong = '';
+ $tree_char_em = false;
+
+ while (1) {
+ #
+ # Get prepared regular expression for seraching emphasis tokens
+ # in current context.
+ #
+ $token_re = $this->em_strong_prepared_relist["$em$strong"];
+
+ #
+ # Each loop iteration seach for the next emphasis token.
+ # Each token is then passed to handleSpanToken.
+ #
+ $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
+ $text_stack[0] .= $parts[0];
+ $token =& $parts[1];
+ $text =& $parts[2];
+
+ if (empty($token)) {
+ # Reached end of text span: empty stack without emitting.
+ # any more emphasis.
+ while ($token_stack[0]) {
+ $text_stack[1] .= array_shift($token_stack);
+ $text_stack[0] .= array_shift($text_stack);
+ }
+ break;
+ }
+
+ $token_len = strlen($token);
+ if ($tree_char_em) {
+ # Reached closing marker while inside a three-char emphasis.
+ if ($token_len == 3) {
+ # Three-char closing marker, close em and strong.
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "<strong><em>$span</em></strong>";
+ $text_stack[0] .= $this->hashPart($span);
+ $em = '';
+ $strong = '';
+ } else {
+ # Other closing marker: close one em or strong and
+ # change current token state to match the other
+ $token_stack[0] = str_repeat($token{0}, 3-$token_len);
+ $tag = $token_len == 2 ? "strong" : "em";
+ $span = $text_stack[0];
+ $span = $this->runSpanGamut($span);
+ $span = "<$tag>$span</$tag>";
+ $text_stack[0] = $this->hashPart($span);
+ $$tag = ''; # $$tag stands for $em or $strong
+ }
+ $tree_char_em = false;
+ } else if ($token_len == 3) {
+ if ($em) {
+ # Reached closing marker for both em and strong.
+ # Closing strong marker:
+ for ($i = 0; $i < 2; ++$i) {
+ $shifted_token = array_shift($token_stack);
+ $tag = strlen($shifted_token) == 2 ? "strong" : "em";
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "<$tag>$span</$tag>";
+ $text_stack[0] .= $this->hashPart($span);
+ $$tag = ''; # $$tag stands for $em or $strong
+ }
+ } else {
+ # Reached opening three-char emphasis marker. Push on token
+ # stack; will be handled by the special condition above.
+ $em = $token{0};
+ $strong = "$em$em";
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $tree_char_em = true;
+ }
+ } else if ($token_len == 2) {
+ if ($strong) {
+ # Unwind any dangling emphasis marker:
+ if (strlen($token_stack[0]) == 1) {
+ $text_stack[1] .= array_shift($token_stack);
+ $text_stack[0] .= array_shift($text_stack);
+ }
+ # Closing strong marker:
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "<strong>$span</strong>";
+ $text_stack[0] .= $this->hashPart($span);
+ $strong = '';
+ } else {
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $strong = $token;
+ }
+ } else {
+ # Here $token_len == 1
+ if ($em) {
+ if (strlen($token_stack[0]) == 1) {
+ # Closing emphasis marker:
+ array_shift($token_stack);
+ $span = array_shift($text_stack);
+ $span = $this->runSpanGamut($span);
+ $span = "<em>$span</em>";
+ $text_stack[0] .= $this->hashPart($span);
+ $em = '';
+ } else {
+ $text_stack[0] .= $token;
+ }
+ } else {
+ array_unshift($token_stack, $token);
+ array_unshift($text_stack, '');
+ $em = $token;
+ }
+ }
+ }
+ return $text_stack[0];
+ }
+
+
+ function doBlockQuotes($text) {
+ $text = preg_replace_callback('/
+ ( # Wrap whole match in $1
+ (?>
+ ^[ ]*>[ ]? # ">" at the start of a line
+ .+\n # rest of the first line
+ (.+\n)* # subsequent consecutive lines
+ \n* # blanks
+ )+
+ )
+ /xm',
+ array(&$this, '_doBlockQuotes_callback'), $text);
+
+ return $text;
+ }
+ function _doBlockQuotes_callback($matches) {
+ $bq = $matches[1];
+ # trim one level of quoting - trim whitespace-only lines
+ $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
+ $bq = $this->runBlockGamut($bq); # recurse
+
+ $bq = preg_replace('/^/m', " ", $bq);
+ # These leading spaces cause problem with <pre> content,
+ # so we need to fix that:
+ $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx',
+ array(&$this, '_DoBlockQuotes_callback2'), $bq);
+
+ return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
+ }
+ function _doBlockQuotes_callback2($matches) {
+ $pre = $matches[1];
+ $pre = preg_replace('/^ /m', '', $pre);
+ return $pre;
+ }
+
+
+ function formParagraphs($text) {
+ #
+ # Params:
+ # $text - string to process with html <p> tags
+ #
+ # Strip leading and trailing lines:
+ $text = preg_replace('/\A\n+|\n+\z/', '', $text);
+
+ $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
+
+ #
+ # Wrap <p> tags and unhashify HTML blocks
+ #
+ foreach ($grafs as $key => $value) {
+ if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
+ # Is a paragraph.
+ $value = $this->runSpanGamut($value);
+ $value = preg_replace('/^([ ]*)/', "<p>", $value);
+ $value .= "</p>";
+ $grafs[$key] = $this->unhash($value);
+ }
+ else {
+ # Is a block.
+ # Modify elements of @grafs in-place...
+ $graf = $value;
+ $block = $this->html_hashes[$graf];
+ $graf = $block;
+// if (preg_match('{
+// \A
+// ( # $1 = <div> tag
+// <div \s+
+// [^>]*
+// \b
+// markdown\s*=\s* ([\'"]) # $2 = attr quote char
+// 1
+// \2
+// [^>]*
+// >
+// )
+// ( # $3 = contents
+// .*
+// )
+// (</div>) # $4 = closing tag
+// \z
+// }xs', $block, $matches))
+// {
+// list(, $div_open, , $div_content, $div_close) = $matches;
+//
+// # We can't call Markdown(), because that resets the hash;
+// # that initialization code should be pulled into its own sub, though.
+// $div_content = $this->hashHTMLBlocks($div_content);
+//
+// # Run document gamut methods on the content.
+// foreach ($this->document_gamut as $method => $priority) {
+// $div_content = $this->$method($div_content);
+// }
+//
+// $div_open = preg_replace(
+// '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
+//
+// $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
+// }
+ $grafs[$key] = $graf;
+ }
+ }
+
+ return implode("\n\n", $grafs);
+ }
+
+
+ function encodeAttribute($text) {
+ #
+ # Encode text for a double-quoted HTML attribute. This function
+ # is *not* suitable for attributes enclosed in single quotes.
+ #
+ $text = $this->encodeAmpsAndAngles($text);
+ $text = str_replace('"', '&quot;', $text);
+ return $text;
+ }
+
+
+ function encodeAmpsAndAngles($text) {
+ #
+ # Smart processing for ampersands and angle brackets that need to
+ # be encoded. Valid character entities are left alone unless the
+ # no-entities mode is set.
+ #
+ if ($this->no_entities) {
+ $text = str_replace('&', '&amp;', $text);
+ } else {
+ # Ampersand-encoding based entirely on Nat Irons's Amputator
+ # MT plugin: <http://bumppo.net/projects/amputator/>
+ $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
+ '&amp;', $text);;
+ }
+ # Encode remaining <'s
+ $text = str_replace('<', '&lt;', $text);
+
+ return $text;
+ }
+
+
+ function doAutoLinks($text) {
+ $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
+ array(&$this, '_doAutoLinks_url_callback'), $text);
+
+ # Email addresses: <address@domain.foo>
+ $text = preg_replace_callback('{
+ <
+ (?:mailto:)?
+ (
+ [-.\w\x80-\xFF]+
+ \@
+ [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
+ )
+ >
+ }xi',
+ array(&$this, '_doAutoLinks_email_callback'), $text);
+
+ return $text;
+ }
+ function _doAutoLinks_url_callback($matches) {
+ $url = $this->encodeAttribute($matches[1]);
+ $link = "<a href=\"$url\">$url</a>";
+ return $this->hashPart($link);
+ }
+ function _doAutoLinks_email_callback($matches) {
+ $address = $matches[1];
+ $link = $this->encodeEmailAddress($address);
+ return $this->hashPart($link);
+ }
+
+
+ function encodeEmailAddress($addr) {
+ #
+ # Input: an email address, e.g. "foo@example.com"
+ #
+ # Output: the email address as a mailto link, with each character
+ # of the address encoded as either a decimal or hex entity, in
+ # the hopes of foiling most address harvesting spam bots. E.g.:
+ #
+ # <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
+ # &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
+ # &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
+ # &#101;&#46;&#x63;&#111;&#x6d;</a></p>
+ #
+ # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
+ # With some optimizations by Milian Wolff.
+ #
+ $addr = "mailto:" . $addr;
+ $chars = preg_split('/(?<!^)(?!$)/', $addr);
+ $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
+
+ foreach ($chars as $key => $char) {
+ $ord = ord($char);
+ # Ignore non-ascii chars.
+ if ($ord < 128) {
+ $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
+ # roughly 10% raw, 45% hex, 45% dec
+ # '@' *must* be encoded. I insist.
+ if ($r > 90 && $char != '@') /* do nothing */;
+ else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
+ else $chars[$key] = '&#'.$ord.';';
+ }
+ }
+
+ $addr = implode('', $chars);
+ $text = implode('', array_slice($chars, 7)); # text without `mailto:`
+ $addr = "<a href=\"$addr\">$text</a>";
+
+ return $addr;
+ }
+
+
+ function parseSpan($str) {
+ #
+ # Take the string $str and parse it into tokens, hashing embeded HTML,
+ # escaped characters and handling code spans.
+ #
+ $output = '';
+
+ $span_re = '{
+ (
+ \\\\'.$this->escape_chars_re.'
+ |
+ (?<![`\\\\])
+ `+ # code span marker
+ '.( $this->no_markup ? '' : '
+ |
+ <!-- .*? --> # comment
+ |
+ <\?.*?\?> | <%.*?%> # processing instruction
+ |
+ <[/!$]?[-a-zA-Z0-9:]+ # regular tags
+ (?>
+ \s
+ (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
+ )?
+ >
+ ').'
+ )
+ }xs';
+
+ while (1) {
+ #
+ # Each loop iteration seach for either the next tag, the next
+ # openning code span marker, or the next escaped character.
+ # Each token is then passed to handleSpanToken.
+ #
+ $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
+
+ # Create token from text preceding tag.
+ if ($parts[0] != "") {
+ $output .= $parts[0];
+ }
+
+ # Check if we reach the end.
+ if (isset($parts[1])) {
+ $output .= $this->handleSpanToken($parts[1], $parts[2]);
+ $str = $parts[2];
+ }
+ else {
+ break;
+ }
+ }
+
+ return $output;
+ }
+
+
+ function handleSpanToken($token, &$str) {
+ #
+ # Handle $token provided by parseSpan by determining its nature and
+ # returning the corresponding value that should replace it.
+ #
+ switch ($token{0}) {
+ case "\\":
+ return $this->hashPart("&#". ord($token{1}). ";");
+ case "`":
+ # Search for end marker in remaining text.
+ if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
+ $str, $matches))
+ {
+ $str = $matches[2];
+ $codespan = $this->makeCodeSpan($matches[1]);
+ return $this->hashPart($codespan);
+ }
+ return $token; // return as text since no ending marker found.
+ default:
+ return $this->hashPart($token);
+ }
+ }
+
+
+ function outdent($text) {
+ #
+ # Remove one level of line-leading tabs or spaces
+ #
+ return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
+ }
+
+
+ # String length function for detab. `_initDetab` will create a function to
+ # hanlde UTF-8 if the default function does not exist.
+ var $utf8_strlen = 'mb_strlen';
+
+ function detab($text) {
+ #
+ # Replace tabs with the appropriate amount of space.
+ #
+ # For each line we separate the line in blocks delemited by
+ # tab characters. Then we reconstruct every line by adding the
+ # appropriate number of space between each blocks.
+
+ $text = preg_replace_callback('/^.*\t.*$/m',
+ array(&$this, '_detab_callback'), $text);
+
+ return $text;
+ }
+ function _detab_callback($matches) {
+ $line = $matches[0];
+ $strlen = $this->utf8_strlen; # strlen function for UTF-8.
+
+ # Split in blocks.
+ $blocks = explode("\t", $line);
+ # Add each blocks to the line.
+ $line = $blocks[0];
+ unset($blocks[0]); # Do not add first block twice.
+ foreach ($blocks as $block) {
+ # Calculate amount of space, insert spaces, insert block.
+ $amount = $this->tab_width -
+ $strlen($line, 'UTF-8') % $this->tab_width;
+ $line .= str_repeat(" ", $amount) . $block;
+ }
+ return $line;
+ }
+ function _initDetab() {
+ #
+ # Check for the availability of the function in the `utf8_strlen` property
+ # (initially `mb_strlen`). If the function is not available, create a
+ # function that will loosely count the number of UTF-8 characters with a
+ # regular expression.
+ #
+ if (function_exists($this->utf8_strlen)) return;
+ $this->utf8_strlen = create_function('$text', 'return preg_match_all(
+ "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
+ $text, $m);');
+ }
+
+
+ function unhash($text) {
+ #
+ # Swap back in all the tags hashed by _HashHTMLBlocks.
+ #
+ return preg_replace_callback('/(.)\x1A[0-9]+\1/',
+ array(&$this, '_unhash_callback'), $text);
+ }
+ function _unhash_callback($matches) {
+ return $this->html_hashes[$matches[0]];
+ }
+
+}
+
+/*
+
+PHP Markdown
+============
+
+Description
+-----------
+
+This is a PHP translation of the original Markdown formatter written in
+Perl by John Gruber.
+
+Markdown is a text-to-HTML filter; it translates an easy-to-read /
+easy-to-write structured text format into HTML. Markdown's text format
+is most similar to that of plain text email, and supports features such
+as headers, *emphasis*, code blocks, blockquotes, and links.
+
+Markdown's syntax is designed not as a generic markup language, but
+specifically to serve as a front-end to (X)HTML. You can use span-level
+HTML tags anywhere in a Markdown document, and you can use block level
+HTML tags (like <div> and <table> as well).
+
+For more information about Markdown's syntax, see:
+
+<http://daringfireball.net/projects/markdown/>
+
+
+Bugs
+----
+
+To file bug reports please send email to:
+
+<michel.fortin@michelf.com>
+
+Please include with your report: (1) the example input; (2) the output you
+expected; (3) the output Markdown actually produced.
+
+
+Version History
+---------------
+
+See the readme file for detailed release notes for this version.
+
+
+Copyright and License
+---------------------
+
+PHP Markdown
+Copyright (c) 2004-2008 Michel Fortin
+<http://www.michelf.com/>
+All rights reserved.
+
+Based on Markdown
+Copyright (c) 2003-2006 John Gruber
+<http://daringfireball.net/>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+* Neither the name "Markdown" nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+*/
+?> \ No newline at end of file
diff --git a/htaccess.sample b/htaccess.sample
new file mode 100644
index 000000000..bd29d318f
--- /dev/null
+++ b/htaccess.sample
@@ -0,0 +1,150 @@
+RewriteEngine On
+
+# NOTE: change this to your actual Laconica path; may be "/".
+
+RewriteBase /mublog/
+
+RewriteRule ^$ index.php?action=public [L,QSA]
+RewriteRule ^rss$ index.php?action=publicrss [L,QSA]
+RewriteRule ^xrds$ index.php?action=publicxrds [L,QSA]
+RewriteRule ^featuredrss$ index.php?action=featuredrss [L,QSA]
+RewriteRule ^favoritedrss$ index.php?action=favoritedrss [L,QSA]
+RewriteRule ^opensearch/people$ index.php?action=opensearch&type=people [L,QSA]
+RewriteRule ^opensearch/notice$ index.php?action=opensearch&type=notice [L,QSA]
+
+RewriteRule ^doc/about$ index.php?action=doc&title=about [L,QSA]
+RewriteRule ^doc/contact$ index.php?action=doc&title=contact [L,QSA]
+RewriteRule ^doc/faq$ index.php?action=doc&title=faq [L,QSA]
+RewriteRule ^doc/help$ index.php?action=doc&title=help [L,QSA]
+RewriteRule ^doc/im$ index.php?action=doc&title=im [L,QSA]
+RewriteRule ^doc/openid$ index.php?action=doc&title=openid [L,QSA]
+RewriteRule ^doc/openmublog$ index.php?action=doc&title=openmublog [L,QSA]
+RewriteRule ^doc/privacy$ index.php?action=doc&title=privacy [L,QSA]
+RewriteRule ^doc/source$ index.php?action=doc&title=source [L,QSA]
+
+RewriteRule ^facebook/$ index.php?action=facebookhome [L,QSA]
+RewriteRule ^facebook/index.php$ index.php?action=facebookhome [L,QSA]
+RewriteRule ^facebook/settings.php$ index.php?action=facebooksettings [L,QSA]
+RewriteRule ^facebook/invite.php$ index.php?action=facebookinvite [L,QSA]
+RewriteRule ^facebook/remove$ index.php?action=facebookremove [L,QSA]
+
+RewriteRule ^main/login$ index.php?action=login [L,QSA]
+RewriteRule ^main/logout$ index.php?action=logout [L,QSA]
+RewriteRule ^main/register/(.*)$ index.php?action=register&code=$1 [L,QSA]
+RewriteRule ^main/register$ index.php?action=register [L,QSA]
+RewriteRule ^main/openid$ index.php?action=openidlogin [L,QSA]
+RewriteRule ^main/remote$ index.php?action=remotesubscribe [L,QSA]
+
+RewriteRule ^main/subscribe$ index.php?action=subscribe [L,QSA]
+RewriteRule ^main/unsubscribe$ index.php?action=unsubscribe [L,QSA]
+RewriteRule ^main/confirmaddress$ index.php?action=confirmaddress [L,QSA]
+RewriteRule ^main/confirmaddress/(.*)$ index.php?action=confirmaddress&code=$1 [L,QSA]
+RewriteRule ^main/recoverpassword$ index.php?action=recoverpassword [L,QSA]
+RewriteRule ^main/recoverpassword/(.*)$ index.php?action=recoverpassword&code=$1 [L,QSA]
+RewriteRule ^main/invite$ index.php?action=invite [L,QSA]
+
+RewriteRule ^main/favor$ index.php?action=favor [L,QSA]
+RewriteRule ^main/disfavor$ index.php?action=disfavor [L,QSA]
+
+RewriteRule ^main/sup$ index.php?action=sup [L,QSA]
+
+RewriteRule ^main/tagother$ index.php?action=tagother [L,QSA]
+
+RewriteRule ^main/block$ index.php?action=block [L,QSA]
+
+RewriteRule ^settings/delete$ index.php?action=deleteprofile [L,QSA]
+RewriteRule ^settings/profile$ index.php?action=profilesettings [L,QSA]
+RewriteRule ^settings/openid$ index.php?action=openidsettings [L,QSA]
+RewriteRule ^settings/im$ index.php?action=imsettings [L,QSA]
+RewriteRule ^settings/email$ index.php?action=emailsettings [L,QSA]
+RewriteRule ^settings/sms$ index.php?action=smssettings [L,QSA]
+RewriteRule ^settings/twitter$ index.php?action=twittersettings [L,QSA]
+RewriteRule ^settings/other$ index.php?action=othersettings [L,QSA]
+
+RewriteRule ^search/people$ index.php?action=peoplesearch [L,QSA]
+RewriteRule ^search/notice$ index.php?action=noticesearch [L,QSA]
+RewriteRule ^search/notice/rss$ index.php?action=noticesearchrss [L,QSA]
+
+RewriteRule ^notice/new$ index.php?action=newnotice [L,QSA]
+RewriteRule ^notice/(\d+)$ index.php?action=shownotice&notice=$1 [L,QSA]
+RewriteRule ^notice/delete/((\d+))?$ index.php?action=deletenotice&notice=$2 [L,QSA]
+RewriteRule ^notice/delete$ index.php?action=deletenotice [L,QSA]
+
+RewriteRule ^message/new$ index.php?action=newmessage [L,QSA]
+RewriteRule ^message/(\d+)$ index.php?action=showmessage&message=$1 [L,QSA]
+
+RewriteRule ^user/(\d+)$ index.php?action=userbyid&id=$1 [L,QSA]
+
+RewriteRule ^tags/?$ index.php?action=tag [L,QSA]
+RewriteRule ^tag/([a-zA-Z0-9]+)/rss$ index.php?action=tagrss&tag=$1 [L,QSA]
+RewriteRule ^tag(/(.*))?$ index.php?action=tag&tag=$2 [L,QSA]
+
+RewriteRule ^peopletag/([a-zA-Z0-9]+)$ index.php?action=peopletag&tag=$1 [L,QSA]
+
+RewriteRule ^featured/?$ index.php?action=featured [L,QSA]
+RewriteRule ^favorited/?$ index.php?action=favorited [L,QSA]
+
+RewriteRule ^(\w+)/subscriptions$ index.php?action=subscriptions&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/subscriptions/([a-zA-Z0-9]+)$ index.php?action=subscriptions&nickname=$1&tag=$2 [L,QSA]
+RewriteRule ^(\w+)/subscribers/([a-zA-Z0-9]+)$ index.php?action=subscribers&nickname=$1&tag=$2 [L,QSA]
+RewriteRule ^(\w+)/subscribers$ index.php?action=subscribers&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/nudge$ index.php?action=nudge&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/xrds$ index.php?action=xrds&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/rss$ index.php?action=userrss&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/all$ index.php?action=all&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/all/rss$ index.php?action=allrss&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/foaf$ index.php?action=foaf&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/replies$ index.php?action=replies&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/replies/rss$ index.php?action=repliesrss&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/avatar/(original|96|48|24)$ index.php?action=avatarbynickname&nickname=$1&size=$2 [L,QSA]
+RewriteRule ^(\w+)/favorites$ index.php?action=showfavorites&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/favorites/rss$ index.php?action=favoritesrss&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/inbox$ index.php?action=inbox&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/outbox$ index.php?action=outbox&nickname=$1 [L,QSA]
+RewriteRule ^(\w+)/microsummary$ index.php?action=microsummary&nickname=$1 [L,QSA]
+
+RewriteRule ^(\w+)$ index.php?action=showstream&nickname=$1 [L,QSA]
+
+# Twitter-compatible API rewrites
+# XXX: Surely these can be refactored a little -- Zach
+RewriteRule ^api/statuses/public_timeline(.*)$ index.php?action=api&apiaction=statuses&method=public_timeline$1 [L,QSA]
+RewriteRule ^api/statuses/friends_timeline(.*)$ index.php?action=api&apiaction=statuses&method=friends_timeline$1 [L,QSA]
+RewriteRule ^api/statuses/user_timeline/(.*)$ index.php?action=api&apiaction=statuses&method=user_timeline&argument=$1 [L,QSA]
+RewriteRule ^api/statuses/user_timeline(.*)$ index.php?action=api&apiaction=statuses&method=user_timeline$1 [L,QSA]
+RewriteRule ^api/statuses/show/(.*)$ index.php?action=api&apiaction=statuses&method=show&argument=$1 [L,QSA]
+RewriteRule ^api/statuses/update(.*)$ index.php?action=api&apiaction=statuses&method=update$1 [L,QSA]
+RewriteRule ^api/statuses/replies(.*)$ index.php?action=api&apiaction=statuses&method=replies&argument=$1 [L,QSA]
+RewriteRule ^api/statuses/destroy/(.*)$ index.php?action=api&apiaction=statuses&method=destroy&argument=$1 [L,QSA]
+RewriteRule ^api/statuses/friends/(.*)$ index.php?action=api&apiaction=statuses&method=friends&argument=$1 [L,QSA]
+RewriteRule ^api/statuses/friends(.*)$ index.php?action=api&apiaction=statuses&method=friends$1 [L,QSA]
+RewriteRule ^api/statuses/followers/(.*)$ index.php?action=api&apiaction=statuses&method=followers&argument=$1 [L,QSA]
+RewriteRule ^api/statuses/followers(.*)$ index.php?action=api&apiaction=statuses&method=followers$1 [L,QSA]
+RewriteRule ^api/statuses/featured(.*)$ index.php?action=api&apiaction=statuses&method=featured$1 [L,QSA]
+RewriteRule ^api/users/show/(.*)$ index.php?action=api&apiaction=users&method=show&argument=$1 [L,QSA]
+RewriteRule ^api/users/show(.*)$ index.php?action=api&apiaction=users&method=show$1 [L,QSA]
+RewriteRule ^api/direct_messages/sent(.*)$ index.php?action=api&apiaction=direct_messages&method=sent$1 [L,QSA]
+RewriteRule ^api/direct_messages/destroy/(.*)$ index.php?action=api&apiaction=direct_messages&method=destroy&argument=$1 [L,QSA]
+RewriteRule ^api/direct_messages/new(.*)$ index.php?action=api&apiaction=direct_messages&method=create$1 [L,QSA]
+RewriteRule ^api/direct_messages(.*)$ index.php?action=api&apiaction=direct_messages&method=direct_messages$1 [L,QSA]
+RewriteRule ^api/friendships/create/(.*)$ index.php?action=api&apiaction=friendships&method=create&argument=$1 [L,QSA]
+RewriteRule ^api/friendships/destroy/(.*)$ index.php?action=api&apiaction=friendships&method=destroy&argument=$1 [L,QSA]
+RewriteRule ^api/friendships/exists(.*)$ index.php?action=api&apiaction=friendships&method=exists$1 [L,QSA]
+RewriteRule ^api/account/verify_credentials(.*)$ index.php?action=api&apiaction=account&method=verify_credentials$1 [L,QSA]
+RewriteRule ^api/account/end_session$ index.php?action=api&apiaction=account&method=end_session$1 [L,QSA]
+RewriteRule ^api/account/update_location(.*)$ index.php?action=api&apiaction=account&method=update_location$1 [L,QSA]
+RewriteRule ^api/account/update_delivery_device(.*)$ index.php?action=api&apiaction=account&method=update_delivery_device$1 [L,QSA]
+RewriteRule ^api/account/rate_limit_status(.*)$ index.php?action=api&apiaction=account&method=rate_limit_status$1 [L,QSA]
+RewriteRule ^api/favorites/create/(.*)$ index.php?action=api&apiaction=favorites&method=create&argument=$1 [L,QSA]
+RewriteRule ^api/favorites/destroy/(.*)$ index.php?action=api&apiaction=favorites&method=destroy&argument=$1 [L,QSA]
+RewriteRule ^api/favorites/(.*)$ index.php?action=api&apiaction=favorites&method=favorites&argument=$1 [L,QSA]
+RewriteRule ^api/favorites(.*)$ index.php?action=api&apiaction=favorites&method=favorites$1 [L,QSA]
+RewriteRule ^api/notifications/follow/(.*)$ index.php?action=api&apiaction=notifications&method=follow&argument=$1 [L,QSA]
+RewriteRule ^api/notifications/leave/(.*)$ index.php?action=api&apiaction=notifications&method=leave&argument=$1 [L,QSA]
+RewriteRule ^api/blocks/create/(.*)$ index.php?action=api&apiaction=blocks&method=create&argument=$1 [L,QSA]
+RewriteRule ^api/blocks/destroy/(.*)$ index.php?action=api&apiaction=blocks&method=destroy&argument=$1 [L,QSA]
+RewriteRule ^api/help/(.*)$ index.php?action=api&apiaction=help&method=$1 [L,QSA]
+
+<FilesMatch "\.(ini)">
+ Order allow,deny
+</FilesMatch>
+
diff --git a/index.php b/index.php
new file mode 100644
index 000000000..d387740fc
--- /dev/null
+++ b/index.php
@@ -0,0 +1,70 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('INSTALLDIR', dirname(__FILE__));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . "/lib/common.php");
+
+# get and cache current user
+
+$user = common_current_user();
+
+# initialize language env
+
+common_init_language();
+
+$action = $_REQUEST['action'];
+
+if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) {
+ common_redirect(common_local_url('public'));
+}
+
+// If the site is private, and they're not on one of the "public"
+// parts of the site, redirect to login
+
+if (!$user && common_config('site', 'private') &&
+ !in_array($action, array('login', 'openidlogin', 'finishopenidlogin',
+ 'recoverpassword', 'api', 'doc', 'register')))
+{
+ common_redirect(common_local_url('login'));
+}
+
+$actionfile = INSTALLDIR."/actions/$action.php";
+
+if (file_exists($actionfile)) {
+ require_once($actionfile);
+ $action_class = ucfirst($action)."Action";
+ $action_obj = new $action_class();
+ if ($config['db']['mirror'] && $action_obj->is_readonly()) {
+ if (is_array($config['db']['mirror'])) {
+ # "load balancing", ha ha
+ $k = array_rand($config['db']['mirror']);
+ $mirror = $config['db']['mirror'][$k];
+ } else {
+ $mirror = $config['db']['mirror'];
+ }
+ $config['db']['database'] = $mirror;
+ }
+ if (call_user_func(array($action_obj, 'prepare'), $_REQUEST)) {
+ call_user_func(array($action_obj, 'handle'), $_REQUEST);
+ }
+} else {
+ common_user_error(_('Unknown action'));
+} \ No newline at end of file
diff --git a/js/jquery.form.js b/js/jquery.form.js
new file mode 100644
index 000000000..cb8b5a660
--- /dev/null
+++ b/js/jquery.form.js
@@ -0,0 +1,632 @@
+/*
+ * jQuery Form Plugin
+ * version: 2.17 (06-NOV-2008)
+ * @requires jQuery v1.2.2 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id$
+ */
+;(function($) {
+
+/*
+ Usage Note:
+ -----------
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
+ functions are intended to be exclusive. Use ajaxSubmit if you want
+ to bind your own submit handler to the form. For example,
+
+ $(document).ready(function() {
+ $('#myForm').bind('submit', function() {
+ $(this).ajaxSubmit({
+ target: '#output'
+ });
+ return false; // <-- important!
+ });
+ });
+
+ Use ajaxForm when you want the plugin to manage all the event binding
+ for you. For example,
+
+ $(document).ready(function() {
+ $('#myForm').ajaxForm({
+ target: '#output'
+ });
+ });
+
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
+ at the appropriate time.
+*/
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ if (typeof options == 'function')
+ options = { success: options };
+
+ options = $.extend({
+ url: this.attr('action') || window.location.toString(),
+ type: this.attr('method') || 'GET'
+ }, options || {});
+
+ // hook for manipulating the form data before it is extracted;
+ // convenient for use with rich editors like tinyMCE or FCKEditor
+ var veto = {};
+ this.trigger('form-pre-serialize', [this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+ return this;
+ }
+
+ // provide opportunity to alter form data before it is serialized
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
+ return this;
+ }
+
+ var a = this.formToArray(options.semantic);
+ if (options.data) {
+ options.extraData = options.data;
+ for (var n in options.data) {
+ if(options.data[n] instanceof Array) {
+ for (var k in options.data[n])
+ a.push( { name: n, value: options.data[n][k] } )
+ }
+ else
+ a.push( { name: n, value: options.data[n] } );
+ }
+ }
+
+ // give pre-submit callback an opportunity to abort the submit
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
+ return this;
+ }
+
+ // fire vetoable 'validate' event
+ this.trigger('form-submit-validate', [a, this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+ return this;
+ }
+
+ var q = $.param(a);
+
+ if (options.type.toUpperCase() == 'GET') {
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+ options.data = null; // data is null for 'get'
+ }
+ else
+ options.data = q; // data is the query string for 'post'
+
+ var $form = this, callbacks = [];
+ if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
+ if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
+
+ // perform a load on the target only if dataType is not provided
+ if (!options.dataType && options.target) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ $(options.target).html(data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success)
+ callbacks.push(options.success);
+
+ options.success = function(data, status) {
+ for (var i=0, max=callbacks.length; i < max; i++)
+ callbacks[i].apply(options, [data, status, $form]);
+ };
+
+ // are there files to upload?
+ var files = $('input:file', this).fieldValue();
+ var found = false;
+ for (var j=0; j < files.length; j++)
+ if (files[j])
+ found = true;
+
+ // options.iframe allows user to force iframe mode
+ if (options.iframe || found) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if ($.browser.safari && options.closeKeepAlive)
+ $.get(options.closeKeepAlive, fileUpload);
+ else
+ fileUpload();
+ }
+ else
+ $.ajax(options);
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUpload() {
+ var form = $form[0];
+
+ if ($(':input[@name=submit]', form).length) {
+ alert('Error: Form elements must not be named "submit".');
+ return;
+ }
+
+ var opts = $.extend({}, $.ajaxSettings, options);
+ var s = jQuery.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
+
+ var id = 'jqFormIO' + (new Date().getTime());
+ var $io = $('<iframe id="' + id + '" name="' + id + '" />');
+ var io = $io[0];
+
+ if ($.browser.msie || $.browser.opera)
+ io.src = 'javascript:false;document.write("");';
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+ var xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function() {
+ this.aborted = 1;
+ $io.attr('src','about:blank'); // abort op in progress
+ }
+ };
+
+ var g = opts.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && ! $.active++) $.event.trigger("ajaxStart");
+ if (g) $.event.trigger("ajaxSend", [xhr, opts]);
+
+ if (s.beforeSend && s.beforeSend(xhr, s) === false) {
+ s.global && jQuery.active--;
+ return;
+ }
+ if (xhr.aborted)
+ return;
+
+ var cbInvoked = 0;
+ var timedOut = 0;
+
+ // add submitting element to data if we know it
+ var sub = form.clk;
+ if (sub) {
+ var n = sub.name;
+ if (n && !sub.disabled) {
+ options.extraData = options.extraData || {};
+ options.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ options.extraData[name+'.x'] = form.clk_x;
+ options.extraData[name+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ setTimeout(function() {
+ // make sure form attrs are set
+ var t = $form.attr('target'), a = $form.attr('action');
+ $form.attr({
+ target: id,
+ method: 'POST',
+ action: opts.url
+ });
+
+ // ie borks in some cases when setting encoding
+ if (! options.skipEncodingOverride) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (opts.timeout)
+ setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (options.extraData)
+ for (var n in options.extraData)
+ extraInputs.push(
+ $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
+ .appendTo(form)[0]);
+
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+ form.submit();
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ $form.attr('action', a);
+ t ? $form.attr('target', t) : $form.removeAttr('target');
+ $(extraInputs).remove();
+ }
+ }, 10);
+
+ function cb() {
+ if (cbInvoked++) return;
+
+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+ var operaHack = 0;
+ var ok = true;
+ try {
+ if (timedOut) throw 'timeout';
+ // extract the server response from the iframe
+ var data, doc;
+
+ doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+
+ if (doc.body == null && !operaHack && $.browser.opera) {
+ // In Opera 9.2.x the iframe DOM is not always traversable when
+ // the onload callback fires so we give Opera 100ms to right itself
+ operaHack = 1;
+ cbInvoked--;
+ setTimeout(cb, 100);
+ return;
+ }
+
+ xhr.responseText = doc.body ? doc.body.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': opts.dataType};
+ return headers[header];
+ };
+
+ if (opts.dataType == 'json' || opts.dataType == 'script') {
+ var ta = doc.getElementsByTagName('textarea')[0];
+ xhr.responseText = ta ? ta.value : xhr.responseText;
+ }
+ else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+ data = $.httpData(xhr, opts.dataType);
+ }
+ catch(e){
+ ok = false;
+ $.handleError(opts, xhr, 'error', e);
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (ok) {
+ opts.success(data, 'success');
+ if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
+ }
+ if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
+ if (g && ! --$.active) $.event.trigger("ajaxStop");
+ if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
+
+ // clean up
+ setTimeout(function() {
+ $io.remove();
+ xhr.responseXML = null;
+ }, 100);
+ };
+
+ function toXml(s, doc) {
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(s);
+ }
+ else
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
+ return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
+ };
+ };
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
+ $(this).ajaxSubmit(options);
+ return false;
+ }).each(function() {
+ // store options in hash
+ $(":submit,input:image", this).bind('click.form-plugin',function(e) {
+ var form = this.form;
+ form.clk = this;
+ if (this.type == 'image') {
+ if (e.offsetX != undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+ var offset = $(this).offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - this.offsetLeft;
+ form.clk_y = e.pageY - this.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
+ });
+ });
+};
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ this.unbind('submit.form-plugin');
+ return this.each(function() {
+ $(":submit,input:image", this).unbind('click.form-plugin');
+ });
+
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic) {
+ var a = [];
+ if (this.length == 0) return a;
+
+ var form = this[0];
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ if (!els) return a;
+ for(var i=0, max=els.length; i < max; i++) {
+ var el = els[i];
+ var n = el.name;
+ if (!n) continue;
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(!el.disabled && form.clk == el)
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ continue;
+ }
+
+ var v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ for(var j=0, jmax=v.length; j < jmax; j++)
+ a.push({name: n, value: v[j]});
+ }
+ else if (v !== null && typeof v != 'undefined')
+ a.push({name: n, value: v});
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle them here
+ var inputs = form.getElementsByTagName("input");
+ for(var i=0, max=inputs.length; i < max; i++) {
+ var input = inputs[i];
+ var n = input.name;
+ if(n && !input.disabled && input.type == "image" && form.clk == input)
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ }
+ return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&amp;name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&amp;name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) return;
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++)
+ a.push({name: n, value: v[i]});
+ }
+ else if (v !== null && typeof v != 'undefined')
+ a.push({name: this.name, value: v});
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ * <form><fieldset>
+ * <input name="A" type="text" />
+ * <input name="A" type="text" />
+ * <input name="B" type="checkbox" value="B1" />
+ * <input name="B" type="checkbox" value="B2"/>
+ * <input name="C" type="radio" value="C1" />
+ * <input name="C" type="radio" value="C2" />
+ * </fieldset></form>
+ *
+ * var v = $(':text').fieldValue();
+ * // if no values are entered into the text inputs
+ * v == ['','']
+ * // if values entered into the text inputs are 'foo' and 'bar'
+ * v == ['foo','bar']
+ *
+ * var v = $(':checkbox').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $(':radio').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
+ continue;
+ v.constructor == Array ? $.merge(val, v) : val.push(v);
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (typeof successful == 'undefined') successful = true;
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1))
+ return null;
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) return null;
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ // extra pain for IE...
+ var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
+ if (one) return v;
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return el.value;
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function() {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields();
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (t == 'text' || t == 'password' || tag == 'textarea')
+ this.value = '';
+ else if (t == 'checkbox' || t == 'radio')
+ this.checked = false;
+ else if (tag == 'select')
+ this.selectedIndex = -1;
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
+ this.reset();
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b == undefined) b = true;
+ return this.each(function() {
+ this.disabled = !b
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select == undefined) select = true;
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio')
+ this.checked = select;
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// helper fn for console logging
+// set $.fn.ajaxSubmit.debug to true to enable debug logging
+function log() {
+ if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
+ window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
+};
+
+})(jQuery);
diff --git a/js/jquery.js b/js/jquery.js
new file mode 100644
index 000000000..88e661eec
--- /dev/null
+++ b/js/jquery.js
@@ -0,0 +1,3549 @@
+(function(){
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+
+// Map over jQuery in case of overwrite
+var _jQuery = window.jQuery,
+// Map over the $ in case of overwrite
+ _$ = window.$;
+
+var jQuery = window.jQuery = window.$ = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context );
+};
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,
+
+// Is it a simple selector
+ isSimple = /^.[^:#\[\.]*$/,
+
+// Will speed up references to undefined, and allows munging its name.
+ undefined;
+
+jQuery.fn = jQuery.prototype = {
+ init: function( selector, context ) {
+ // Make sure that a selection was provided
+ selector = selector || document;
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+ // Handle HTML strings
+ if ( typeof selector == "string" ) {
+ // Are we dealing with HTML string or an ID?
+ var match = quickExpr.exec( selector );
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] )
+ selector = jQuery.clean( [ match[1] ], context );
+
+ // HANDLE: $("#id")
+ else {
+ var elem = document.getElementById( match[3] );
+
+ // Make sure an element was located
+ if ( elem ){
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id != match[3] )
+ return jQuery().find( selector );
+
+ // Otherwise, we inject the element directly into the jQuery object
+ return jQuery( elem );
+ }
+ selector = [];
+ }
+
+ // HANDLE: $(expr, [context])
+ // (which is just equivalent to: $(content).find(expr)
+ } else
+ return jQuery( context ).find( selector );
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) )
+ return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+ return this.setArray(jQuery.makeArray(selector));
+ },
+
+ // The current version of jQuery being used
+ jquery: "1.2.6",
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ // The number of elements contained in the matched element set
+ length: 0,
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == undefined ?
+
+ // Return a 'clean' array
+ jQuery.makeArray( this ) :
+
+ // Return just the object
+ this[ num ];
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems ) {
+ // Build a new jQuery matched element set
+ var ret = jQuery( elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Force the current matched set of elements to become
+ // the specified array of elements (destroying the stack in the process)
+ // You should use pushStack() in order to do this, but maintain the stack
+ setArray: function( elems ) {
+ // Resetting the length to 0, then using the native Array push
+ // is a super-fast way to populate an object with array-like properties
+ this.length = 0;
+ Array.prototype.push.apply( this, elems );
+
+ return this;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+ var ret = -1;
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem && elem.jquery ? elem[0] : elem
+ , this );
+ },
+
+ attr: function( name, value, type ) {
+ var options = name;
+
+ // Look for the case where we're accessing a style value
+ if ( name.constructor == String )
+ if ( value === undefined )
+ return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+ else {
+ options = {};
+ options[ name ] = value;
+ }
+
+ // Check to see if we're setting style values
+ return this.each(function(i){
+ // Set all the styles
+ for ( name in options )
+ jQuery.attr(
+ type ?
+ this.style :
+ this,
+ name, jQuery.prop( this, options[ name ], type, i, name )
+ );
+ });
+ },
+
+ css: function( key, value ) {
+ // ignore negative width and height values
+ if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+ value = undefined;
+ return this.attr( key, value, "curCSS" );
+ },
+
+ text: function( text ) {
+ if ( typeof text != "object" && text != null )
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+ var ret = "";
+
+ jQuery.each( text || this, function(){
+ jQuery.each( this.childNodes, function(){
+ if ( this.nodeType != 8 )
+ ret += this.nodeType != 1 ?
+ this.nodeValue :
+ jQuery.fn.text( [ this ] );
+ });
+ });
+
+ return ret;
+ },
+
+ wrapAll: function( html ) {
+ if ( this[0] )
+ // The elements to wrap the target around
+ jQuery( html, this[0].ownerDocument )
+ .clone()
+ .insertBefore( this[0] )
+ .map(function(){
+ var elem = this;
+
+ while ( elem.firstChild )
+ elem = elem.firstChild;
+
+ return elem;
+ })
+ .append(this);
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ return this.each(function(){
+ jQuery( this ).contents().wrapAll( html );
+ });
+ },
+
+ wrap: function( html ) {
+ return this.each(function(){
+ jQuery( this ).wrapAll( html );
+ });
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, false, function(elem){
+ if (this.nodeType == 1)
+ this.appendChild( elem );
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, true, function(elem){
+ if (this.nodeType == 1)
+ this.insertBefore( elem, this.firstChild );
+ });
+ },
+
+ before: function() {
+ return this.domManip(arguments, false, false, function(elem){
+ this.parentNode.insertBefore( elem, this );
+ });
+ },
+
+ after: function() {
+ return this.domManip(arguments, false, true, function(elem){
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ },
+
+ end: function() {
+ return this.prevObject || jQuery( [] );
+ },
+
+ find: function( selector ) {
+ var elems = jQuery.map(this, function(elem){
+ return jQuery.find( selector, elem );
+ });
+
+ return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+ jQuery.unique( elems ) :
+ elems );
+ },
+
+ clone: function( events ) {
+ // Do the clone
+ var ret = this.map(function(){
+ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+ // IE copies events bound via attachEvent when
+ // using cloneNode. Calling detachEvent on the
+ // clone will also remove the events from the orignal
+ // In order to get around this, we use innerHTML.
+ // Unfortunately, this means some modifications to
+ // attributes in IE that are actually only stored
+ // as properties will not be copied (such as the
+ // the name attribute on an input).
+ var clone = this.cloneNode(true),
+ container = document.createElement("div");
+ container.appendChild(clone);
+ return jQuery.clean([container.innerHTML])[0];
+ } else
+ return this.cloneNode(true);
+ });
+
+ // Need to set the expando to null on the cloned set if it exists
+ // removeData doesn't work here, IE removes it from the original as well
+ // this is primarily for IE but the data expando shouldn't be copied over in any browser
+ var clone = ret.find("*").andSelf().each(function(){
+ if ( this[ expando ] != undefined )
+ this[ expando ] = null;
+ });
+
+ // Copy the events from the original to the clone
+ if ( events === true )
+ this.find("*").andSelf().each(function(i){
+ if (this.nodeType == 3)
+ return;
+ var events = jQuery.data( this, "events" );
+
+ for ( var type in events )
+ for ( var handler in events[ type ] )
+ jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+ });
+
+ // Return the cloned set
+ return ret;
+ },
+
+ filter: function( selector ) {
+ return this.pushStack(
+ jQuery.isFunction( selector ) &&
+ jQuery.grep(this, function(elem, i){
+ return selector.call( elem, i );
+ }) ||
+
+ jQuery.multiFilter( selector, this ) );
+ },
+
+ not: function( selector ) {
+ if ( selector.constructor == String )
+ // test special case where just one selector is passed in
+ if ( isSimple.test( selector ) )
+ return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+ else
+ selector = jQuery.multiFilter( selector, this );
+
+ var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+ return this.filter(function() {
+ return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+ });
+ },
+
+ add: function( selector ) {
+ return this.pushStack( jQuery.unique( jQuery.merge(
+ this.get(),
+ typeof selector == 'string' ?
+ jQuery( selector ) :
+ jQuery.makeArray( selector )
+ )));
+ },
+
+ is: function( selector ) {
+ return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+ },
+
+ hasClass: function( selector ) {
+ return this.is( "." + selector );
+ },
+
+ val: function( value ) {
+ if ( value == undefined ) {
+
+ if ( this.length ) {
+ var elem = this[0];
+
+ // We need to handle select boxes special
+ if ( jQuery.nodeName( elem, "select" ) ) {
+ var index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type == "select-one";
+
+ // Nothing was selected
+ if ( index < 0 )
+ return null;
+
+ // Loop through all the selected options
+ for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+ var option = options[ i ];
+
+ if ( option.selected ) {
+ // Get the specifc value for the option
+ value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+
+ // We don't need an array for one selects
+ if ( one )
+ return value;
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+
+ // Everything else, we just grab the value
+ } else
+ return (this[0].value || "").replace(/\r/g, "");
+
+ }
+
+ return undefined;
+ }
+
+ if( value.constructor == Number )
+ value += '';
+
+ return this.each(function(){
+ if ( this.nodeType != 1 )
+ return;
+
+ if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+ this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+ jQuery.inArray(this.name, value) >= 0);
+
+ else if ( jQuery.nodeName( this, "select" ) ) {
+ var values = jQuery.makeArray(value);
+
+ jQuery( "option", this ).each(function(){
+ this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+ jQuery.inArray( this.text, values ) >= 0);
+ });
+
+ if ( !values.length )
+ this.selectedIndex = -1;
+
+ } else
+ this.value = value;
+ });
+ },
+
+ html: function( value ) {
+ return value == undefined ?
+ (this[0] ?
+ this[0].innerHTML :
+ null) :
+ this.empty().append( value );
+ },
+
+ replaceWith: function( value ) {
+ return this.after( value ).remove();
+ },
+
+ eq: function( i ) {
+ return this.slice( i, i + 1 );
+ },
+
+ slice: function() {
+ return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function(elem, i){
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ },
+
+ data: function( key, value ){
+ var parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ if ( data === undefined && this.length )
+ data = jQuery.data( this[0], key );
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ } else
+ return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+ jQuery.data( this, key, value );
+ });
+ },
+
+ removeData: function( key ){
+ return this.each(function(){
+ jQuery.removeData( this, key );
+ });
+ },
+
+ domManip: function( args, table, reverse, callback ) {
+ var clone = this.length > 1, elems;
+
+ return this.each(function(){
+ if ( !elems ) {
+ elems = jQuery.clean( args, this.ownerDocument );
+
+ if ( reverse )
+ elems.reverse();
+ }
+
+ var obj = this;
+
+ if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+ obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+ var scripts = jQuery( [] );
+
+ jQuery.each(elems, function(){
+ var elem = clone ?
+ jQuery( this ).clone( true )[0] :
+ this;
+
+ // execute all scripts after the elements have been injected
+ if ( jQuery.nodeName( elem, "script" ) )
+ scripts = scripts.add( elem );
+ else {
+ // Remove any inner scripts for later evaluation
+ if ( elem.nodeType == 1 )
+ scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+ // Inject the elements into the document
+ callback.call( obj, elem );
+ }
+ });
+
+ scripts.each( evalScript );
+ });
+ }
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+ if ( elem.src )
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+
+ else
+ jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+ if ( elem.parentNode )
+ elem.parentNode.removeChild( elem );
+}
+
+function now(){
+ return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+ // copy reference to target object
+ var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+ // Handle a deep copy situation
+ if ( target.constructor == Boolean ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target != "object" && typeof target != "function" )
+ target = {};
+
+ // extend jQuery itself if only one argument is passed
+ if ( length == i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ )
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null )
+ // Extend the base object
+ for ( var name in options ) {
+ var src = target[ name ], copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy )
+ continue;
+
+ // Recurse if we're merging object values
+ if ( deep && copy && typeof copy == "object" && !copy.nodeType )
+ target[ name ] = jQuery.extend( deep,
+ // Never move original objects, clone them
+ src || ( copy.length != null ? [ ] : { } )
+ , copy );
+
+ // Don't bring in undefined values
+ else if ( copy !== undefined )
+ target[ name ] = copy;
+
+ }
+
+ // Return the modified object
+ return target;
+};
+
+var expando = "jQuery" + now(), uuid = 0, windowData = {},
+ // exclude the following css properties to add px
+ exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+ // cache defaultView
+ defaultView = document.defaultView || {};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ window.$ = _$;
+
+ if ( deep )
+ window.jQuery = _jQuery;
+
+ return jQuery;
+ },
+
+ // See test/unit/core.js for details concerning this function.
+ isFunction: function( fn ) {
+ return !!fn && typeof fn != "string" && !fn.nodeName &&
+ fn.constructor != Array && /^[\s[]?function/.test( fn + "" );
+ },
+
+ // check if an element is in a (or is an) XML document
+ isXMLDoc: function( elem ) {
+ return elem.documentElement && !elem.body ||
+ elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+ },
+
+ // Evalulates a script in a global context
+ globalEval: function( data ) {
+ data = jQuery.trim( data );
+
+ if ( data ) {
+ // Inspired by code by Andrea Giammarchi
+ // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+ var head = document.getElementsByTagName("head")[0] || document.documentElement,
+ script = document.createElement("script");
+
+ script.type = "text/javascript";
+ if ( jQuery.browser.msie )
+ script.text = data;
+ else
+ script.appendChild( document.createTextNode( data ) );
+
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709).
+ head.insertBefore( script, head.firstChild );
+ head.removeChild( script );
+ }
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+ },
+
+ cache: {},
+
+ data: function( elem, name, data ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // Compute a unique ID for the element
+ if ( !id )
+ id = elem[ expando ] = ++uuid;
+
+ // Only generate the data cache if we're
+ // trying to access or manipulate it
+ if ( name && !jQuery.cache[ id ] )
+ jQuery.cache[ id ] = {};
+
+ // Prevent overriding the named cache with undefined values
+ if ( data !== undefined )
+ jQuery.cache[ id ][ name ] = data;
+
+ // Return the named cache data, or the ID for the element
+ return name ?
+ jQuery.cache[ id ][ name ] :
+ id;
+ },
+
+ removeData: function( elem, name ) {
+ elem = elem == window ?
+ windowData :
+ elem;
+
+ var id = elem[ expando ];
+
+ // If we want to remove a specific section of the element's data
+ if ( name ) {
+ if ( jQuery.cache[ id ] ) {
+ // Remove the section of cache data
+ delete jQuery.cache[ id ][ name ];
+
+ // If we've removed all the data, remove the element's cache
+ name = "";
+
+ for ( name in jQuery.cache[ id ] )
+ break;
+
+ if ( !name )
+ jQuery.removeData( elem );
+ }
+
+ // Otherwise, we want to remove all of the element's data
+ } else {
+ // Clean up the element expando
+ try {
+ delete elem[ expando ];
+ } catch(e){
+ // IE has trouble directly removing the expando
+ // but it's ok with using removeAttribute
+ if ( elem.removeAttribute )
+ elem.removeAttribute( expando );
+ }
+
+ // Completely remove the data cache
+ delete jQuery.cache[ id ];
+ }
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0, length = object.length;
+
+ if ( args ) {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.apply( object[ name ], args ) === false )
+ break;
+ } else
+ for ( ; i < length; )
+ if ( callback.apply( object[ i++ ], args ) === false )
+ break;
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( length == undefined ) {
+ for ( name in object )
+ if ( callback.call( object[ name ], name, object[ name ] ) === false )
+ break;
+ } else
+ for ( var value = object[0];
+ i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+ }
+
+ return object;
+ },
+
+ prop: function( elem, value, type, i, name ) {
+ // Handle executable functions
+ if ( jQuery.isFunction( value ) )
+ value = value.call( elem, i );
+
+ // Handle passing in a number to a CSS property
+ return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+ value + "px" :
+ value;
+ },
+
+ className: {
+ // internal only, use addClass("class")
+ add: function( elem, classNames ) {
+ jQuery.each((classNames || "").split(/\s+/), function(i, className){
+ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+ elem.className += (elem.className ? " " : "") + className;
+ });
+ },
+
+ // internal only, use removeClass("class")
+ remove: function( elem, classNames ) {
+ if (elem.nodeType == 1)
+ elem.className = classNames != undefined ?
+ jQuery.grep(elem.className.split(/\s+/), function(className){
+ return !jQuery.className.has( classNames, className );
+ }).join(" ") :
+ "";
+ },
+
+ // internal only, use hasClass("class")
+ has: function( elem, className ) {
+ return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( var name in options )
+ elem.style[ name ] = old[ name ];
+ },
+
+ css: function( elem, name, force ) {
+ if ( name == "width" || name == "height" ) {
+ var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+ function getWH() {
+ val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+ var padding = 0, border = 0;
+ jQuery.each( which, function() {
+ padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+ border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+ });
+ val -= Math.round(padding + border);
+ }
+
+ if ( jQuery(elem).is(":visible") )
+ getWH();
+ else
+ jQuery.swap( elem, props, getWH );
+
+ return Math.max(0, val);
+ }
+
+ return jQuery.curCSS( elem, name, force );
+ },
+
+ curCSS: function( elem, name, force ) {
+ var ret, style = elem.style;
+
+ // A helper method for determining if an element's values are broken
+ function color( elem ) {
+ if ( !jQuery.browser.safari )
+ return false;
+
+ // defaultView is cached
+ var ret = defaultView.getComputedStyle( elem, null );
+ return !ret || ret.getPropertyValue("color") == "";
+ }
+
+ // We need to handle opacity special in IE
+ if ( name == "opacity" && jQuery.browser.msie ) {
+ ret = jQuery.attr( style, "opacity" );
+
+ return ret == "" ?
+ "1" :
+ ret;
+ }
+ // Opera sometimes will give the wrong display answer, this fixes it, see #2037
+ if ( jQuery.browser.opera && name == "display" ) {
+ var save = style.outline;
+ style.outline = "0 solid black";
+ style.outline = save;
+ }
+
+ // Make sure we're using the right name for getting the float value
+ if ( name.match( /float/i ) )
+ name = styleFloat;
+
+ if ( !force && style && style[ name ] )
+ ret = style[ name ];
+
+ else if ( defaultView.getComputedStyle ) {
+
+ // Only "float" is needed here
+ if ( name.match( /float/i ) )
+ name = "float";
+
+ name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+ var computedStyle = defaultView.getComputedStyle( elem, null );
+
+ if ( computedStyle && !color( elem ) )
+ ret = computedStyle.getPropertyValue( name );
+
+ // If the element isn't reporting its values properly in Safari
+ // then some display: none elements are involved
+ else {
+ var swap = [], stack = [], a = elem, i = 0;
+
+ // Locate all of the parent display: none elements
+ for ( ; a && color(a); a = a.parentNode )
+ stack.unshift(a);
+
+ // Go through and make them visible, but in reverse
+ // (It would be better if we knew the exact display type that they had)
+ for ( ; i < stack.length; i++ )
+ if ( color( stack[ i ] ) ) {
+ swap[ i ] = stack[ i ].style.display;
+ stack[ i ].style.display = "block";
+ }
+
+ // Since we flip the display style, we have to handle that
+ // one special, otherwise get the value
+ ret = name == "display" && swap[ stack.length - 1 ] != null ?
+ "none" :
+ ( computedStyle && computedStyle.getPropertyValue( name ) ) || "";
+
+ // Finally, revert the display styles back
+ for ( i = 0; i < swap.length; i++ )
+ if ( swap[ i ] != null )
+ stack[ i ].style.display = swap[ i ];
+ }
+
+ // We should always get a number back from opacity
+ if ( name == "opacity" && ret == "" )
+ ret = "1";
+
+ } else if ( elem.currentStyle ) {
+ var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+ // Remember the original values
+ var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ style.left = ret || 0;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret;
+ },
+
+ clean: function( elems, context ) {
+ var ret = [];
+ context = context || document;
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if (typeof context.createElement == 'undefined')
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+ jQuery.each(elems, function(i, elem){
+ if ( !elem )
+ return;
+
+ if ( elem.constructor == Number )
+ elem += '';
+
+ // Convert html string into DOM nodes
+ if ( typeof elem == "string" ) {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+ all :
+ front + "></" + tag + ">";
+ });
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+ var wrap =
+ // option or optgroup
+ !tags.indexOf("<opt") &&
+ [ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+ !tags.indexOf("<leg") &&
+ [ 1, "<fieldset>", "</fieldset>" ] ||
+
+ tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+ [ 1, "<table>", "</table>" ] ||
+
+ !tags.indexOf("<tr") &&
+ [ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+ // <thead> matched above
+ (!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+ [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+ !tags.indexOf("<col") &&
+ [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+ // IE can't serialize <link> and <script> tags normally
+ jQuery.browser.msie &&
+ [ 1, "div<div>", "</div>" ] ||
+
+ [ 0, "", "" ];
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( wrap[0]-- )
+ div = div.lastChild;
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( jQuery.browser.msie ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+ div.childNodes :
+ [];
+
+ for ( var j = tbody.length - 1; j >= 0 ; --j )
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( /^\s/.test( elem ) )
+ div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+
+ }
+
+ elem = jQuery.makeArray( div.childNodes );
+ }
+
+ if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+ return;
+
+ if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+ ret.push( elem );
+
+ else
+ ret = jQuery.merge( ret, elem );
+
+ });
+
+ return ret;
+ },
+
+ attr: function( elem, name, value ) {
+ // don't set attributes on text and comment nodes
+ if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+ return undefined;
+
+ var notxml = !jQuery.isXMLDoc( elem ),
+ // Whether we are setting (or getting)
+ set = value !== undefined,
+ msie = jQuery.browser.msie;
+
+ // Try to normalize/fix the name
+ name = notxml && jQuery.props[ name ] || name;
+
+ // Only do all the following if this is a node (faster for style)
+ // IE elem.getAttribute passes even for style
+ if ( elem.tagName ) {
+
+ // These attributes require special treatment
+ var special = /href|src|style/.test( name );
+
+ // Safari mis-reports the default selected property of a hidden option
+ // Accessing the parent's selectedIndex property fixes it
+ if ( name == "selected" && jQuery.browser.safari )
+ elem.parentNode.selectedIndex;
+
+ // If applicable, access the attribute via the DOM 0 way
+ if ( name in elem && notxml && !special ) {
+ if ( set ){
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+ throw "type property can't be changed";
+
+ elem[ name ] = value;
+ }
+
+ // browsers index elements by id/name on forms, give priority to attributes.
+ if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+ return elem.getAttributeNode( name ).nodeValue;
+
+ return elem[ name ];
+ }
+
+ if ( msie && notxml && name == "style" )
+ return jQuery.attr( elem.style, "cssText", value );
+
+ if ( set )
+ // convert the value to a string (all browsers do this but IE) see #1070
+ elem.setAttribute( name, "" + value );
+
+ var attr = msie && notxml && special
+ // Some attributes require a special call on IE
+ ? elem.getAttribute( name, 2 )
+ : elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return attr === null ? undefined : attr;
+ }
+
+ // elem is actually elem.style ... set the style
+
+ // IE uses filters for opacity
+ if ( msie && name == "opacity" ) {
+ if ( set ) {
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ elem.zoom = 1;
+
+ // Set the alpha filter to set the opacity
+ elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+ (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+ }
+
+ return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+ (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+ "";
+ }
+
+ name = name.replace(/-([a-z])/ig, function(all, letter){
+ return letter.toUpperCase();
+ });
+
+ if ( set )
+ elem[ name ] = value;
+
+ return elem[ name ];
+ },
+
+ trim: function( text ) {
+ return (text || "").replace( /^\s+|\s+$/g, "" );
+ },
+
+ makeArray: function( array ) {
+ var ret = [];
+
+ if( array != null ){
+ var i = array.length;
+ //the window, strings and functions also have 'length'
+ if( i == null || array.split || array.setInterval || array.call )
+ ret[0] = array;
+ else
+ while( i )
+ ret[--i] = array[i];
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array ) {
+ for ( var i = 0, length = array.length; i < length; i++ )
+ // Use === because on IE, window == document
+ if ( array[ i ] === elem )
+ return i;
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ // We have to loop this way because IE & Opera overwrite the length
+ // expando of getElementsByTagName
+ var i = 0, elem, pos = first.length;
+ // Also, we need to make sure that the correct elements are being returned
+ // (IE returns comment nodes in a '*' query)
+ if ( jQuery.browser.msie ) {
+ while ( elem = second[ i++ ] )
+ if ( elem.nodeType != 8 )
+ first[ pos++ ] = elem;
+
+ } else
+ while ( elem = second[ i++ ] )
+ first[ pos++ ] = elem;
+
+ return first;
+ },
+
+ unique: function( array ) {
+ var ret = [], done = {};
+
+ try {
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ var id = jQuery.data( array[ i ] );
+
+ if ( !done[ id ] ) {
+ done[ id ] = true;
+ ret.push( array[ i ] );
+ }
+ }
+
+ } catch( e ) {
+ ret = array;
+ }
+
+ return ret;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [];
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ )
+ if ( !inv != !callback( elems[ i ], i ) )
+ ret.push( elems[ i ] );
+
+ return ret;
+ },
+
+ map: function( elems, callback ) {
+ var ret = [];
+
+ // Go through the array, translating each of the items to their
+ // new value (or values).
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ var value = callback( elems[ i ], i );
+
+ if ( value != null )
+ ret[ ret.length ] = value;
+ }
+
+ return ret.concat.apply( [], ret );
+ }
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+ version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+ safari: /webkit/.test( userAgent ),
+ opera: /opera/.test( userAgent ),
+ msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+ mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+ "styleFloat" :
+ "cssFloat";
+
+jQuery.extend({
+ // Check to see if the W3C box model is being used
+ boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+
+ props: {
+ "for": "htmlFor",
+ "class": "className",
+ "float": styleFloat,
+ cssFloat: styleFloat,
+ styleFloat: styleFloat,
+ readonly: "readOnly",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing"
+ }
+});
+
+jQuery.each({
+ parent: function(elem){return elem.parentNode;},
+ parents: function(elem){return jQuery.dir(elem,"parentNode");},
+ next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+ prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+ nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+ prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+ siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+ children: function(elem){return jQuery.sibling(elem.firstChild);},
+ contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = jQuery.map( this, fn );
+
+ if ( selector && typeof selector == "string" )
+ ret = jQuery.multiFilter( selector, ret );
+
+ return this.pushStack( jQuery.unique( ret ) );
+ };
+});
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function(name, original){
+ jQuery.fn[ name ] = function() {
+ var args = arguments;
+
+ return this.each(function(){
+ for ( var i = 0, length = args.length; i < length; i++ )
+ jQuery( args[ i ] )[ original ]( this );
+ });
+ };
+});
+
+jQuery.each({
+ removeAttr: function( name ) {
+ jQuery.attr( this, name, "" );
+ if (this.nodeType == 1)
+ this.removeAttribute( name );
+ },
+
+ addClass: function( classNames ) {
+ jQuery.className.add( this, classNames );
+ },
+
+ removeClass: function( classNames ) {
+ jQuery.className.remove( this, classNames );
+ },
+
+ toggleClass: function( classNames ) {
+ jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+ },
+
+ remove: function( selector ) {
+ if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+ // Prevent memory leaks
+ jQuery( "*", this ).add(this).each(function(){
+ jQuery.event.remove(this);
+ jQuery.removeData(this);
+ });
+ if (this.parentNode)
+ this.parentNode.removeChild( this );
+ }
+ },
+
+ empty: function() {
+ // Remove element nodes and prevent memory leaks
+ jQuery( ">*", this ).remove();
+
+ // Remove any remaining nodes
+ while ( this.firstChild )
+ this.removeChild( this.firstChild );
+ }
+}, function(name, fn){
+ jQuery.fn[ name ] = function(){
+ return this.each( fn, arguments );
+ };
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+ var type = name.toLowerCase();
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ return this[0] == window ?
+ // Opera reports document.body.client[Width/Height] properly in both quirks and standards
+ jQuery.browser.opera && document.body[ "client" + name ] ||
+
+ // Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+ jQuery.browser.safari && window[ "inner" + name ] ||
+
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+
+ // Get document width or height
+ this[0] == document ?
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ Math.max(
+ Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]),
+ Math.max(document.body["offset" + name], document.documentElement["offset" + name])
+ ) :
+
+ // Get or set width or height on the element
+ size == undefined ?
+ // Get width or height on the element
+ (this.length ? jQuery.css( this[0], type ) : null) :
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ this.css( type, size.constructor == String ? size : size + "px" );
+ };
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+ return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+ "(?:[\\w*_-]|\\\\.)" :
+ "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+ quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+ quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+ quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+ expr: {
+ "": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},
+ "#": function(a,i,m){return a.getAttribute("id")==m[2];},
+ ":": {
+ // Position Checks
+ lt: function(a,i,m){return i<m[3]-0;},
+ gt: function(a,i,m){return i>m[3]-0;},
+ nth: function(a,i,m){return m[3]-0==i;},
+ eq: function(a,i,m){return m[3]-0==i;},
+ first: function(a,i){return i==0;},
+ last: function(a,i,m,r){return i==r.length-1;},
+ even: function(a,i){return i%2==0;},
+ odd: function(a,i){return i%2;},
+
+ // Child Checks
+ "first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},
+ "last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},
+ "only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},
+
+ // Parent Checks
+ parent: function(a){return a.firstChild;},
+ empty: function(a){return !a.firstChild;},
+
+ // Text Check
+ contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},
+
+ // Visibility
+ visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},
+ hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},
+
+ // Form attributes
+ enabled: function(a){return !a.disabled;},
+ disabled: function(a){return a.disabled;},
+ checked: function(a){return a.checked;},
+ selected: function(a){return a.selected||jQuery.attr(a,"selected");},
+
+ // Form elements
+ text: function(a){return "text"==a.type;},
+ radio: function(a){return "radio"==a.type;},
+ checkbox: function(a){return "checkbox"==a.type;},
+ file: function(a){return "file"==a.type;},
+ password: function(a){return "password"==a.type;},
+ submit: function(a){return "submit"==a.type;},
+ image: function(a){return "image"==a.type;},
+ reset: function(a){return "reset"==a.type;},
+ button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");},
+ input: function(a){return /input|select|textarea|button/i.test(a.nodeName);},
+
+ // :has()
+ has: function(a,i,m){return jQuery.find(m[3],a).length;},
+
+ // :header
+ header: function(a){return /h\d/i.test(a.nodeName);},
+
+ // :animated
+ animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}
+ }
+ },
+
+ // The regular expressions that power the parsing engine
+ parse: [
+ // Match: [@value='test'], [@foo]
+ /^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+ // Match: :contains('foo')
+ /^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+ // Match: :even, :last-child, #id, .class
+ new RegExp("^([:.#]*)(" + chars + "+)")
+ ],
+
+ multiFilter: function( expr, elems, not ) {
+ var old, cur = [];
+
+ while ( expr && expr != old ) {
+ old = expr;
+ var f = jQuery.filter( expr, elems, not );
+ expr = f.t.replace(/^\s*,\s*/, "" );
+ cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+ }
+
+ return cur;
+ },
+
+ find: function( t, context ) {
+ // Quickly handle non-string expressions
+ if ( typeof t != "string" )
+ return [ t ];
+
+ // check to make sure context is a DOM element or a document
+ if ( context && context.nodeType != 1 && context.nodeType != 9)
+ return [ ];
+
+ // Set the correct context (if none is provided)
+ context = context || document;
+
+ // Initialize the search
+ var ret = [context], done = [], last, nodeName;
+
+ // Continue while a selector expression exists, and while
+ // we're no longer looping upon ourselves
+ while ( t && last != t ) {
+ var r = [];
+ last = t;
+
+ t = jQuery.trim(t);
+
+ var foundToken = false,
+
+ // An attempt at speeding up child selectors that
+ // point to a specific element tag
+ re = quickChild,
+
+ m = re.exec(t);
+
+ if ( m ) {
+ nodeName = m[1].toUpperCase();
+
+ // Perform our own iteration and filter
+ for ( var i = 0; ret[i]; i++ )
+ for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+ if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+ r.push( c );
+
+ ret = r;
+ t = t.replace( re, "" );
+ if ( t.indexOf(" ") == 0 ) continue;
+ foundToken = true;
+ } else {
+ re = /^([>+~])\s*(\w*)/i;
+
+ if ( (m = re.exec(t)) != null ) {
+ r = [];
+
+ var merge = {};
+ nodeName = m[2].toUpperCase();
+ m = m[1];
+
+ for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+ var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+ for ( ; n; n = n.nextSibling )
+ if ( n.nodeType == 1 ) {
+ var id = jQuery.data(n);
+
+ if ( m == "~" && merge[id] ) break;
+
+ if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+ if ( m == "~" ) merge[id] = true;
+ r.push( n );
+ }
+
+ if ( m == "+" ) break;
+ }
+ }
+
+ ret = r;
+
+ // And remove the token
+ t = jQuery.trim( t.replace( re, "" ) );
+ foundToken = true;
+ }
+ }
+
+ // See if there's still an expression, and that we haven't already
+ // matched a token
+ if ( t && !foundToken ) {
+ // Handle multiple expressions
+ if ( !t.indexOf(",") ) {
+ // Clean the result set
+ if ( context == ret[0] ) ret.shift();
+
+ // Merge the result sets
+ done = jQuery.merge( done, ret );
+
+ // Reset the context
+ r = ret = [context];
+
+ // Touch up the selector string
+ t = " " + t.substr(1,t.length);
+
+ } else {
+ // Optimize for the case nodeName#idName
+ var re2 = quickID;
+ var m = re2.exec(t);
+
+ // Re-organize the results, so that they're consistent
+ if ( m ) {
+ m = [ 0, m[2], m[3], m[1] ];
+
+ } else {
+ // Otherwise, do a traditional filter check for
+ // ID, class, and element selectors
+ re2 = quickClass;
+ m = re2.exec(t);
+ }
+
+ m[2] = m[2].replace(/\\/g, "");
+
+ var elem = ret[ret.length-1];
+
+ // Try to do a global search by ID, where we can
+ if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+ // Optimization for HTML document case
+ var oid = elem.getElementById(m[2]);
+
+ // Do a quick check for the existence of the actual ID attribute
+ // to avoid selecting by the name attribute in IE
+ // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+ if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+ oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+ // Do a quick check for node name (where applicable) so
+ // that div#foo searches will be really fast
+ ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+ } else {
+ // We need to find all descendant elements
+ for ( var i = 0; ret[i]; i++ ) {
+ // Grab the tag name being searched for
+ var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+ // Handle IE7 being really dumb about <object>s
+ if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+ tag = "param";
+
+ r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+ }
+
+ // It's faster to filter by class and be done with it
+ if ( m[1] == "." )
+ r = jQuery.classFilter( r, m[2] );
+
+ // Same with ID filtering
+ if ( m[1] == "#" ) {
+ var tmp = [];
+
+ // Try to find the element with the ID
+ for ( var i = 0; r[i]; i++ )
+ if ( r[i].getAttribute("id") == m[2] ) {
+ tmp = [ r[i] ];
+ break;
+ }
+
+ r = tmp;
+ }
+
+ ret = r;
+ }
+
+ t = t.replace( re2, "" );
+ }
+
+ }
+
+ // If a selector string still exists
+ if ( t ) {
+ // Attempt to filter it
+ var val = jQuery.filter(t,r);
+ ret = r = val.r;
+ t = jQuery.trim(val.t);
+ }
+ }
+
+ // An error occurred with the selector;
+ // just return an empty set instead
+ if ( t )
+ ret = [];
+
+ // Remove the root context
+ if ( ret && context == ret[0] )
+ ret.shift();
+
+ // And combine the results
+ done = jQuery.merge( done, ret );
+
+ return done;
+ },
+
+ classFilter: function(r,m,not){
+ m = " " + m + " ";
+ var tmp = [];
+ for ( var i = 0; r[i]; i++ ) {
+ var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+ if ( !not && pass || not && !pass )
+ tmp.push( r[i] );
+ }
+ return tmp;
+ },
+
+ filter: function(t,r,not) {
+ var last;
+
+ // Look for common filter expressions
+ while ( t && t != last ) {
+ last = t;
+
+ var p = jQuery.parse, m;
+
+ for ( var i = 0; p[i]; i++ ) {
+ m = p[i].exec( t );
+
+ if ( m ) {
+ // Remove what we just matched
+ t = t.substring( m[0].length );
+
+ m[2] = m[2].replace(/\\/g, "");
+ break;
+ }
+ }
+
+ if ( !m )
+ break;
+
+ // :not() is a special case that can be optimized by
+ // keeping it out of the expression list
+ if ( m[1] == ":" && m[2] == "not" )
+ // optimize if only one selector found (most common case)
+ r = isSimple.test( m[3] ) ?
+ jQuery.filter(m[3], r, true).r :
+ jQuery( r ).not( m[3] );
+
+ // We can get a big speed boost by filtering by class here
+ else if ( m[1] == "." )
+ r = jQuery.classFilter(r, m[2], not);
+
+ else if ( m[1] == "[" ) {
+ var tmp = [], type = m[3];
+
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+
+ if ( z == null || /href|src|selected/.test(m[2]) )
+ z = jQuery.attr(a,m[2]) || '';
+
+ if ( (type == "" && !!z ||
+ type == "=" && z == m[5] ||
+ type == "!=" && z != m[5] ||
+ type == "^=" && z && !z.indexOf(m[5]) ||
+ type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+ (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+ tmp.push( a );
+ }
+
+ r = tmp;
+
+ // We can get a speed boost by handling nth-child here
+ } else if ( m[1] == ":" && m[2] == "nth-child" ) {
+ var merge = {}, tmp = [],
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+ !/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+ // calculate the numbers (first)n+(last) including if they are negative
+ first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+
+ // loop through all the elements left in the jQuery object
+ for ( var i = 0, rl = r.length; i < rl; i++ ) {
+ var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+ if ( !merge[id] ) {
+ var c = 1;
+
+ for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+ if ( n.nodeType == 1 )
+ n.nodeIndex = c++;
+
+ merge[id] = true;
+ }
+
+ var add = false;
+
+ if ( first == 0 ) {
+ if ( node.nodeIndex == last )
+ add = true;
+ } else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+ add = true;
+
+ if ( add ^ not )
+ tmp.push( node );
+ }
+
+ r = tmp;
+
+ // Otherwise, find the expression to execute
+ } else {
+ var fn = jQuery.expr[ m[1] ];
+ if ( typeof fn == "object" )
+ fn = fn[ m[2] ];
+
+ if ( typeof fn == "string" )
+ fn = eval("false||function(a,i){return " + fn + ";}");
+
+ // Execute it against the current filter
+ r = jQuery.grep( r, function(elem, i){
+ return fn(elem, i, m, r);
+ }, not );
+ }
+ }
+
+ // Return an array of filtered elements (r)
+ // and the modified expression string (t)
+ return { r: r, t: t };
+ },
+
+ dir: function( elem, dir ){
+ var matched = [],
+ cur = elem[dir];
+ while ( cur && cur != document ) {
+ if ( cur.nodeType == 1 )
+ matched.push( cur );
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function(cur,result,dir,elem){
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] )
+ if ( cur.nodeType == 1 && ++num == result )
+ break;
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType == 1 && n != elem )
+ r.push( n );
+ }
+
+ return r;
+ }
+});
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+ // Bind an event to an element
+ // Original by Dean Edwards
+ add: function(elem, types, handler, data) {
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ // For whatever reason, IE has trouble passing the window object
+ // around, causing it to be cloned in the process
+ if ( jQuery.browser.msie && elem.setInterval )
+ elem = window;
+
+ // Make sure that the function being executed has a unique ID
+ if ( !handler.guid )
+ handler.guid = this.guid++;
+
+ // if data is passed, bind to handler
+ if( data != undefined ) {
+ // Create temporary function pointer to original handler
+ var fn = handler;
+
+ // Create unique handler function, wrapped around original handler
+ handler = this.proxy( fn, function() {
+ // Pass arguments and context to original handler
+ return fn.apply(this, arguments);
+ });
+
+ // Store data in unique handler
+ handler.data = data;
+ }
+
+ // Init the element's event structure
+ var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+ handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+ // Handle the second event of a trigger and when
+ // an event is called after a page has unloaded
+ if ( typeof jQuery != "undefined" && !jQuery.event.triggered )
+ return jQuery.event.handle.apply(arguments.callee.elem, arguments);
+ });
+ // Add elem as a property of the handle function
+ // This is to prevent a memory leak with non-native
+ // event in IE.
+ handle.elem = elem;
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type) {
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+ handler.type = parts[1];
+
+ // Get the current list of functions bound to this event
+ var handlers = events[type];
+
+ // Init the event handler queue
+ if (!handlers) {
+ handlers = events[type] = {};
+
+ // Check for a special event handler
+ // Only use addEventListener/attachEvent if the special
+ // events handler returns false
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+ // Bind the global event handler to the element
+ if (elem.addEventListener)
+ elem.addEventListener(type, handle, false);
+ else if (elem.attachEvent)
+ elem.attachEvent("on" + type, handle);
+ }
+ }
+
+ // Add the function to the element's handler list
+ handlers[handler.guid] = handler;
+
+ // Keep track of which events have been used, for global triggering
+ jQuery.event.global[type] = true;
+ });
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ guid: 1,
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function(elem, types, handler) {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return;
+
+ var events = jQuery.data(elem, "events"), ret, index;
+
+ if ( events ) {
+ // Unbind all events for the element
+ if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") )
+ for ( var type in events )
+ this.remove( elem, type + (types || "") );
+ else {
+ // types is actually an event object here
+ if ( types.type ) {
+ handler = types.handler;
+ types = types.type;
+ }
+
+ // Handle multiple events seperated by a space
+ // jQuery(...).unbind("mouseover mouseout", fn);
+ jQuery.each(types.split(/\s+/), function(index, type){
+ // Namespaced event handlers
+ var parts = type.split(".");
+ type = parts[0];
+
+ if ( events[type] ) {
+ // remove the given handler for the given type
+ if ( handler )
+ delete events[type][handler.guid];
+
+ // remove all handlers for the given type
+ else
+ for ( handler in events[type] )
+ // Handle the removal of namespaced events
+ if ( !parts[1] || events[type][handler].type == parts[1] )
+ delete events[type][handler];
+
+ // remove generic event handler if no more handlers exist
+ for ( ret in events[type] ) break;
+ if ( !ret ) {
+ if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+ if (elem.removeEventListener)
+ elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+ else if (elem.detachEvent)
+ elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+ }
+ ret = null;
+ delete events[type];
+ }
+ }
+ });
+ }
+
+ // Remove the expando if it's no longer used
+ for ( ret in events ) break;
+ if ( !ret ) {
+ var handle = jQuery.data( elem, "handle" );
+ if ( handle ) handle.elem = null;
+ jQuery.removeData( elem, "events" );
+ jQuery.removeData( elem, "handle" );
+ }
+ }
+ },
+
+ trigger: function(type, data, elem, donative, extra) {
+ // Clone the incoming data, if any
+ data = jQuery.makeArray(data);
+
+ if ( type.indexOf("!") >= 0 ) {
+ type = type.slice(0, -1);
+ var exclusive = true;
+ }
+
+ // Handle a global trigger
+ if ( !elem ) {
+ // Only trigger if we've ever bound an event for it
+ if ( this.global[type] )
+ jQuery("*").add([window, document]).trigger(type, data);
+
+ // Handle triggering a single element
+ } else {
+ // don't do events on text and comment nodes
+ if ( elem.nodeType == 3 || elem.nodeType == 8 )
+ return undefined;
+
+ var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+ // Check to see if we need to provide a fake event, or not
+ event = !data[0] || !data[0].preventDefault;
+
+ // Pass along a fake event
+ if ( event ) {
+ data.unshift({
+ type: type,
+ target: elem,
+ preventDefault: function(){},
+ stopPropagation: function(){},
+ timeStamp: now()
+ });
+ data[0][expando] = true; // no need to fix fake event
+ }
+
+ // Enforce the right trigger type
+ data[0].type = type;
+ if ( exclusive )
+ data[0].exclusive = true;
+
+ // Trigger the event, it is assumed that "handle" is a function
+ var handle = jQuery.data(elem, "handle");
+ if ( handle )
+ val = handle.apply( elem, data );
+
+ // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+ if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+ val = false;
+
+ // Extra functions don't get the custom event object
+ if ( event )
+ data.shift();
+
+ // Handle triggering of extra function
+ if ( extra && jQuery.isFunction( extra ) ) {
+ // call the extra function and tack the current return value on the end for possible inspection
+ ret = extra.apply( elem, val == null ? data : data.concat( val ) );
+ // if anything is returned, give it precedence and have it overwrite the previous value
+ if (ret !== undefined)
+ val = ret;
+ }
+
+ // Trigger the native events (except for clicks on links)
+ if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+ this.triggered = true;
+ try {
+ elem[ type ]();
+ // prevent IE from throwing an error for some hidden elements
+ } catch (e) {}
+ }
+
+ this.triggered = false;
+ }
+
+ return val;
+ },
+
+ handle: function(event) {
+ // returned undefined or false
+ var val, ret, namespace, all, handlers;
+
+ event = arguments[0] = jQuery.event.fix( event || window.event );
+
+ // Namespaced event handlers
+ namespace = event.type.split(".");
+ event.type = namespace[0];
+ namespace = namespace[1];
+ // Cache this now, all = true means, any handler
+ all = !namespace && !event.exclusive;
+
+ handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+ for ( var j in handlers ) {
+ var handler = handlers[j];
+
+ // Filter the functions by class
+ if ( all || handler.type == namespace ) {
+ // Pass in a reference to the handler function itself
+ // So that we can later remove it
+ event.handler = handler;
+ event.data = handler.data;
+
+ ret = handler.apply( this, arguments );
+
+ if ( val !== false )
+ val = ret;
+
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+
+ return val;
+ },
+
+ fix: function(event) {
+ if ( event[expando] == true )
+ return event;
+
+ // store a copy of the original event object
+ // and "clone" to set read-only properties
+ var originalEvent = event;
+ event = { originalEvent: originalEvent };
+ var props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");
+ for ( var i=props.length; i; i-- )
+ event[ props[i] ] = originalEvent[ props[i] ];
+
+ // Mark it as fixed
+ event[expando] = true;
+
+ // add preventDefault and stopPropagation since
+ // they will not work on the clone
+ event.preventDefault = function() {
+ // if preventDefault exists run it on the original event
+ if (originalEvent.preventDefault)
+ originalEvent.preventDefault();
+ // otherwise set the returnValue property of the original event to false (IE)
+ originalEvent.returnValue = false;
+ };
+ event.stopPropagation = function() {
+ // if stopPropagation exists run it on the original event
+ if (originalEvent.stopPropagation)
+ originalEvent.stopPropagation();
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ originalEvent.cancelBubble = true;
+ };
+
+ // Fix timeStamp
+ event.timeStamp = event.timeStamp || now();
+
+ // Fix target property, if necessary
+ if ( !event.target )
+ event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+ // check if target is a textnode (safari)
+ if ( event.target.nodeType == 3 )
+ event.target = event.target.parentNode;
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && event.fromElement )
+ event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && event.clientX != null ) {
+ var doc = document.documentElement, body = document.body;
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+ }
+
+ // Add which for key events
+ if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+ event.which = event.charCode || event.keyCode;
+
+ // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+ if ( !event.metaKey && event.ctrlKey )
+ event.metaKey = event.ctrlKey;
+
+ // Add which for click: 1 == left; 2 == middle; 3 == right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && event.button )
+ event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+ return event;
+ },
+
+ proxy: function( fn, proxy ){
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+ // So proxy can be declared as an argument
+ return proxy;
+ },
+
+ special: {
+ ready: {
+ setup: function() {
+ // Make sure the ready event is setup
+ bindReady();
+ return;
+ },
+
+ teardown: function() { return; }
+ },
+
+ mouseenter: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseenter
+ event.type = "mouseenter";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ },
+
+ mouseleave: {
+ setup: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ teardown: function() {
+ if ( jQuery.browser.msie ) return false;
+ jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+ return true;
+ },
+
+ handler: function(event) {
+ // If we actually just moused on to a sub-element, ignore it
+ if ( withinElement(event, this) ) return true;
+ // Execute the right handlers by setting the event type to mouseleave
+ event.type = "mouseleave";
+ return jQuery.event.handle.apply(this, arguments);
+ }
+ }
+ }
+};
+
+jQuery.fn.extend({
+ bind: function( type, data, fn ) {
+ return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+ jQuery.event.add( this, type, fn || data, fn && data );
+ });
+ },
+
+ one: function( type, data, fn ) {
+ var one = jQuery.event.proxy( fn || data, function(event) {
+ jQuery(this).unbind(event, one);
+ return (fn || data).apply( this, arguments );
+ });
+ return this.each(function(){
+ jQuery.event.add( this, type, one, fn && data);
+ });
+ },
+
+ unbind: function( type, fn ) {
+ return this.each(function(){
+ jQuery.event.remove( this, type, fn );
+ });
+ },
+
+ trigger: function( type, data, fn ) {
+ return this.each(function(){
+ jQuery.event.trigger( type, data, this, true, fn );
+ });
+ },
+
+ triggerHandler: function( type, data, fn ) {
+ return this[0] && jQuery.event.trigger( type, data, this[0], false, fn );
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments, i = 1;
+
+ // link all the functions, so any of them can unbind this click handler
+ while( i < args.length )
+ jQuery.event.proxy( fn, args[i++] );
+
+ return this.click( jQuery.event.proxy( fn, function(event) {
+ // Figure out which function to execute
+ this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ this.lastToggle++ ].apply( this, arguments ) || false;
+ }));
+ },
+
+ hover: function(fnOver, fnOut) {
+ return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+ },
+
+ ready: function(fn) {
+ // Attach the listeners
+ bindReady();
+
+ // If the DOM is already ready
+ if ( jQuery.isReady )
+ // Execute the function immediately
+ fn.call( document, jQuery );
+
+ // Otherwise, remember the function for later
+ else
+ // Add the function to the wait list
+ jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+
+ return this;
+ }
+});
+
+jQuery.extend({
+ isReady: false,
+ readyList: [],
+ // Handle when the DOM is ready
+ ready: function() {
+ // Make sure that the DOM is not already loaded
+ if ( !jQuery.isReady ) {
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If there are functions bound, to execute
+ if ( jQuery.readyList ) {
+ // Execute all of them
+ jQuery.each( jQuery.readyList, function(){
+ this.call( document );
+ });
+
+ // Reset the list of functions
+ jQuery.readyList = null;
+ }
+
+ // Trigger any bound ready events
+ jQuery(document).triggerHandler("ready");
+ }
+ }
+});
+
+var readyBound = false;
+
+function bindReady(){
+ if ( readyBound ) return;
+ readyBound = true;
+
+ // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+ if ( document.addEventListener && !jQuery.browser.opera)
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+
+ // If IE is used and is not in a frame
+ // Continually check to see if the document is ready
+ if ( jQuery.browser.msie && window == top ) (function(){
+ if (jQuery.isReady) return;
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch( error ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+
+ if ( jQuery.browser.opera )
+ document.addEventListener( "DOMContentLoaded", function () {
+ if (jQuery.isReady) return;
+ for (var i = 0; i < document.styleSheets.length; i++)
+ if (document.styleSheets[i].disabled) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ }, false);
+
+ if ( jQuery.browser.safari ) {
+ var numStyles;
+ (function(){
+ if (jQuery.isReady) return;
+ if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ if ( numStyles === undefined )
+ numStyles = jQuery("style, link[rel=stylesheet]").length;
+ if ( document.styleSheets.length != numStyles ) {
+ setTimeout( arguments.callee, 0 );
+ return;
+ }
+ // and execute any waiting functions
+ jQuery.ready();
+ })();
+ }
+
+ // A fallback to window.onload, that will always work
+ jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+ "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
+ "submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+ // Handle event binding
+ jQuery.fn[name] = function(fn){
+ return fn ? this.bind(name, fn) : this.trigger(name);
+ };
+});
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event, elem) {
+ // Check if mouse(over|out) are still within the same parent element
+ var parent = event.relatedTarget;
+ // Traverse up the tree
+ while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
+ // Return true if we actually just moused on to a sub-element
+ return parent == elem;
+};
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery(window).bind("unload", function() {
+ jQuery("*").add(document).unbind();
+});
+jQuery.fn.extend({
+ // Keep a copy of the old load
+ _load: jQuery.fn.load,
+
+ load: function( url, params, callback ) {
+ if ( typeof url != 'string' )
+ return this._load( url );
+
+ var off = url.indexOf(" ");
+ if ( off >= 0 ) {
+ var selector = url.slice(off, url.length);
+ url = url.slice(0, off);
+ }
+
+ callback = callback || function(){};
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params )
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = null;
+
+ // Otherwise, build a param string
+ } else {
+ params = jQuery.param( params );
+ type = "POST";
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function(res, status){
+ // If successful, inject the HTML into all the matched elements
+ if ( status == "success" || status == "notmodified" )
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div/>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ res.responseText );
+
+ self.each( callback, [res.responseText, status, res] );
+ }
+ });
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param(this.serializeArray());
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return jQuery.nodeName(this, "form") ?
+ jQuery.makeArray(this.elements) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ (this.checked || /select|textarea/i.test(this.nodeName) ||
+ /text|hidden|password/i.test(this.type));
+ })
+ .map(function(i, elem){
+ var val = jQuery(this).val();
+ return val == null ? null :
+ val.constructor == Array ?
+ jQuery.map( val, function(val, i){
+ return {name: elem.name, value: val};
+ }) :
+ {name: elem.name, value: val};
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+ jQuery.fn[o] = function(f){
+ return this.bind(o, f);
+ };
+});
+
+var jsc = now();
+
+jQuery.extend({
+ get: function( url, data, callback, type ) {
+ // shift arguments if data argument was ommited
+ if ( jQuery.isFunction( data ) ) {
+ callback = data;
+ data = null;
+ }
+
+ return jQuery.ajax({
+ type: "GET",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get(url, null, callback, "script");
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get(url, data, callback, "json");
+ },
+
+ post: function( url, data, callback, type ) {
+ if ( jQuery.isFunction( data ) ) {
+ callback = data;
+ data = {};
+ }
+
+ return jQuery.ajax({
+ type: "POST",
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ },
+
+ ajaxSetup: function( settings ) {
+ jQuery.extend( jQuery.ajaxSettings, settings );
+ },
+
+ ajaxSettings: {
+ url: location.href,
+ global: true,
+ type: "GET",
+ timeout: 0,
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ data: null,
+ username: null,
+ password: null,
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ script: "text/javascript, application/javascript",
+ json: "application/json, text/javascript",
+ text: "text/plain",
+ _default: "*/*"
+ }
+ },
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+
+ ajax: function( s ) {
+ // Extend the settings, but re-extend 's' so that it can be
+ // checked again later (in the test suite, specifically)
+ s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+ var jsonp, jsre = /=\?(&|$)/g, status, data,
+ type = s.type.toUpperCase();
+
+ // convert data if not already a string
+ if ( s.data && s.processData && typeof s.data != "string" )
+ s.data = jQuery.param(s.data);
+
+ // Handle JSONP Parameter Callbacks
+ if ( s.dataType == "jsonp" ) {
+ if ( type == "GET" ) {
+ if ( !s.url.match(jsre) )
+ s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ } else if ( !s.data || !s.data.match(jsre) )
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+ jsonp = "jsonp" + jsc++;
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data )
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = function(tmp){
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+ try{ delete window[ jsonp ]; } catch(e){}
+ if ( head )
+ head.removeChild( script );
+ };
+ }
+
+ if ( s.dataType == "script" && s.cache == null )
+ s.cache = false;
+
+ if ( s.cache === false && type == "GET" ) {
+ var ts = now();
+ // try replacing _= if it is there
+ var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+ }
+
+ // If data is available, append data to url for get requests
+ if ( s.data && type == "GET" ) {
+ s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+ // IE likes to send both get and post data, prevent this
+ s.data = null;
+ }
+
+ // Watch for a new set of requests
+ if ( s.global && ! jQuery.active++ )
+ jQuery.event.trigger( "ajaxStart" );
+
+ // Matches an absolute URL, and saves the domain
+ var remote = /^(?:\w+:)?\/\/([^\/?#]+)/;
+
+ // If we're requesting a remote document
+ // and trying to load JSON or Script with a GET
+ if ( s.dataType == "script" && type == "GET"
+ && remote.test(s.url) && remote.exec(s.url)[1] != location.host ){
+ var head = document.getElementsByTagName("head")[0];
+ var script = document.createElement("script");
+ script.src = s.url;
+ if (s.scriptCharset)
+ script.charset = s.scriptCharset;
+
+ // Handle Script loading
+ if ( !jsonp ) {
+ var done = false;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function(){
+ if ( !done && (!this.readyState ||
+ this.readyState == "loaded" || this.readyState == "complete") ) {
+ done = true;
+ success();
+ complete();
+ head.removeChild( script );
+ }
+ };
+ }
+
+ head.appendChild(script);
+
+ // We handle everything using the script element injection
+ return undefined;
+ }
+
+ var requestDone = false;
+
+ // Create the request object; Microsoft failed to properly
+ // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+ var xhr = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if( s.username )
+ xhr.open(type, s.url, s.async, s.username, s.password);
+ else
+ xhr.open(type, s.url, s.async);
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ // Set the correct header, if data is being sent
+ if ( s.data )
+ xhr.setRequestHeader("Content-Type", s.contentType);
+
+ // Set the If-Modified-Since header, if ifModified mode.
+ if ( s.ifModified )
+ xhr.setRequestHeader("If-Modified-Since",
+ jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+ // Set header so the called script knows that it's an XMLHttpRequest
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+ // Set the Accepts header for the server, depending on the dataType
+ xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+ s.accepts[ s.dataType ] + ", */*" :
+ s.accepts._default );
+ } catch(e){}
+
+ // Allow custom headers/mimetypes
+ if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
+ // cleanup active request counter
+ s.global && jQuery.active--;
+ // close opended socket
+ xhr.abort();
+ return false;
+ }
+
+ if ( s.global )
+ jQuery.event.trigger("ajaxSend", [xhr, s]);
+
+ // Wait for a response to come back
+ var onreadystatechange = function(isTimeout){
+ // The transfer is complete and the data is available, or the request timed out
+ if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+ requestDone = true;
+
+ // clear poll interval
+ if (ival) {
+ clearInterval(ival);
+ ival = null;
+ }
+
+ status = isTimeout == "timeout" && "timeout" ||
+ !jQuery.httpSuccess( xhr ) && "error" ||
+ s.ifModified && jQuery.httpNotModified( xhr, s.url ) && "notmodified" ||
+ "success";
+
+ if ( status == "success" ) {
+ // Watch for, and catch, XML document parse errors
+ try {
+ // process the data (runs the xml through httpData regardless of callback)
+ data = jQuery.httpData( xhr, s.dataType, s.dataFilter );
+ } catch(e) {
+ status = "parsererror";
+ }
+ }
+
+ // Make sure that the request was successful or notmodified
+ if ( status == "success" ) {
+ // Cache Last-Modified header, if ifModified mode.
+ var modRes;
+ try {
+ modRes = xhr.getResponseHeader("Last-Modified");
+ } catch(e) {} // swallow exception thrown by FF if header is not available
+
+ if ( s.ifModified && modRes )
+ jQuery.lastModified[s.url] = modRes;
+
+ // JSONP handles its own success callback
+ if ( !jsonp )
+ success();
+ } else
+ jQuery.handleError(s, xhr, status);
+
+ // Fire the complete handlers
+ complete();
+
+ // Stop memory leaks
+ if ( s.async )
+ xhr = null;
+ }
+ };
+
+ if ( s.async ) {
+ // don't attach the handler to the request, just poll it instead
+ var ival = setInterval(onreadystatechange, 13);
+
+ // Timeout checker
+ if ( s.timeout > 0 )
+ setTimeout(function(){
+ // Check to see if the request is still happening
+ if ( xhr ) {
+ // Cancel the request
+ xhr.abort();
+
+ if( !requestDone )
+ onreadystatechange( "timeout" );
+ }
+ }, s.timeout);
+ }
+
+ // Send the data
+ try {
+ xhr.send(s.data);
+ } catch(e) {
+ jQuery.handleError(s, xhr, null, e);
+ }
+
+ // firefox 1.5 doesn't fire statechange for sync requests
+ if ( !s.async )
+ onreadystatechange();
+
+ function success(){
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success )
+ s.success( data, status );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
+ }
+
+ function complete(){
+ // Process result
+ if ( s.complete )
+ s.complete(xhr, status);
+
+ // The request was completed
+ if ( s.global )
+ jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active )
+ jQuery.event.trigger( "ajaxStop" );
+ }
+
+ // return XMLHttpRequest to allow aborting the request etc.
+ return xhr;
+ },
+
+ handleError: function( s, xhr, status, e ) {
+ // If a local callback was specified, fire it
+ if ( s.error ) s.error( xhr, status, e );
+
+ // Fire the global callback
+ if ( s.global )
+ jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Determines if an XMLHttpRequest was successful or not
+ httpSuccess: function( xhr ) {
+ try {
+ // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+ return !xhr.status && location.protocol == "file:" ||
+ ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223 ||
+ jQuery.browser.safari && xhr.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ // Determines if an XMLHttpRequest returns NotModified
+ httpNotModified: function( xhr, url ) {
+ try {
+ var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+ // Firefox always returns 200. check Last-Modified date
+ return xhr.status == 304 || xhrRes == jQuery.lastModified[url] ||
+ jQuery.browser.safari && xhr.status == undefined;
+ } catch(e){}
+ return false;
+ },
+
+ httpData: function( xhr, type, filter ) {
+ var ct = xhr.getResponseHeader("content-type"),
+ xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if ( xml && data.documentElement.tagName == "parsererror" )
+ throw "parsererror";
+
+ // Allow a pre-filtering function to sanitize the response
+ if( filter )
+ data = filter( data, type );
+
+ // If the type is "script", eval it in global context
+ if ( type == "script" )
+ jQuery.globalEval( data );
+
+ // Get the JavaScript object, if JSON is used.
+ if ( type == "json" )
+ data = eval("(" + data + ")");
+
+ return data;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a ) {
+ var s = [];
+
+ // If an array was passed in, assume that it is an array
+ // of form elements
+ if ( a.constructor == Array || a.jquery )
+ // Serialize the form elements
+ jQuery.each( a, function(){
+ s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+ });
+
+ // Otherwise, assume that it's an object of key/value pairs
+ else
+ // Serialize the key/values
+ for ( var j in a )
+ // If the value is an array then the key names need to be repeated
+ if ( a[j] && a[j].constructor == Array )
+ jQuery.each( a[j], function(){
+ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+ });
+ else
+ s.push( encodeURIComponent(j) + "=" + encodeURIComponent( jQuery.isFunction(a[j]) ? a[j]() : a[j] ) );
+
+ // Return the resulting serialization
+ return s.join("&").replace(/%20/g, "+");
+ }
+
+});
+jQuery.fn.extend({
+ show: function(speed,callback){
+ return speed ?
+ this.animate({
+ height: "show", width: "show", opacity: "show"
+ }, speed, callback) :
+
+ this.filter(":hidden").each(function(){
+ this.style.display = this.oldblock || "";
+ if ( jQuery.css(this,"display") == "none" ) {
+ var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+ this.style.display = elem.css("display");
+ // handle an edge condition where css is - div { display:none; } or similar
+ if (this.style.display == "none")
+ this.style.display = "block";
+ elem.remove();
+ }
+ }).end();
+ },
+
+ hide: function(speed,callback){
+ return speed ?
+ this.animate({
+ height: "hide", width: "hide", opacity: "hide"
+ }, speed, callback) :
+
+ this.filter(":visible").each(function(){
+ this.oldblock = this.oldblock || jQuery.css(this,"display");
+ this.style.display = "none";
+ }).end();
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2 ){
+ return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+ this._toggle.apply( this, arguments ) :
+ fn ?
+ this.animate({
+ height: "toggle", width: "toggle", opacity: "toggle"
+ }, fn, fn2) :
+ this.each(function(){
+ jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+ });
+ },
+
+ slideDown: function(speed,callback){
+ return this.animate({height: "show"}, speed, callback);
+ },
+
+ slideUp: function(speed,callback){
+ return this.animate({height: "hide"}, speed, callback);
+ },
+
+ slideToggle: function(speed, callback){
+ return this.animate({height: "toggle"}, speed, callback);
+ },
+
+ fadeIn: function(speed, callback){
+ return this.animate({opacity: "show"}, speed, callback);
+ },
+
+ fadeOut: function(speed, callback){
+ return this.animate({opacity: "hide"}, speed, callback);
+ },
+
+ fadeTo: function(speed,to,callback){
+ return this.animate({opacity: to}, speed, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed(speed, easing, callback);
+
+ return this[ optall.queue === false ? "each" : "queue" ](function(){
+ if ( this.nodeType != 1)
+ return false;
+
+ var opt = jQuery.extend({}, optall), p,
+ hidden = jQuery(this).is(":hidden"), self = this;
+
+ for ( p in prop ) {
+ if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+ return opt.complete.call(this);
+
+ if ( p == "height" || p == "width" ) {
+ // Store display property
+ opt.display = jQuery.css(this, "display");
+
+ // Make sure that nothing sneaks out
+ opt.overflow = this.style.overflow;
+ }
+ }
+
+ if ( opt.overflow != null )
+ this.style.overflow = "hidden";
+
+ opt.curAnim = jQuery.extend({}, prop);
+
+ jQuery.each( prop, function(name, val){
+ var e = new jQuery.fx( self, opt, name );
+
+ if ( /toggle|show|hide/.test(val) )
+ e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+ else {
+ var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+ start = e.cur(true) || 0;
+
+ if ( parts ) {
+ var end = parseFloat(parts[2]),
+ unit = parts[3] || "px";
+
+ // We need to compute starting value
+ if ( unit != "px" ) {
+ self.style[ name ] = (end || 1) + unit;
+ start = ((end || 1) / e.cur(true)) * start;
+ self.style[ name ] = start + unit;
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] )
+ end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+ e.custom( start, end, unit );
+ } else
+ e.custom( start, val, "" );
+ }
+ });
+
+ // For JS strict compliance
+ return true;
+ });
+ },
+
+ queue: function(type, fn){
+ if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+ fn = type;
+ type = "fx";
+ }
+
+ if ( !type || (typeof type == "string" && !fn) )
+ return queue( this[0], type );
+
+ return this.each(function(){
+ if ( fn.constructor == Array )
+ queue(this, type, fn);
+ else {
+ queue(this, type).push( fn );
+
+ if ( queue(this, type).length == 1 )
+ fn.call(this);
+ }
+ });
+ },
+
+ stop: function(clearQueue, gotoEnd){
+ var timers = jQuery.timers;
+
+ if (clearQueue)
+ this.queue([]);
+
+ this.each(function(){
+ // go in reverse order so anything added to the queue during the loop is ignored
+ for ( var i = timers.length - 1; i >= 0; i-- )
+ if ( timers[i].elem == this ) {
+ if (gotoEnd)
+ // force the next step to be the last
+ timers[i](true);
+ timers.splice(i, 1);
+ }
+ });
+
+ // start the next in the queue if the last step wasn't forced
+ if (!gotoEnd)
+ this.dequeue();
+
+ return this;
+ }
+
+});
+
+var queue = function( elem, type, array ) {
+ if ( elem ){
+
+ type = type || "fx";
+
+ var q = jQuery.data( elem, type + "queue" );
+
+ if ( !q || array )
+ q = jQuery.data( elem, type + "queue", jQuery.makeArray(array) );
+
+ }
+ return q;
+};
+
+jQuery.fn.dequeue = function(type){
+ type = type || "fx";
+
+ return this.each(function(){
+ var q = queue(this, type);
+
+ q.shift();
+
+ if ( q.length )
+ q[0].call( this );
+ });
+};
+
+jQuery.extend({
+
+ speed: function(speed, easing, fn) {
+ var opt = speed && speed.constructor == Object ? speed : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && easing.constructor != Function && easing
+ };
+
+ opt.duration = (opt.duration && opt.duration.constructor == Number ?
+ opt.duration :
+ jQuery.fx.speeds[opt.duration]) || jQuery.fx.speeds.def;
+
+ // Queueing
+ opt.old = opt.complete;
+ opt.complete = function(){
+ if ( opt.queue !== false )
+ jQuery(this).dequeue();
+ if ( jQuery.isFunction( opt.old ) )
+ opt.old.call( this );
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+ timerId: null,
+
+ fx: function( elem, options, prop ){
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ if ( !options.orig )
+ options.orig = {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+
+ // Simple function for setting a style value
+ update: function(){
+ if ( this.options.step )
+ this.options.step.call( this.elem, this.now, this );
+
+ (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+ // Set display property to block for height/width animations
+ if ( this.prop == "height" || this.prop == "width" )
+ this.elem.style.display = "block";
+ },
+
+ // Get the current size
+ cur: function(force){
+ if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+ return this.elem[ this.prop ];
+
+ var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+ return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+ },
+
+ // Start an animation from one number to another
+ custom: function(from, to, unit){
+ this.startTime = now();
+ this.start = from;
+ this.end = to;
+ this.unit = unit || this.unit || "px";
+ this.now = this.start;
+ this.pos = this.state = 0;
+ this.update();
+
+ var self = this;
+ function t(gotoEnd){
+ return self.step(gotoEnd);
+ }
+
+ t.elem = this.elem;
+
+ jQuery.timers.push(t);
+
+ if ( jQuery.timerId == null ) {
+ jQuery.timerId = setInterval(function(){
+ var timers = jQuery.timers;
+
+ for ( var i = 0; i < timers.length; i++ )
+ if ( !timers[i]() )
+ timers.splice(i--, 1);
+
+ if ( !timers.length ) {
+ clearInterval( jQuery.timerId );
+ jQuery.timerId = null;
+ }
+ }, 13);
+ }
+ },
+
+ // Simple 'show' function
+ show: function(){
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ this.custom(0, this.cur());
+
+ // Make sure that we start at a small width/height to avoid any
+ // flash of content
+ if ( this.prop == "width" || this.prop == "height" )
+ this.elem.style[this.prop] = "1px";
+
+ // Start by showing the element
+ jQuery(this.elem).show();
+ },
+
+ // Simple 'hide' function
+ hide: function(){
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom(this.cur(), 0);
+ },
+
+ // Each step of an animation
+ step: function(gotoEnd){
+ var t = now();
+
+ if ( gotoEnd || t > this.options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ this.options.curAnim[ this.prop ] = true;
+
+ var done = true;
+ for ( var i in this.options.curAnim )
+ if ( this.options.curAnim[i] !== true )
+ done = false;
+
+ if ( done ) {
+ if ( this.options.display != null ) {
+ // Reset the overflow
+ this.elem.style.overflow = this.options.overflow;
+
+ // Reset the display
+ this.elem.style.display = this.options.display;
+ if ( jQuery.css(this.elem, "display") == "none" )
+ this.elem.style.display = "block";
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( this.options.hide )
+ this.elem.style.display = "none";
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( this.options.hide || this.options.show )
+ for ( var p in this.options.curAnim )
+ jQuery.attr(this.elem.style, p, this.options.orig[p]);
+ }
+
+ if ( done )
+ // Execute the complete function
+ this.options.complete.call( this.elem );
+
+ return false;
+ } else {
+ var n = t - this.startTime;
+ this.state = n / this.options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+
+};
+
+jQuery.extend( jQuery.fx, {
+ speeds:{
+ slow: 600,
+ fast: 200,
+ // Default speed
+ def: 400
+ },
+ step: {
+ scrollLeft: function(fx){
+ fx.elem.scrollLeft = fx.now;
+ },
+
+ scrollTop: function(fx){
+ fx.elem.scrollTop = fx.now;
+ },
+
+ opacity: function(fx){
+ jQuery.attr(fx.elem.style, "opacity", fx.now);
+ },
+
+ _default: function(fx){
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ }
+ }
+});
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+ var left = 0, top = 0, elem = this[0], results;
+
+ if ( elem ) with ( jQuery.browser ) {
+ var parent = elem.parentNode,
+ offsetChild = elem,
+ offsetParent = elem.offsetParent,
+ doc = elem.ownerDocument,
+ safari2 = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
+ css = jQuery.curCSS,
+ fixed = css(elem, "position") == "fixed";
+
+ // Use getBoundingClientRect if available
+ if ( elem.getBoundingClientRect ) {
+ var box = elem.getBoundingClientRect();
+
+ // Add the document scroll offsets
+ add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+ box.top + Math.max(doc.documentElement.scrollTop, doc.body.scrollTop));
+
+ // IE adds the HTML element's border, by default it is medium which is 2px
+ // IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+ // IE 7 standards mode, the border is always 2px
+ // This border/offset is typically represented by the clientLeft and clientTop properties
+ // However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+ // Therefore this method will be off by 2px in IE while in quirksmode
+ add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+
+ // Otherwise loop through the offsetParents and parentNodes
+ } else {
+
+ // Initial element offsets
+ add( elem.offsetLeft, elem.offsetTop );
+
+ // Get parent offsets
+ while ( offsetParent ) {
+ // Add offsetParent offsets
+ add( offsetParent.offsetLeft, offsetParent.offsetTop );
+
+ // Mozilla and Safari > 2 does not include the border on offset parents
+ // However Mozilla adds the border for table or table cells
+ if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+ border( offsetParent );
+
+ // Add the document scroll offsets if position is fixed on any offsetParent
+ if ( !fixed && css(offsetParent, "position") == "fixed" )
+ fixed = true;
+
+ // Set offsetChild to previous offsetParent unless it is the body element
+ offsetChild = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+ // Get next offsetParent
+ offsetParent = offsetParent.offsetParent;
+ }
+
+ // Get parent scroll offsets
+ while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+ // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+ if ( !/^inline|table.*$/i.test(css(parent, "display")) )
+ // Subtract parent scroll offsets
+ add( -parent.scrollLeft, -parent.scrollTop );
+
+ // Mozilla does not add the border for a parent that has overflow != visible
+ if ( mozilla && css(parent, "overflow") != "visible" )
+ border( parent );
+
+ // Get next parent
+ parent = parent.parentNode;
+ }
+
+ // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+ // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+ if ( (safari2 && (fixed || css(offsetChild, "position") == "absolute")) ||
+ (mozilla && css(offsetChild, "position") != "absolute") )
+ add( -doc.body.offsetLeft, -doc.body.offsetTop );
+
+ // Add the document scroll offsets if position is fixed
+ if ( fixed )
+ add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+ Math.max(doc.documentElement.scrollTop, doc.body.scrollTop));
+ }
+
+ // Return an object with top and left properties
+ results = { top: top, left: left };
+ }
+
+ function border(elem) {
+ add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
+ }
+
+ function add(l, t) {
+ left += parseInt(l, 10) || 0;
+ top += parseInt(t, 10) || 0;
+ }
+
+ return results;
+};
+
+
+jQuery.fn.extend({
+ position: function() {
+ var left = 0, top = 0, results;
+
+ if ( this[0] ) {
+ // Get *real* offsetParent
+ var offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= num( this, 'marginTop' );
+ offset.left -= num( this, 'marginLeft' );
+
+ // Add offsetParent borders
+ parentOffset.top += num( offsetParent, 'borderTopWidth' );
+ parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+ // Subtract the two offsets
+ results = {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ }
+
+ return results;
+ },
+
+ offsetParent: function() {
+ var offsetParent = this[0].offsetParent;
+ while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+ offsetParent = offsetParent.offsetParent;
+ return jQuery(offsetParent);
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+ var method = 'scroll' + name;
+
+ jQuery.fn[ method ] = function(val) {
+ if (!this[0]) return;
+
+ return val != undefined ?
+
+ // Set the scroll offset
+ this.each(function() {
+ this == window || this == document ?
+ window.scrollTo(
+ !i ? val : jQuery(window).scrollLeft(),
+ i ? val : jQuery(window).scrollTop()
+ ) :
+ this[ method ] = val;
+ }) :
+
+ // Return the scroll offset
+ this[0] == window || this[0] == document ?
+ self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+ jQuery.boxModel && document.documentElement[ method ] ||
+ document.body[ method ] :
+ this[0][ method ];
+ };
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+ var tl = i ? "Left" : "Top", // top or left
+ br = i ? "Right" : "Bottom"; // bottom or right
+
+ // innerHeight and innerWidth
+ jQuery.fn["inner" + name] = function(){
+ return this[ name.toLowerCase() ]() +
+ num(this, "padding" + tl) +
+ num(this, "padding" + br);
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn["outer" + name] = function(margin) {
+ return this["inner" + name]() +
+ num(this, "border" + tl + "Width") +
+ num(this, "border" + br + "Width") +
+ (margin ?
+ num(this, "margin" + tl) + num(this, "margin" + br) : 0);
+ };
+
+});})();
diff --git a/js/jquery.min.js b/js/jquery.min.js
new file mode 100644
index 000000000..82b98e1d7
--- /dev/null
+++ b/js/jquery.min.js
@@ -0,0 +1,32 @@
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else
+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else
+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else
+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else
+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else
+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else
+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&&copy&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else
+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else
+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else
+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else
+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&&notxml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&&notxml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else
+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else
+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click(jQuery.event.proxy(fn,function(event){this.lastToggle=(this.lastToggle||0)%i;event.preventDefault();return args[this.lastToggle++].apply(this,arguments)||false;}));},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else
+jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i<document.styleSheets.length;i++)if(document.styleSheets[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready();},false);if(jQuery.browser.safari){var numStyles;(function(){if(jQuery.isReady)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery("style, link[rel=stylesheet]").length;if(document.styleSheets.length!=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind(name,fn):this.trigger(name);};});var withinElement=function(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)try{parent=parent.parentNode;}catch(error){parent=elem;}return parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add(document).unbind();});jQuery.fn.extend({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof url!='string')return this._load(url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else
+xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else
+jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else
+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
+s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else
+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file
diff --git a/js/util.js b/js/util.js
new file mode 100644
index 000000000..38a958968
--- /dev/null
+++ b/js/util.js
@@ -0,0 +1,179 @@
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+$(document).ready(function(){
+ // count character on keyup
+ function counter(event){
+ var maxLength = 140;
+ var currentLength = $("#status_textarea").val().length;
+ var remaining = maxLength - currentLength;
+ var counter = $("#counter");
+ counter.text(remaining);
+
+ if (remaining <= 0) {
+ $("#status_form").addClass("response_error");
+ } else {
+ $("#status_form").removeClass("response_error");
+ }
+ }
+
+ function submitonreturn(event) {
+ if (event.keyCode == 13) {
+ $("#status_form").submit();
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+ }
+ return true;
+ }
+
+ if ($("#status_textarea").length) {
+ $("#status_textarea").bind("keyup", counter);
+ $("#status_textarea").bind("keydown", submitonreturn);
+
+ // run once in case there's something in there
+ counter();
+
+ // set the focus
+ $("#status_textarea").focus();
+ }
+
+ // XXX: refactor this code
+
+ var favoptions = { dataType: 'xml',
+ success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
+ var dis = new_form.id;
+ var fav = dis.replace('disfavor', 'favor');
+ $('form#'+fav).replaceWith(new_form);
+ $('form#'+dis).ajaxForm(disoptions).each(addAjaxHidden);
+ }
+ };
+
+ var disoptions = { dataType: 'xml',
+ success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
+ var fav = new_form.id;
+ var dis = fav.replace('favor', 'disfavor');
+ $('form#'+dis).replaceWith(new_form);
+ $('form#'+fav).ajaxForm(favoptions).each(addAjaxHidden);
+ }
+ };
+
+ function addAjaxHidden() {
+ var ajax = document.createElement('input');
+ ajax.setAttribute('type', 'hidden');
+ ajax.setAttribute('name', 'ajax');
+ ajax.setAttribute('value', 1);
+ this.appendChild(ajax);
+ }
+
+ $("form.favor").ajaxForm(favoptions);
+ $("form.disfavor").ajaxForm(disoptions);
+ $("form.favor").each(addAjaxHidden);
+ $("form.disfavor").each(addAjaxHidden);
+
+ $("#nudge").ajaxForm ({ dataType: 'xml',
+ beforeSubmit: function(xml) { $("form#nudge input[type=submit]").attr("disabled", "disabled");
+ $("form#nudge input[type=submit]").addClass("disabled");
+ },
+ success: function(xml) { $("#nudge").replaceWith(document._importNode($("#nudge_response", xml).get(0),true));
+ $("#nudge input[type=submit]").removeAttr("disabled");
+ $("#nudge input[type=submit]").removeClass("disabled");
+ }
+ });
+ $("#nudge").each(addAjaxHidden);
+
+ var Subscribe = { dataType: 'xml',
+ beforeSubmit: function(formData, jqForm, options) { $("form.subscribe input[type=submit]").attr("disabled", "disabled");
+ $("form.subscribe input[type=submit]").addClass("disabled");
+ },
+ success: function(xml) { var form_unsubscribe = document._importNode($('form', xml).get(0), true);
+ var form_unsubscribe_id = form_unsubscribe.id;
+ var form_subscribe_id = form_unsubscribe_id.replace('unsubscribe', 'subscribe');
+ $("form#"+form_subscribe_id).replaceWith(form_unsubscribe);
+ $("form#"+form_unsubscribe_id).ajaxForm(UnSubscribe).each(addAjaxHidden);
+ $("dd.subscribers").text(parseInt($("dd.subscribers").text())+1);
+ $("form.subscribe input[type=submit]").removeAttr("disabled");
+ $("form.subscribe input[type=submit]").removeClass("disabled");
+ }
+ };
+
+ var UnSubscribe = { dataType: 'xml',
+ beforeSubmit: function(formData, jqForm, options) { $("form.unsubscribe input[type=submit]").attr("disabled", "disabled");
+ $("form.unsubscribe input[type=submit]").addClass("disabled");
+ },
+ success: function(xml) { var form_subscribe = document._importNode($('form', xml).get(0), true);
+ var form_subscribe_id = form_subscribe.id;
+ var form_unsubscribe_id = form_subscribe_id.replace('subscribe', 'unsubscribe');
+ $("form#"+form_unsubscribe_id).replaceWith(form_subscribe);
+ $("form#"+form_subscribe_id).ajaxForm(Subscribe).each(addAjaxHidden);
+ $("#profile_send_a_new_message").remove();
+ $("#profile_nudge").remove();
+ $("dd.subscribers").text(parseInt($("dd.subscribers").text())-1);
+ $("form.unsubscribe input[type=submit]").removeAttr("disabled");
+ $("form.unsubscribe input[type=submit]").removeClass("disabled");
+ }
+ };
+
+ $("form.subscribe").ajaxForm(Subscribe);
+ $("form.unsubscribe").ajaxForm(UnSubscribe);
+ $("form.subscribe").each(addAjaxHidden);
+ $("form.unsubscribe").each(addAjaxHidden);
+
+
+ var PostNotice = { dataType: 'xml',
+ beforeSubmit: function(formData, jqForm, options) { if ($("#status_textarea").get(0).value.length == 0) {
+ $("#status_form").addClass("response_error");
+ return false;
+ }
+ $("#status_form input[type=submit]").attr("disabled", "disabled");
+ $("#status_form input[type=submit]").addClass("disabled");
+ return true;
+ },
+ success: function(xml) { if ($("#error", xml).length > 0 || $("#command_result", xml).length > 0) {
+ var result = document._importNode($("p", xml).get(0), true);
+ result = result.textContent || result.innerHTML;
+ alert(result);
+ }
+ else {
+ $("#notices").prepend(document._importNode($("li", xml).get(0), true));
+ $("#status_textarea").val("");
+ counter();
+ $(".notice_single:first").css({display:"none"});
+ $(".notice_single:first").fadeIn(2500);
+ }
+ $("#status_form input[type=submit]").removeAttr("disabled");
+ $("#status_form input[type=submit]").removeClass("disabled");
+ }
+ };
+ $("#status_form").ajaxForm(PostNotice);
+ $("#status_form").each(addAjaxHidden);
+});
+
+function doreply(nick,id) {
+ rgx_username = /^[0-9a-zA-Z\-_.]*$/;
+ if (nick.match(rgx_username)) {
+ replyto = "@" + nick + " ";
+ if ($("#status_textarea").length) {
+ $("#status_textarea").val(replyto);
+ $("form#status_form input#inreplyto").val(id);
+ $("#status_textarea").focus();
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/js/xbImportNode.js b/js/xbImportNode.js
new file mode 100644
index 000000000..1da6bae69
--- /dev/null
+++ b/js/xbImportNode.js
@@ -0,0 +1,46 @@
+/* is this stuff defined? */
+if (!document.ELEMENT_NODE) {
+ document.ELEMENT_NODE = 1;
+ document.ATTRIBUTE_NODE = 2;
+ document.TEXT_NODE = 3;
+ document.CDATA_SECTION_NODE = 4;
+ document.ENTITY_REFERENCE_NODE = 5;
+ document.ENTITY_NODE = 6;
+ document.PROCESSING_INSTRUCTION_NODE = 7;
+ document.COMMENT_NODE = 8;
+ document.DOCUMENT_NODE = 9;
+ document.DOCUMENT_TYPE_NODE = 10;
+ document.DOCUMENT_FRAGMENT_NODE = 11;
+ document.NOTATION_NODE = 12;
+}
+
+document._importNode = function(node, allChildren) {
+ /* find the node type to import */
+ switch (node.nodeType) {
+ case document.ELEMENT_NODE:
+ /* create a new element */
+ var newNode = document.createElement(node.nodeName);
+ /* does the node have any attributes to add? */
+ if (node.attributes && node.attributes.length > 0)
+ /* add all of the attributes */
+ for (var i = 0, il = node.attributes.length; i < il;) {
+ if (node.attributes[i].nodeName == 'class') {
+ newNode.className = node.getAttribute(node.attributes[i++].nodeName);
+ } else {
+ newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i++].nodeName));
+ }
+ }
+ /* are we going after children too, and does the node have any? */
+ if (allChildren && node.childNodes && node.childNodes.length > 0)
+ /* recursively get all of the child nodes */
+ for (var i = 0, il = node.childNodes.length; i < il;)
+ newNode.appendChild(document._importNode(node.childNodes[i++], allChildren));
+ return newNode;
+ break;
+ case document.TEXT_NODE:
+ case document.CDATA_SECTION_NODE:
+ case document.COMMENT_NODE:
+ return document.createTextNode(node.nodeValue);
+ break;
+ }
+};
diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php
new file mode 100644
index 000000000..7beae0ec6
--- /dev/null
+++ b/lib/Shorturl_api.php
@@ -0,0 +1,121 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class ShortUrlApi {
+ protected $service_url;
+
+ function __construct($service_url) {
+ $this->service_url = $service_url;
+ }
+
+ function shorten($url) {
+ if ($this->is_long($url)) return $this->shorten_imp($url);
+ return $url;
+ }
+
+ protected function shorten_imp($url) {
+ return "To Override";
+ }
+
+ private function is_long($url) {
+ return strlen($url) >= 30;
+ }
+
+ protected function http_post($data) {
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $this->service_url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_POST, 1);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+ $response = curl_exec($ch);
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+ if (($code < 200) || ($code >= 400)) return false;
+ return $response;
+ }
+
+ protected function http_get($url) {
+ $encoded_url = urlencode($url);
+ return file_get_contents("{$this->service_url}$encoded_url");
+ }
+
+ protected function tidy($response) {
+ $response = str_replace('&nbsp;', ' ', $response);
+ $config = array('output-xhtml' => true);
+ $tidy = new tidy;
+ $tidy->parseString($response, $config, 'utf8');
+ $tidy->cleanRepair();
+ return (string)$tidy;
+ }
+}
+
+class LilUrl extends ShortUrlApi {
+ function __construct() {
+ parent::__construct('http://ur1.ca/');
+ }
+
+ protected function shorten_imp($url) {
+ $data['longurl'] = $url;
+ $response = $this->http_post($data);
+ if (!$response) return $url;
+ $y = @simplexml_load_string($response);
+ if (!isset($y->body)) return $url;
+ $x = $y->body->p[0]->a->attributes();
+ if (isset($x['href'])) return $x['href'];
+ return $url;
+ }
+}
+
+
+class PtitUrl extends ShortUrlApi {
+ function __construct() {
+ parent::__construct('http://ptiturl.com/?creer=oui&action=Reduire&url=');
+ }
+
+ protected function shorten_imp($url) {
+ $response = $this->http_get($url);
+ if (!$response) return $url;
+ $response = $this->tidy($response);
+ $y = @simplexml_load_string($response);
+ if (!isset($y->body)) return $url;
+ $xml = $y->body->center->table->tr->td->pre->a->attributes();
+ if (isset($xml['href'])) return $xml['href'];
+ return $url;
+ }
+}
+
+class TightUrl extends ShortUrlApi {
+ function __construct() {
+ parent::__construct('http://2tu.us/?save=y&url=');
+ }
+
+ protected function shorten_imp($url) {
+ $response = $this->http_get($url);
+ if (!$response) return $url;
+ $response = $this->tidy($response);
+ $y = @simplexml_load_string($response);
+ if (!isset($y->body)) return $url;
+ $xml = $y->body->p[0]->code[0]->a->attributes();
+ if (isset($xml['href'])) return $xml['href'];
+ return $url;
+ }
+}
+
diff --git a/lib/action.php b/lib/action.php
new file mode 100644
index 000000000..7a2461bb5
--- /dev/null
+++ b/lib/action.php
@@ -0,0 +1,142 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class Action { // lawsuit
+
+ var $args;
+
+ function Action() {
+ }
+
+ # For initializing members of the class
+
+ function prepare($argarray) {
+ $this->args =& common_copy_args($argarray);
+ return true;
+ }
+
+ # For comparison with If-Last-Modified
+ # If not applicable, return NULL
+
+ function last_modified() {
+ return NULL;
+ }
+
+ function etag() {
+ return NULL;
+ }
+
+ function is_readonly() {
+ return false;
+ }
+
+ function arg($key, $def=NULL) {
+ if (array_key_exists($key, $this->args)) {
+ return $this->args[$key];
+ } else {
+ return $def;
+ }
+ }
+
+ function trimmed($key, $def=NULL) {
+ $arg = $this->arg($key, $def);
+ return (is_string($arg)) ? trim($arg) : $arg;
+ }
+
+ # Note: argarray ignored, since it's now passed in in prepare()
+
+ function handle($argarray=NULL) {
+
+ $lm = $this->last_modified();
+ $etag = $this->etag();
+
+ if ($etag) {
+ header('ETag: ' . $etag);
+ }
+
+ if ($lm) {
+ header('Last-Modified: ' . date(DATE_RFC1123, $lm));
+ $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+ if ($if_modified_since) {
+ $ims = strtotime($if_modified_since);
+ if ($lm <= $ims) {
+ if (!$etag || $this->_has_etag($etag, $_SERVER['HTTP_IF_NONE_MATCH'])) {
+ header('HTTP/1.1 304 Not Modified');
+ # Better way to do this?
+ exit(0);
+ }
+ }
+ }
+ }
+ }
+
+ function _has_etag($etag, $if_none_match) {
+ return ($if_none_match) && in_array($etag, explode(',', $if_none_match));
+ }
+
+ function boolean($key, $def=false) {
+ $arg = strtolower($this->trimmed($key));
+
+ if (is_null($arg)) {
+ return $def;
+ } else if (in_array($arg, array('true', 'yes', '1'))) {
+ return true;
+ } else if (in_array($arg, array('false', 'no', '0'))) {
+ return false;
+ } else {
+ return $def;
+ }
+ }
+
+ function server_error($msg, $code=500) {
+ $action = $this->trimmed('action');
+ common_debug("Server error '$code' on '$action': $msg", __FILE__);
+ common_server_error($msg, $code);
+ }
+
+ function client_error($msg, $code=400) {
+ $action = $this->trimmed('action');
+ common_debug("User error '$code' on '$action': $msg", __FILE__);
+ common_user_error($msg, $code);
+ }
+
+ function self_url() {
+ $action = $this->trimmed('action');
+ $args = $this->args;
+ unset($args['action']);
+ foreach (array_keys($_COOKIE) as $cookie) {
+ unset($args[$cookie]);
+ }
+ return common_local_url($action, $args);
+ }
+
+ function nav_menu($menu) {
+ $action = $this->trimmed('action');
+ common_element_start('ul', array('id' => 'nav_views'));
+ foreach ($menu as $menuaction => $menudesc) {
+ common_menu_item(common_local_url($menuaction, isset($menudesc[2]) ? $menudesc[2] : NULL),
+ $menudesc[0],
+ $menudesc[1],
+ $action == $menuaction);
+ }
+ common_element_end('ul');
+ }
+}
diff --git a/lib/common.php b/lib/common.php
new file mode 100644
index 000000000..f7308d0b4
--- /dev/null
+++ b/lib/common.php
@@ -0,0 +1,172 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+define('LACONICA_VERSION', '0.6.4.3');
+
+define('AVATAR_PROFILE_SIZE', 96);
+define('AVATAR_STREAM_SIZE', 48);
+define('AVATAR_MINI_SIZE', 24);
+define('MAX_AVATAR_SIZE', 256 * 1024);
+
+define('NOTICES_PER_PAGE', 20);
+define('PROFILES_PER_PAGE', 20);
+
+define('FOREIGN_NOTICE_SEND', 1);
+define('FOREIGN_NOTICE_RECV', 2);
+define('FOREIGN_NOTICE_SEND_REPLY', 4);
+
+define('FOREIGN_FRIEND_SEND', 1);
+define('FOREIGN_FRIEND_RECV', 2);
+
+define_syslog_variables();
+
+# append our extlib dir as the last-resort place to find libs
+
+set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/');
+
+# global configuration object
+
+require_once('PEAR.php');
+require_once('DB/DataObject.php');
+require_once('DB/DataObject/Cast.php'); # for dates
+
+require_once(INSTALLDIR.'/lib/language.php');
+
+// default configuration, overwritten in config.php
+
+$config =
+ array('site' =>
+ array('name' => 'Just another Laconica microblog',
+ 'server' => 'localhost',
+ 'theme' => 'default',
+ 'path' => '/',
+ 'logfile' => NULL,
+ 'fancy' => false,
+ 'locale_path' => INSTALLDIR.'/locale',
+ 'language' => 'en_US',
+ 'languages' => get_all_languages(),
+ 'email' =>
+ array_key_exists('SERVER_ADMIN', $_SERVER) ? $_SERVER['SERVER_ADMIN'] : NULL,
+ 'broughtby' => NULL,
+ 'timezone' => 'UTC',
+ 'broughtbyurl' => NULL,
+ 'closed' => false,
+ 'inviteonly' => false,
+ 'private' => false),
+ 'syslog' =>
+ array('appname' => 'laconica', # for syslog
+ 'priority' => 'debug'), # XXX: currently ignored
+ 'queue' =>
+ array('enabled' => false),
+ 'license' =>
+ array('url' => 'http://creativecommons.org/licenses/by/3.0/',
+ 'title' => 'Creative Commons Attribution 3.0',
+ 'image' => 'http://i.creativecommons.org/l/by/3.0/88x31.png'),
+ 'mail' =>
+ array('backend' => 'mail',
+ 'params' => NULL),
+ 'nickname' =>
+ array('blacklist' => array(),
+ 'featured' => array()),
+ 'profile' =>
+ array('banned' => array()),
+ 'avatar' =>
+ array('server' => NULL),
+ 'public' =>
+ array('localonly' => true,
+ 'blacklist' => array()),
+ 'theme' =>
+ array('server' => NULL),
+ 'throttle' =>
+ array('enabled' => false, // whether to throttle edits; false by default
+ 'count' => 20, // number of allowed messages in timespan
+ 'timespan' => 600), // timespan for throttling
+ 'xmpp' =>
+ array('enabled' => false,
+ 'server' => 'INVALID SERVER',
+ 'port' => 5222,
+ 'user' => 'update',
+ 'encryption' => true,
+ 'resource' => 'uniquename',
+ 'password' => 'blahblahblah',
+ 'host' => NULL, # only set if != server
+ 'debug' => false, # print extra debug info
+ 'public' => array()), # JIDs of users who want to receive the public stream
+ 'sphinx' =>
+ array('enabled' => false,
+ 'server' => 'localhost',
+ 'port' => 3312),
+ 'tag' =>
+ array('dropoff' => 864000.0),
+ 'popular' =>
+ array('dropoff' => 864000.0),
+ 'daemon' =>
+ array('piddir' => '/var/run',
+ 'user' => false,
+ 'group' => false),
+ 'integration' =>
+ array('source' => 'Laconica'), # source attribute for Twitter
+ 'memcached' =>
+ array('enabled' => false,
+ 'server' => 'localhost',
+ 'port' => 11211),
+ 'inboxes' =>
+ array('enabled' => true), # on by default for new sites
+ );
+
+$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
+
+$config['db'] =
+ array('database' => 'YOU HAVE TO SET THIS IN config.php',
+ 'schema_location' => INSTALLDIR . '/classes',
+ 'class_location' => INSTALLDIR . '/classes',
+ 'require_prefix' => 'classes/',
+ 'class_prefix' => '',
+ 'mirror' => NULL,
+ 'db_driver' => 'DB', # XXX: JanRain libs only work with DB
+ 'quote_identifiers' => false,
+ 'type' => 'mysql' );
+
+if (function_exists('date_default_timezone_set')) {
+ /* Work internally in UTC */
+ date_default_timezone_set('UTC');
+}
+
+require_once(INSTALLDIR.'/config.php');
+
+require_once('Validate.php');
+require_once('markdown.php');
+
+require_once(INSTALLDIR.'/lib/util.php');
+require_once(INSTALLDIR.'/lib/action.php');
+require_once(INSTALLDIR.'/lib/theme.php');
+require_once(INSTALLDIR.'/lib/mail.php');
+require_once(INSTALLDIR.'/lib/subs.php');
+require_once(INSTALLDIR.'/lib/Shorturl_api.php');
+require_once(INSTALLDIR.'/lib/twitter.php');
+
+function __autoload($class) {
+ if ($class == 'OAuthRequest') {
+ require_once('OAuth.php');
+ } else if (file_exists(INSTALLDIR.'/classes/' . $class . '.php')) {
+ require_once(INSTALLDIR.'/classes/' . $class . '.php');
+ }
+}
diff --git a/lib/daemon.php b/lib/daemon.php
new file mode 100644
index 000000000..359a4343b
--- /dev/null
+++ b/lib/daemon.php
@@ -0,0 +1,133 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class Daemon {
+
+ function name() {
+ return NULL;
+ }
+
+ function background() {
+ $pid = pcntl_fork();
+ if ($pid < 0) { # error
+ common_log(LOG_ERR, "Could not fork.");
+ return false;
+ } else if ($pid > 0) { # parent
+ common_log(LOG_INFO, "Successfully forked.");
+ exit(0);
+ } else { # child
+ return true;
+ }
+ }
+
+ function alreadyRunning() {
+
+ $pidfilename = $this->pidFilename();
+
+ if (!$pidfilename) {
+ return false;
+ }
+
+ if (!file_exists($pidfilename)) {
+ return false;
+ }
+ $contents = file_get_contents($pidfilename);
+ if (posix_kill(trim($contents),0)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function writePidFile() {
+ $pidfilename = $this->pidFilename();
+
+ if (!$pidfilename) {
+ return false;
+ }
+
+ return file_put_contents($pidfilename, posix_getpid() . "\n");
+ }
+
+ function clearPidFile() {
+ $pidfilename = $this->pidFilename();
+ if (!$pidfilename) {
+ return false;
+ }
+ return unlink($pidfilename);
+ }
+
+ function pidFilename() {
+ $piddir = common_config('daemon', 'piddir');
+ if (!$piddir) {
+ return NULL;
+ }
+ $name = $this->name();
+ if (!$name) {
+ return NULL;
+ }
+ return $piddir . '/' . $name . '.pid';
+ }
+
+ function changeUser() {
+
+ $username = common_config('daemon', 'user');
+
+ if ($username) {
+ $user_info = posix_getpwnam($username);
+ if (!$user_info) {
+ common_log(LOG_WARNING, 'Ignoring unknown user for daemon: ' . $username);
+ } else {
+ common_log(LOG_INFO, "Setting user to " . $username);
+ posix_setuid($user_info['uid']);
+ }
+ }
+
+ $groupname = common_config('daemon', 'group');
+
+ if ($groupname) {
+ $group_info = posix_getgrnam($groupname);
+ if (!$group_info) {
+ common_log(LOG_WARNING, 'Ignoring unknown group for daemon: ' . $groupname);
+ } else {
+ common_log(LOG_INFO, "Setting group to " . $groupname);
+ posix_setgid($group_info['gid']);
+ }
+ }
+ }
+
+ function runOnce() {
+ if ($this->alreadyRunning()) {
+ common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
+ exit(0);
+ }
+ if ($this->background()) {
+ $this->writePidFile();
+ $this->changeUser();
+ $this->run();
+ $this->clearPidFile();
+ }
+ }
+
+ function run() {
+ return true;
+ }
+}
diff --git a/lib/deleteaction.php b/lib/deleteaction.php
new file mode 100644
index 000000000..5ba0e7e44
--- /dev/null
+++ b/lib/deleteaction.php
@@ -0,0 +1,61 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class DeleteAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ $user = common_current_user();
+ $notice_id = $this->trimmed('notice');
+ $notice = Notice::staticGet($notice_id);
+ if (!$notice) {
+ common_user_error(_('No such notice.'));
+ exit;
+ }
+
+ $profile = $notice->getProfile();
+ $user_profile = $user->getProfile();
+
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ exit;
+ } else if ($notice->profile_id != $user_profile->id) {
+ common_user_error(_('Can\'t delete this notice.'));
+ exit;
+ }
+ }
+
+ function show_top($arr=NULL) {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+
+ function get_title() {
+ return NULL;
+ }
+
+ function show_header() {
+ return;
+ }
+}
diff --git a/lib/facebookaction.php b/lib/facebookaction.php
new file mode 100644
index 000000000..87a82ba01
--- /dev/null
+++ b/lib/facebookaction.php
@@ -0,0 +1,283 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/extlib/facebook/facebook.php');
+
+class FacebookAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ }
+
+ function get_facebook() {
+ $apikey = common_config('facebook', 'apikey');
+ $secret = common_config('facebook', 'secret');
+ return new Facebook($apikey, $secret);
+ }
+
+ function update_profile_box($facebook, $fbuid, $user) {
+
+ $notice = $user->getCurrentNotice();
+
+ # Need to include inline CSS for styling the Profile box
+
+ $style = '<style>
+ #notices {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 600px;
+ border-top: 1px solid #dec5b5;
+ }
+ #notices a:hover {
+ text-decoration: underline;
+ }
+ .notice_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #dec5b5;
+ background-color:#FCFFF5;
+ opacity:1;
+ }
+ .notice_single:hover {
+ background-color: #f7ebcc;
+ }
+ .notice_single p {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ }
+ </style>';
+
+ $html = $this->render_notice($notice);
+
+ $fbml = "<fb:wide>$content $html</fb:wide>";
+ $fbml .= "<fb:narrow>$content $html</fb:narrow>";
+
+ $fbml_main = "<fb:narrow>$content $html</fb:narrow>";
+
+ $facebook->api_client->profile_setFBML(NULL, $fbuid, $fbml, NULL, NULL, $fbml_main);
+ }
+
+ # Display methods
+
+ function show_header($selected ='Home') {
+
+ # Add a timestamp to the CSS file so Facebook cache wont ignore our changes
+ $ts = filemtime(theme_file('facebookapp.css'));
+ $cssurl = theme_path('facebookapp.css') . "?ts=$ts";
+
+ $header = '<link rel="stylesheet" type="text/css" href="'. $cssurl . '" />';
+ # $header .='<script src="" ></script>';
+ $header .= '<fb:dashboard/>';
+
+ $header .=
+ '<fb:tabs>'
+ .'<fb:tab-item title="Home" href="index.php" selected="' . ($selected == 'Home') .'" />'
+ .'<fb:tab-item title="Invite Friends" href="invite.php" selected="' . ($selected == 'Invite') . '" />'
+ .'<fb:tab-item title="Settings" href="settings.php" selected="' . ($selected == 'Settings') . '" />'
+ .'</fb:tabs>';
+ $header .= '<div id="main_body">';
+
+ echo $header;
+
+ }
+
+ function show_footer() {
+ $footer = '</div>';
+ echo $footer;
+ }
+
+ function show_login_form() {
+
+ $loginform =
+ ' <h2>To add the Identi.ca application, you need to log into your Identi.ca account.</h2>'
+ .'<a href="http://identi.ca/">'
+ .' <img src="http://theme.identi.ca/identica/logo.png" alt="Identi.ca" id="logo"/>'
+ .'</a>'
+ .'<h1 class="pagetitle">Login</h1>'
+ .'<div class="instructions">'
+ .' <p>Login with your username and password. Don\'t have a username yet?'
+ .' <a href="http://identi.ca/main/register">Register</a> a new account.'
+ .' </p>'
+ .'</div>'
+ .'<div id="content">'
+ .' <form method="post" id="login">'
+ .' <p>'
+ .' <label for="nickname">Nickname</label>'
+ .' <input name="nickname" type="text" class="input_text" id="nickname"/>'
+ .' </p>'
+ .' <p>'
+ .' <label for="password">Password</label>'
+ .' <input name="password" type="password" class="password" id="password"/>'
+ .' </p>'
+ .' <p>'
+ .' <input type="submit" id="submit" name="submit" class="submit" value="Login"/>'
+ .' </p>'
+ .' </form>'
+ .' <p>'
+ .' <a href="http://identi.ca/main/recoverpassword">Lost or forgotten password?</a>'
+ .' </p>'
+ .'</div';
+
+ echo $loginform;
+ }
+
+ function render_notice($notice) {
+
+ global $config;
+
+ $profile = $notice->getProfile();
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+
+ $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
+
+ # XXX: we need to figure this out better. Is this right?
+ if (strcmp($notice->uri, $noticeurl) != 0 && preg_match('/^http/', $notice->uri)) {
+ $noticeurl = $notice->uri;
+ }
+
+ $html =
+ '<li class="notice_single" id="' . $notice->id . '">'
+ .'<a href="' . $profile->profileurl . '">'
+ .'<img src="';
+
+ if ($avatar) {
+ $html .= common_avatar_display_url($avatar);
+ } else {
+ $html .= common_default_avatar(AVATAR_STREAM_SIZE);
+ }
+
+ $html .=
+ '" class="avatar stream" width="'
+ . AVATAR_STREAM_SIZE . '" height="' . AVATAR_STREAM_SIZE .'"'
+ .' alt="';
+
+ if ($profile->fullname) {
+ $html .= $profile->fullname;
+ } else {
+ $html .= $profile->nickname;
+ }
+
+ $html .=
+ '"></a>'
+ .'<a href="' . $profile->profileurl . '" class="nickname">' . $profile->nickname . '</a>'
+ .'<p class="content">' . $notice->rendered . '</p>'
+ .'<p class="time">'
+ .'<a class="permalink" href="' . $noticeurl . '" title="' . common_exact_date($notice->created) . '">' . common_date_string($notice->created) . '</a>';
+
+ if ($notice->source) {
+ $html .= _(' from ');
+ $html .= $this->source_link($notice->source);
+ }
+
+ if ($notice->reply_to) {
+ $replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
+ $html .=
+ ' (<a class="inreplyto" href="' . $replyurl . '">' . _('in reply to...') . ')';
+ }
+
+ $html .= '</p></li>';
+
+ return $html;
+ }
+
+ function source_link($source) {
+ $source_name = _($source);
+
+ $html = '<span class="noticesource">';
+
+ switch ($source) {
+ case 'web':
+ case 'xmpp':
+ case 'mail':
+ case 'omb':
+ case 'api':
+ $html .= $source_name;
+ break;
+ default:
+ $ns = Notice_source::staticGet($source);
+ if ($ns) {
+ $html .= '<a href="' . $ns->url . '">' . $ns->name . '</a>';
+ } else {
+ $html .= $source_name;
+ }
+ break;
+ }
+
+ $html .= '</span>';
+
+ return $html;
+ }
+
+ function pagination($have_before, $have_after, $page, $fbaction, $args=NULL) {
+
+ $html = '';
+
+ if ($have_before || $have_after) {
+ $html = '<div id="pagination">';
+ $html .'<ul id="nav_pagination">';
+ }
+
+ if ($have_before) {
+ $pargs = array('page' => $page-1);
+ $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
+ $html .= '<li class="before">';
+ $html .'<a href="' . $this->pagination_url($fbaction, $newargs) . '">' . _('« After') . '</a>';
+ $html .'</li>';
+ }
+
+ if ($have_after) {
+ $pargs = array('page' => $page+1);
+ $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
+ $html .= '<li class="after">';
+ $html .'<a href="' . $this->pagination_url($fbaction, $newargs) . '">' . _('Before »') . '</a>';
+ $html .'</li>';
+ }
+
+ if ($have_before || $have_after) {
+ $html .= '<ul>';
+ $html .'<div>';
+ }
+ }
+
+ function pagination_url($fbaction, $args=NULL) {
+ global $config;
+
+ $extra = '';
+
+ if ($args) {
+ foreach ($args as $key => $value) {
+ $extra .= "&${key}=${value}";
+ }
+ }
+
+ return "$fbaction?${extra}";
+ }
+
+}
diff --git a/lib/gallery.php b/lib/gallery.php
new file mode 100644
index 000000000..0dd351bab
--- /dev/null
+++ b/lib/gallery.php
@@ -0,0 +1,320 @@
+<?php
+
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/profilelist.php');
+
+# 10x8
+
+define('AVATARS_PER_PAGE', 80);
+
+class GalleryAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+
+ # Post from the tag dropdown; redirect to a GET
+
+ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ common_redirect($this->self_url(), 307);
+ }
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->no_such_user();
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ $this->server_error(_('User without matching profile in system.'));
+ return;
+ }
+
+ $page = $this->arg('page');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ $display = $this->arg('display');
+
+ if (!$display) {
+ $display = 'list';
+ }
+
+ $tag = $this->arg('tag');
+
+ common_show_header($profile->nickname . ": " . $this->gallery_type(),
+ NULL, $profile,
+ array($this, 'show_top'));
+
+ $this->display_links($profile, $page, $display);
+ $this->show_tags_dropdown($profile);
+
+ $this->show_gallery($profile, $page, $display, $tag);
+ common_show_footer();
+ }
+
+ function no_such_user() {
+ $this->client_error(_('No such user.'));
+ }
+
+ function show_tags_dropdown($profile) {
+ $tag = $this->trimmed('tag');
+ list($lst, $usr) = $this->fields();
+ $tags = $this->get_all_tags($profile, $lst, $usr);
+ $content = array();
+ foreach ($tags as $t) {
+ $content[$t] = $t;
+ }
+ if ($tags) {
+ common_element_start('dl', array('id'=>'filter_tags'));
+ common_element('dt', null, _('Filter tags'));
+ common_element_start('dd');
+ common_element_start('ul');
+ common_element_start('li', array('id'=>'filter_tags_all', 'class'=>'child_1'));
+ common_element('a', array('href' => common_local_url($this->trimmed('action'),
+ array('nickname' => $profile->nickname))),
+ _('All'));
+ common_element_end('li');
+ common_element_start('li', array('id'=>'filter_tags_item'));
+ common_element_start('form', array('name' => 'bytag', 'id' => 'bytag', 'method' => 'post'));
+ common_dropdown('tag', _('Tag'), $content,
+ _('Choose a tag to narrow list'), FALSE, $tag);
+ common_submit('go', _('Go'));
+ common_element_end('form');
+ common_element_end('li');
+ common_element_end('ul');
+ common_element_end('dd');
+ common_element_end('dl');
+ }
+ }
+
+ function show_top($profile) {
+ common_element('div', 'instructions',
+ $this->get_instructions($profile));
+ $this->show_menu();
+ }
+
+ function show_menu() {
+ # action => array('prompt', 'title', $args)
+ $action = $this->trimmed('action');
+ $nickname = $this->trimmed('nickname');
+ $menu =
+ array('subscriptions' =>
+ array( _('Subscriptions'),
+ _('Subscriptions'),
+ array('nickname' => $nickname)),
+ 'subscribers' =>
+ array(
+ _('Subscribers'),
+ _('Subscribers'),
+ array('nickname' => $nickname)),
+ );
+ $this->nav_menu($menu);
+ }
+
+ function show_gallery($profile, $page, $display='list', $tag=NULL) {
+
+ $other = new Profile();
+
+ list($lst, $usr) = $this->fields();
+
+ $per_page = ($display == 'list') ? PROFILES_PER_PAGE : AVATARS_PER_PAGE;
+
+ $offset = ($page-1)*$per_page;
+ $limit = $per_page + 1;
+
+ if (common_config('db','type') == 'pgsql') {
+ $lim = ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+ } else {
+ $lim = ' LIMIT ' . $offset . ', ' . $limit;
+ }
+
+ # XXX: memcached results
+ # FIXME: SQL injection on $tag
+
+ $other->query('SELECT profile.* ' .
+ 'FROM profile JOIN subscription ' .
+ 'ON profile.id = subscription.' . $lst . ' ' .
+ (($tag) ? 'JOIN profile_tag ON (profile.id = profile_tag.tagged AND subscription.'.$usr.'= profile_tag.tagger) ' : '') .
+ 'WHERE ' . $usr . ' = ' . $profile->id . ' ' .
+ 'AND subscriber != subscribed ' .
+ (($tag) ? 'AND profile_tag.tag= "' . $tag . '" ': '') .
+ 'ORDER BY subscription.created DESC, profile.id DESC ' .
+ $lim);
+
+ if ($display == 'list') {
+ $cls = $this->profile_list_class();
+ $profile_list = new $cls($other, $profile, $this->trimmed('action'));
+ $cnt = $profile_list->show_list();
+ } else {
+ $cnt = $this->icon_list($other);
+ }
+
+ # For building the pagination URLs
+
+ $args = array('nickname' => $profile->nickname);
+
+ if ($display != 'list') {
+ $args['display'] = $display;
+ }
+
+ common_pagination($page > 1,
+ $cnt > $per_page,
+ $page,
+ $this->trimmed('action'),
+ $args);
+ }
+
+ function profile_list_class() {
+ return 'ProfileList';
+ }
+
+ function icon_list($other) {
+
+ common_element_start('ul', $this->div_class());
+
+ $cnt = 0;
+
+ while ($other->fetch()) {
+
+ $cnt++;
+
+ if ($cnt > AVATARS_PER_PAGE) {
+ break;
+ }
+
+ common_element_start('li');
+
+ common_element_start('a', array('title' => ($other->fullname) ?
+ $other->fullname :
+ $other->nickname,
+ 'href' => $other->profileurl,
+ 'class' => 'subscription'));
+ $avatar = $other->getAvatar(AVATAR_STREAM_SIZE);
+ common_element('img',
+ array('src' =>
+ (($avatar) ? common_avatar_display_url($avatar) :
+ common_default_avatar(AVATAR_STREAM_SIZE)),
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'class' => 'avatar stream',
+ 'alt' => ($other->fullname) ?
+ $other->fullname :
+ $other->nickname));
+ common_element_end('a');
+
+ # XXX: subscribe form here
+
+ common_element_end('li');
+ }
+
+ common_element_end('ul');
+
+ return $cnt;
+ }
+
+ function gallery_type() {
+ return NULL;
+ }
+
+ function get_instructions(&$profile) {
+ return NULL;
+ }
+
+ function fields() {
+ return NULL;
+ }
+
+ function div_class() {
+ return '';
+ }
+
+ function display_links($profile, $page, $display) {
+ $tag = $this->trimmed('tag');
+
+ common_element_start('dl', array('id'=>'subscriptions_nav'));
+ common_element('dt', null, _('Subscriptions navigation'));
+ common_element_start('dd');
+ common_element_start('ul', array('class'=>'nav'));
+
+ switch ($display) {
+ case 'list':
+ common_element('li', array('class'=>'child_1'), _('List'));
+ common_element_start('li');
+ $url_args = array('display' => 'icons',
+ 'nickname' => $profile->nickname,
+ 'page' => 1 + floor((($page - 1) * PROFILES_PER_PAGE) / AVATARS_PER_PAGE));
+ if ($tag) {
+ $url_args['tag'] = $tag;
+ }
+ $url = common_local_url($this->trimmed('action'), $url_args);
+ common_element('a', array('href' => $url),
+ _('Icons'));
+ common_element_end('li');
+ break;
+ default:
+ common_element_start('li', array('class'=>'child_1'));
+ $url_args = array('nickname' => $profile->nickname,
+ 'page' => 1 + floor((($page - 1) * AVATARS_PER_PAGE) / PROFILES_PER_PAGE));
+ if ($tag) {
+ $url_args['tag'] = $tag;
+ }
+ $url = common_local_url($this->trimmed('action'), $url_args);
+ common_element('a', array('href' => $url),
+ _('List'));
+ common_element_end('li');
+ common_element('li', NULL, _('Icons'));
+ break;
+ }
+
+ common_element_end('ul');
+ common_element_end('dd');
+ common_element_end('dl');
+ }
+
+ # Get list of tags we tagged other users with
+
+ function get_all_tags($profile, $lst, $usr) {
+ $profile_tag = new Notice_tag();
+ $profile_tag->query('SELECT DISTINCT(tag) ' .
+ 'FROM profile_tag, subscription ' .
+ 'WHERE tagger = ' . $profile->id . ' ' .
+ 'AND ' . $usr . ' = ' . $profile->id . ' ' .
+ 'AND ' . $lst . ' = tagged ' .
+ 'AND tagger != tagged');
+ $tags = array();
+ while ($profile_tag->fetch()) {
+ $tags[] = $profile_tag->tag;
+ }
+ $profile_tag->free();
+ return $tags;
+ }
+} \ No newline at end of file
diff --git a/lib/jabber.php b/lib/jabber.php
new file mode 100644
index 000000000..ab0fd6af8
--- /dev/null
+++ b/lib/jabber.php
@@ -0,0 +1,300 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once('XMPPHP/XMPP.php');
+
+function jabber_valid_base_jid($jid) {
+ # Cheap but effective
+ return Validate::email($jid);
+}
+
+function jabber_normalize_jid($jid) {
+ if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) {
+ $node = $matches[1];
+ $server = $matches[2];
+ return strtolower($node.'@'.$server);
+ } else {
+ return NULL;
+ }
+}
+
+function jabber_daemon_address() {
+ return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
+}
+
+function jabber_connect($resource=NULL) {
+ static $conn = NULL;
+ if (!$conn) {
+ $conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ?
+ common_config('xmpp', 'host') :
+ common_config('xmpp', 'server'),
+ common_config('xmpp', 'port'),
+ common_config('xmpp', 'user'),
+ common_config('xmpp', 'password'),
+ ($resource) ? $resource :
+ common_config('xmpp', 'resource'),
+ common_config('xmpp', 'server'),
+ common_config('xmpp', 'debug') ?
+ true : false,
+ common_config('xmpp', 'debug') ?
+ XMPPHP_Log::LEVEL_VERBOSE : NULL
+ );
+
+ if (!$conn) {
+ return false;
+ }
+
+ $conn->autoSubscribe();
+ $conn->useEncryption(common_config('xmpp', 'encryption'));
+
+ try {
+ $conn->connect(true); # true = persistent connection
+ } catch (XMPPHP_Exception $e) {
+ common_log(LOG_ERROR, $e->getMessage());
+ return false;
+ }
+
+ $conn->processUntil('session_start');
+ }
+ return $conn;
+}
+
+function jabber_send_notice($to, $notice) {
+ $conn = jabber_connect();
+ if (!$conn) {
+ return false;
+ }
+ $profile = Profile::staticGet($notice->profile_id);
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to send notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ return false;
+ }
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = jabber_format_entry($profile, $notice);
+ $conn->message($to, $msg, 'chat', NULL, $entry);
+ $profile->free();
+ return true;
+}
+
+# Extra stuff defined by Twitter, needed by twitter clients
+
+function jabber_format_entry($profile, $notice) {
+
+ # FIXME: notice url might be remote
+
+ $noticeurl = common_local_url('shownotice',
+ array('notice' => $notice->id));
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = "\n<entry xmlns='http://www.w3.org/2005/Atom'>\n";
+ $entry .= "<source>\n";
+ $entry .= "<title>" . $profile->nickname . " - " . common_config('site', 'name') . "</title>\n";
+ $entry .= "<link href='" . htmlspecialchars($profile->profileurl) . "'/>\n";
+ $entry .= "<link rel='self' type='application/rss+xml' href='" . common_local_url('userrss', array('nickname' => $profile->nickname)) . "'/>\n";
+ $entry .= "<author><name>" . $profile->nickname . "</name></author>\n";
+ $entry .= "<icon>" . common_profile_avatar_url($profile, AVATAR_PROFILE_SIZE) . "</icon>\n";
+ $entry .= "</source>\n";
+ $entry .= "<title>" . htmlspecialchars($msg) . "</title>\n";
+ $entry .= "<summary>" . htmlspecialchars($msg) . "</summary>\n";
+ $entry .= "<link rel='alternate' href='" . $noticeurl . "' />\n";
+ $entry .= "<id>". $notice->uri . "</id>\n";
+ $entry .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
+ $entry .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
+ $entry .= "</entry>\n";
+
+ $html = "\n<html xmlns='http://jabber.org/protocol/xhtml-im'>\n";
+ $html .= "<body xmlns='http://www.w3.org/1999/xhtml'>\n";
+ $html .= "<a href='".htmlspecialchars($profile->profileurl)."'>".$profile->nickname."</a>: ";
+ $html .= ($notice->rendered) ? $notice->rendered : common_render_content($notice->content, $notice);
+ $html .= "\n</body>\n";
+ $html .= "\n</html>\n";
+
+ $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
+ $address .= "<address type='replyto' jid='" . jabber_daemon_address() . "' />\n";
+ $address .= "</addresses>\n";
+
+ # FIXME: include a pubsub event, too.
+
+ return $html . $entry . $address;
+}
+
+function jabber_send_message($to, $body, $type='chat', $subject=NULL) {
+ $conn = jabber_connect();
+ if (!$conn) {
+ return false;
+ }
+ $conn->message($to, $body, $type, $subject);
+ return true;
+}
+
+function jabber_send_presence($status, $show='available', $to=NULL,
+ $type = 'available', $priority=NULL)
+{
+ $conn = jabber_connect();
+ if (!$conn) {
+ return false;
+ }
+ $conn->presence($status, $show, $to, $type, $priority);
+ return true;
+}
+
+function jabber_confirm_address($code, $nickname, $address) {
+ $body = 'User "' . $nickname . '" on ' . common_config('site', 'name') . ' ' .
+ 'has said that your Jabber ID belongs to them. ' .
+ 'If that\'s true, you can confirm by clicking on this URL: ' .
+ common_local_url('confirmaddress', array('code' => $code)) .
+ ' . (If you cannot click it, copy-and-paste it into the ' .
+ 'address bar of your browser). If that user isn\'t you, ' .
+ 'or if you didn\'t request this confirmation, just ignore this message.';
+
+ return jabber_send_message($address, $body);
+}
+
+function jabber_special_presence($type, $to=NULL, $show=NULL, $status=NULL) {
+ $conn = jabber_connect();
+
+ $to = htmlspecialchars($to);
+ $status = htmlspecialchars($status);
+ $out = "<presence";
+ if($to) $out .= " to='$to'";
+ if($type) $out .= " type='$type'";
+ if($show == 'available' and !$status) {
+ $out .= "/>";
+ } else {
+ $out .= ">";
+ if($show && ($show != 'available')) $out .= "<show>$show</show>";
+ if($status) $out .= "<status>$status</status>";
+ $out .= "</presence>";
+ }
+ $conn->send($out);
+}
+
+function jabber_broadcast_notice($notice) {
+
+ if (!common_config('xmpp', 'enabled')) {
+ return true;
+ }
+ $profile = Profile::staticGet($notice->profile_id);
+
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ return false;
+ }
+
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = jabber_format_entry($profile, $notice);
+
+ $profile->free();
+ unset($profile);
+
+ $sent_to = array();
+ $conn = jabber_connect();
+
+ # First, get users to whom this is a direct reply
+ $user = new User();
+ $user->query('SELECT user.id, user.jabber ' .
+ 'FROM user JOIN reply ON user.id = reply.profile_id ' .
+ 'WHERE reply.notice_id = ' . $notice->id . ' ' .
+ 'AND user.jabber is not null ' .
+ 'AND user.jabbernotify = 1 ' .
+ 'AND user.jabberreplies = 1 ');
+
+ while ($user->fetch()) {
+ common_log(LOG_INFO,
+ 'Sending reply notice ' . $notice->id . ' to ' . $user->jabber,
+ __FILE__);
+ $conn->message($user->jabber, $msg, 'chat', NULL, $entry);
+ $conn->processTime(0);
+ $sent_to[$user->id] = 1;
+ }
+
+ $user->free();
+
+ # Now, get users subscribed to this profile
+
+ $user = new User();
+ $user->query('SELECT user.id, user.jabber ' .
+ 'FROM user JOIN subscription ON user.id = subscription.subscriber ' .
+ 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
+ 'AND user.jabber is not null ' .
+ 'AND user.jabbernotify = 1 ' .
+ 'AND subscription.jabber = 1 ');
+
+ while ($user->fetch()) {
+ if (!array_key_exists($user->id, $sent_to)) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to ' . $user->jabber,
+ __FILE__);
+ $conn->message($user->jabber, $msg, 'chat', NULL, $entry);
+ # To keep the incoming queue from filling up, we service it after each send.
+ $conn->processTime(0);
+ }
+ }
+
+ $user->free();
+
+ return true;
+}
+
+function jabber_public_notice($notice) {
+
+ # Now, users who want everything
+
+ $public = common_config('xmpp', 'public');
+
+ # FIXME PRIV don't send out private messages here
+ # XXX: should we send out non-local messages if public,localonly
+ # = false? I think not
+
+ if ($public && $notice->is_local) {
+ $profile = Profile::staticGet($notice->profile_id);
+
+ if (!$profile) {
+ common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
+ 'unknown profile ' . common_log_objstring($notice),
+ __FILE__);
+ return false;
+ }
+
+ $msg = jabber_format_notice($profile, $notice);
+ $entry = jabber_format_entry($profile, $notice);
+
+ $conn = jabber_connect();
+
+ foreach ($public as $address) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to public listener ' . $address,
+ __FILE__);
+ $conn->message($address, $msg, 'chat', NULL, $entry);
+ $conn->processTime(0);
+ }
+ $profile->free();
+ }
+
+ return true;
+}
+
+function jabber_format_notice(&$profile, &$notice) {
+ return $profile->nickname . ': ' . $notice->content;
+}
diff --git a/lib/language.php b/lib/language.php
new file mode 100644
index 000000000..a9be24b19
--- /dev/null
+++ b/lib/language.php
@@ -0,0 +1,88 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+function client_prefered_language($httplang) {
+ $client_langs = array();
+ $all_languages = common_config('site','languages');
+
+ preg_match_all('"(((\S\S)-?(\S\S)?)(;q=([0-9.]+))?)\s*(,\s*|$)"',strtolower($httplang),$httplang);
+ for ($i = 0; $i < count($httplang); $i++) {
+ if(!empty($httplang[2][$i])) {
+ #if no q default to 1.0
+ $client_langs[$httplang[2][$i]] = ($httplang[6][$i]? (float) $httplang[6][$i] : 1.0);
+ }
+ if(!empty($httplang[3][$i]) && empty($client_langs[$httplang[3][$i]])) {
+ #if a catchall default 0.01 lower
+ $client_langs[$httplang[3][$i]] = ($httplang[6][$i]? (float) $httplang[6][$i]-0.01 : 0.99);
+ }
+ }
+ #sort in decending q
+ arsort($client_langs);
+
+ foreach ($client_langs as $lang => $q) {
+ if (isset($all_languages[$lang])) {
+ return($all_languages[$lang]['lang']);
+ }
+ }
+ return FALSE;
+}
+
+function get_nice_language_list() {
+ $nice_lang = array();
+ $all_languages = common_config('site','languages');
+ foreach ($all_languages as $lang) {
+ $nice_lang = $nice_lang + array($lang['lang'] => $lang['name']);
+ }
+ return $nice_lang;
+}
+
+// Get a list of all languages that are enabled in the default config. This
+// should ONLY be called when setting up the default config in common.php.
+// Any other attempt to get a list of lanugages should instead call
+// common_config('site','languages')
+function get_all_languages() {
+ return array(
+ 'en-us' => array('q' => 1, 'lang' => 'en_US', 'name' => 'English (US)', 'direction' => 'ltr'),
+ 'en-gb' => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
+ 'en' => array('q' => 1, 'lang' => 'en', 'name' => 'English', 'direction' => 'ltr'),
+ 'nl' => array('q' => 1, 'lang' => 'nl_NL', 'name' => 'Dutch', 'direction' => 'ltr'),
+ 'fr-fr' => array('q' => 0.9, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'),
+ 'de' => array('q' => 1, 'lang' => 'de_DE', 'name' => 'German', 'direction' => 'ltr'),
+ 'it' => array('q' => 1, 'lang' => 'it_IT', 'name' => 'Italian', 'direction' => 'ltr'),
+ 'ko' => array('q' => 0.1, 'lang' => 'ko', 'name' => 'Korean', 'direction' => 'ltr'),
+ 'nb' => array('q' => 1, 'lang' => 'nb_NO', 'name' => 'Norwegian (bokmal)', 'direction' => 'ltr'),
+ 'pt' => array('q' => 0.2, 'lang' => 'pt', 'name' => 'Portuguese', 'direction' => 'ltr'),
+ 'pt-br' => array('q' => 1, 'lang' => 'pt_BR', 'name' => 'Portuguese Brazil', 'direction' => 'ltr'),
+# 'ru' => array('q' => 0.1, 'lang' => 'ru_RU', 'name' => 'Russian', 'direction' => 'ltr'),
+ 'es' => array('q' => 1, 'lang' => 'es', 'name' => 'Spanish', 'direction' => 'ltr'),
+ 'tr' => array('q' => 1, 'lang' => 'tr_TR', 'name' => 'Turkish', 'direction' => 'ltr'),
+ 'uk' => array('q' => 1, 'lang' => 'uk_UA', 'name' => 'Ukrainian', 'direction' => 'ltr'),
+# 'lt' => array('q' => 0.1, 'lang' => 'lt_LT', 'name' => 'Lithuanian', 'direction' => 'ltr'),
+# 'sv' => array('q' => 1, 'lang' => 'sv_SE', 'name' => 'Swedish', 'direction' => 'ltr'),
+ 'pl' => array('q' => 1, 'lang' => 'pl_PL', 'name' => 'Polish', 'direction' => 'ltr'),
+ 'mk' => array('q' => 1, 'lang' => 'mk_MK', 'name' => 'Macedonian', 'direction' => 'ltr'),
+ 'jp' => array('q' => 0.1, 'lang' => 'ja_JP', 'name' => 'Japanese', 'direction' => 'ltr'),
+ 'cs' => array('q' => 1, 'lang' => 'cs_CZ', 'name' => 'Czech', 'direction' => 'ltr'),
+ 'ca' => array('q' => 1, 'lang' => 'ca_ES', 'name' => 'Catalan', 'direction' => 'ltr'),
+# 'hr' => array('q' => 0.1, 'lang' => 'he_IL', 'name' => 'Hebrew', 'direction' => 'ltr')
+ );
+}
+
diff --git a/lib/mail.php b/lib/mail.php
new file mode 100644
index 000000000..a7cbab858
--- /dev/null
+++ b/lib/mail.php
@@ -0,0 +1,309 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once('Mail.php');
+
+function mail_backend() {
+ static $backend = NULL;
+
+ if (!$backend) {
+ global $config;
+ $backend = Mail::factory($config['mail']['backend'],
+ ($config['mail']['params']) ? $config['mail']['params'] : array());
+ if (PEAR::isError($backend)) {
+ common_server_error($backend->getMessage(), 500);
+ }
+ }
+ return $backend;
+}
+
+# XXX: use Mail_Queue... maybe
+
+function mail_send($recipients, $headers, $body) {
+ $backend = mail_backend();
+ if (!isset($headers['Content-Type'])) {
+ $headers['Content-Type'] = 'text/plain; charset=UTF-8';
+ }
+ assert($backend); # throws an error if it's bad
+ $sent = $backend->send($recipients, $headers, $body);
+ if (PEAR::isError($sent)) {
+ common_log(LOG_ERR, 'Email error: ' . $sent->getMessage());
+ return false;
+ }
+ return true;
+}
+
+function mail_domain() {
+ $maildomain = common_config('mail', 'domain');
+ if (!$maildomain) {
+ $maildomain = common_config('site', 'server');
+ }
+ return $maildomain;
+}
+
+function mail_notify_from() {
+ $notifyfrom = common_config('mail', 'notifyfrom');
+ if (!$notifyfrom) {
+ $domain = mail_domain();
+ $notifyfrom = common_config('site', 'name') .' <noreply@'.$domain.'>';
+ }
+ return $notifyfrom;
+}
+
+function mail_to_user(&$user, $subject, $body, $address=NULL) {
+ if (!$address) {
+ $address = $user->email;
+ }
+
+ $recipients = $address;
+ $profile = $user->getProfile();
+
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $profile->getBestName() . ' <' . $address . '>';
+ $headers['Subject'] = $subject;
+
+ return mail_send($recipients, $headers, $body);
+}
+
+# For confirming a Jabber address
+
+function mail_confirm_address($user, $code, $nickname, $address) {
+
+ $subject = _('Email address confirmation');
+
+ $body = sprintf(_("Hey, %s.\n\nSomeone just entered this email address on %s.\n\n" .
+ "If it was you, and you want to confirm your entry, use the URL below:\n\n\t%s\n\n" .
+ "If not, just ignore this message.\n\nThanks for your time, \n%s\n")
+ , $nickname, common_config('site', 'name')
+ , common_local_url('confirmaddress', array('code' => $code)), common_config('site', 'name'));
+ return mail_to_user($user, $subject, $body, $address);
+}
+
+function mail_subscribe_notify($listenee, $listener) {
+ $other = $listener->getProfile();
+ mail_subscribe_notify_profile($listenee, $other);
+}
+
+function mail_subscribe_notify_profile($listenee, $other) {
+ if ($listenee->email && $listenee->emailnotifysub) {
+ // use the recipients localization
+ common_init_locale($listenee->language);
+ $profile = $listenee->getProfile();
+ $name = $profile->getBestName();
+ $long_name = ($other->fullname) ? ($other->fullname . ' (' . $other->nickname . ')') : $other->nickname;
+ $recipients = $listenee->email;
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $name . ' <' . $listenee->email . '>';
+ $headers['Subject'] = sprintf(_('%1$s is now listening to your notices on %2$s.'), $other->getBestName(),
+ common_config('site', 'name'));
+ $body = sprintf(_('%1$s is now listening to your notices on %2$s.'."\n\n".
+ "\t".'%3$s'."\n\n".
+ 'Faithfully yours,'."\n".'%4$s.'."\n"),
+ $long_name,
+ common_config('site', 'name'),
+ $other->profileurl,
+ common_config('site', 'name'));
+
+ // reset localization
+ common_init_locale();
+ mail_send($recipients, $headers, $body);
+ }
+}
+
+function mail_new_incoming_notify($user) {
+
+ $profile = $user->getProfile();
+ $name = $profile->getBestName();
+
+ $headers['From'] = $user->incomingemail;
+ $headers['To'] = $name . ' <' . $user->email . '>';
+ $headers['Subject'] = sprintf(_('New email address for posting to %s'),
+ common_config('site', 'name'));
+
+ $body = sprintf(_("You have a new posting address on %1\$s.\n\n".
+ "Send email to %2\$s to post new messages.\n\n".
+ "More email instructions at %3\$s.\n\n".
+ "Faithfully yours,\n%4\$s"),
+ common_config('site', 'name'),
+ $user->incomingemail,
+ common_local_url('doc', array('title' => 'email')),
+ common_config('site', 'name'));
+
+ mail_send($user->email, $headers, $body);
+}
+
+function mail_new_incoming_address() {
+ $prefix = common_confirmation_code(64);
+ $suffix = mail_domain();
+ return $prefix . '@' . $suffix;
+}
+
+function mail_broadcast_notice_sms($notice) {
+
+ # Now, get users subscribed to this profile
+
+ $user = new User();
+
+ $user->query('SELECT nickname, smsemail, incomingemail ' .
+ 'FROM user JOIN subscription ' .
+ 'ON user.id = subscription.subscriber ' .
+ 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
+ 'AND user.smsemail IS NOT NULL ' .
+ 'AND user.smsnotify = 1 ' .
+ 'AND subscription.sms = 1 ');
+
+ while ($user->fetch()) {
+ common_log(LOG_INFO,
+ 'Sending notice ' . $notice->id . ' to ' . $user->smsemail,
+ __FILE__);
+ $success = mail_send_sms_notice_address($notice, $user->smsemail, $user->incomingemail);
+ if (!$success) {
+ # XXX: Not sure, but I think that's the right thing to do
+ common_log(LOG_WARNING,
+ 'Sending notice ' . $notice->id . ' to ' . $user->smsemail . ' FAILED, cancelling.',
+ __FILE__);
+ return false;
+ }
+ }
+
+ $user->free();
+ unset($user);
+
+ return true;
+}
+
+function mail_send_sms_notice($notice, $user) {
+ return mail_send_sms_notice_address($notice, $user->smsemail, $user->incomingemail);
+}
+
+function mail_send_sms_notice_address($notice, $smsemail, $incomingemail) {
+
+ $to = $nickname . ' <' . $smsemail . '>';
+ $other = $notice->getProfile();
+
+ common_log(LOG_INFO, "Sending notice " . $notice->id . " to " . $smsemail, __FILE__);
+
+ $headers = array();
+ $headers['From'] = (isset($incomingemail)) ? $incomingemail : mail_notify_from();
+ $headers['To'] = $to;
+ $headers['Subject'] = sprintf(_('%s status'),
+ $other->getBestName());
+ $body = $notice->content;
+
+ return mail_send($smsemail, $headers, $body);
+}
+
+function mail_confirm_sms($code, $nickname, $address) {
+
+ $recipients = $address;
+
+ $headers['From'] = mail_notify_from();
+ $headers['To'] = $nickname . ' <' . $address . '>';
+ $headers['Subject'] = _('SMS confirmation');
+
+ $body = "$nickname: confirm you own this phone number with this code:";
+ $body .= "\n\n";
+ $body .= $code;
+ $body .= "\n\n";
+
+ mail_send($recipients, $headers, $body);
+}
+
+function mail_notify_nudge($from, $to) {
+ common_init_locale($to->language);
+ $subject = sprintf(_('You\'ve been nudged by %s'), $from->nickname);
+
+ $from_profile = $from->getProfile();
+
+ $body = sprintf(_("%1\$s (%2\$s) is wondering what you are up to these days and is inviting you to post some news.\n\n".
+ "So let's hear from you :)\n\n".
+ "%3\$s\n\n".
+ "Don't reply to this email; it won't get to them.\n\n".
+ "With kind regards,\n".
+ "%4\$s\n"),
+ $from_profile->getBestName(),
+ $from->nickname,
+ common_local_url('all', array('nickname' => $to->nickname)),
+ common_config('site', 'name'));
+ common_init_locale();
+ return mail_to_user($to, $subject, $body);
+}
+
+function mail_notify_message($message, $from=NULL, $to=NULL) {
+
+ if (is_null($from)) {
+ $from = User::staticGet('id', $message->from_profile);
+ }
+
+ if (is_null($to)) {
+ $to = User::staticGet('id', $message->to_profile);
+ }
+
+ if (is_null($to->email) || !$to->emailnotifymsg) {
+ return true;
+ }
+
+ common_init_locale($to->language);
+ $subject = sprintf(_('New private message from %s'), $from->nickname);
+
+ $from_profile = $from->getProfile();
+
+ $body = sprintf(_("%1\$s (%2\$s) sent you a private message:\n\n".
+ "------------------------------------------------------\n".
+ "%3\$s\n".
+ "------------------------------------------------------\n\n".
+ "You can reply to their message here:\n\n".
+ "%4\$s\n\n".
+ "Don't reply to this email; it won't get to them.\n\n".
+ "With kind regards,\n".
+ "%5\$s\n"),
+ $from_profile->getBestName(),
+ $from->nickname,
+ $message->content,
+ common_local_url('newmessage', array('to' => $from->id)),
+ common_config('site', 'name'));
+
+ common_init_locale();
+ return mail_to_user($to, $subject, $body);
+}
+
+function mail_notify_fave($other, $user, $notice) {
+
+ $profile = $user->getProfile();
+ $bestname = $profile->getBestName();
+ common_init_locale($other->language);
+ $subject = sprintf(_('%s added your notice as a favorite'), $bestname);
+ $body = sprintf(_("%1\$s just added your notice from %2\$s as one of their favorites.\n\n" .
+ "In case you forgot, you can see the text of your notice here:\n\n" .
+ "%3\$s\n\n" .
+ "You can see the list of %1\$s's favorites here:\n\n" .
+ "%4\$s\n\n" .
+ "Faithfully yours,\n" .
+ "%5\$s\n"),
+ $bestname,
+ common_exact_date($notice->created),
+ common_local_url('shownotice', array('notice' => $notice->id)),
+ common_local_url('showfavorites', array('nickname' => $user->nickname)),
+ common_config('site', 'name'));
+
+ common_init_locale();
+ mail_to_user($other, $subject, $body);
+}
diff --git a/lib/mailbox.php b/lib/mailbox.php
new file mode 100644
index 000000000..4ed8d1758
--- /dev/null
+++ b/lib/mailbox.php
@@ -0,0 +1,172 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/personal.php');
+
+define('MESSAGES_PER_PAGE', 20);
+
+class MailboxAction extends PersonalAction {
+
+ function handle($args) {
+
+ parent::handle($args);
+
+ $nickname = common_canonical_nickname($this->arg('nickname'));
+ $user = User::staticGet('nickname', $nickname);
+
+ if (!$user) {
+ $this->client_error(_('No such user.'), 404);
+ return;
+ }
+
+ $cur = common_current_user();
+
+ if (!$cur || $cur->id != $user->id) {
+ $this->client_error(_('Only the user can read their own mailboxes.'), 403);
+ return;
+ }
+
+ $profile = $user->getProfile();
+
+ if (!$profile) {
+ $this->server_error(_('User has no profile.'));
+ return;
+ }
+
+ $page = $this->trimmed('page');
+
+ if (!$page) {
+ $page = 1;
+ }
+
+ $this->show_page($user, $page);
+ }
+
+ function get_title($user, $page) {
+ return '';
+ }
+
+ function get_instructions() {
+ return '';
+ }
+
+ function show_top() {
+
+ $cur = common_current_user();
+
+ common_message_form(NULL, $cur, NULL);
+
+ $this->views_menu();
+ }
+
+ function show_page($user, $page) {
+
+ common_show_header($this->get_title($user, $page),
+ NULL, NULL,
+ array($this, 'show_top'));
+
+ $this->show_box($user, $page);
+
+ common_show_footer();
+ }
+
+ function show_box($user, $page) {
+
+ $message = $this->get_messages($user, $page);
+
+ if ($message) {
+
+ $cnt = 0;
+ common_element_start('ul', array('id' => 'messages'));
+
+ while ($message->fetch() && $cnt <= MESSAGES_PER_PAGE) {
+ $cnt++;
+
+ if ($cnt > MESSAGES_PER_PAGE) {
+ break;
+ }
+
+ $this->show_message($message);
+ }
+
+ common_element_end('ul');
+
+ common_pagination($page > 1, $cnt > MESSAGES_PER_PAGE,
+ $page, $this->trimmed('action'),
+ array('nickname' => $user->nickname));
+
+ $message->free();
+ unset($message);
+ }
+ }
+
+ # returns the profile we want to show with the message
+
+ function get_message_profile($message) {
+ return NULL;
+ }
+
+ function show_message($message) {
+
+ common_element_start('li', array('class' => 'message_single',
+ 'id' => 'message-' . $message->id));
+
+ $profile = $this->get_message_profile($message);
+
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+ common_element_start('a', array('href' => $profile->profileurl));
+ common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
+ 'class' => 'avatar stream',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($profile->fullname) ? $profile->fullname :
+ $profile->nickname));
+ common_element_end('a');
+ common_element('a', array('href' => $profile->profileurl,
+ 'class' => 'nickname'),
+ $profile->nickname);
+ # FIXME: URL, image, video, audio
+ common_element_start('p', array('class' => 'content'));
+ common_raw($message->rendered);
+ common_element_end('p');
+
+ $messageurl = common_local_url('showmessage', array('message' => $message->id));
+
+ # XXX: we need to figure this out better. Is this right?
+ if (strcmp($message->uri, $messageurl) != 0 && preg_match('/^http/', $message->uri)) {
+ $messageurl = $message->uri;
+ }
+ common_element_start('p', 'time');
+ common_element('a', array('class' => 'permalink',
+ 'href' => $messageurl,
+ 'title' => common_exact_date($message->created)),
+ common_date_string($message->created));
+ if ($message->source) {
+ common_text(_(' from '));
+ $this->source_link($message->source);
+ }
+
+ common_element_end('p');
+
+ common_element_end('li');
+ }
+}
diff --git a/lib/noticelist.php b/lib/noticelist.php
new file mode 100644
index 000000000..415c062e4
--- /dev/null
+++ b/lib/noticelist.php
@@ -0,0 +1,224 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class NoticeList {
+
+ var $notice = NULL;
+
+ function __construct($notice) {
+ $this->notice = $notice;
+ }
+
+ function show() {
+
+ common_element_start('ul', array('id' => 'notices'));
+
+ $cnt = 0;
+
+ while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
+ $cnt++;
+
+ if ($cnt > NOTICES_PER_PAGE) {
+ break;
+ }
+
+ $item = $this->new_list_item($this->notice);
+ $item->show();
+ }
+
+ common_element_end('ul');
+
+ return $cnt;
+ }
+
+ function new_list_item($notice) {
+ return new NoticeListItem($notice);
+ }
+}
+
+class NoticeListItem {
+
+ var $notice = NULL;
+ var $profile = NULL;
+
+ function __construct($notice) {
+ $this->notice = $notice;
+ $this->profile = $notice->getProfile();
+ }
+
+ function show() {
+ $this->show_start();
+ $this->show_fave_form();
+ $this->show_author();
+ $this->show_content();
+ $this->show_start_time_section();
+ $this->show_notice_link();
+ $this->show_notice_source();
+ $this->show_reply_to();
+ $this->show_reply_link();
+ $this->show_delete_link();
+ $this->show_end_time_section();
+ $this->show_end();
+ }
+
+ function show_start() {
+ # XXX: RDFa
+ common_element_start('li', array('class' => 'notice_single hentry',
+ 'id' => 'notice-' . $this->notice->id));
+ }
+
+ function show_fave_form() {
+ $user = common_current_user();
+ if ($user) {
+ if ($user->hasFave($this->notice)) {
+ common_disfavor_form($this->notice);
+ } else {
+ common_favor_form($this->notice);
+ }
+ }
+ }
+
+ function show_author() {
+ common_element_start('span', 'vcard author');
+ $this->show_avatar();
+ $this->show_nickname();
+ common_element_end('span');
+ }
+
+ function show_avatar() {
+ $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
+ common_element_start('a', array('href' => $this->profile->profileurl));
+ common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
+ 'class' => 'avatar stream photo',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($this->profile->fullname) ? $this->profile->fullname :
+ $this->profile->nickname));
+ common_element_end('a');
+ }
+
+ function show_nickname() {
+ common_element('a', array('href' => $this->profile->profileurl,
+ 'class' => 'nickname fn url'),
+ $this->profile->nickname);
+ }
+
+ function show_content() {
+ # FIXME: URL, image, video, audio
+ common_element_start('p', array('class' => 'content entry-title'));
+ if ($this->notice->rendered) {
+ common_raw($this->notice->rendered);
+ } else {
+ # XXX: may be some uncooked notices in the DB,
+ # we cook them right now. This should probably disappear in future
+ # versions (>> 0.4.x)
+ common_raw(common_render_content($this->notice->content, $this->notice));
+ }
+ common_element_end('p');
+ }
+
+ function show_start_time_section() {
+ common_element_start('p', 'time');
+ }
+
+ function show_notice_link() {
+ $noticeurl = common_local_url('shownotice', array('notice' => $this->notice->id));
+ # XXX: we need to figure this out better. Is this right?
+ if (strcmp($this->notice->uri, $noticeurl) != 0 && preg_match('/^http/', $this->notice->uri)) {
+ $noticeurl = $this->notice->uri;
+ }
+ common_element_start('a', array('class' => 'permalink',
+ 'rel' => 'bookmark',
+ 'href' => $noticeurl));
+ common_element('abbr', array('class' => 'published',
+ 'title' => common_date_iso8601($this->notice->created)),
+ common_date_string($this->notice->created));
+ common_element_end('a');
+ }
+
+ function show_notice_source() {
+ if ($this->notice->source) {
+ common_element('span', null, _(' from '));
+ $source_name = _($this->notice->source);
+ switch ($this->notice->source) {
+ case 'web':
+ case 'xmpp':
+ case 'mail':
+ case 'omb':
+ case 'api':
+ common_element('span', 'noticesource', $source_name);
+ break;
+ default:
+ $ns = Notice_source::staticGet($this->notice->source);
+ if ($ns) {
+ common_element('a', array('href' => $ns->url),
+ $ns->name);
+ } else {
+ common_element('span', 'noticesource', $source_name);
+ }
+ break;
+ }
+ }
+ }
+
+ function show_reply_to() {
+ if ($this->notice->reply_to) {
+ $replyurl = common_local_url('shownotice', array('notice' => $this->notice->reply_to));
+ common_text(' (');
+ common_element('a', array('class' => 'inreplyto',
+ 'href' => $replyurl),
+ _('in reply to...'));
+ common_text(')');
+ }
+ }
+
+ function show_reply_link() {
+ common_element_start('a',
+ array('href' => common_local_url('newnotice',
+ array('replyto' => $this->profile->nickname)),
+ 'onclick' => 'return doreply("'.$this->profile->nickname.'", '.$this->notice->id.');',
+ 'title' => _('reply'),
+ 'class' => 'replybutton'));
+ common_raw(' &#8594;');
+ common_element_end('a');
+ }
+
+ function show_delete_link() {
+ $user = common_current_user();
+ if ($user && $this->notice->profile_id == $user->id) {
+ $deleteurl = common_local_url('deletenotice', array('notice' => $this->notice->id));
+ common_element_start('a', array('class' => 'deletenotice',
+ 'href' => $deleteurl,
+ 'title' => _('delete')));
+ common_raw(' &#215;');
+ common_element_end('a');
+ }
+ }
+
+ function show_end_time_section() {
+ common_element_end('p');
+ }
+
+ function show_end() {
+ common_element_end('li');
+ }
+}
diff --git a/lib/oauthstore.php b/lib/oauthstore.php
new file mode 100644
index 000000000..d7f9c9ff1
--- /dev/null
+++ b/lib/oauthstore.php
@@ -0,0 +1,144 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/omb.php');
+
+class LaconicaOAuthDataStore extends OAuthDataStore {
+
+ # We keep a record of who's contacted us
+
+ function lookup_consumer($consumer_key) {
+ $con = Consumer::staticGet('consumer_key', $consumer_key);
+ if (!$con) {
+ $con = new Consumer();
+ $con->consumer_key = $consumer_key;
+ $con->seed = common_good_rand(16);
+ $con->created = DB_DataObject_Cast::dateTime();
+ if (!$con->insert()) {
+ return NULL;
+ }
+ }
+ return new OAuthConsumer($con->consumer_key, '');
+ }
+
+ function lookup_token($consumer, $token_type, $token_key) {
+ $t = new Token();
+ $t->consumer_key = $consumer->key;
+ $t->tok = $token_key;
+ $t->type = ($token_type == 'access') ? 1 : 0;
+ if ($t->find(true)) {
+ return new OAuthToken($t->tok, $t->secret);
+ } else {
+ return NULL;
+ }
+ }
+
+ function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+ $n = new Nonce();
+ $n->consumer_key = $consumer->key;
+ $n->tok = $token->key;
+ $n->nonce = $nonce;
+ if ($n->find(TRUE)) {
+ return TRUE;
+ } else {
+ $n->timestamp = $timestamp;
+ $n->created = DB_DataObject_Cast::dateTime();
+ $n->insert();
+ return FALSE;
+ }
+ }
+
+ function new_request_token($consumer) {
+ $t = new Token();
+ $t->consumer_key = $consumer->key;
+ $t->tok = common_good_rand(16);
+ $t->secret = common_good_rand(16);
+ $t->type = 0; # request
+ $t->state = 0; # unauthorized
+ $t->created = DB_DataObject_Cast::dateTime();
+ if (!$t->insert()) {
+ return NULL;
+ } else {
+ return new OAuthToken($t->tok, $t->secret);
+ }
+ }
+
+ # defined in OAuthDataStore, but not implemented anywhere
+
+ function fetch_request_token($consumer) {
+ return $this->new_request_token($consumer);
+ }
+
+ function new_access_token($token, $consumer) {
+ common_debug('new_access_token("'.$token->key.'","'.$consumer->key.'")', __FILE__);
+ $rt = new Token();
+ $rt->consumer_key = $consumer->key;
+ $rt->tok = $token->key;
+ $rt->type = 0; # request
+ if ($rt->find(TRUE) && $rt->state == 1) { # authorized
+ common_debug('request token found.', __FILE__);
+ $at = new Token();
+ $at->consumer_key = $consumer->key;
+ $at->tok = common_good_rand(16);
+ $at->secret = common_good_rand(16);
+ $at->type = 1; # access
+ $at->created = DB_DataObject_Cast::dateTime();
+ if (!$at->insert()) {
+ $e = $at->_lastError;
+ common_debug('access token "'.$at->tok.'" not inserted: "'.$e->message.'"', __FILE__);
+ return NULL;
+ } else {
+ common_debug('access token "'.$at->tok.'" inserted', __FILE__);
+ # burn the old one
+ $orig_rt = clone($rt);
+ $rt->state = 2; # used
+ if (!$rt->update($orig_rt)) {
+ return NULL;
+ }
+ common_debug('request token "'.$rt->tok.'" updated', __FILE__);
+ # Update subscription
+ # XXX: mixing levels here
+ $sub = Subscription::staticGet('token', $rt->tok);
+ if (!$sub) {
+ return NULL;
+ }
+ common_debug('subscription for request token found', __FILE__);
+ $orig_sub = clone($sub);
+ $sub->token = $at->tok;
+ $sub->secret = $at->secret;
+ if (!$sub->update($orig_sub)) {
+ return NULL;
+ } else {
+ common_debug('subscription updated to use access token', __FILE__);
+ return new OAuthToken($at->tok, $at->secret);
+ }
+ }
+ } else {
+ return NULL;
+ }
+ }
+
+ # defined in OAuthDataStore, but not implemented anywhere
+
+ function fetch_access_token($consumer) {
+ return $this->new_access_token($consumer);
+ }
+}
diff --git a/lib/omb.php b/lib/omb.php
new file mode 100644
index 000000000..96736b4d4
--- /dev/null
+++ b/lib/omb.php
@@ -0,0 +1,299 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once('OAuth.php');
+require_once(INSTALLDIR.'/lib/oauthstore.php');
+
+require_once(INSTALLDIR.'/classes/Consumer.php');
+require_once(INSTALLDIR.'/classes/Nonce.php');
+require_once(INSTALLDIR.'/classes/Token.php');
+
+require_once('Auth/Yadis/Yadis.php');
+
+define('OAUTH_NAMESPACE', 'http://oauth.net/core/1.0/');
+define('OMB_NAMESPACE', 'http://openmicroblogging.org/protocol/0.1');
+define('OMB_VERSION_01', 'http://openmicroblogging.org/protocol/0.1');
+define('OAUTH_DISCOVERY', 'http://oauth.net/discovery/1.0');
+
+define('OMB_ENDPOINT_UPDATEPROFILE', OMB_NAMESPACE.'/updateProfile');
+define('OMB_ENDPOINT_POSTNOTICE', OMB_NAMESPACE.'/postNotice');
+define('OAUTH_ENDPOINT_REQUEST', OAUTH_NAMESPACE.'endpoint/request');
+define('OAUTH_ENDPOINT_AUTHORIZE', OAUTH_NAMESPACE.'endpoint/authorize');
+define('OAUTH_ENDPOINT_ACCESS', OAUTH_NAMESPACE.'endpoint/access');
+define('OAUTH_ENDPOINT_RESOURCE', OAUTH_NAMESPACE.'endpoint/resource');
+define('OAUTH_AUTH_HEADER', OAUTH_NAMESPACE.'parameters/auth-header');
+define('OAUTH_POST_BODY', OAUTH_NAMESPACE.'parameters/post-body');
+define('OAUTH_HMAC_SHA1', OAUTH_NAMESPACE.'signature/HMAC-SHA1');
+
+function omb_oauth_consumer() {
+ static $con = NULL;
+ if (!$con) {
+ $con = new OAuthConsumer(common_root_url(), '');
+ }
+ return $con;
+}
+
+function omb_oauth_server() {
+ static $server = null;
+ if (!$server) {
+ $server = new OAuthServer(omb_oauth_datastore());
+ $server->add_signature_method(omb_hmac_sha1());
+ }
+ return $server;
+}
+
+function omb_oauth_datastore() {
+ static $store = NULL;
+ if (!$store) {
+ $store = new LaconicaOAuthDataStore();
+ }
+ return $store;
+}
+
+function omb_hmac_sha1() {
+ static $hmac_method = NULL;
+ if (!$hmac_method) {
+ $hmac_method = new OAuthSignatureMethod_HMAC_SHA1();
+ }
+ return $hmac_method;
+}
+
+function omb_get_services($xrd, $type) {
+ return $xrd->services(array(omb_service_filter($type)));
+}
+
+function omb_service_filter($type) {
+ return create_function('$s',
+ 'return omb_match_service($s, \''.$type.'\');');
+}
+
+function omb_match_service($service, $type) {
+ return in_array($type, $service->getTypes());
+}
+
+function omb_service_uri($service) {
+ if (!$service) {
+ return NULL;
+ }
+ $uris = $service->getURIs();
+ if (!$uris) {
+ return NULL;
+ }
+ return $uris[0];
+}
+
+function omb_local_id($service) {
+ if (!$service) {
+ return NULL;
+ }
+ $els = $service->getElements('xrd:LocalID');
+ if (!$els) {
+ return NULL;
+ }
+ $el = $els[0];
+ return $service->parser->content($el);
+}
+
+function omb_broadcast_remote_subscribers($notice) {
+
+ # First, get remote users subscribed to this profile
+ $rp = new Remote_profile();
+
+ $rp->query('SELECT postnoticeurl, token, secret ' .
+ 'FROM subscription JOIN remote_profile ' .
+ 'ON subscription.subscriber = remote_profile.id ' .
+ 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ');
+
+ $posted = array();
+
+ while ($rp->fetch()) {
+ if (!$posted[$rp->postnoticeurl]) {
+ common_log(LOG_DEBUG, 'Posting to ' . $rp->postnoticeurl);
+ if (omb_post_notice_keys($notice, $rp->postnoticeurl, $rp->token, $rp->secret)) {
+ common_log(LOG_DEBUG, 'Finished to ' . $rp->postnoticeurl);
+ $posted[$rp->postnoticeurl] = TRUE;
+ } else {
+ common_log(LOG_DEBUG, 'Failed posting to ' . $rp->postnoticeurl);
+ }
+ }
+ }
+
+ $rp->free();
+ unset($rp);
+
+ return true;
+}
+
+function omb_post_notice($notice, $remote_profile, $subscription) {
+ return omb_post_notice_keys($notice, $remote_profile->postnoticeurl, $subscription->token, $subscription->secret);
+}
+
+function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) {
+
+ common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
+
+ $user = User::staticGet('id', $notice->profile_id);
+
+ if (!$user) {
+ common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
+ return false;
+ }
+
+ $con = omb_oauth_consumer();
+
+ $token = new OAuthToken($tk, $secret);
+
+ $url = $postnoticeurl;
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
+
+ $req = OAuthRequest::from_consumer_and_token($con, $token,
+ 'POST', $url, $params);
+
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+ $req->set_parameter('omb_listenee', $user->uri);
+ $req->set_parameter('omb_notice', $notice->uri);
+ $req->set_parameter('omb_notice_content', $notice->content);
+ $req->set_parameter('omb_notice_url', common_local_url('shownotice',
+ array('notice' =>
+ $notice->id)));
+ $req->set_parameter('omb_notice_license', common_config('license', 'url'));
+
+ $user->free();
+ unset($user);
+
+ $req->sign_request(omb_hmac_sha1(), $con, $token);
+
+ # We re-use this tool's fetcher, since it's pretty good
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ if (!$fetcher) {
+ common_log(LOG_WARNING, 'Failed to initialize Yadis fetcher.', __FILE__);
+ return false;
+ }
+
+ $result = $fetcher->post($req->get_normalized_http_url(),
+ $req->to_postdata(),
+ array('User-Agent' => 'Laconica/' . LACONICA_VERSION));
+
+ common_debug('Got HTTP result "'.print_r($result,TRUE).'"', __FILE__);
+
+ if ($result->status == 403) { # not authorized, don't send again
+ common_debug('403 result, deleting subscription', __FILE__);
+ # FIXME: figure out how to delete this
+ # $subscription->delete();
+ return false;
+ } else if ($result->status != 200) {
+ common_debug('Error status '.$result->status, __FILE__);
+ return false;
+ } else { # success!
+ parse_str($result->body, $return);
+ if ($return['omb_version'] == OMB_VERSION_01) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+function omb_broadcast_profile($profile) {
+ # First, get remote users subscribed to this profile
+ # XXX: use a join here rather than looping through results
+ $sub = new Subscription();
+ $sub->subscribed = $profile->id;
+ if ($sub->find()) {
+ $updated = array();
+ while ($sub->fetch()) {
+ $rp = Remote_profile::staticGet('id', $sub->subscriber);
+ if ($rp) {
+ if (!$updated[$rp->updateprofileurl]) {
+ if (omb_update_profile($profile, $rp, $sub)) {
+ $updated[$rp->updateprofileurl] = TRUE;
+ }
+ }
+ }
+ }
+ }
+}
+
+function omb_update_profile($profile, $remote_profile, $subscription) {
+ global $config; # for license URL
+ $user = User::staticGet($profile->id);
+ $con = omb_oauth_consumer();
+ $token = new OAuthToken($subscription->token, $subscription->secret);
+ $url = $remote_profile->updateprofileurl;
+ $parsed = parse_url($url);
+ $params = array();
+ parse_str($parsed['query'], $params);
+ $req = OAuthRequest::from_consumer_and_token($con, $token,
+ "POST", $url, $params);
+ $req->set_parameter('omb_version', OMB_VERSION_01);
+ $req->set_parameter('omb_listenee', $user->uri);
+ $req->set_parameter('omb_listenee_profile', common_profile_url($profile->nickname));
+ $req->set_parameter('omb_listenee_nickname', $profile->nickname);
+
+ # We use blanks to force emptying any existing values in these optional fields
+
+ $req->set_parameter('omb_listenee_fullname',
+ ($profile->fullname) ? $profile->fullname : '');
+ $req->set_parameter('omb_listenee_homepage',
+ ($profile->homepage) ? $profile->homepage : '');
+ $req->set_parameter('omb_listenee_bio',
+ ($profile->bio) ? $profile->bio : '');
+ $req->set_parameter('omb_listenee_location',
+ ($profile->location) ? $profile->location : '');
+
+ $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
+ $req->set_parameter('omb_listenee_avatar',
+ ($avatar) ? $avatar->url : '');
+
+ $req->sign_request(omb_hmac_sha1(), $con, $token);
+
+ # We re-use this tool's fetcher, since it's pretty good
+
+ $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
+
+ common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__);
+ common_debug('postdata = '.$req->to_postdata(), __FILE__);
+ $result = $fetcher->post($req->get_normalized_http_url(),
+ $req->to_postdata(),
+ array('User-Agent' => 'Laconica/' . LACONICA_VERSION));
+
+ common_debug('Got HTTP result "'.print_r($result,TRUE).'"', __FILE__);
+
+ if ($result->status == 403) { # not authorized, don't send again
+ common_debug('403 result, deleting subscription', __FILE__);
+ $subscription->delete();
+ return false;
+ } else if ($result->status != 200) {
+ common_debug('Error status '.$result->status, __FILE__);
+ return false;
+ } else { # success!
+ parse_str($result->body, $return);
+ if ($return['omb_version'] == OMB_VERSION_01) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/lib/openid.php b/lib/openid.php
new file mode 100644
index 000000000..6e501c2b1
--- /dev/null
+++ b/lib/openid.php
@@ -0,0 +1,242 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/classes/User_openid.php');
+
+require_once('Auth/OpenID.php');
+require_once('Auth/OpenID/Consumer.php');
+require_once('Auth/OpenID/SReg.php');
+require_once('Auth/OpenID/MySQLStore.php');
+
+# About one year cookie expiry
+
+define('OPENID_COOKIE_EXPIRY', round(365.25 * 24 * 60 * 60));
+define('OPENID_COOKIE_KEY', 'lastusedopenid');
+
+function oid_store() {
+ static $store = NULL;
+ if (!$store) {
+ # Can't be called statically
+ $user = new User();
+ $conn = $user->getDatabaseConnection();
+ $store = new Auth_OpenID_MySQLStore($conn);
+ }
+ return $store;
+}
+
+function oid_consumer() {
+ $store = oid_store();
+ $consumer = new Auth_OpenID_Consumer($store);
+ return $consumer;
+}
+
+function oid_clear_last() {
+ oid_set_last('');
+}
+
+function oid_set_last($openid_url) {
+ common_set_cookie(OPENID_COOKIE_KEY,
+ $openid_url,
+ time() + OPENID_COOKIE_EXPIRY);
+}
+
+function oid_get_last() {
+ $openid_url = $_COOKIE[OPENID_COOKIE_KEY];
+ if ($openid_url && strlen($openid_url) > 0) {
+ return $openid_url;
+ } else {
+ return NULL;
+ }
+}
+
+function oid_link_user($id, $canonical, $display) {
+
+ $oid = new User_openid();
+ $oid->user_id = $id;
+ $oid->canonical = $canonical;
+ $oid->display = $display;
+ $oid->created = DB_DataObject_Cast::dateTime();
+
+ if (!$oid->insert()) {
+ $err = PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_debug('DB error ' . $err->code . ': ' . $err->message, __FILE__);
+ return false;
+ }
+
+ return true;
+}
+
+function oid_get_user($openid_url) {
+ $user = NULL;
+ $oid = User_openid::staticGet('canonical', $openid_url);
+ if ($oid) {
+ $user = User::staticGet('id', $oid->user_id);
+ }
+ return $user;
+}
+
+function oid_check_immediate($openid_url, $backto=NULL) {
+ if (!$backto) {
+ $action = $_REQUEST['action'];
+ $args = common_copy_args($_GET);
+ unset($args['action']);
+ $backto = common_local_url($action, $args);
+ }
+ common_debug('going back to "' . $backto . '"', __FILE__);
+
+ common_ensure_session();
+
+ $_SESSION['openid_immediate_backto'] = $backto;
+ common_debug('passed-in variable is "' . $backto . '"', __FILE__);
+ common_debug('session variable is "' . $_SESSION['openid_immediate_backto'] . '"', __FILE__);
+
+ oid_authenticate($openid_url,
+ 'finishimmediate',
+ true);
+}
+
+function oid_authenticate($openid_url, $returnto, $immediate=false) {
+
+ $consumer = oid_consumer();
+
+ if (!$consumer) {
+ common_server_error(_('Cannot instantiate OpenID consumer object.'));
+ return false;
+ }
+
+ common_ensure_session();
+
+ $auth_request = $consumer->begin($openid_url);
+
+ // Handle failure status return values.
+ if (!$auth_request) {
+ return _('Not a valid OpenID.');
+ } else if (Auth_OpenID::isFailure($auth_request)) {
+ return sprintf(_('OpenID failure: %s'), $auth_request->message);
+ }
+
+ $sreg_request = Auth_OpenID_SRegRequest::build(// Required
+ array(),
+ // Optional
+ array('nickname',
+ 'email',
+ 'fullname',
+ 'language',
+ 'timezone',
+ 'postcode',
+ 'country'));
+
+ if ($sreg_request) {
+ $auth_request->addExtension($sreg_request);
+ }
+
+ $trust_root = common_local_url('public');
+ $process_url = common_local_url($returnto);
+
+ if ($auth_request->shouldSendRedirect()) {
+ $redirect_url = $auth_request->redirectURL($trust_root,
+ $process_url,
+ $immediate);
+ if (!$redirect_url) {
+ } else if (Auth_OpenID::isFailure($redirect_url)) {
+ return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
+ } else {
+ common_redirect($redirect_url);
+ }
+ } else {
+ // Generate form markup and render it.
+ $form_id = 'openid_message';
+ $form_html = $auth_request->formMarkup($trust_root, $process_url,
+ $immediate, array('id' => $form_id));
+
+ # XXX: This is cheap, but things choke if we don't escape ampersands
+ # in the HTML attributes
+
+ $form_html = preg_replace('/&/', '&amp;', $form_html);
+
+ // Display an error if the form markup couldn't be generated;
+ // otherwise, render the HTML.
+ if (Auth_OpenID::isFailure($form_html)) {
+ $this->show_form(sprintf(_('Could not create OpenID form: %s'), $form_html->message));
+ } else {
+ common_show_header(_('OpenID Auto-Submit'), NULL, NULL, '_oid_print_instructions');
+ common_raw($form_html);
+ common_element('script', NULL,
+ '$(document).ready(function() { ' .
+ ' $("#'. $form_id .'").submit(); '.
+ '});');
+ common_show_footer();
+ }
+ }
+}
+
+# Half-assed attempt at a module-private function
+
+function _oid_print_instructions() {
+ common_element('div', 'instructions',
+ _('This form should automatically submit itself. '.
+ 'If not, click the submit button to go to your '.
+ 'OpenID provider.'));
+}
+
+# update a user from sreg parameters
+
+function oid_update_user(&$user, &$sreg) {
+
+ $profile = $user->getProfile();
+
+ $orig_profile = clone($profile);
+
+ if ($sreg['fullname'] && strlen($sreg['fullname']) <= 255) {
+ $profile->fullname = $sreg['fullname'];
+ }
+
+ if ($sreg['country']) {
+ if ($sreg['postcode']) {
+ # XXX: use postcode to get city and region
+ # XXX: also, store postcode somewhere -- it's valuable!
+ $profile->location = $sreg['postcode'] . ', ' . $sreg['country'];
+ } else {
+ $profile->location = $sreg['country'];
+ }
+ }
+
+ # XXX save language if it's passed
+ # XXX save timezone if it's passed
+
+ if (!$profile->update($orig_profile)) {
+ common_server_error(_('Error saving the profile.'));
+ return false;
+ }
+
+ $orig_user = clone($user);
+
+ if ($sreg['email'] && Validate::email($sreg['email'], true)) {
+ $user->email = $sreg['email'];
+ }
+
+ if (!$user->update($orig_user)) {
+ common_server_error(_('Error saving the user.'));
+ return false;
+ }
+
+ return true;
+}
diff --git a/lib/personal.php b/lib/personal.php
new file mode 100644
index 000000000..86433b486
--- /dev/null
+++ b/lib/personal.php
@@ -0,0 +1,206 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class PersonalAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ common_set_returnto($this->self_url());
+ }
+
+ function views_menu() {
+
+ $user = NULL;
+ $action = $this->trimmed('action');
+ $nickname = $this->trimmed('nickname');
+
+ if ($nickname) {
+ $user = User::staticGet('nickname', $nickname);
+ $user_profile = $user->getProfile();
+ } else {
+ $user_profile = false;
+ }
+
+ common_element_start('ul', array('id' => 'nav_views'));
+
+ common_menu_item(common_local_url('all', array('nickname' =>
+ $nickname)),
+ _('Personal'),
+ sprintf(_('%s and friends'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
+ $action == 'all');
+ common_menu_item(common_local_url('replies', array('nickname' =>
+ $nickname)),
+ _('Replies'),
+ sprintf(_('Replies to %s'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
+ $action == 'replies');
+ common_menu_item(common_local_url('showstream', array('nickname' =>
+ $nickname)),
+ _('Profile'),
+ ($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname,
+ $action == 'showstream');
+ common_menu_item(common_local_url('showfavorites', array('nickname' =>
+ $nickname)),
+ _('Favorites'),
+ sprintf(_('%s\'s favorite notices'), ($user_profile) ? $user_profile->getBestName() : _('User')),
+ $action == 'showfavorites');
+
+ $cur = common_current_user();
+
+ if ($cur && $cur->id == $user->id) {
+
+ common_menu_item(common_local_url('inbox', array('nickname' =>
+ $nickname)),
+ _('Inbox'),
+ _('Your incoming messages'),
+ $action == 'inbox');
+ common_menu_item(common_local_url('outbox', array('nickname' =>
+ $nickname)),
+ _('Outbox'),
+ _('Your sent messages'),
+ $action == 'outbox');
+ }
+
+ common_element_end('ul');
+ }
+
+ function show_feeds_list($feeds) {
+ common_element_start('div', array('class' => 'feeds'));
+ common_element('p', null, 'Feeds:');
+ common_element_start('ul', array('class' => 'xoxo'));
+
+ foreach ($feeds as $key => $value) {
+ $this->common_feed_item($feeds[$key]);
+ }
+ common_element_end('ul');
+ common_element_end('div');
+ }
+
+ function common_feed_item($feed) {
+ $nickname = $this->trimmed('nickname');
+
+ switch($feed['item']) {
+ case 'notices': default:
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's ".$feed['version']." notice feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'allrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = $feed['version']." feed for $nickname and friends";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'repliesrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = $feed['version']." feed for replies to $nickname";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'publicrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Public timeline ".$feed['version']." feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'publicatom':
+ $feed_classname = "atom";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Public timeline ".$feed['version']." feed";
+ $feed['textContent'] = "Atom";
+ break;
+
+ case 'tagrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = $feed['version']." feed for this tag";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'favoritedrss':
+ $feed_classname = $feed['type'];
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Favorited ".$feed['version']." feed";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'foaf':
+ $feed_classname = "foaf";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's FOAF file";
+ $feed['textContent'] = "FOAF";
+ break;
+
+ case 'favoritesrss':
+ $feed_classname = "favorites";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "Feed for favorites of $nickname";
+ $feed['textContent'] = "RSS";
+ break;
+
+ case 'usertimeline':
+ $feed_classname = "atom";
+ $feed_mimetype = "application/".$feed['type']."+xml";
+ $feed_title = "$nickname's ".$feed['version']." notice feed";
+ $feed['textContent'] = "Atom";
+ break;
+ }
+ common_element_start('li');
+ common_element('a', array('href' => $feed['href'],
+ 'class' => $feed_classname,
+ 'type' => $feed_mimetype,
+ 'title' => $feed_title),
+ $feed['textContent']);
+ common_element_end('li');
+ }
+
+
+ function source_link($source) {
+ $source_name = _($source);
+ switch ($source) {
+ case 'web':
+ case 'xmpp':
+ case 'mail':
+ case 'omb':
+ case 'api':
+ common_element('span', 'noticesource', $source_name);
+ break;
+ default:
+ $ns = Notice_source::staticGet($source);
+ if ($ns) {
+ common_element('a', array('href' => $ns->url),
+ $ns->name);
+ } else {
+ common_element('span', 'noticesource', $source_name);
+ }
+ break;
+ }
+ return;
+ }
+}
diff --git a/lib/profilelist.php b/lib/profilelist.php
new file mode 100644
index 000000000..9079ea9d7
--- /dev/null
+++ b/lib/profilelist.php
@@ -0,0 +1,169 @@
+<?php
+
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+define('PROFILES_PER_PAGE', 20);
+
+class ProfileList {
+
+ var $profile = NULL;
+ var $owner = NULL;
+ var $action = NULL;
+
+ function __construct($profile, $owner=NULL, $action=NULL) {
+ $this->profile = $profile;
+ $this->owner = $owner;
+ $this->action = $action;
+ }
+
+ function show_list() {
+
+ common_element_start('ul', array('id' => 'profiles', 'class' => 'profile_list'));
+
+ $cnt = 0;
+
+ while ($this->profile->fetch()) {
+ $cnt++;
+ if($cnt > PROFILES_PER_PAGE) {
+ break;
+ }
+ $this->show();
+ }
+
+ common_element_end('ul');
+
+ return $cnt;
+ }
+
+ function show() {
+
+ common_element_start('li', array('class' => 'profile_single',
+ 'id' => 'profile-' . $this->profile->id));
+
+ $user = common_current_user();
+
+ if ($user && $user->id != $this->profile->id) {
+ # XXX: special-case for user looking at own
+ # subscriptions page
+ if ($user->isSubscribed($this->profile)) {
+ common_unsubscribe_form($this->profile);
+ } else {
+ common_subscribe_form($this->profile);
+ }
+ }
+
+ $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
+ common_element_start('a', array('href' => $this->profile->profileurl));
+ common_element('img', array('src' => ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE),
+ 'class' => 'avatar stream',
+ 'width' => AVATAR_STREAM_SIZE,
+ 'height' => AVATAR_STREAM_SIZE,
+ 'alt' =>
+ ($this->profile->fullname) ? $this->profile->fullname :
+ $this->profile->nickname));
+ common_element_end('a');
+ common_element_start('p');
+ common_element_start('a', array('href' => $this->profile->profileurl,
+ 'class' => 'nickname'));
+ common_raw($this->highlight($this->profile->nickname));
+ common_element_end('a');
+ if ($this->profile->fullname) {
+ common_text(' | ');
+ common_element_start('span', 'fullname');
+ common_raw($this->highlight($this->profile->fullname));
+ common_element_end('span');
+ }
+ if ($this->profile->location) {
+ common_text(' | ');
+ common_element_start('span', 'location');
+ common_raw($this->highlight($this->profile->location));
+ common_element_end('span');
+ }
+ common_element_end('p');
+ if ($this->profile->homepage) {
+ common_element_start('p', 'website');
+ common_element_start('a', array('href' => $this->profile->homepage));
+ common_raw($this->highlight($this->profile->homepage));
+ common_element_end('a');
+ common_element_end('p');
+ }
+ if ($this->profile->bio) {
+ common_element_start('p', 'bio');
+ common_raw($this->highlight($this->profile->bio));
+ common_element_end('p');
+ }
+
+ # If we're on a list with an owner (subscriptions or subscribers)...
+
+ if ($this->owner) {
+ # Get tags
+ $tags = Profile_tag::getTags($this->owner->id, $this->profile->id);
+
+ common_element_start('div', 'tags_user');
+ common_element_start('dl');
+ common_element_start('dt');
+ if ($user->id == $this->owner->id) {
+ common_element('a', array('href' => common_local_url('tagother',
+ array('id' => $this->profile->id))),
+ _('Tags'));
+ } else {
+ common_text(_('Tags'));
+ }
+ common_text(":");
+ common_element_end('dt');
+ common_element_start('dd');
+ if ($tags) {
+ common_element_start('ul', 'tags xoxo');
+ foreach ($tags as $tag) {
+ common_element_start('li');
+ common_element('a', array('rel' => 'tag',
+ 'href' => common_local_url($this->action,
+ array('nickname' => $this->owner->nickname,
+ 'tag' => $tag))),
+ $tag);
+ common_element_end('li');
+ }
+ common_element_end('ul');
+ } else {
+ common_text(_('(none)'));
+ }
+ common_element_end('dd');
+ common_element_end('dl');
+ common_element_end('div');
+ }
+
+ if ($user && $user->id == $this->owner->id) {
+ $this->show_owner_controls($this->profile);
+ }
+
+ common_element_end('li');
+ }
+
+ /* Override this in subclasses. */
+
+ function show_owner_controls($profile) {
+ return;
+ }
+
+ function highlight($text) {
+ return htmlspecialchars($text);
+ }
+} \ No newline at end of file
diff --git a/lib/queuehandler.php b/lib/queuehandler.php
new file mode 100644
index 000000000..23f295c45
--- /dev/null
+++ b/lib/queuehandler.php
@@ -0,0 +1,132 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define('CLAIM_TIMEOUT', 1200);
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/daemon.php');
+require_once(INSTALLDIR.'/classes/Queue_item.php');
+require_once(INSTALLDIR.'/classes/Notice.php');
+
+class QueueHandler extends Daemon {
+
+ var $_id = 'generic';
+
+ function QueueHandler($id=NULL) {
+ if ($id) {
+ $this->set_id($id);
+ }
+ }
+
+ function class_name() {
+ return ucfirst($this->transport()) . 'Handler';
+ }
+
+ function name() {
+ return strtolower($this->class_name().'.'.$this->get_id());
+ }
+
+ function get_id() {
+ return $this->_id;
+ }
+
+ function set_id($id) {
+ $this->_id = $id;
+ }
+
+ function transport() {
+ return NULL;
+ }
+
+ function start() {
+ }
+
+ function finish() {
+ }
+
+ function handle_notice($notice) {
+ return true;
+ }
+
+ function run() {
+ if (!$this->start()) {
+ return false;
+ }
+ $this->log(LOG_INFO, 'checking for queued notices');
+ $transport = $this->transport();
+ do {
+ $qi = Queue_item::top($transport);
+ if ($qi) {
+ $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created));
+ $notice = Notice::staticGet($qi->notice_id);
+ if ($notice) {
+ $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
+ # XXX: what to do if broadcast fails?
+ $result = $this->handle_notice($notice);
+ if (!$result) {
+ $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
+ $orig = $qi;
+ $qi->claimed = NULL;
+ $qi->update($orig);
+ $this->log(LOG_WARNING, 'Abandoned claim for notice ID = ' . $notice->id);
+ continue;
+ }
+ $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
+ $notice->free();
+ unset($notice);
+ $notice = NULL;
+ } else {
+ $this->log(LOG_WARNING, 'queue item for notice that does not exist');
+ }
+ $qi->delete();
+ $qi->free();
+ unset($qi);
+ $this->idle(0);
+ } else {
+ $this->clear_old_claims();
+ $this->idle(5);
+ }
+ } while (true);
+ if (!$this->finish()) {
+ return false;
+ }
+ return true;
+ }
+
+ function idle($timeout=0) {
+ if ($timeout>0) {
+ sleep($timeout);
+ }
+ }
+
+ function clear_old_claims() {
+ $qi = new Queue_item();
+ $qi->transport = $this->transport();
+ $qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
+ $qi->update(DB_DATAOBJECT_WHEREADD_ONLY);
+ $qi->free();
+ unset($qi);
+ }
+
+ function log($level, $msg) {
+ common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
+ }
+}
+ \ No newline at end of file
diff --git a/lib/rssaction.php b/lib/rssaction.php
new file mode 100644
index 000000000..777511506
--- /dev/null
+++ b/lib/rssaction.php
@@ -0,0 +1,189 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+define('DEFAULT_RSS_LIMIT', 48);
+
+class Rss10Action extends Action {
+
+ # This will contain the details of each feed item's author and be used to generate SIOC data.
+ var $creators = array();
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ $limit = (int) $this->trimmed('limit');
+ if ($limit == 0) {
+ $limit = DEFAULT_RSS_LIMIT;
+ }
+ $this->show_rss($limit);
+ }
+
+ function init() {
+ return true;
+ }
+
+ function get_notices() {
+ return array();
+ }
+
+ function get_channel() {
+ return array('url' => '',
+ 'title' => '',
+ 'link' => '',
+ 'description' => '');
+ }
+
+ function get_image() {
+ return NULL;
+ }
+
+ function show_rss($limit=0) {
+
+ if (!$this->init()) {
+ return;
+ }
+
+ $notices = $this->get_notices($limit);
+
+ $this->init_rss();
+ $this->show_channel($notices);
+ $this->show_image();
+
+ foreach ($notices as $n) {
+ $this->show_item($n);
+ }
+
+ $this->show_creators();
+ $this->end_rss();
+ }
+
+ function show_channel($notices) {
+
+ $channel = $this->get_channel();
+ $image = $this->get_image();
+
+ common_element_start('channel', array('rdf:about' => $channel['url']));
+ common_element('title', NULL, $channel['title']);
+ common_element('link', NULL, $channel['link']);
+ common_element('description', NULL, $channel['description']);
+ common_element('cc:licence', array('rdf:resource' => common_config('license','url')));
+
+ if ($image) {
+ common_element('image', array('rdf:resource' => $image));
+ }
+
+ common_element_start('items');
+ common_element_start('rdf:Seq');
+
+ foreach ($notices as $notice) {
+ common_element('sioct:MicroblogPost', array('rdf:resource' => $notice->uri));
+ }
+
+ common_element_end('rdf:Seq');
+ common_element_end('items');
+
+ common_element_end('channel');
+ }
+
+ function show_image() {
+ $image = $this->get_image();
+ if ($image) {
+ $channel = $this->get_channel();
+ common_element_start('image', array('rdf:about' => $image));
+ common_element('title', NULL, $channel['title']);
+ common_element('link', NULL, $channel['link']);
+ common_element('url', NULL, $image);
+ common_element_end('image');
+ }
+ }
+
+ function show_item($notice) {
+ $profile = Profile::staticGet($notice->profile_id);
+ $nurl = common_local_url('shownotice', array('notice' => $notice->id));
+ $creator_uri = common_profile_uri($profile);
+ common_element_start('item', array('rdf:about' => $notice->uri));
+ $title = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
+ common_element('title', NULL, $title);
+ common_element('link', NULL, $nurl);
+ common_element('description', NULL, $profile->nickname."'s status on ".common_exact_date($notice->created));
+ common_element('dc:date', NULL, common_date_w3dtf($notice->created));
+ common_element('dc:creator', NULL, ($profile->fullname) ? $profile->fullname : $profile->nickname);
+ common_element('sioc:has_creator', array('rdf:resource' => $creator_uri));
+ common_element('laconica:postIcon', array('rdf:resource' => common_profile_avatar_url($profile)));
+ common_element('cc:licence', array('rdf:resource' => common_config('license', 'url')));
+ common_element_end('item');
+ $this->creators[$creator_uri] = $profile;
+ }
+
+ function show_creators() {
+ foreach ($this->creators as $uri => $profile) {
+ $id = $profile->id;
+ $nickname = $profile->nickname;
+ common_element_start('sioc:User', array('rdf:about' => $uri));
+ common_element('foaf:nick', NULL, $nickname);
+ if ($profile->fullname) {
+ common_element('foaf:name', NULL, $profile->fullname);
+ }
+ common_element('sioc:id', NULL, $id);
+ $avatar = common_profile_avatar_url($profile);
+ common_element('sioc:avatar', array('rdf:resource' => $avatar));
+ common_element_end('sioc:User');
+ }
+ }
+
+ function init_rss() {
+ $channel = $this->get_channel();
+ header('Content-Type: application/rdf+xml');
+
+ common_start_xml();
+ common_element_start('rdf:RDF', array('xmlns:rdf' =>
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+ 'xmlns:dc' =>
+ 'http://purl.org/dc/elements/1.1/',
+ 'xmlns:cc' =>
+ 'http://web.resource.org/cc/',
+ 'xmlns:content' =>
+ 'http://purl.org/rss/1.0/modules/content/',
+ 'xmlns:foaf' =>
+ 'http://xmlns.com/foaf/0.1/',
+ 'xmlns:sioc' =>
+ 'http://rdfs.org/sioc/ns#',
+ 'xmlns:sioct' =>
+ 'http://rdfs.org/sioc/types#',
+ 'xmlns:laconica' =>
+ 'http://laconi.ca/ont/',
+ 'xmlns' => 'http://purl.org/rss/1.0/'));
+ common_element_start('sioc:Site', array('rdf:about' => common_root_url()));
+ common_element('sioc:name', NULL, common_config('site', 'name'));
+ common_element_start('sioc:container_of');
+ common_element('sioc:Container', array('rdf:about' =>
+ $channel['url']));
+ common_element_end('sioc:container_of');
+ common_element_end('sioc:Site');
+ }
+
+ function end_rss() {
+ common_element_end('rdf:RDF');
+ }
+}
diff --git a/lib/search_engines.php b/lib/search_engines.php
new file mode 100644
index 000000000..7fcc1ffcb
--- /dev/null
+++ b/lib/search_engines.php
@@ -0,0 +1,116 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class SearchEngine {
+ protected $target;
+ protected $table;
+
+ function __construct($target, $table) {
+ $this->target = $target;
+ $this->table = $table;
+ }
+
+ function query($q) {
+ }
+
+ function limit($offset, $count, $rss = false) {
+ return $this->target->limit($offset, $count);
+ }
+
+ function set_sort_mode($mode) {
+ if ('chron' === $mode)
+ return $this->target->orderBy('created desc');
+ }
+}
+
+class SphinxSearch extends SearchEngine {
+ private $sphinx;
+ private $connected;
+
+ function __construct($target, $table) {
+ $fp = @fsockopen(common_config('sphinx', 'server'), common_config('sphinx', 'port'));
+ if (!$fp) {
+ $this->connected = false;
+ return;
+ }
+ fclose($fp);
+ parent::__construct($target, $table);
+ $this->sphinx = new SphinxClient;
+ $this->sphinx->setServer(common_config('sphinx', 'server'), common_config('sphinx', 'port'));
+ $this->connected = true;
+ }
+
+ function is_connected() {
+ return $this->connected;
+ }
+
+ function limit($offset, $count, $rss = false) {
+ //FIXME without LARGEST_POSSIBLE, the most recent results aren't returned
+ // this probably has a large impact on performance
+ $LARGEST_POSSIBLE = 1e6;
+
+ if ($rss) {
+ $this->sphinx->setLimits($offset, $count, $count, $LARGEST_POSSIBLE);
+ }
+ else {
+ // return at most 50 pages of results
+ $this->sphinx->setLimits($offset, $count, 50 * ($count - 1), $LARGEST_POSSIBLE);
+ }
+
+ return $this->target->limit(0, $count);
+ }
+
+ function query($q) {
+ $result = $this->sphinx->query($q, $this->table);
+ if (!isset($result['matches'])) return false;
+ $id_set = join(', ', array_keys($result['matches']));
+ $this->target->whereAdd("id in ($id_set)");
+ return true;
+ }
+
+ function set_sort_mode($mode) {
+ if ('chron' === $mode) {
+ $this->sphinx->SetSortMode(SPH_SORT_ATTR_DESC, 'created_ts');
+ return $this->target->orderBy('created desc');
+ }
+ }
+}
+
+class MySQLSearch extends SearchEngine {
+ function query($q) {
+ if ('identica_people' === $this->table)
+ return $this->target->whereAdd('MATCH(nickname, fullname, location, bio, homepage) ' .
+ 'against (\''.addslashes($q).'\')');
+ if ('identica_notices' === $this->table)
+ return $this->target->whereAdd('MATCH(content) ' .
+ 'against (\''.addslashes($q).'\')');
+ }
+}
+
+class PGSearch extends SearchEngine {
+ function query($q) {
+ if ('identica_people' === $this->table)
+ return $this->target->whereAdd('textsearch @@ plainto_tsquery(\''.addslashes($q).'\')');
+ if ('identica_notices' === $this->table)
+ return $this->target->whereAdd('to_tsvector(\'english\', content) @@ plainto_tsquery(\''.addslashes($q).'\')');
+ }
+}
+
diff --git a/lib/searchaction.php b/lib/searchaction.php
new file mode 100644
index 000000000..f99883b25
--- /dev/null
+++ b/lib/searchaction.php
@@ -0,0 +1,110 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class SearchAction extends Action {
+
+ function is_readonly() {
+ return true;
+ }
+
+ function handle($args) {
+ parent::handle($args);
+ $this->show_form();
+ }
+
+ function show_top($arr=NULL) {
+ if ($arr) {
+ $error = $arr[1];
+ }
+ if ($error) {
+ common_element('p', 'error', $error);
+ } else {
+ $instr = $this->get_instructions();
+ $output = common_markup_to_html($instr);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+ $this->search_menu();
+ }
+
+ function get_title() {
+ return NULL;
+ }
+
+ function show_header($arr) {
+ return;
+ }
+
+ function show_form($error=NULL) {
+ global $config;
+
+ $q = $this->trimmed('q');
+ $page = $this->trimmed('page', 1);
+
+ common_show_header($this->get_title(), array($this, 'show_header'), array($q, $error),
+ array($this, 'show_top'));
+ common_element_start('form', array('method' => 'get',
+ 'id' => 'login',
+ 'action' => common_local_url($this->trimmed('action'))));
+ common_element_start('p');
+ if (!isset($config['site']['fancy']) || !$config['site']['fancy']) {
+ common_element('input', array('name' => 'action',
+ 'type' => 'hidden',
+ 'value' => $this->trimmed('action')));
+ }
+ common_element('input', array('name' => 'q',
+ 'id' => 'q',
+ 'type' => 'text',
+ 'class' => 'input_text',
+ 'value' => ($q) ? $q : ''));
+ common_text(' ');
+ common_element('input', array('type' => 'submit',
+ 'id' => 'search',
+ 'name' => 'search',
+ 'class' => 'submit',
+ 'value' => _('Search')));
+
+ common_element_end('p');
+ common_element_end('form');
+ if ($q) {
+ $this->show_results($q, $page);
+ }
+ common_show_footer();
+ }
+
+ function search_menu() {
+ # action => array('prompt', 'title', $args)
+ $action = $this->trimmed('action');
+ $menu =
+ array('peoplesearch' =>
+ array(
+ _('People'),
+ _('Find people on this site'),
+ ($action != 'peoplesearch' && $this->trimmed('q')) ? array('q' => $this->trimmed('q')) : NULL),
+ 'noticesearch' =>
+ array( _('Text'),
+ _('Find content of notices'),
+ ($action != 'noticesearch' && $this->trimmed('q')) ? array('q' => $this->trimmed('q')) : NULL)
+ );
+ $this->nav_menu($menu);
+ }
+}
diff --git a/lib/settingsaction.php b/lib/settingsaction.php
new file mode 100644
index 000000000..9e783431f
--- /dev/null
+++ b/lib/settingsaction.php
@@ -0,0 +1,119 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class SettingsAction extends Action {
+
+ function handle($args) {
+ parent::handle($args);
+ if (!common_logged_in()) {
+ common_user_error(_('Not logged in.'));
+ return;
+ } else if (!common_is_real_login()) {
+ # Cookie theft means that automatic logins can't
+ # change important settings or see private info, and
+ # _all_ our settings are important
+ common_set_returnto($this->self_url());
+ common_redirect(common_local_url('login'));
+ } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $this->handle_post();
+ } else {
+ $this->show_form();
+ }
+ }
+
+ # override!
+ function handle_post() {
+ return false;
+ }
+
+ function show_form($msg=NULL, $success=false) {
+ return false;
+ }
+
+ function message($msg, $success) {
+ if ($msg) {
+ common_element('div', ($success) ? 'success' : 'error',
+ $msg);
+ }
+ }
+
+ function form_header($title, $msg=NULL, $success=false) {
+ common_show_header($title,
+ NULL,
+ array($msg, $success),
+ array($this, 'show_top'));
+ }
+
+ function show_top($arr) {
+ $msg = $arr[0];
+ $success = $arr[1];
+ if ($msg) {
+ $this->message($msg, $success);
+ } else {
+ $inst = $this->get_instructions();
+ $output = common_markup_to_html($inst);
+ common_element_start('div', 'instructions');
+ common_raw($output);
+ common_element_end('div');
+ }
+ $this->settings_menu();
+ }
+
+ function settings_menu() {
+ # action => array('prompt', 'title')
+ $menu =
+ array('profilesettings' =>
+ array(_('Profile'),
+ _('Change your profile settings')),
+ 'emailsettings' =>
+ array(_('Email'),
+ _('Change email handling')),
+ 'openidsettings' =>
+ array(_('OpenID'),
+ _('Add or remove OpenIDs')),
+ 'smssettings' =>
+ array(_('SMS'),
+ _('Updates by SMS')),
+ 'imsettings' =>
+ array(_('IM'),
+ _('Updates by instant messenger (IM)')),
+ 'twittersettings' =>
+ array(_('Twitter'),
+ _('Twitter integration options')),
+ 'othersettings' =>
+ array(_('Other'),
+ _('Other options')));
+
+ $action = $this->trimmed('action');
+ common_element_start('ul', array('id' => 'nav_views'));
+ foreach ($menu as $menuaction => $menudesc) {
+ if ($menuaction == 'imsettings' &&
+ !common_config('xmpp', 'enabled')) {
+ continue;
+ }
+ common_menu_item(common_local_url($menuaction),
+ $menudesc[0],
+ $menudesc[1],
+ $action == $menuaction);
+ }
+ common_element_end('ul');
+ }
+}
diff --git a/lib/stream.php b/lib/stream.php
new file mode 100644
index 000000000..27ab78137
--- /dev/null
+++ b/lib/stream.php
@@ -0,0 +1,55 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/personal.php');
+require_once(INSTALLDIR.'/lib/noticelist.php');
+
+class StreamAction extends PersonalAction {
+
+ function public_views_menu() {
+
+ $action = $this->trimmed('action');
+
+ common_element_start('ul', array('id' => 'nav_views'));
+
+ common_menu_item(common_local_url('public'), _('Public'),
+ _('Public timeline'), $action == 'public');
+
+ common_menu_item(common_local_url('tag'), _('Recent tags'),
+ _('Recent tags'), $action == 'tag');
+
+ if (count(common_config('nickname', 'featured')) > 0) {
+ common_menu_item(common_local_url('featured'), _('Featured'),
+ _('Featured users'), $action == 'featured');
+ }
+
+ common_menu_item(common_local_url('favorited'), _('Popular'),
+ _("Popular notices"), $action == 'favorited');
+
+ common_element_end('ul');
+
+ }
+
+ function show_notice_list($notice) {
+ $nl = new NoticeList($notice);
+ return $nl->show();
+ }
+}
diff --git a/lib/subs.php b/lib/subs.php
new file mode 100644
index 000000000..a2699f7b9
--- /dev/null
+++ b/lib/subs.php
@@ -0,0 +1,140 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once('XMPPHP/XMPP.php');
+
+/* Subscribe $user to nickname $other_nickname
+ Returns true or an error message.
+*/
+
+function subs_subscribe_user($user, $other_nickname) {
+
+ $other = User::staticGet('nickname', $other_nickname);
+
+ if (!$other) {
+ return _('No such user.');
+ }
+
+ return subs_subscribe_to($user, $other);
+}
+
+/* Subscribe user $user to other user $other.
+ * Note: $other must be a local user, not a remote profile.
+ * Because the other way is quite a bit more complicated.
+ */
+
+function subs_subscribe_to($user, $other) {
+
+ if ($user->isSubscribed($other)) {
+ return _('Already subscribed!');
+ }
+
+ if ($other->hasBlocked($user)) {
+ return _('User has blocked you.');
+ }
+
+ if (!$user->subscribeTo($other)) {
+ return _('Could not subscribe.');
+ return;
+ }
+
+ subs_notify($other, $user);
+
+ if (common_config('memcached', 'enabled')) {
+ $cache = new Memcache();
+ if ($cache->connect(common_config('memcached', 'server'), common_config('memcached', 'port'))) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
+ }
+ }
+
+ if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
+ if (!$other->subscribeTo($user)) {
+ return _('Could not subscribe other to you.');
+ }
+ if (common_config('memcached', 'enabled')) {
+ $cache = new Memcache();
+ if ($cache->connect(common_config('memcached', 'server'), common_config('memcached', 'port'))) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $other->id));
+ }
+ }
+
+ subs_notify($user, $other);
+ }
+
+ return true;
+}
+
+function subs_notify($listenee, $listener) {
+ # XXX: add other notifications (Jabber, SMS) here
+ # XXX: queue this and handle it offline
+ # XXX: Whatever happens, do it in Twitter-like API, too
+ subs_notify_email($listenee, $listener);
+}
+
+function subs_notify_email($listenee, $listener) {
+ mail_subscribe_notify($listenee, $listener);
+}
+
+/* Unsubscribe $user from nickname $other_nickname
+ Returns true or an error message.
+*/
+
+function subs_unsubscribe_user($user, $other_nickname) {
+
+ $other = User::staticGet('nickname', $other_nickname);
+
+ if (!$other) {
+ return _('No such user.');
+ }
+
+ return subs_unsubscribe_to($user, $other->getProfile());
+}
+
+/* Unsubscribe user $user from profile $other
+ * NB: other can be a remote user. */
+
+function subs_unsubscribe_to($user, $other) {
+
+ if (!$user->isSubscribed($other))
+ return _('Not subscribed!');
+
+ $sub = DB_DataObject::factory('subscription');
+
+ $sub->subscriber = $user->id;
+ $sub->subscribed = $other->id;
+
+ $sub->find(true);
+
+ // note we checked for existence above
+
+ if (!$sub->delete())
+ return _('Couldn\'t delete subscription.');
+
+ if (common_config('memcached', 'enabled')) {
+ $cache = new Memcache();
+ if ($cache->connect(common_config('memcached', 'server'), common_config('memcached', 'port'))) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
+ }
+ }
+
+ return true;
+}
+
diff --git a/lib/theme.php b/lib/theme.php
new file mode 100644
index 000000000..80982aa82
--- /dev/null
+++ b/lib/theme.php
@@ -0,0 +1,35 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+function theme_file($relative) {
+ $theme = common_config('site', 'theme');
+ return INSTALLDIR.'/theme/'.$theme.'/'.$relative;
+}
+
+function theme_path($relative) {
+ $theme = common_config('site', 'theme');
+ $server = common_config('theme', 'server');
+ if ($server) {
+ return 'http://'.$server.'/'.$theme.'/'.$relative;
+ } else {
+ return common_path('theme/'.$theme.'/'.$relative);
+ }
+} \ No newline at end of file
diff --git a/lib/twitter.php b/lib/twitter.php
new file mode 100644
index 000000000..7f75a1afd
--- /dev/null
+++ b/lib/twitter.php
@@ -0,0 +1,202 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+function get_twitter_data($uri, $screen_name, $password) {
+
+ $options = array(
+ CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ // CURLOPT_USERAGENT => "identi.ca",
+ CURLOPT_CONNECTTIMEOUT => 120,
+ CURLOPT_TIMEOUT => 120,
+
+ # Twitter is strict about accepting invalid "Expect" headers
+ CURLOPT_HTTPHEADER => array('Expect:')
+ );
+
+
+ $ch = curl_init($uri);
+ curl_setopt_array($ch, $options);
+ $data = curl_exec($ch);
+ $errmsg = curl_error($ch);
+
+ if ($errmsg) {
+ common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $twit_user.",
+ __FILE__);
+ }
+
+ curl_close($ch);
+
+ return $data;
+}
+
+function twitter_user_info($screen_name, $password) {
+
+ $uri = "http://twitter.com/users/show/$screen_name.json";
+ $data = get_twitter_data($uri, $screen_name, $password);
+
+ if (!$data) {
+ return false;
+ }
+
+ $twit_user = json_decode($data);
+
+ if (!$twit_user) {
+ return false;
+ }
+
+ return $twit_user;
+}
+
+function update_twitter_user($fuser, $twitter_id, $screen_name) {
+
+ $original = clone($fuser);
+ $fuser->nickname = $screen_name;
+ $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $result = $fuser->updateKeys($original);
+
+ if (!$result) {
+ common_log_db_error($fuser, 'UPDATE', __FILE__);
+ return false;
+ }
+
+ return true;
+}
+
+function add_twitter_user($twitter_id, $screen_name) {
+
+ // Otherwise, create a new Twitter user
+ $fuser = DB_DataObject::factory('foreign_user');
+
+ $fuser->nickname = $screen_name;
+ $fuser->uri = 'http://twitter.com/' . $screen_name;
+ $fuser->id = $twitter_id;
+ $fuser->service = 1; // Twitter
+ $fuser->created = common_sql_now();
+ $result = $fuser->insert();
+
+ if (!$result) {
+ common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+ common_log_db_error($fuser, 'INSERT', __FILE__);
+ return false;
+ }
+
+ common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+
+ return true;
+}
+
+// Creates or Updates a Twitter user
+function save_twitter_user($twitter_id, $screen_name) {
+
+ // Check to see whether the Twitter user is already in the system,
+ // and update its screen name and uri if so.
+ $fuser = Foreign_user::getForeignUser($twitter_id, 1);
+
+ if ($fuser) {
+
+ // Only update if Twitter screen name has changed
+ if ($fuser->nickname != $screen_name) {
+
+ common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
+ "$fuser->id to $screen_name, was $fuser->nickname");
+
+ return update_twitter_user($fuser, $twitter_id, $screen_name);
+ }
+
+ } else {
+ return add_twitter_user($twitter_id, $screen_name);
+ }
+
+ return true;
+}
+
+function retreive_twitter_friends($twitter_id, $screen_name, $password) {
+
+ $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
+ $twitter_user = twitter_user_info($screen_name, $password);
+
+ // Calculate how many pages to get...
+ $pages = ceil($twitter_user->friends_count / 100);
+
+ if ($pages == 0) {
+ common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
+ }
+
+ $friends = array();
+
+ for ($i = 1; $i <= $pages; $i++) {
+
+ $data = get_twitter_data($uri . $i, $screen_name, $password);
+
+ if (!$data) {
+ return NULL;
+ }
+
+ $more_friends = json_decode($data);
+
+ if (!$more_friends) {
+ return NULL;
+ }
+
+ $friends = array_merge($friends, $more_friends);
+ }
+
+ return $friends;
+}
+
+function save_twitter_friends($user, $twitter_id, $screen_name, $password) {
+
+ $friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
+
+ if (is_null($friends)) {
+ common_debug("Twitter bridge - Couldn't get friends data from Twitter.");
+ return false;
+ }
+
+ foreach ($friends as $friend) {
+
+ $friend_name = $friend->screen_name;
+ $friend_id = $friend->id;
+
+ // Update or create the Foreign_user record
+ if (!save_twitter_user($friend_id, $friend_name)) {
+ return false;
+ }
+
+ // Check to see if there's a related local user
+ $flink = Foreign_link::getByForeignID($friend_id, 1);
+
+ if ($flink) {
+
+ // Get associated user and subscribe her
+ $friend_user = User::staticGet('id', $flink->user_id);
+ subs_subscribe_to($user, $friend_user);
+ common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
+ }
+ }
+
+ return true;
+}
+
diff --git a/lib/twitterapi.php b/lib/twitterapi.php
new file mode 100644
index 000000000..2083e8961
--- /dev/null
+++ b/lib/twitterapi.php
@@ -0,0 +1,583 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+class TwitterapiAction extends Action {
+
+ var $auth_user;
+
+ function handle($args) {
+ parent::handle($args);
+ }
+
+ function twitter_user_array($profile, $get_notice=false) {
+
+ $twitter_user = array();
+
+ $twitter_user['name'] = $profile->getBestName();
+ $twitter_user['followers_count'] = $this->count_subscriptions($profile);
+ $twitter_user['screen_name'] = $profile->nickname;
+ $twitter_user['description'] = ($profile->bio) ? $profile->bio : NULL;
+ $twitter_user['location'] = ($profile->location) ? $profile->location : NULL;
+ $twitter_user['id'] = intval($profile->id);
+
+ $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+
+ $twitter_user['profile_image_url'] = ($avatar) ? common_avatar_display_url($avatar) : common_default_avatar(AVATAR_STREAM_SIZE);
+ $twitter_user['protected'] = 'false'; # not supported by Laconica yet
+ $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : NULL;
+
+ if ($get_notice) {
+ $notice = $profile->getCurrentNotice();
+ if ($notice) {
+ # don't get user!
+ $twitter_user['status'] = $this->twitter_status_array($notice, false);
+ }
+ }
+
+ return $twitter_user;
+ }
+
+ function twitter_status_array($notice, $include_user=true) {
+
+ $profile = $notice->getProfile();
+
+ $twitter_status = array();
+ $twitter_status['text'] = $notice->content;
+ $twitter_status['truncated'] = 'false'; # Not possible on Laconica
+ $twitter_status['created_at'] = $this->date_twitter($notice->created);
+ $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ? intval($notice->reply_to) : NULL;
+ $twitter_status['source'] = $this->source_link($notice->source);
+ $twitter_status['id'] = intval($notice->id);
+ $twitter_status['in_reply_to_user_id'] = ($notice->reply_to) ? $this->replier_by_reply(intval($notice->reply_to)) : NULL;
+
+ if (isset($this->auth_user)) {
+ $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false';
+ } else {
+ $twitter_status['favorited'] = 'false';
+ }
+
+ if ($include_user) {
+ # Don't get notice (recursive!)
+ $twitter_user = $this->twitter_user_array($profile, false);
+ $twitter_status['user'] = $twitter_user;
+ }
+
+ return $twitter_status;
+ }
+
+ function twitter_rss_entry_array($notice) {
+
+ $profile = $notice->getProfile();
+
+ $server = common_config('site', 'server');
+ $entry = array();
+
+ # We trim() to avoid extraneous whitespace in the output
+
+ $entry['content'] = common_xml_safe_str(trim($notice->rendered));
+ $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
+ $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
+ $entry['published'] = common_date_iso8601($notice->created);
+ $entry['id'] = "tag:$server,2008:$entry[link]";
+ $entry['updated'] = $entry['published'];
+
+ # RSS Item specific
+ $entry['description'] = $entry['content'];
+ $entry['pubDate'] = common_date_rfc2822($notice->created);
+ $entry['guid'] = $entry['link'];
+
+ return $entry;
+ }
+
+ function twitter_rss_dmsg_array($message) {
+
+ $server = common_config('site', 'server');
+ $entry = array();
+
+ $entry['title'] = sprintf('Message from %s to %s',
+ $message->getFrom()->nickname, $message->getTo()->nickname);
+
+ $entry['content'] = common_xml_safe_str(trim($message->content));
+ $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
+ $entry['published'] = common_date_iso8601($message->created);
+ $entry['id'] = "tag:$server,2008:$entry[link]";
+ $entry['updated'] = $entry['published'];
+
+ # RSS Item specific
+ $entry['description'] = $entry['content'];
+ $entry['pubDate'] = common_date_rfc2822($message->created);
+ $entry['guid'] = $entry['link'];
+
+ return $entry;
+ }
+
+ function twitter_dmsg_array($message) {
+
+ $twitter_dm = array();
+
+ $from_profile = $message->getFrom();
+ $to_profile = $message->getTo();
+
+ $twitter_dm['id'] = $message->id;
+ $twitter_dm['sender_id'] = $message->from_profile;
+ $twitter_dm['text'] = trim($message->content);
+ $twitter_dm['recipient_id'] = $message->to_profile;
+ $twitter_dm['created_at'] = $this->date_twitter($message->created);
+ $twitter_dm['sender_screen_name'] = $from_profile->nickname;
+ $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
+ $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
+ $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
+
+ return $twitter_dm;
+ }
+
+ function show_twitter_xml_status($twitter_status) {
+ common_element_start('status');
+ foreach($twitter_status as $element => $value) {
+ switch ($element) {
+ case 'user':
+ $this->show_twitter_xml_user($twitter_status['user']);
+ break;
+ case 'text':
+ common_element($element, NULL, common_xml_safe_str($value));
+ break;
+ default:
+ common_element($element, NULL, $value);
+ }
+ }
+ common_element_end('status');
+ }
+
+ function show_twitter_xml_user($twitter_user, $role='user') {
+ common_element_start($role);
+ foreach($twitter_user as $element => $value) {
+ if ($element == 'status') {
+ $this->show_twitter_xml_status($twitter_user['status']);
+ } else {
+ common_element($element, NULL, $value);
+ }
+ }
+ common_element_end($role);
+ }
+
+ function show_twitter_rss_item($entry) {
+ common_element_start('item');
+ common_element('title', NULL, $entry['title']);
+ common_element('description', NULL, $entry['description']);
+ common_element('pubDate', NULL, $entry['pubDate']);
+ common_element('guid', NULL, $entry['guid']);
+ common_element('link', NULL, $entry['link']);
+ common_element_end('item');
+ }
+
+ function show_twitter_atom_entry($entry) {
+ common_element_start('entry');
+ common_element('title', NULL, $entry['title']);
+ common_element('content', array('type' => 'html'), $entry['content']);
+ common_element('id', NULL, $entry['id']);
+ common_element('published', NULL, $entry['published']);
+ common_element('updated', NULL, $entry['updated']);
+ common_element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), NULL);
+ common_element_end('entry');
+ }
+
+ function show_json_objects($objects) {
+ print(json_encode($objects));
+ }
+
+ function show_single_xml_status($notice) {
+ $this->init_document('xml');
+ $twitter_status = $this->twitter_status_array($notice);
+ $this->show_twitter_xml_status($twitter_status);
+ $this->end_document('xml');
+ }
+
+ function show_single_json_status($notice) {
+ $this->init_document('json');
+ $status = $this->twitter_status_array($notice);
+ $this->show_json_objects($status);
+ $this->end_document('json');
+ }
+
+ function show_single_xml_dmsg($message) {
+ $this->init_document('xml');
+ $dmsg = $this->twitter_dmsg_array($message);
+ $this->show_twitter_xml_dmsg($dmsg);
+ $this->end_document('xml');
+ }
+
+ function show_single_json_dmsg($message) {
+ $this->init_document('json');
+ $dmsg = $this->twitter_dmsg_array($message);
+ $this->show_json_objects($dmsg);
+ $this->end_document('json');
+ }
+
+ function show_twitter_xml_dmsg($twitter_dm) {
+ common_element_start('direct_message');
+ foreach($twitter_dm as $element => $value) {
+ switch ($element) {
+ case 'sender':
+ case 'recipient':
+ $this->show_twitter_xml_user($value, $element);
+ break;
+ case 'text':
+ common_element($element, NULL, common_xml_safe_str($value));
+ break;
+ default:
+ common_element($element, NULL, $value);
+ }
+ }
+ common_element_end('direct_message');
+ }
+
+ function show_xml_timeline($notice) {
+
+ $this->init_document('xml');
+ common_element_start('statuses', array('type' => 'array'));
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $twitter_status = $this->twitter_status_array($n);
+ $this->show_twitter_xml_status($twitter_status);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $twitter_status = $this->twitter_status_array($notice);
+ $this->show_twitter_xml_status($twitter_status);
+ }
+ }
+
+ common_element_end('statuses');
+ $this->end_document('xml');
+ }
+
+ function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=NULL) {
+
+ $this->init_document('rss');
+
+ common_element_start('channel');
+ common_element('title', NULL, $title);
+ common_element('link', NULL, $link);
+ if (!is_null($suplink)) {
+ # For FriendFeed's SUP protocol
+ common_element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
+ 'rel' => 'http://api.friendfeed.com/2008/03#sup',
+ 'href' => $suplink,
+ 'type' => 'application/json'));
+ }
+ common_element('description', NULL, $subtitle);
+ common_element('language', NULL, 'en-us');
+ common_element('ttl', NULL, '40');
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $entry = $this->twitter_rss_entry_array($n);
+ $this->show_twitter_rss_item($entry);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $entry = $this->twitter_rss_entry_array($notice);
+ $this->show_twitter_rss_item($entry);
+ }
+ }
+
+ common_element_end('channel');
+ $this->end_twitter_rss();
+ }
+
+ function show_atom_timeline($notice, $title, $id, $link, $subtitle=NULL, $suplink=NULL) {
+
+ $this->init_document('atom');
+
+ common_element('title', NULL, $title);
+ common_element('id', NULL, $id);
+ common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), NULL);
+ if (!is_null($suplink)) {
+ # For FriendFeed's SUP protocol
+ common_element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
+ 'href' => $suplink,
+ 'type' => 'application/json'));
+ }
+ common_element('subtitle', NULL, $subtitle);
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $entry = $this->twitter_rss_entry_array($n);
+ $this->show_twitter_atom_entry($entry);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $entry = $this->twitter_rss_entry_array($notice);
+ $this->show_twitter_atom_entry($entry);
+ }
+ }
+
+ $this->end_document('atom');
+
+ }
+
+ function show_json_timeline($notice) {
+
+ $this->init_document('json');
+
+ $statuses = array();
+
+ if (is_array($notice)) {
+ foreach ($notice as $n) {
+ $twitter_status = $this->twitter_status_array($n);
+ array_push($statuses, $twitter_status);
+ }
+ } else {
+ while ($notice->fetch()) {
+ $twitter_status = $this->twitter_status_array($notice);
+ array_push($statuses, $twitter_status);
+ }
+ }
+
+ $this->show_json_objects($statuses);
+
+ $this->end_document('json');
+ }
+
+ // Anyone know what date format this is?
+ // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
+ function date_twitter($dt) {
+ $t = strtotime($dt);
+ return date("D M d G:i:s O Y", $t);
+ }
+
+ function replier_by_reply($reply_id) {
+ $notice = Notice::staticGet($reply_id);
+ if ($notice) {
+ $profile = $notice->getProfile();
+ if ($profile) {
+ return intval($profile->id);
+ } else {
+ common_debug('Can\'t find a profile for notice: ' . $notice->id, __FILE__);
+ }
+ } else {
+ common_debug("Can't get notice: $reply_id", __FILE__);
+ }
+ return NULL;
+ }
+
+ // XXX: Candidate for a general utility method somewhere?
+ function count_subscriptions($profile) {
+
+ $count = 0;
+ $sub = new Subscription();
+ $sub->subscribed = $profile->id;
+
+ $count = $sub->find();
+
+ if ($count > 0) {
+ return $count - 1;
+ } else {
+ return 0;
+ }
+ }
+
+ function init_document($type='xml') {
+ switch ($type) {
+ case 'xml':
+ header('Content-Type: application/xml; charset=utf-8');
+ common_start_xml();
+ break;
+ case 'json':
+ header('Content-Type: application/json; charset=utf-8');
+
+ // Check for JSONP callback
+ $callback = $this->arg('callback');
+ if ($callback) {
+ print $callback . '(';
+ }
+ break;
+ case 'rss':
+ header("Content-Type: application/rss+xml; charset=utf-8");
+ $this->init_twitter_rss();
+ break;
+ case 'atom':
+ header('Content-Type: application/atom+xml; charset=utf-8');
+ $this->init_twitter_atom();
+ break;
+ default:
+ $this->client_error(_('Not a supported data format.'));
+ break;
+ }
+
+ return;
+ }
+
+ function end_document($type='xml') {
+ switch ($type) {
+ case 'xml':
+ common_end_xml();
+ break;
+ case 'json':
+
+ // Check for JSONP callback
+ $callback = $this->arg('callback');
+ if ($callback) {
+ print ')';
+ }
+ break;
+ case 'rss':
+ $this->end_twitter_rss();
+ break;
+ case 'atom':
+ $this->end_twitter_rss();
+ break;
+ default:
+ $this->client_error(_('Not a supported data format.'));
+ break;
+ }
+ return;
+ }
+
+ function client_error($msg, $code = 400, $content_type = 'json') {
+
+ static $status = array(400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed');
+
+ $action = $this->trimmed('action');
+
+ common_debug("User error '$code' on '$action': $msg", __FILE__);
+
+ if (!array_key_exists($code, $status)) {
+ $code = 400;
+ }
+
+ $status_string = $status[$code];
+ header('HTTP/1.1 '.$code.' '.$status_string);
+
+ if ($content_type == 'xml') {
+ $this->init_document('xml');
+ common_element_start('hash');
+ common_element('error', NULL, $msg);
+ common_element('request', NULL, $_SERVER['REQUEST_URI']);
+ common_element_end('hash');
+ $this->end_document('xml');
+ } else {
+ $this->init_document('json');
+ $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
+ print(json_encode($error_array));
+ $this->end_document('json');
+ }
+
+ }
+
+ function init_twitter_rss() {
+ common_start_xml();
+ common_element_start('rss', array('version' => '2.0'));
+ }
+
+ function end_twitter_rss() {
+ common_element_end('rss');
+ common_end_xml();
+ }
+
+ function init_twitter_atom() {
+ common_start_xml();
+ common_element_start('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
+ }
+
+ function end_twitter_atom() {
+ common_end_xml();
+ common_element_end('feed');
+ }
+
+ function show_profile($profile, $content_type='xml', $notice=NULL) {
+ $profile_array = $this->twitter_user_array($profile, true);
+ switch ($content_type) {
+ case 'xml':
+ $this->show_twitter_xml_user($profile_array);
+ break;
+ case 'json':
+ $this->show_json_objects($profile_array);
+ break;
+ default:
+ $this->client_error(_('Not a supported data format.'));
+ return;
+ }
+ return;
+ }
+
+ function get_user($id, $apidata=NULL) {
+ if (!$id) {
+ return $apidata['user'];
+ } else if (is_numeric($id)) {
+ return User::staticGet($id);
+ } else {
+ $nickname = common_canonical_nickname($id);
+ return User::staticGet('nickname', $nickname);
+ }
+ }
+
+ function get_profile($id) {
+ if (is_numeric($id)) {
+ return Profile::staticGet($id);
+ } else {
+ $user = User::staticGet('nickname', $id);
+ if ($user) {
+ return $user->getProfile();
+ } else {
+ return NULL;
+ }
+ }
+ }
+
+ function source_link($source) {
+ $source_name = _($source);
+ switch ($source) {
+ case 'web':
+ case 'xmpp':
+ case 'mail':
+ case 'omb':
+ case 'api':
+ break;
+ default:
+ $ns = Notice_source::staticGet($source);
+ if ($ns) {
+ $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
+ }
+ break;
+ }
+ return $source_name;
+ }
+
+} \ No newline at end of file
diff --git a/lib/util.php b/lib/util.php
new file mode 100644
index 000000000..fc45311fc
--- /dev/null
+++ b/lib/util.php
@@ -0,0 +1,2227 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* XXX: break up into separate modules (HTTP, HTML, user, files) */
+
+# Show a server error
+
+function common_server_error($msg, $code=500) {
+ static $status = array(500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported');
+
+ if (!array_key_exists($code, $status)) {
+ $code = 500;
+ }
+
+ $status_string = $status[$code];
+
+ header('HTTP/1.1 '.$code.' '.$status_string);
+ header('Content-type: text/plain');
+
+ print $msg;
+ print "\n";
+ exit();
+}
+
+# Show a user error
+function common_user_error($msg, $code=400) {
+ static $status = array(400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed');
+
+ if (!array_key_exists($code, $status)) {
+ $code = 400;
+ }
+
+ $status_string = $status[$code];
+
+ header('HTTP/1.1 '.$code.' '.$status_string);
+
+ common_show_header('Error');
+ common_element('div', array('class' => 'error'), $msg);
+ common_show_footer();
+}
+
+$xw = null;
+
+# Start an HTML element
+function common_element_start($tag, $attrs=NULL) {
+ global $xw;
+ $xw->startElement($tag);
+ if (is_array($attrs)) {
+ foreach ($attrs as $name => $value) {
+ $xw->writeAttribute($name, $value);
+ }
+ } else if (is_string($attrs)) {
+ $xw->writeAttribute('class', $attrs);
+ }
+}
+
+function common_element_end($tag) {
+ static $empty_tag = array('base', 'meta', 'link', 'hr',
+ 'br', 'param', 'img', 'area',
+ 'input', 'col');
+ global $xw;
+ # XXX: check namespace
+ if (in_array($tag, $empty_tag)) {
+ $xw->endElement();
+ } else {
+ $xw->fullEndElement();
+ }
+}
+
+function common_element($tag, $attrs=NULL, $content=NULL) {
+ common_element_start($tag, $attrs);
+ global $xw;
+ if (!is_null($content)) {
+ $xw->text($content);
+ }
+ common_element_end($tag);
+}
+
+function common_start_xml($doc=NULL, $public=NULL, $system=NULL, $indent=true) {
+ global $xw;
+ $xw = new XMLWriter();
+ $xw->openURI('php://output');
+ $xw->setIndent($indent);
+ $xw->startDocument('1.0', 'UTF-8');
+ if ($doc) {
+ $xw->writeDTD($doc, $public, $system);
+ }
+}
+
+function common_end_xml() {
+ global $xw;
+ $xw->endDocument();
+ $xw->flush();
+}
+
+function common_init_locale($language=null) {
+ if(!$language) {
+ $language = common_language();
+ }
+ putenv('LANGUAGE='.$language);
+ putenv('LANG='.$language);
+ return setlocale(LC_ALL, $language . ".utf8",
+ $language . ".UTF8",
+ $language . ".utf-8",
+ $language . ".UTF-8",
+ $language);
+}
+
+function common_init_language() {
+ mb_internal_encoding('UTF-8');
+ $language = common_language();
+ # So we don't have to make people install the gettext locales
+ $locale_set = common_init_locale($language);
+ bindtextdomain("laconica", common_config('site','locale_path'));
+ bind_textdomain_codeset("laconica", "UTF-8");
+ textdomain("laconica");
+ setlocale(LC_CTYPE, 'C');
+ if(!$locale_set) {
+ common_log(LOG_INFO,'Language requested:'.$language.' - locale could not be set:',__FILE__);
+ }
+}
+
+define('PAGE_TYPE_PREFS', 'text/html,application/xhtml+xml,application/xml;q=0.3,text/xml;q=0.2');
+
+function common_show_header($pagetitle, $callable=NULL, $data=NULL, $headercall=NULL) {
+
+ global $config, $xw;
+ global $action; /* XXX: kind of cheating here. */
+
+ common_start_html();
+
+ common_element_start('head');
+ common_element('title', NULL,
+ $pagetitle . " - " . $config['site']['name']);
+ common_element('link', array('rel' => 'stylesheet',
+ 'type' => 'text/css',
+ 'href' => theme_path('display.css') . '?version=' . LACONICA_VERSION,
+ 'media' => 'screen, projection, tv'));
+ foreach (array(6,7) as $ver) {
+ if (file_exists(theme_file('ie'.$ver.'.css'))) {
+ # Yes, IE people should be put in jail.
+ $xw->writeComment('[if lte IE '.$ver.']><link rel="stylesheet" type="text/css" '.
+ 'href="'.theme_path('ie'.$ver.'.css').'?version='.LACONICA_VERSION.'" /><![endif]');
+ }
+ }
+
+ common_element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/jquery.min.js')),
+ ' ');
+ common_element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/jquery.form.js')),
+ ' ');
+ common_element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/xbImportNode.js')),
+ ' ');
+ common_element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
+ ' ');
+ common_element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
+ 'href' => common_local_url('opensearch', array('type' => 'people')),
+ 'title' => common_config('site', 'name').' People Search'));
+
+ common_element('link', array('rel' => 'search', 'type' => 'application/opensearchdescription+xml',
+ 'href' => common_local_url('opensearch', array('type' => 'notice')),
+ 'title' => common_config('site', 'name').' Notice Search'));
+
+ if ($callable) {
+ if ($data) {
+ call_user_func($callable, $data);
+ } else {
+ call_user_func($callable);
+ }
+ }
+ common_element_end('head');
+ common_element_start('body', $action);
+ common_element_start('div', array('id' => 'wrap'));
+ common_element_start('div', array('id' => 'header'));
+ common_nav_menu();
+ if ((isset($config['site']['logo']) && is_string($config['site']['logo']) && (strlen($config['site']['logo']) > 0))
+ || file_exists(theme_file('logo.png')))
+ {
+ common_element_start('a', array('href' => common_local_url('public')));
+ common_element('img', array('src' => isset($config['site']['logo']) ?
+ ($config['site']['logo']) : theme_path('logo.png'),
+ 'alt' => $config['site']['name'],
+ 'id' => 'logo'));
+ common_element_end('a');
+ } else {
+ common_element_start('p', array('id' => 'branding'));
+ common_element('a', array('href' => common_local_url('public')),
+ $config['site']['name']);
+ common_element_end('p');
+ }
+
+ common_element('h1', 'pagetitle', $pagetitle);
+
+ if ($headercall) {
+ if ($data) {
+ call_user_func($headercall, $data);
+ } else {
+ call_user_func($headercall);
+ }
+ }
+ common_element_end('div');
+ common_element_start('div', array('id' => 'content'));
+}
+
+function common_start_html($type=NULL, $indent=true) {
+
+ if (!$type) {
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? $_SERVER['HTTP_ACCEPT'] : NULL;
+
+ # XXX: allow content negotiation for RDF, RSS, or XRDS
+
+ $type = common_negotiate_type(common_accept_to_prefs($httpaccept),
+ common_accept_to_prefs(PAGE_TYPE_PREFS));
+
+ if (!$type) {
+ common_user_error(_('This page is not available in a media type you accept'), 406);
+ exit(0);
+ }
+ }
+
+ header('Content-Type: '.$type);
+
+ common_start_xml('html',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', $indent);
+
+ # FIXME: correct language for interface
+
+ $language = common_language();
+
+ common_element_start('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
+ 'xml:lang' => $language,
+ 'lang' => $language));
+}
+
+function common_show_footer() {
+ global $xw, $config;
+ common_element_end('div'); # content div
+ common_foot_menu();
+ common_element_start('div', array('id' => 'footer'));
+ common_element_start('div', 'laconica');
+ if (common_config('site', 'broughtby')) {
+ $instr = _('**%%site.name%%** is a microblogging service brought to you by [%%site.broughtby%%](%%site.broughtbyurl%%). ');
+ } else {
+ $instr = _('**%%site.name%%** is a microblogging service. ');
+ }
+ $instr .= sprintf(_('It runs the [Laconica](http://laconi.ca/) microblogging software, version %s, available under the [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html).'), LACONICA_VERSION);
+ $output = common_markup_to_html($instr);
+ common_raw($output);
+ common_element_end('div');
+ common_element('img', array('id' => 'cc',
+ 'src' => $config['license']['image'],
+ 'alt' => $config['license']['title']));
+ common_element_start('p');
+ common_text(_('Unless otherwise specified, contents of this site are copyright by the contributors and available under the '));
+ common_element('a', array('class' => 'license',
+ 'rel' => 'license',
+ 'href' => $config['license']['url']),
+ $config['license']['title']);
+ common_text(_('. Contributors should be attributed by full name or nickname.'));
+ common_element_end('p');
+ common_element_end('div');
+ common_element_end('div');
+ common_element_end('body');
+ common_element_end('html');
+ common_end_xml();
+}
+
+function common_text($txt) {
+ global $xw;
+ $xw->text($txt);
+}
+
+function common_raw($xml) {
+ global $xw;
+ $xw->writeRaw($xml);
+}
+
+function common_nav_menu() {
+ $user = common_current_user();
+ common_element_start('ul', array('id' => 'nav'));
+ if ($user) {
+ common_menu_item(common_local_url('all', array('nickname' => $user->nickname)),
+ _('Home'));
+ }
+ common_menu_item(common_local_url('peoplesearch'), _('Search'));
+ if ($user) {
+ common_menu_item(common_local_url('profilesettings'),
+ _('Settings'));
+ common_menu_item(common_local_url('invite'),
+ _('Invite'));
+ common_menu_item(common_local_url('logout'),
+ _('Logout'));
+ } else {
+ common_menu_item(common_local_url('login'), _('Login'));
+ if (!common_config('site', 'closed')) {
+ common_menu_item(common_local_url('register'), _('Register'));
+ }
+ common_menu_item(common_local_url('openidlogin'), _('OpenID'));
+ }
+ common_menu_item(common_local_url('doc', array('title' => 'help')),
+ _('Help'));
+ common_element_end('ul');
+}
+
+function common_foot_menu() {
+ common_element_start('ul', array('id' => 'nav_sub'));
+ common_menu_item(common_local_url('doc', array('title' => 'help')),
+ _('Help'));
+ common_menu_item(common_local_url('doc', array('title' => 'about')),
+ _('About'));
+ common_menu_item(common_local_url('doc', array('title' => 'faq')),
+ _('FAQ'));
+ common_menu_item(common_local_url('doc', array('title' => 'privacy')),
+ _('Privacy'));
+ common_menu_item(common_local_url('doc', array('title' => 'source')),
+ _('Source'));
+ common_menu_item(common_local_url('doc', array('title' => 'contact')),
+ _('Contact'));
+ common_element_end('ul');
+}
+
+function common_menu_item($url, $text, $title=NULL, $is_selected=false) {
+ $lattrs = array();
+ if ($is_selected) {
+ $lattrs['class'] = 'current';
+ }
+ common_element_start('li', $lattrs);
+ $attrs['href'] = $url;
+ if ($title) {
+ $attrs['title'] = $title;
+ }
+ common_element('a', $attrs, $text);
+ common_element_end('li');
+}
+
+function common_input($id, $label, $value=NULL,$instructions=NULL) {
+ common_element_start('p');
+ common_element('label', array('for' => $id), $label);
+ $attrs = array('name' => $id,
+ 'type' => 'text',
+ 'class' => 'input_text',
+ 'id' => $id);
+ if ($value) {
+ $attrs['value'] = htmlspecialchars($value);
+ }
+ common_element('input', $attrs);
+ if ($instructions) {
+ common_element('span', 'input_instructions', $instructions);
+ }
+ common_element_end('p');
+}
+
+function common_checkbox($id, $label, $checked=false, $instructions=NULL, $value='true', $disabled=false)
+{
+ common_element_start('p');
+ $attrs = array('name' => $id,
+ 'type' => 'checkbox',
+ 'class' => 'checkbox',
+ 'id' => $id);
+ if ($value) {
+ $attrs['value'] = htmlspecialchars($value);
+ }
+ if ($checked) {
+ $attrs['checked'] = 'checked';
+ }
+ if ($disabled) {
+ $attrs['disabled'] = 'true';
+ }
+ common_element('input', $attrs);
+ common_text(' ');
+ common_element('label', array('class' => 'checkbox_label', 'for' => $id), $label);
+ common_text(' ');
+ if ($instructions) {
+ common_element('span', 'input_instructions', $instructions);
+ }
+ common_element_end('p');
+}
+
+function common_dropdown($id, $label, $content, $instructions=NULL, $blank_select=FALSE, $selected=NULL) {
+ common_element_start('p');
+ common_element('label', array('for' => $id), $label);
+ common_element_start('select', array('id' => $id, 'name' => $id));
+ if ($blank_select) {
+ common_element('option', array('value' => ''));
+ }
+ foreach ($content as $value => $option) {
+ if ($value == $selected) {
+ common_element('option', array('value' => $value, 'selected' => $value), $option);
+ } else {
+ common_element('option', array('value' => $value), $option);
+ }
+ }
+ common_element_end('select');
+ if ($instructions) {
+ common_element('span', 'input_instructions', $instructions);
+ }
+ common_element_end('p');
+}
+function common_hidden($id, $value) {
+ common_element('input', array('name' => $id,
+ 'type' => 'hidden',
+ 'id' => $id,
+ 'value' => $value));
+}
+
+function common_password($id, $label, $instructions=NULL) {
+ common_element_start('p');
+ common_element('label', array('for' => $id), $label);
+ $attrs = array('name' => $id,
+ 'type' => 'password',
+ 'class' => 'password',
+ 'id' => $id);
+ common_element('input', $attrs);
+ if ($instructions) {
+ common_element('span', 'input_instructions', $instructions);
+ }
+ common_element_end('p');
+}
+
+function common_submit($id, $label, $cls='submit') {
+ global $xw;
+ common_element_start('p');
+ common_element('input', array('type' => 'submit',
+ 'id' => $id,
+ 'name' => $id,
+ 'class' => $cls,
+ 'value' => $label));
+ common_element_end('p');
+}
+
+function common_textarea($id, $label, $content=NULL, $instructions=NULL) {
+ common_element_start('p');
+ common_element('label', array('for' => $id), $label);
+ common_element('textarea', array('rows' => 3,
+ 'cols' => 40,
+ 'name' => $id,
+ 'id' => $id),
+ ($content) ? $content : '');
+ if ($instructions) {
+ common_element('span', 'input_instructions', $instructions);
+ }
+ common_element_end('p');
+}
+
+function common_timezone() {
+ if (common_logged_in()) {
+ $user = common_current_user();
+ if ($user->timezone) {
+ return $user->timezone;
+ }
+ }
+
+ global $config;
+ return $config['site']['timezone'];
+}
+
+function common_language() {
+
+ // If there is a user logged in and they've set a language preference
+ // then return that one...
+ if (common_logged_in()) {
+ $user = common_current_user();
+ $user_language = $user->language;
+ if ($user_language)
+ return $user_language;
+ }
+
+ // Otherwise, find the best match for the languages requested by the
+ // user's browser...
+ $httplang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : NULL;
+ if (!empty($httplang)) {
+ $language = client_prefered_language($httplang);
+ if ($language)
+ return $language;
+ }
+
+ // Finally, if none of the above worked, use the site's default...
+ return common_config('site', 'language');
+}
+# salted, hashed passwords are stored in the DB
+
+function common_munge_password($password, $id) {
+ return md5($password . $id);
+}
+
+# check if a username exists and has matching password
+function common_check_user($nickname, $password) {
+ # NEVER allow blank passwords, even if they match the DB
+ if (mb_strlen($password) == 0) {
+ return false;
+ }
+ $user = User::staticGet('nickname', $nickname);
+ if (is_null($user)) {
+ return false;
+ } else {
+ if (0 == strcmp(common_munge_password($password, $user->id),
+ $user->password)) {
+ return $user;
+ } else {
+ return false;
+ }
+ }
+}
+
+# is the current user logged in?
+function common_logged_in() {
+ return (!is_null(common_current_user()));
+}
+
+function common_have_session() {
+ return (0 != strcmp(session_id(), ''));
+}
+
+function common_ensure_session() {
+ if (!common_have_session()) {
+ @session_start();
+ }
+}
+
+# Three kinds of arguments:
+# 1) a user object
+# 2) a nickname
+# 3) NULL to clear
+
+# Initialize to false; set to NULL if none found
+
+$_cur = false;
+
+function common_set_user($user) {
+
+ global $_cur;
+
+ if (is_null($user) && common_have_session()) {
+ $_cur = NULL;
+ unset($_SESSION['userid']);
+ return true;
+ } else if (is_string($user)) {
+ $nickname = $user;
+ $user = User::staticGet('nickname', $nickname);
+ } else if (!($user instanceof User)) {
+ return false;
+ }
+
+ if ($user) {
+ common_ensure_session();
+ $_SESSION['userid'] = $user->id;
+ $_cur = $user;
+ return $_cur;
+ }
+ return false;
+}
+
+function common_set_cookie($key, $value, $expiration=0) {
+ $path = common_config('site', 'path');
+ $server = common_config('site', 'server');
+
+ if ($path && ($path != '/')) {
+ $cookiepath = '/' . $path . '/';
+ } else {
+ $cookiepath = '/';
+ }
+ return setcookie($key,
+ $value,
+ $expiration,
+ $cookiepath,
+ $server);
+}
+
+define('REMEMBERME', 'rememberme');
+define('REMEMBERME_EXPIRY', 30 * 24 * 60 * 60); # 30 days
+
+function common_rememberme($user=NULL) {
+ if (!$user) {
+ $user = common_current_user();
+ if (!$user) {
+ common_debug('No current user to remember', __FILE__);
+ return false;
+ }
+ }
+
+ $rm = new Remember_me();
+
+ $rm->code = common_good_rand(16);
+ $rm->user_id = $user->id;
+
+ # Wrap the insert in some good ol' fashioned transaction code
+
+ $rm->query('BEGIN');
+
+ $result = $rm->insert();
+
+ if (!$result) {
+ common_log_db_error($rm, 'INSERT', __FILE__);
+ common_debug('Error adding rememberme record for ' . $user->nickname, __FILE__);
+ return false;
+ }
+
+ $rm->query('COMMIT');
+
+ common_debug('Inserted rememberme record (' . $rm->code . ', ' . $rm->user_id . '); result = ' . $result . '.', __FILE__);
+
+ $cookieval = $rm->user_id . ':' . $rm->code;
+
+ common_log(LOG_INFO, 'adding rememberme cookie "' . $cookieval . '" for ' . $user->nickname);
+
+ common_set_cookie(REMEMBERME, $cookieval, time() + REMEMBERME_EXPIRY);
+
+ return true;
+}
+
+function common_remembered_user() {
+
+ $user = NULL;
+
+ $packed = isset($_COOKIE[REMEMBERME]) ? $_COOKIE[REMEMBERME] : NULL;
+
+ if (!$packed) {
+ return NULL;
+ }
+
+ list($id, $code) = explode(':', $packed);
+
+ if (!$id || !$code) {
+ common_log(LOG_WARNING, 'Malformed rememberme cookie: ' . $packed);
+ common_forgetme();
+ return NULL;
+ }
+
+ $rm = Remember_me::staticGet($code);
+
+ if (!$rm) {
+ common_log(LOG_WARNING, 'No such remember code: ' . $code);
+ common_forgetme();
+ return NULL;
+ }
+
+ if ($rm->user_id != $id) {
+ common_log(LOG_WARNING, 'Rememberme code for wrong user: ' . $rm->user_id . ' != ' . $id);
+ common_forgetme();
+ return NULL;
+ }
+
+ $user = User::staticGet($rm->user_id);
+
+ if (!$user) {
+ common_log(LOG_WARNING, 'No such user for rememberme: ' . $rm->user_id);
+ common_forgetme();
+ return NULL;
+ }
+
+ # successful!
+ $result = $rm->delete();
+
+ if (!$result) {
+ common_log_db_error($rm, 'DELETE', __FILE__);
+ common_log(LOG_WARNING, 'Could not delete rememberme: ' . $code);
+ common_forgetme();
+ return NULL;
+ }
+
+ common_log(LOG_INFO, 'logging in ' . $user->nickname . ' using rememberme code ' . $rm->code);
+
+ common_set_user($user);
+ common_real_login(false);
+
+ # We issue a new cookie, so they can log in
+ # automatically again after this session
+
+ common_rememberme($user);
+
+ return $user;
+}
+
+# must be called with a valid user!
+
+function common_forgetme() {
+ common_set_cookie(REMEMBERME, '', 0);
+}
+
+# who is the current user?
+function common_current_user() {
+ global $_cur;
+
+ if ($_cur === false) {
+
+ if (isset($_REQUEST[session_name()]) || (isset($_SESSION['userid']) && $_SESSION['userid'])) {
+ common_ensure_session();
+ $id = isset($_SESSION['userid']) ? $_SESSION['userid'] : false;
+ if ($id) {
+ $_cur = User::staticGet($id);
+ return $_cur;
+ }
+ }
+
+ # that didn't work; try to remember; will init $_cur to NULL on failure
+ $_cur = common_remembered_user();
+
+ if ($_cur) {
+ common_debug("Got User " . $_cur->nickname);
+ common_debug("Faking session on remembered user");
+ # XXX: Is this necessary?
+ $_SESSION['userid'] = $_cur->id;
+ }
+ }
+
+ return $_cur;
+}
+
+# Logins that are 'remembered' aren't 'real' -- they're subject to
+# cookie-stealing. So, we don't let them do certain things. New reg,
+# OpenID, and password logins _are_ real.
+
+function common_real_login($real=true) {
+ common_ensure_session();
+ $_SESSION['real_login'] = $real;
+}
+
+function common_is_real_login() {
+ return common_logged_in() && $_SESSION['real_login'];
+}
+
+# get canonical version of nickname for comparison
+function common_canonical_nickname($nickname) {
+ # XXX: UTF-8 canonicalization (like combining chars)
+ return strtolower($nickname);
+}
+
+# get canonical version of email for comparison
+function common_canonical_email($email) {
+ # XXX: canonicalize UTF-8
+ # XXX: lcase the domain part
+ return $email;
+}
+
+define('URL_REGEX', '^|[ \t\r\n])((ftp|http|https|gopher|mailto|news|nntp|telnet|wais|file|prospero|aim|webcal):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))');
+
+function common_render_content($text, $notice) {
+ $r = common_render_text($text);
+ $id = $notice->profile_id;
+ $r = preg_replace('/(^|\s+)@([A-Za-z0-9]{1,64})/e', "'\\1@'.common_at_link($id, '\\2')", $r);
+ $r = preg_replace('/^T ([A-Z0-9]{1,64}) /e', "'T '.common_at_link($id, '\\1').' '", $r);
+ $r = preg_replace('/(^|\s+)@#([A-Za-z0-9]{1,64})/e', "'\\1@#'.common_at_hash_link($id, '\\2')", $r);
+ return $r;
+}
+
+function common_render_text($text) {
+ $r = htmlspecialchars($text);
+
+ $r = preg_replace('/[\x{0}-\x{8}\x{b}-\x{c}\x{e}-\x{19}]/', '', $r);
+ $r = preg_replace_callback('@https?://[^\]>\s]+@', 'common_render_uri_thingy', $r);
+ $r = preg_replace('/(^|\s+)#([A-Za-z0-9_\-\.]{1,64})/e', "'\\1#'.common_tag_link('\\2')", $r);
+ # XXX: machine tags
+ return $r;
+}
+
+function common_render_uri_thingy($matches) {
+ $uri = $matches[0];
+ $trailer = '';
+
+ # Some heuristics for extracting URIs from surrounding punctuation
+ # Strip from trailing text...
+ if (preg_match('/^(.*)([,.:"\']+)$/', $uri, $matches)) {
+ $uri = $matches[1];
+ $trailer = $matches[2];
+ }
+
+ $pairs = array(
+ ']' => '[', # technically disallowed in URIs, but used in Java docs
+ ')' => '(', # far too frequent in Wikipedia and MSDN
+ );
+ $final = substr($uri, -1, 1);
+ if (isset($pairs[$final])) {
+ $openers = substr_count($uri, $pairs[$final]);
+ $closers = substr_count($uri, $final);
+ if ($closers > $openers) {
+ // Assume the paren was opened outside the URI
+ $uri = substr($uri, 0, -1);
+ $trailer = $final . $trailer;
+ }
+ }
+ if ($longurl = common_longurl($uri)) {
+ $longurl = htmlentities($longurl, ENT_QUOTES, 'UTF-8');
+ $title = " title='$longurl'";
+ }
+ else $title = '';
+
+ return '<a href="' . $uri . '"' . $title . ' class="extlink">' . $uri . '</a>' . $trailer;
+}
+
+function common_longurl($short_url) {
+ $long_url = common_shorten_link($short_url, true);
+ if ($long_url === $short_url) return false;
+ return $long_url;
+}
+
+function common_longurl2($uri) {
+ $uri_e = urlencode($uri);
+ $longurl = unserialize(file_get_contents("http://api.longurl.org/v1/expand?format=php&url=$uri_e"));
+ if (empty($longurl['long_url']) || $uri === $longurl['long_url']) return false;
+ return stripslashes($longurl['long_url']);
+}
+
+function common_shorten_links($text) {
+ if (mb_strlen($text) <= 140) return $text;
+ static $cache = array();
+ if (isset($cache[$text])) return $cache[$text];
+ // \s = not a horizontal whitespace character (since PHP 5.2.4)
+ return $cache[$text] = preg_replace('@https?://[^)\]>\s]+@e', "common_shorten_link('\\0')", $text);
+}
+
+function common_shorten_link($url, $reverse = false) {
+ static $url_cache = array();
+ if ($reverse) return isset($url_cache[$url]) ? $url_cache[$url] : $url;
+
+ $user = common_current_user();
+
+ $curlh = curl_init();
+ curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
+ curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
+ curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
+
+ switch($user->urlshorteningservice) {
+ case 'ur1.ca':
+ $short_url_service = new LilUrl;
+ $short_url = $short_url_service->shorten($url);
+ break;
+
+ case '2tu.us':
+ $short_url_service = new TightUrl;
+ $short_url = $short_url_service->shorten($url);
+ break;
+
+ case 'ptiturl.com':
+ $short_url_service = new PtitUrl;
+ $short_url = $short_url_service->shorten($url);
+ break;
+
+ case 'bit.ly':
+ curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($url));
+ $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
+ break;
+
+ case 'is.gd':
+ curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($url));
+ $short_url = curl_exec($curlh);
+ break;
+ case 'snipr.com':
+ curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($url));
+ $short_url = curl_exec($curlh);
+ break;
+ case 'metamark.net':
+ curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($url));
+ $short_url = curl_exec($curlh);
+ break;
+ case 'tinyurl.com':
+ curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($url));
+ $short_url = curl_exec($curlh);
+ break;
+ default:
+ $short_url = false;
+ }
+
+ curl_close($curlh);
+
+ if ($short_url) {
+ $url_cache[(string)$short_url] = $url;
+ return (string)$short_url;
+ }
+ return $url;
+}
+
+function common_xml_safe_str($str) {
+ $xmlStr = htmlentities(iconv('UTF-8', 'UTF-8//IGNORE', $str), ENT_NOQUOTES, 'UTF-8');
+
+ // Replace control, formatting, and surrogate characters with '*', ala Twitter
+ return preg_replace('/[\p{Cc}\p{Cf}\p{Cs}]/u', '*', $str);
+}
+
+function common_tag_link($tag) {
+ $canonical = common_canonical_tag($tag);
+ $url = common_local_url('tag', array('tag' => $canonical));
+ return '<a href="' . htmlspecialchars($url) . '" rel="tag" class="hashlink">' . htmlspecialchars($tag) . '</a>';
+}
+
+function common_canonical_tag($tag) {
+ return strtolower(str_replace(array('-', '_', '.'), '', $tag));
+}
+
+function common_valid_profile_tag($str) {
+ return preg_match('/^[A-Za-z0-9_\-\.]{1,64}$/', $str);
+}
+
+function common_at_link($sender_id, $nickname) {
+ $sender = Profile::staticGet($sender_id);
+ $recipient = common_relative_profile($sender, common_canonical_nickname($nickname));
+ if ($recipient) {
+ return '<a href="'.htmlspecialchars($recipient->profileurl).'" class="atlink">'.$nickname.'</a>';
+ } else {
+ return $nickname;
+ }
+}
+
+function common_at_hash_link($sender_id, $tag) {
+ $user = User::staticGet($sender_id);
+ if (!$user) {
+ return $tag;
+ }
+ $tagged = Profile_tag::getTagged($user->id, common_canonical_tag($tag));
+ if ($tagged) {
+ $url = common_local_url('subscriptions',
+ array('nickname' => $user->nickname,
+ 'tag' => $tag));
+ return '<a href="'.htmlspecialchars($url).'" class="atlink">'.$tag.'</a>';
+ } else {
+ return $tag;
+ }
+}
+
+function common_relative_profile($sender, $nickname, $dt=NULL) {
+ # Try to find profiles this profile is subscribed to that have this nickname
+ $recipient = new Profile();
+ # XXX: use a join instead of a subquery
+ $recipient->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.$sender->id.' and subscribed = id)', 'AND');
+ $recipient->whereAdd('nickname = "' . trim($nickname) . '"', 'AND');
+ if ($recipient->find(TRUE)) {
+ # XXX: should probably differentiate between profiles with
+ # the same name by date of most recent update
+ return $recipient;
+ }
+ # Try to find profiles that listen to this profile and that have this nickname
+ $recipient = new Profile();
+ # XXX: use a join instead of a subquery
+ $recipient->whereAdd('EXISTS (SELECT subscriber from subscription where subscribed = '.$sender->id.' and subscriber = id)', 'AND');
+ $recipient->whereAdd('nickname = "' . trim($nickname) . '"', 'AND');
+ if ($recipient->find(TRUE)) {
+ # XXX: should probably differentiate between profiles with
+ # the same name by date of most recent update
+ return $recipient;
+ }
+ # If this is a local user, try to find a local user with that nickname.
+ $sender = User::staticGet($sender->id);
+ if ($sender) {
+ $recipient_user = User::staticGet('nickname', $nickname);
+ if ($recipient_user) {
+ return $recipient_user->getProfile();
+ }
+ }
+ # Otherwise, no links. @messages from local users to remote users,
+ # or from remote users to other remote users, are just
+ # outside our ability to make intelligent guesses about
+ return NULL;
+}
+
+// where should the avatar go for this user?
+
+function common_avatar_filename($id, $extension, $size=NULL, $extra=NULL) {
+ global $config;
+
+ if ($size) {
+ return $id . '-' . $size . (($extra) ? ('-' . $extra) : '') . $extension;
+ } else {
+ return $id . '-original' . (($extra) ? ('-' . $extra) : '') . $extension;
+ }
+}
+
+function common_avatar_path($filename) {
+ global $config;
+ return INSTALLDIR . '/avatar/' . $filename;
+}
+
+function common_avatar_url($filename) {
+ return common_path('avatar/'.$filename);
+}
+
+function common_avatar_display_url($avatar) {
+ $server = common_config('avatar', 'server');
+ if ($server) {
+ return 'http://'.$server.'/'.$avatar->filename;
+ } else {
+ return $avatar->url;
+ }
+}
+
+function common_default_avatar($size) {
+ static $sizenames = array(AVATAR_PROFILE_SIZE => 'profile',
+ AVATAR_STREAM_SIZE => 'stream',
+ AVATAR_MINI_SIZE => 'mini');
+ return theme_path('default-avatar-'.$sizenames[$size].'.png');
+}
+
+function common_local_url($action, $args=NULL, $fragment=NULL) {
+ $url = NULL;
+ if (common_config('site','fancy')) {
+ $url = common_fancy_url($action, $args);
+ } else {
+ $url = common_simple_url($action, $args);
+ }
+ if (!is_null($fragment)) {
+ $url .= '#'.$fragment;
+ }
+ return $url;
+}
+
+function common_fancy_url($action, $args=NULL) {
+ switch (strtolower($action)) {
+ case 'public':
+ if ($args && isset($args['page'])) {
+ return common_path('?page=' . $args['page']);
+ } else {
+ return common_path('');
+ }
+ case 'featured':
+ if ($args && isset($args['page'])) {
+ return common_path('featured?page=' . $args['page']);
+ } else {
+ return common_path('featured');
+ }
+ case 'favorited':
+ if ($args && isset($args['page'])) {
+ return common_path('favorited?page=' . $args['page']);
+ } else {
+ return common_path('favorited');
+ }
+ case 'publicrss':
+ return common_path('rss');
+ case 'publicatom':
+ return common_path("api/statuses/public_timeline.atom");
+ case 'publicxrds':
+ return common_path('xrds');
+ case 'featuredrss':
+ return common_path('featuredrss');
+ case 'favoritedrss':
+ return common_path('favoritedrss');
+ case 'opensearch':
+ if ($args && $args['type']) {
+ return common_path('opensearch/'.$args['type']);
+ } else {
+ return common_path('opensearch/people');
+ }
+ case 'doc':
+ return common_path('doc/'.$args['title']);
+ case 'block':
+ case 'login':
+ case 'logout':
+ case 'subscribe':
+ case 'unsubscribe':
+ case 'invite':
+ return common_path('main/'.$action);
+ case 'tagother':
+ return common_path('main/tagother?id='.$args['id']);
+ case 'register':
+ if ($args && $args['code']) {
+ return common_path('main/register/'.$args['code']);
+ } else {
+ return common_path('main/register');
+ }
+ case 'remotesubscribe':
+ if ($args && $args['nickname']) {
+ return common_path('main/remote?nickname=' . $args['nickname']);
+ } else {
+ return common_path('main/remote');
+ }
+ case 'nudge':
+ return common_path($args['nickname'].'/nudge');
+ case 'openidlogin':
+ return common_path('main/openid');
+ case 'profilesettings':
+ return common_path('settings/profile');
+ case 'emailsettings':
+ return common_path('settings/email');
+ case 'openidsettings':
+ return common_path('settings/openid');
+ case 'smssettings':
+ return common_path('settings/sms');
+ case 'twittersettings':
+ return common_path('settings/twitter');
+ case 'othersettings':
+ return common_path('settings/other');
+ case 'deleteprofile':
+ return common_path('settings/delete');
+ case 'newnotice':
+ if ($args && $args['replyto']) {
+ return common_path('notice/new?replyto='.$args['replyto']);
+ } else {
+ return common_path('notice/new');
+ }
+ case 'shownotice':
+ return common_path('notice/'.$args['notice']);
+ case 'deletenotice':
+ if ($args && $args['notice']) {
+ return common_path('notice/delete/'.$args['notice']);
+ } else {
+ return common_path('notice/delete');
+ }
+ case 'microsummary':
+ case 'xrds':
+ case 'foaf':
+ return common_path($args['nickname'].'/'.$action);
+ case 'all':
+ case 'replies':
+ case 'inbox':
+ case 'outbox':
+ if ($args && isset($args['page'])) {
+ return common_path($args['nickname'].'/'.$action.'?page=' . $args['page']);
+ } else {
+ return common_path($args['nickname'].'/'.$action);
+ }
+ case 'subscriptions':
+ case 'subscribers':
+ $nickname = $args['nickname'];
+ unset($args['nickname']);
+ if (isset($args['tag'])) {
+ $tag = $args['tag'];
+ unset($args['tag']);
+ }
+ $params = http_build_query($args);
+ if ($params) {
+ return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : '') . '?' . $params);
+ } else {
+ return common_path($nickname.'/'.$action . (($tag) ? '/' . $tag : ''));
+ }
+ case 'allrss':
+ return common_path($args['nickname'].'/all/rss');
+ case 'repliesrss':
+ return common_path($args['nickname'].'/replies/rss');
+ case 'userrss':
+ if (isset($args['limit']))
+ return common_path($args['nickname'].'/rss?limit=' . $args['limit']);
+ return common_path($args['nickname'].'/rss');
+ case 'showstream':
+ if ($args && isset($args['page'])) {
+ return common_path($args['nickname'].'?page=' . $args['page']);
+ } else {
+ return common_path($args['nickname']);
+ }
+
+ case 'usertimeline':
+ return common_path("api/statuses/user_timeline/".$args['nickname'].".atom");
+ case 'confirmaddress':
+ return common_path('main/confirmaddress/'.$args['code']);
+ case 'userbyid':
+ return common_path('user/'.$args['id']);
+ case 'recoverpassword':
+ $path = 'main/recoverpassword';
+ if ($args['code']) {
+ $path .= '/' . $args['code'];
+ }
+ return common_path($path);
+ case 'imsettings':
+ return common_path('settings/im');
+ case 'peoplesearch':
+ return common_path('search/people' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'noticesearch':
+ return common_path('search/notice' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'noticesearchrss':
+ return common_path('search/notice/rss' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'avatarbynickname':
+ return common_path($args['nickname'].'/avatar/'.$args['size']);
+ case 'tag':
+ if (isset($args['tag']) && $args['tag']) {
+ $path = 'tag/' . $args['tag'];
+ unset($args['tag']);
+ } else {
+ $path = 'tags';
+ }
+ return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'tagrss':
+ $path = 'tag/' . $args['tag'] . '/rss';
+ unset($args['tag']);
+ return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'peopletag':
+ $path = 'peopletag/' . $args['tag'];
+ unset($args['tag']);
+ return common_path($path . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'tags':
+ return common_path('tags' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'favor':
+ return common_path('main/favor');
+ case 'disfavor':
+ return common_path('main/disfavor');
+ case 'showfavorites':
+ if ($args && isset($args['page'])) {
+ return common_path($args['nickname'].'/favorites?page=' . $args['page']);
+ } else {
+ return common_path($args['nickname'].'/favorites');
+ }
+ case 'favoritesrss':
+ return common_path($args['nickname'].'/favorites/rss');
+ case 'showmessage':
+ return common_path('message/' . $args['message']);
+ case 'newmessage':
+ return common_path('message/new' . (($args) ? ('?' . http_build_query($args)) : ''));
+ case 'api':
+ # XXX: do fancy URLs for all the API methods
+ switch (strtolower($args['apiaction'])) {
+ case 'statuses':
+ switch (strtolower($args['method'])) {
+ case 'user_timeline.rss':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.rss');
+ case 'user_timeline.atom':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.atom');
+ case 'user_timeline.json':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.json');
+ case 'user_timeline.xml':
+ return common_path('api/statuses/user_timeline/'.$args['argument'].'.xml');
+ default: return common_simple_url($action, $args);
+ }
+ default: return common_simple_url($action, $args);
+ }
+ case 'sup':
+ if ($args && isset($args['seconds'])) {
+ return common_path('main/sup?seconds='.$args['seconds']);
+ } else {
+ return common_path('main/sup');
+ }
+ default:
+ return common_simple_url($action, $args);
+ }
+}
+
+function common_simple_url($action, $args=NULL) {
+ global $config;
+ /* XXX: pretty URLs */
+ $extra = '';
+ if ($args) {
+ foreach ($args as $key => $value) {
+ $extra .= "&${key}=${value}";
+ }
+ }
+ return common_path("index.php?action=${action}${extra}");
+}
+
+function common_path($relative) {
+ global $config;
+ $pathpart = ($config['site']['path']) ? $config['site']['path']."/" : '';
+ return "http://".$config['site']['server'].'/'.$pathpart.$relative;
+}
+
+function common_date_string($dt) {
+ // XXX: do some sexy date formatting
+ // return date(DATE_RFC822, $dt);
+ $t = strtotime($dt);
+ $now = time();
+ $diff = $now - $t;
+
+ if ($now < $t) { # that shouldn't happen!
+ return common_exact_date($dt);
+ } else if ($diff < 60) {
+ return _('a few seconds ago');
+ } else if ($diff < 92) {
+ return _('about a minute ago');
+ } else if ($diff < 3300) {
+ return sprintf(_('about %d minutes ago'), round($diff/60));
+ } else if ($diff < 5400) {
+ return _('about an hour ago');
+ } else if ($diff < 22 * 3600) {
+ return sprintf(_('about %d hours ago'), round($diff/3600));
+ } else if ($diff < 37 * 3600) {
+ return _('about a day ago');
+ } else if ($diff < 24 * 24 * 3600) {
+ return sprintf(_('about %d days ago'), round($diff/(24*3600)));
+ } else if ($diff < 46 * 24 * 3600) {
+ return _('about a month ago');
+ } else if ($diff < 330 * 24 * 3600) {
+ return sprintf(_('about %d months ago'), round($diff/(30*24*3600)));
+ } else if ($diff < 480 * 24 * 3600) {
+ return _('about a year ago');
+ } else {
+ return common_exact_date($dt);
+ }
+}
+
+function common_exact_date($dt) {
+ static $_utc;
+ static $_siteTz;
+
+ if (!$_utc) {
+ $_utc = new DateTimeZone('UTC');
+ $_siteTz = new DateTimeZone(common_timezone());
+ }
+
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, $_utc);
+ $d->setTimezone($_siteTz);
+ return $d->format(DATE_RFC850);
+}
+
+function common_date_w3dtf($dt) {
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+ $d->setTimezone(new DateTimeZone(common_timezone()));
+ return $d->format(DATE_W3C);
+}
+
+function common_date_rfc2822($dt) {
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+ $d->setTimezone(new DateTimeZone(common_timezone()));
+ return $d->format('r');
+}
+
+function common_date_iso8601($dt) {
+ $dateStr = date('d F Y H:i:s', strtotime($dt));
+ $d = new DateTime($dateStr, new DateTimeZone('UTC'));
+ $d->setTimezone(new DateTimeZone(common_timezone()));
+ return $d->format('c');
+}
+
+function common_sql_now() {
+ return strftime('%Y-%m-%d %H:%M:%S', time());
+}
+
+function common_redirect($url, $code=307) {
+ static $status = array(301 => "Moved Permanently",
+ 302 => "Found",
+ 303 => "See Other",
+ 307 => "Temporary Redirect");
+ header("Status: ${code} $status[$code]");
+ header("Location: $url");
+
+ common_start_xml('a',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+ common_element('a', array('href' => $url), $url);
+ common_end_xml();
+ exit;
+}
+
+function common_save_replies($notice) {
+ # Alternative reply format
+ $tname = false;
+ if (preg_match('/^T ([A-Z0-9]{1,64}) /', $notice->content, $match)) {
+ $tname = $match[1];
+ }
+ # extract all @messages
+ $cnt = preg_match_all('/(?:^|\s)@([a-z0-9]{1,64})/', $notice->content, $match);
+
+ $names = array();
+
+ if ($cnt || $tname) {
+ # XXX: is there another way to make an array copy?
+ $names = ($tname) ? array_unique(array_merge(array(strtolower($tname)), $match[1])) : array_unique($match[1]);
+ }
+
+ $sender = Profile::staticGet($notice->profile_id);
+
+ $replied = array();
+
+ # store replied only for first @ (what user/notice what the reply directed,
+ # we assume first @ is it)
+
+ for ($i=0; $i<count($names); $i++) {
+ $nickname = $names[$i];
+ $recipient = common_relative_profile($sender, $nickname, $notice->created);
+ if (!$recipient) {
+ continue;
+ }
+ if ($i == 0 && ($recipient->id != $sender->id) && !$notice->reply_to) { # Don't save reply to self
+ $reply_for = $recipient;
+ $recipient_notice = $reply_for->getCurrentNotice();
+ if ($recipient_notice) {
+ $orig = clone($notice);
+ $notice->reply_to = $recipient_notice->id;
+ $notice->update($orig);
+ }
+ }
+ # Don't save replies from blocked profile to local user
+ $recipient_user = User::staticGet('id', $recipient->id);
+ if ($recipient_user && $recipient_user->hasBlocked($sender)) {
+ continue;
+ }
+ $reply = new Reply();
+ $reply->notice_id = $notice->id;
+ $reply->profile_id = $recipient->id;
+ $id = $reply->insert();
+ if (!$id) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, 'DB error inserting reply: ' . $last_error->message);
+ common_server_error(sprintf(_('DB error inserting reply: %s'), $last_error->message));
+ return;
+ } else {
+ $replied[$recipient->id] = 1;
+ }
+ }
+
+ # Hash format replies, too
+ $cnt = preg_match_all('/(?:^|\s)@#([a-z0-9]{1,64})/', $notice->content, $match);
+ if ($cnt) {
+ foreach ($match[1] as $tag) {
+ $tagged = Profile_tag::getTagged($sender->id, $tag);
+ foreach ($tagged as $t) {
+ if (!$replied[$t->id]) {
+ # Don't save replies from blocked profile to local user
+ $t_user = User::staticGet('id', $t->id);
+ if ($t_user && $t_user->hasBlocked($sender)) {
+ continue;
+ }
+ $reply = new Reply();
+ $reply->notice_id = $notice->id;
+ $reply->profile_id = $t->id;
+ $id = $reply->insert();
+ if (!$id) {
+ common_log_db_error($reply, 'INSERT', __FILE__);
+ return;
+ }
+ }
+ }
+ }
+ }
+}
+
+function common_broadcast_notice($notice, $remote=false) {
+
+ // Check to see if notice should go to Twitter
+ $flink = Foreign_link::getByUserID($notice->profile_id, 1); // 1 == Twitter
+ if (($flink->noticesync & FOREIGN_NOTICE_SEND) == FOREIGN_NOTICE_SEND) {
+
+ // If it's not a Twitter-style reply, or if the user WANTS to send replies...
+
+ if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
+ (($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) == FOREIGN_NOTICE_SEND_REPLY)) {
+
+ $result = common_twitter_broadcast($notice, $flink);
+
+ if (!$result) {
+ common_debug('Unable to send notice: ' . $notice->id . ' to Twitter.', __FILE__);
+ }
+ }
+ }
+
+ if (common_config('queue', 'enabled')) {
+ # Do it later!
+ return common_enqueue_notice($notice);
+ } else {
+ return common_real_broadcast($notice, $remote);
+ }
+}
+
+function common_twitter_broadcast($notice, $flink) {
+ global $config;
+ $success = true;
+ $fuser = $flink->getForeignUser();
+ $twitter_user = $fuser->nickname;
+ $twitter_password = $flink->credentials;
+ $uri = 'http://www.twitter.com/statuses/update.json';
+
+ // XXX: Hack to get around PHP cURL's use of @ being a a meta character
+ $statustxt = preg_replace('/^@/', ' @', $notice->content);
+
+ $options = array(
+ CURLOPT_USERPWD => "$twitter_user:$twitter_password",
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => array(
+ 'status' => $statustxt,
+ 'source' => $config['integration']['source']
+ ),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FAILONERROR => true,
+ CURLOPT_HEADER => false,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_USERAGENT => "Laconica",
+ CURLOPT_CONNECTTIMEOUT => 120, // XXX: Scary!!!! How long should this be?
+ CURLOPT_TIMEOUT => 120,
+
+ # Twitter is strict about accepting invalid "Expect" headers
+ CURLOPT_HTTPHEADER => array('Expect:')
+ );
+
+ $ch = curl_init($uri);
+ curl_setopt_array($ch, $options);
+ $data = curl_exec($ch);
+ $errmsg = curl_error($ch);
+
+ if ($errmsg) {
+ common_debug("cURL error: $errmsg - trying to send notice for $twitter_user.",
+ __FILE__);
+ $success = false;
+ }
+
+ curl_close($ch);
+
+ if (!$data) {
+ common_debug("No data returned by Twitter's API trying to send update for $twitter_user",
+ __FILE__);
+ $success = false;
+ }
+
+ // Twitter should return a status
+ $status = json_decode($data);
+
+ if (!$status->id) {
+ common_debug("Unexpected data returned by Twitter API trying to send update for $twitter_user",
+ __FILE__);
+ $success = false;
+ }
+
+ return $success;
+}
+
+# Stick the notice on the queue
+
+function common_enqueue_notice($notice) {
+ foreach (array('jabber', 'omb', 'sms', 'public') as $transport) {
+ $qi = new Queue_item();
+ $qi->notice_id = $notice->id;
+ $qi->transport = $transport;
+ $qi->created = $notice->created;
+ $result = $qi->insert();
+ if (!$result) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
+ return false;
+ }
+ common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
+ }
+ return $result;
+}
+
+function common_dequeue_notice($notice) {
+ $qi = Queue_item::staticGet($notice->id);
+ if ($qi) {
+ $result = $qi->delete();
+ if (!$result) {
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, 'DB error deleting queue item: ' . $last_error->message);
+ return false;
+ }
+ common_log(LOG_DEBUG, 'complete dequeueing notice ID = ' . $notice->id);
+ return $result;
+ } else {
+ return false;
+ }
+}
+
+function common_real_broadcast($notice, $remote=false) {
+ $success = true;
+ if (!$remote) {
+ # Make sure we have the OMB stuff
+ require_once(INSTALLDIR.'/lib/omb.php');
+ $success = omb_broadcast_remote_subscribers($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id);
+ }
+ }
+ if ($success) {
+ require_once(INSTALLDIR.'/lib/jabber.php');
+ $success = jabber_broadcast_notice($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id);
+ }
+ }
+ if ($success) {
+ require_once(INSTALLDIR.'/lib/mail.php');
+ $success = mail_broadcast_notice_sms($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id);
+ }
+ }
+ if ($success) {
+ $success = jabber_public_notice($notice);
+ if (!$success) {
+ common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
+ }
+ }
+ // XXX: broadcast notices to other IM
+ return $success;
+}
+
+function common_broadcast_profile($profile) {
+ // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
+ require_once(INSTALLDIR.'/lib/omb.php');
+ omb_broadcast_profile($profile);
+ // XXX: Other broadcasts...?
+ return true;
+}
+
+function common_profile_url($nickname) {
+ return common_local_url('showstream', array('nickname' => $nickname));
+}
+
+# Don't call if nobody's logged in
+
+function common_notice_form($action=NULL, $content=NULL) {
+ $user = common_current_user();
+ assert(!is_null($user));
+ common_element_start('form', array('id' => 'status_form',
+ 'method' => 'post',
+ 'action' => common_local_url('newnotice')));
+ common_element_start('p');
+ common_element('label', array('for' => 'status_textarea',
+ 'id' => 'status_label'),
+ sprintf(_('What\'s up, %s?'), $user->nickname));
+ common_element('span', array('id' => 'counter', 'class' => 'counter'), '140');
+ common_element('textarea', array('id' => 'status_textarea',
+ 'cols' => 60,
+ 'rows' => 3,
+ 'name' => 'status_textarea'),
+ ($content) ? $content : '');
+ common_hidden('token', common_session_token());
+ if ($action) {
+ common_hidden('returnto', $action);
+ }
+ # set by JavaScript
+ common_hidden('inreplyto', 'false');
+ common_element('input', array('id' => 'status_submit',
+ 'name' => 'status_submit',
+ 'type' => 'submit',
+ 'value' => _('Send')));
+ common_element_end('p');
+ common_element_end('form');
+}
+
+# Should make up a reasonable root URL
+
+function common_root_url() {
+ return common_path('');
+}
+
+# returns $bytes bytes of random data as a hexadecimal string
+# "good" here is a goal and not a guarantee
+
+function common_good_rand($bytes) {
+ # XXX: use random.org...?
+ if (file_exists('/dev/urandom')) {
+ return common_urandom($bytes);
+ } else { # FIXME: this is probably not good enough
+ return common_mtrand($bytes);
+ }
+}
+
+function common_urandom($bytes) {
+ $h = fopen('/dev/urandom', 'rb');
+ # should not block
+ $src = fread($h, $bytes);
+ fclose($h);
+ $enc = '';
+ for ($i = 0; $i < $bytes; $i++) {
+ $enc .= sprintf("%02x", (ord($src[$i])));
+ }
+ return $enc;
+}
+
+function common_mtrand($bytes) {
+ $enc = '';
+ for ($i = 0; $i < $bytes; $i++) {
+ $enc .= sprintf("%02x", mt_rand(0, 255));
+ }
+ return $enc;
+}
+
+function common_set_returnto($url) {
+ common_ensure_session();
+ $_SESSION['returnto'] = $url;
+}
+
+function common_get_returnto() {
+ common_ensure_session();
+ return $_SESSION['returnto'];
+}
+
+function common_timestamp() {
+ return date('YmdHis');
+}
+
+function common_ensure_syslog() {
+ static $initialized = false;
+ if (!$initialized) {
+ global $config;
+ openlog($config['syslog']['appname'], 0, LOG_USER);
+ $initialized = true;
+ }
+}
+
+function common_log($priority, $msg, $filename=NULL) {
+ $logfile = common_config('site', 'logfile');
+ if ($logfile) {
+ $log = fopen($logfile, "a");
+ if ($log) {
+ static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
+ 'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
+ $output = date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
+ fwrite($log, $output);
+ fclose($log);
+ }
+ } else {
+ common_ensure_syslog();
+ syslog($priority, $msg);
+ }
+}
+
+function common_debug($msg, $filename=NULL) {
+ if ($filename) {
+ common_log(LOG_DEBUG, basename($filename).' - '.$msg);
+ } else {
+ common_log(LOG_DEBUG, $msg);
+ }
+}
+
+function common_log_db_error(&$object, $verb, $filename=NULL) {
+ $objstr = common_log_objstring($object);
+ $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+ common_log(LOG_ERR, $last_error->message . '(' . $verb . ' on ' . $objstr . ')', $filename);
+}
+
+function common_log_objstring(&$object) {
+ if (is_null($object)) {
+ return "NULL";
+ }
+ $arr = $object->toArray();
+ $fields = array();
+ foreach ($arr as $k => $v) {
+ $fields[] = "$k='$v'";
+ }
+ $objstring = $object->tableName() . '[' . implode(',', $fields) . ']';
+ return $objstring;
+}
+
+function common_valid_http_url($url) {
+ return Validate::uri($url, array('allowed_schemes' => array('http', 'https')));
+}
+
+function common_valid_tag($tag) {
+ if (preg_match('/^tag:(.*?),(\d{4}(-\d{2}(-\d{2})?)?):(.*)$/', $tag, $matches)) {
+ return (Validate::email($matches[1]) ||
+ preg_match('/^([\w-\.]+)$/', $matches[1]));
+ }
+ return false;
+}
+
+# Does a little before-after block for next/prev page
+
+function common_pagination($have_before, $have_after, $page, $action, $args=NULL) {
+
+ if ($have_before || $have_after) {
+ common_element_start('div', array('id' => 'pagination'));
+ common_element_start('ul', array('id' => 'nav_pagination'));
+ }
+
+ if ($have_before) {
+ $pargs = array('page' => $page-1);
+ $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
+
+ common_element_start('li', 'before');
+ common_element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'prev'),
+ _('« After'));
+ common_element_end('li');
+ }
+
+ if ($have_after) {
+ $pargs = array('page' => $page+1);
+ $newargs = ($args) ? array_merge($args,$pargs) : $pargs;
+ common_element_start('li', 'after');
+ common_element('a', array('href' => common_local_url($action, $newargs), 'rel' => 'next'),
+ _('Before »'));
+ common_element_end('li');
+ }
+
+ if ($have_before || $have_after) {
+ common_element_end('ul');
+ common_element_end('div');
+ }
+}
+
+/* Following functions are copied from MediaWiki GlobalFunctions.php
+ * and written by Evan Prodromou. */
+
+function common_accept_to_prefs($accept, $def = '*/*') {
+ # No arg means accept anything (per HTTP spec)
+ if(!$accept) {
+ return array($def => 1);
+ }
+
+ $prefs = array();
+
+ $parts = explode(',', $accept);
+
+ foreach($parts as $part) {
+ # FIXME: doesn't deal with params like 'text/html; level=1'
+ @list($value, $qpart) = explode(';', $part);
+ $match = array();
+ if(!isset($qpart)) {
+ $prefs[$value] = 1;
+ } elseif(preg_match('/q\s*=\s*(\d*\.\d+)/', $qpart, $match)) {
+ $prefs[$value] = $match[1];
+ }
+ }
+
+ return $prefs;
+}
+
+function common_mime_type_match($type, $avail) {
+ if(array_key_exists($type, $avail)) {
+ return $type;
+ } else {
+ $parts = explode('/', $type);
+ if(array_key_exists($parts[0] . '/*', $avail)) {
+ return $parts[0] . '/*';
+ } elseif(array_key_exists('*/*', $avail)) {
+ return '*/*';
+ } else {
+ return NULL;
+ }
+ }
+}
+
+function common_negotiate_type($cprefs, $sprefs) {
+ $combine = array();
+
+ foreach(array_keys($sprefs) as $type) {
+ $parts = explode('/', $type);
+ if($parts[1] != '*') {
+ $ckey = common_mime_type_match($type, $cprefs);
+ if($ckey) {
+ $combine[$type] = $sprefs[$type] * $cprefs[$ckey];
+ }
+ }
+ }
+
+ foreach(array_keys($cprefs) as $type) {
+ $parts = explode('/', $type);
+ if($parts[1] != '*' && !array_key_exists($type, $sprefs)) {
+ $skey = common_mime_type_match($type, $sprefs);
+ if($skey) {
+ $combine[$type] = $sprefs[$skey] * $cprefs[$type];
+ }
+ }
+ }
+
+ $bestq = 0;
+ $besttype = "text/html";
+
+ foreach(array_keys($combine) as $type) {
+ if($combine[$type] > $bestq) {
+ $besttype = $type;
+ $bestq = $combine[$type];
+ }
+ }
+
+ return $besttype;
+}
+
+function common_config($main, $sub) {
+ global $config;
+ return isset($config[$main][$sub]) ? $config[$main][$sub] : false;
+}
+
+function common_copy_args($from) {
+ $to = array();
+ $strip = get_magic_quotes_gpc();
+ foreach ($from as $k => $v) {
+ $to[$k] = ($strip) ? stripslashes($v) : $v;
+ }
+ return $to;
+}
+
+// Neutralise the evil effects of magic_quotes_gpc in the current request.
+// This is used before handing a request off to OAuthRequest::from_request.
+function common_remove_magic_from_request() {
+ if(get_magic_quotes_gpc()) {
+ $_POST=array_map('stripslashes',$_POST);
+ $_GET=array_map('stripslashes',$_GET);
+ }
+}
+
+function common_user_uri(&$user) {
+ return common_local_url('userbyid', array('id' => $user->id));
+}
+
+function common_notice_uri(&$notice) {
+ return common_local_url('shownotice',
+ array('notice' => $notice->id));
+}
+
+# 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
+
+function common_confirmation_code($bits) {
+ # 36 alphanums - lookalikes (0, O, 1, I) = 32 chars = 5 bits
+ static $codechars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
+ $chars = ceil($bits/5);
+ $code = '';
+ for ($i = 0; $i < $chars; $i++) {
+ # XXX: convert to string and back
+ $num = hexdec(common_good_rand(1));
+ # XXX: randomness is too precious to throw away almost
+ # 40% of the bits we get!
+ $code .= $codechars[$num%32];
+ }
+ return $code;
+}
+
+# convert markup to HTML
+
+function common_markup_to_html($c) {
+ $c = preg_replace('/%%action.(\w+)%%/e', "common_local_url('\\1')", $c);
+ $c = preg_replace('/%%doc.(\w+)%%/e', "common_local_url('doc', array('title'=>'\\1'))", $c);
+ $c = preg_replace('/%%(\w+).(\w+)%%/e', 'common_config(\'\\1\', \'\\2\')', $c);
+ return Markdown($c);
+}
+
+function common_profile_avatar_url($profile, $size=AVATAR_PROFILE_SIZE) {
+ $avatar = $profile->getAvatar($size);
+ if ($avatar) {
+ return common_avatar_display_url($avatar);
+ } else {
+ return common_default_avatar($size);
+ }
+}
+
+function common_profile_uri($profile) {
+ if (!$profile) {
+ return NULL;
+ }
+ $user = User::staticGet($profile->id);
+ if ($user) {
+ return $user->uri;
+ }
+
+ $remote = Remote_profile::staticGet($profile->id);
+ if ($remote) {
+ return $remote->uri;
+ }
+ # XXX: this is a very bad profile!
+ return NULL;
+}
+
+function common_canonical_sms($sms) {
+ # strip non-digits
+ preg_replace('/\D/', '', $sms);
+ return $sms;
+}
+
+function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
+ switch ($errno) {
+ case E_USER_ERROR:
+ common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)");
+ exit(1);
+ break;
+
+ case E_USER_WARNING:
+ common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
+ break;
+
+ case E_USER_NOTICE:
+ common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
+ break;
+ }
+
+ # FIXME: show error page if we're on the Web
+ /* Don't execute PHP internal error handler */
+ return true;
+}
+
+function common_session_token() {
+ common_ensure_session();
+ if (!array_key_exists('token', $_SESSION)) {
+ $_SESSION['token'] = common_good_rand(64);
+ }
+ return $_SESSION['token'];
+}
+
+function common_disfavor_form($notice) {
+ common_element_start('form', array('id' => 'disfavor-' . $notice->id,
+ 'method' => 'post',
+ 'class' => 'disfavor',
+ 'action' => common_local_url('disfavor')));
+
+ common_element('input', array('type' => 'hidden',
+ 'name' => 'token-'. $notice->id,
+ 'id' => 'token-'. $notice->id,
+ 'class' => 'token',
+ 'value' => common_session_token()));
+
+ common_element('input', array('type' => 'hidden',
+ 'name' => 'notice',
+ 'id' => 'notice-n'. $notice->id,
+ 'class' => 'notice',
+ 'value' => $notice->id));
+
+ common_element('input', array('type' => 'submit',
+ 'id' => 'disfavor-submit-' . $notice->id,
+ 'name' => 'disfavor-submit-' . $notice->id,
+ 'class' => 'disfavor',
+ 'value' => 'Disfavor favorite',
+ 'title' => 'Remove this message from favorites'));
+ common_element_end('form');
+}
+
+function common_favor_form($notice) {
+ common_element_start('form', array('id' => 'favor-' . $notice->id,
+ 'method' => 'post',
+ 'class' => 'favor',
+ 'action' => common_local_url('favor')));
+
+ common_element('input', array('type' => 'hidden',
+ 'name' => 'token-'. $notice->id,
+ 'id' => 'token-'. $notice->id,
+ 'class' => 'token',
+ 'value' => common_session_token()));
+
+ common_element('input', array('type' => 'hidden',
+ 'name' => 'notice',
+ 'id' => 'notice-n'. $notice->id,
+ 'class' => 'notice',
+ 'value' => $notice->id));
+
+ common_element('input', array('type' => 'submit',
+ 'id' => 'favor-submit-' . $notice->id,
+ 'name' => 'favor-submit-' . $notice->id,
+ 'class' => 'favor',
+ 'value' => 'Add to favorites',
+ 'title' => 'Add this message to favorites'));
+ common_element_end('form');
+}
+
+function common_nudge_form($profile) {
+ common_element_start('form', array('id' => 'nudge', 'method' => 'post',
+ 'action' => common_local_url('nudge', array('nickname' => $profile->nickname))));
+ common_hidden('token', common_session_token());
+ common_element('input', array('type' => 'submit',
+ 'class' => 'submit',
+ 'value' => _('Send a nudge')));
+ common_element_end('form');
+}
+function common_nudge_response() {
+ common_element('p', array('id' => 'nudge_response'), _('Nudge sent!'));
+}
+
+function common_subscribe_form($profile) {
+ common_element_start('form', array('id' => 'subscribe-' . $profile->id,
+ 'method' => 'post',
+ 'class' => 'subscribe',
+ 'action' => common_local_url('subscribe')));
+ common_hidden('token', common_session_token());
+ common_element('input', array('id' => 'subscribeto-' . $profile->id,
+ 'name' => 'subscribeto',
+ 'type' => 'hidden',
+ 'value' => $profile->id));
+ common_element('input', array('type' => 'submit',
+ 'class' => 'submit',
+ 'value' => _('Subscribe')));
+ common_element_end('form');
+}
+
+function common_unsubscribe_form($profile) {
+ common_element_start('form', array('id' => 'unsubscribe-' . $profile->id,
+ 'method' => 'post',
+ 'class' => 'unsubscribe',
+ 'action' => common_local_url('unsubscribe')));
+ common_hidden('token', common_session_token());
+ common_element('input', array('id' => 'unsubscribeto-' . $profile->id,
+ 'name' => 'unsubscribeto',
+ 'type' => 'hidden',
+ 'value' => $profile->id));
+ common_element('input', array('type' => 'submit',
+ 'class' => 'submit',
+ 'value' => _('Unsubscribe')));
+ common_element_end('form');
+}
+
+// XXX: Refactor this code
+function common_profile_new_message_nudge ($cur, $profile) {
+ $user = User::staticGet('id', $profile->id);
+
+ if ($cur && $cur->id != $user->id && $cur->mutuallySubscribed($user)) {
+ common_element_start('li', array('id' => 'profile_send_a_new_message'));
+ common_element('a', array('href' => common_local_url('newmessage', array('to' => $user->id))),
+ _('Send a message'));
+ common_element_end('li');
+
+ if ($user->email && $user->emailnotifynudge) {
+ common_element_start('li', array('id' => 'profile_nudge'));
+ common_nudge_form($user);
+ common_element_end('li');
+ }
+ }
+}
+
+function common_cache_key($extra) {
+ return 'laconica:' . common_keyize(common_config('site', 'name')) . ':' . $extra;
+}
+
+function common_keyize($str) {
+ $str = strtolower($str);
+ $str = preg_replace('/\s/', '_', $str);
+ return $str;
+}
+
+function common_message_form($content, $user, $to) {
+
+ common_element_start('form', array('id' => 'message_form',
+ 'method' => 'post',
+ 'action' => common_local_url('newmessage')));
+
+ $mutual_users = $user->mutuallySubscribedUsers();
+
+ $mutual = array();
+
+ while ($mutual_users->fetch()) {
+ if ($mutual_users->id != $user->id) {
+ $mutual[$mutual_users->id] = $mutual_users->nickname;
+ }
+ }
+
+ $mutual_users->free();
+ unset($mutual_users);
+
+ common_dropdown('to', _('To'), $mutual, NULL, FALSE, $to->id);
+
+ common_element_start('p');
+
+ common_element('textarea', array('id' => 'message_content',
+ 'cols' => 60,
+ 'rows' => 3,
+ 'name' => 'content'),
+ ($content) ? $content : '');
+
+ common_element('input', array('id' => 'message_send',
+ 'name' => 'message_send',
+ 'type' => 'submit',
+ 'value' => _('Send')));
+
+ common_hidden('token', common_session_token());
+
+ common_element_end('p');
+ common_element_end('form');
+}
+
+function common_memcache() {
+ static $cache = NULL;
+ if (!common_config('memcached', 'enabled')) {
+ return NULL;
+ } else {
+ if (!$cache) {
+ $cache = new Memcache();
+ $servers = common_config('memcached', 'server');
+ if (is_array($servers)) {
+ foreach($servers as $server) {
+ $cache->addServer($server);
+ }
+ } else {
+ $cache->addServer($servers);
+ }
+ }
+ return $cache;
+ }
+}
+
+function common_compatible_license($from, $to) {
+ # XXX: better compatibility check needed here!
+ return ($from == $to);
+}
+
+/* These are almost identical, so we use a helper function */
+
+function common_block_form($profile, $args=NULL) {
+ common_blocking_form('block', _('Block'), $profile, $args);
+}
+
+function common_unblock_form($profile, $args=NULL) {
+ common_blocking_form('unblock', _('Unblock'), $profile, $args);
+}
+
+function common_blocking_form($type, $label, $profile, $args=NULL) {
+ common_element_start('form', array('id' => $type . '-' . $profile->id,
+ 'method' => 'post',
+ 'class' => $type,
+ 'action' => common_local_url($type)));
+ common_hidden('token', common_session_token());
+ common_element('input', array('id' => $type . 'to-' . $profile->id,
+ 'name' => $type . 'to',
+ 'type' => 'hidden',
+ 'value' => $profile->id));
+ common_element('input', array('type' => 'submit',
+ 'class' => 'submit',
+ 'name' => $type,
+ 'value' => $label));
+ if ($args) {
+ foreach ($args as $k => $v) {
+ common_hidden('returnto-' . $k, $v);
+ }
+ }
+ common_element_end('form');
+ return;
+}
+
diff --git a/lib/xmppqueuehandler.php b/lib/xmppqueuehandler.php
new file mode 100644
index 000000000..cfc9642e4
--- /dev/null
+++ b/lib/xmppqueuehandler.php
@@ -0,0 +1,91 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/lib/queuehandler.php');
+
+/**
+ * Common superclass for all XMPP-using queue handlers. They all need to
+ * service their message queues on idle, and forward any incoming messages
+ * to the XMPP listener connection. So, we abstract out common code to a
+ * superclass.
+ */
+
+class XmppQueueHandler extends QueueHandler {
+
+ function start() {
+ # Low priority; we don't want to receive messages
+ $this->log(LOG_INFO, "INITIALIZE");
+ $this->conn = jabber_connect($this->_id);
+ if ($this->conn) {
+ $this->conn->addEventHandler('message', 'forward_message', $this);
+ $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
+ $this->conn->setReconnectTimeout(600);
+ jabber_send_presence("Send me a message to post a notice", 'available', NULL, 'available', -1);
+ }
+ return !is_null($this->conn);
+ }
+
+ function handle_reconnect(&$pl) {
+ $this->conn->processUntil('session_start');
+ $this->conn->presence(NULL, 'available', NULL, 'available', -1);
+ }
+
+ function idle($timeout=0) {
+ # Process the queue for as long as needed
+ try {
+ if ($this->conn) {
+ $this->conn->processTime($timeout);
+ }
+ } catch (XMPPHP_Exception $e) {
+ $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
+ die($e->getMessage());
+ }
+ }
+
+ function forward_message(&$pl) {
+ if ($pl['type'] != 'chat') {
+ $this->log(LOG_DEBUG, 'Ignoring message of type ' . $pl['type'] . ' from ' . $pl['from']);
+ return;
+ }
+ $listener = $this->listener();
+ if (strtolower($listener) == strtolower($pl['from'])) {
+ $this->log(LOG_WARNING, 'Ignoring loop message.');
+ return;
+ }
+ $this->log(LOG_INFO, 'Forwarding message from ' . $pl['from'] . ' to ' . $listener);
+ $this->conn->message($this->listener(), $pl['body'], 'chat', NULL, $this->ofrom($pl['from']));
+ }
+
+ function ofrom($from) {
+ $address = "<addresses xmlns='http://jabber.org/protocol/address'>\n";
+ $address .= "<address type='ofrom' jid='$from' />\n";
+ $address .= "</addresses>\n";
+ return $address;
+ }
+
+ function listener() {
+ if (common_config('xmpp', 'listener')) {
+ return common_config('xmpp', 'listener');
+ } else {
+ return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener';
+ }
+ }
+}
diff --git a/locale/bg_BG/LC_MESSAGES/laconica.mo b/locale/bg_BG/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..b563ad3a8
--- /dev/null
+++ b/locale/bg_BG/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/bg_BG/LC_MESSAGES/laconica.po b/locale/bg_BG/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..cbdbc8ba4
--- /dev/null
+++ b/locale/bg_BG/LC_MESSAGES/laconica.po
@@ -0,0 +1,2873 @@
+# #-#-#-#-# laconica.pot (Laconica 0.6.4) #-#-#-#-#
+# Laconica Bulgarian translation.
+# Copyright (C) 2008
+# This file is distributed under the same license as the Laconica package.
+# Yasen Pramatarov <yasen@lindeas.com>, 2008
+# Stoyan Zhekov <laconica@zh.otherinbox.com>, 2008
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"#-#-#-#-# laconica.pot (Laconica 0.6.4) #-#-#-#-#\n"
+"Project-Id-Version: Laconica 0.6.4\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: 2008-12-12 00:00+0300\n"
+"Last-Translator: Yasen Pramatarov <yasen@lindeas.com>\n"
+"Language-Team: Bulgarian <dict@fsa-bg.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "ТърÑене на \"%s\" в потока"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr "оÑвен тези лични данни: парола, е-поща, меÑинджър, телефон."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s вече получава бележките ви в %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s вече получава бележките ви в %2$s.\n"
+"\n"
+"\t%3$s\n\nС уважение,\n%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "Бележка на %1$s от %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Общ поток в %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s и приÑтели"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** е уÑлуга за микроблогване, "
+"предоÑтавена ви от [%%site.broughtby%%](%%site.broughtbyurl%%). "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** е уÑлуга за микроблогване. "
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ". ИзпиÑват Ñе пълните имена или пÑевдоними на учаÑтниците."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "От 1 до 64 малки букви или цифри, без Ð¿ÑƒÐ½ÐºÑ‚Ð¾Ð°Ñ†Ð¸Ñ Ð¸ интервали"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 или повече знака"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 или повече знака. И не ги забравÑйте!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Ðа меÑинджъра ви е изпратен код за "
+"потвърждение. За да получавате ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚ %s, Ñ‚Ñ€Ñбва да го одобрите."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "ОтноÑно"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Приемане"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "ДобавÑне"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "ДобавÑне на OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "ÐдреÑ"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Ð’Ñички абонаменти"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Ð’Ñички бележки за %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Ð’Ñички бележки, намерени Ñ \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Вече Ñте влезли."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Вече Ñте абонирани!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "ОдобрÑване на абонамента"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Ðвтоматично влизане занапред. Да не Ñе ползва на общи компютри!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Ðватар"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Ðватарът е обновен."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Oчаква Ñе потвърждение на този адреÑ. "
+"Проверете акаунта Ñи в Jabber/GTalk за Ñъобщение Ñ Ð¸Ð½Ñтрукции. (Добавихте ли %s в ÑпиÑъка Ñи там?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Преди »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "За мен"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "ÐвтобиографиÑта е твърде дълга (до 140 Ñимвола)."
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Грешка при четене адреÑа на аватара '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Грешка при запазване на новата парола."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Отказ"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Грешка при Ñъздаване на потребителÑки OpenID обект"
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Грешка при Ð½Ð¾Ñ€Ð¼Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° този Jabber ID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "ПромÑна"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "СмÑна на паролата"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Потвърждаване"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Потвърждаване на адреÑа"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Потвърждаването е прекъÑнато."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Кодът за потвърждение не е открит."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Свързване"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Свързване на ÑъщеÑтвуваща Ñметка"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Контакт"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Грешка при Ñъздаване на OpenID форма: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Грешка при пренаÑочване към Ñървър: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Грешка при запазване данните на аватара"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Грешка при запазване на Ð½Ð¾Ð²Ð¸Ñ Ð¿Ñ€Ð¾Ñ„Ð¸Ð»"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Ðе може да Ñе потвърди електронната поща."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Грешка при преобразуване на tokens за одобрение в tokens за доÑтъп."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Грешка при Ñъздаване на нов абонамент."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Грешка при изтриване потвърждението по е-поща."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Грешка при изтриване на абонамента."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Ðе е получен token за одобрение."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Ðе може да Ñе вмъкне код за потвърждение."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Грешка при добавÑне на нов абонамент."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Грешка при запазване на профила."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Грешка при обновÑване на потребителÑ."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Създаване"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Създаване на нов потребител Ñ Ñ‚Ð¾Ð·Ð¸ пÑевдоним."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Създаване на нова Ñметка"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Създаване на нов акаунт за OpenID, който вече има потребител."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Текущ потвърден Jabber/GTalk адреÑ."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "В момента"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Грешка в базата от данни — отговор при вмъкването: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Опишете Ñебе Ñи и интереÑите Ñи в до 140 букви"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Е-поща"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° е-поща"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "ÐдреÑÑŠÑ‚ на е-поща вече Ñе използва."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Потвърждаване адреÑа на е-поща"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Въведете пÑевдоним или е-поща."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Грешка при одобрÑване на token"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Грешка при Ñвързване на Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ ÐºÑŠÐ¼ OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Грешка при Ñвързване на потребителÑ."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Грешка при вмъкване на аватар"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Грешка при вмъкване на нов профил"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Грешка при вмъкване на бележка"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Грешка при вмъкване на отдалечен профил"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Грешка при запазване на потвърждение за адреÑ"
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Грешка при запазване на отдалечен профил"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Грешка при запазване на профил"
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Грешка при запазване на потребител."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Грешка при запазване на потребител — невалидноÑÑ‚."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Грешка в наÑтройките на потребителÑ."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Грешка при обновÑване на профил"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Грешка при обновÑване на отдалечен профил"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Грешка в кода за потвърждение."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "СъщеÑтвуващ пÑевдоним"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "ВъпроÑи"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "ÐеуÑпешно обновÑване на аватара."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "ЕмиÑÐ¸Ñ Ñ Ð¿Ñ€Ð¸Ñтелите на %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "ЕмиÑÐ¸Ñ Ñ Ð¾Ñ‚Ð³Ð¾Ð²Ð¾Ñ€Ð¸Ñ‚Ðµ към %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"За по-голÑма ÑигурноÑÑ‚, Ð¼Ð¾Ð»Ñ Ð²ÑŠÐ²ÐµÐ´ÐµÑ‚Ðµ "
+"отново потребителÑкото Ñи име и парола при промÑна на наÑтройките."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Пълно име"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Пълното име е твърде дълго (макÑ. 255 знака)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Помощ"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Ðачало"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Лична Ñтраница"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "ÐдреÑÑŠÑ‚ на личната Ñтраница не е правилен URL."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM адреÑ"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM наÑтройки"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Ðко вече имате Ñметка, за да Ñ Ñвържете Ñ "
+"OpenID влезте Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñко Ñи име и парола."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Ðко иÑкате да добавите OpenID към Ñметката "
+"Ñи, въведете го в кутийката отдолу и натиÑнете \"ДобавÑне\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Ðко Ñте забравили или изгубили паролата "
+"Ñи, може да ви бъде изпратена нова на е-пощата, запиÑана в Ñметката ви."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Грешна Ñтара парола"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Грешно име или парола."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Ðа е-пощата, Ñ ÐºÐ¾Ñто Ñте региÑтрирани Ñа "
+"изпратени инÑтрукции за възÑтановÑване на паролата."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° аватар '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° домашна Ñтраница '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° лиценз '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Ðевалидно Ñъдържание на бележка"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° бележка"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° бележка"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° профил '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° профил (грешен формат)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Върнат от Ñървъра неправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° профила."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Ðеправилен размер."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Ðеправилно име или парола."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Ползва [Laconica](http://laconi.ca/) верÑÐ¸Ñ %s, ÑиÑтема "
+"за микроблогване, доÑтъпна под [GNU Affero General "
+"Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Този Jabber ID принадлежи на друг потребител."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber или GTalk адреÑ, като \"UserName@example.org\". "
+"Първо Ñе уверете, че Ñте добавили %s в "
+"ÑпиÑъка Ñи Ñ Ð¿Ñ€Ð¸Ñтели в IM или GTalk клиента Ñи."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "МеÑтоположение"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Името на меÑтоположението е твърде дълго (макÑ. 255 знака)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Вход"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Влизане Ñ [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Влезте Ñ Ð¸Ð¼Ðµ и парола. ÐÑмате такива? "
+"[РегиÑтрирайте](%%action.register%%) нова Ñметка или опитайте Ñ [OpenID](%%action.openidlogin%%). "
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Изход"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Загубена или забравена парола"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "УчаÑтник от"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Микроблог на %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "ТекÑтовете и файловите ми Ñа доÑтъпни под"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Ðов пÑевдоним"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Ðова бележка"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Ðова парола"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Ðовата парола е запазена. ВлÑзохте уÑпешно."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "ПÑевдоним"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Опитайте друг пÑевдоним, този вече е зает."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"ПÑевдонимът може да Ñъдържа Ñамо малки "
+"букви, чиÑла и никакво разÑтоÑние между Ñ‚ÑÑ…."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Този пÑевдоним не е разрешен тук."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "ПÑевдоним на потребител, когото иÑкате да Ñледите"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "ПÑевдоним или е-поща"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "ÐÑма Jabber ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "ÐÑма заÑвка за одобрение."
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "ÐÑма код за потвърждение."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "ÐÑма Ñъдържание!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "ÐÑма id."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "ОтдалечениÑÑ‚ Ñървър не е предоÑтавил пÑевдоним."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "ÐÑма пÑевдоним."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "ÐÑма потвърждениÑ, очакващи да бъдат отказани."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Сървърът не е върнал Ð°Ð´Ñ€ÐµÑ Ð½Ð° профила."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "ÐÑма указана е-поща за този потребител."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "ЗаÑвката не е намерена!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "ÐÑма резултати"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "ÐÑма размер."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "ÐÑма такъв OpenID-адреÑ."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "ÐÑма такъв документ."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "ÐÑма такава бележка."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "ÐÑма такъв код за възÑтановÑване."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "ÐÑма такъв абонамент"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "ÐÑма такъв потребител"
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "ÐÑма никого за показване!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Това не е код за възÑтановÑване."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Ðеправилен Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Ðеправилен OpenID"
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Ðеправилен пÑевдоним."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° профил (грешна уÑлуга)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° профил (нÑма зададен XRDS)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° профил (нÑма YADIS документ)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Файлът не е изображение или е повреден."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Забранено."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Ðеочакван отговор."
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Ðе Ñте влезли в ÑиÑтемата."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Ðе Ñте абонирани!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "ЕмиÑÐ¸Ñ Ñ Ð±ÐµÐ»ÐµÐ¶ÐºÐ¸ на %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Бележката нÑма профил"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Бележки"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Стара парола"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "ÐаÑтройки на OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Ðвтоматично предаване на OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Влизане Ñ OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Влизането Ñ OpenID е прекратено."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Грешка при влизане Ñ OpenID: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Проблем Ñ OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID е премахнат."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "ÐаÑтройки на OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "ЧаÑтично качване на файла."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Парола"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Паролата и потвърждението й не Ñъвпадат."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Паролата Ñ‚Ñ€Ñбва да е от поне 6 знака."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "ПоиÑкано е възÑтановÑване на парола"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Паролата е запиÑана."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Паролите не Ñъвпадат."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "ТърÑене на хора"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Лично"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Проверете тези детайли и Ñе уверете, че "
+"иÑкате да Ñе абонирате за бележките на "
+"този потребител. Ðко не иÑкате абонамента, натиÑнете \"Cancel\" (Отказ)."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Публикуване промÑната на ÑÑŠÑтоÑнието ми в Jabber/GTalk."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "ÐаÑтройки"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "ÐаÑтройките Ñа запазени."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "ПоверителноÑÑ‚"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Проблем при запиÑване на бележката."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Профил"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° профила"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "ÐаÑтройки на профила"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Ðепознат профил"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Общ поток"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "ЕмиÑÐ¸Ñ Ð½Ð° Ð¾Ð±Ñ‰Ð¸Ñ Ð¿Ð¾Ñ‚Ð¾Ðº"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Общ поток"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "ВъзÑтановÑване"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "ВъзÑтановÑване на паролата"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Код за възÑтановÑване на непознат потребител."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "РегиÑтриране"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "ОхвърлÑне"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Запомни ме"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Отдалечен профил без ÑъответÑтващ локален"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Отдалечен абонамент"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Премахване"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Премахване на OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Премахването на единÑÑ‚Ð²ÐµÐ½Ð¸Ñ OpenID ще "
+"направи влизането в ÑиÑтемата невъзможно. За да го изтриете, първо добавете друг OpenID."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Отговори"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Отговори на %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "ОбновÑване"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Ðова парола"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Също като паролата по-горе"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Запазване"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "ТърÑене"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "ТърÑене в емиÑиÑта на потока"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"ТърÑене на бележки в %%site.name%% по "
+"Ñъдържанието им. ОтделÑйте фразите за "
+"Ñ‚ÑŠÑ€Ñене (Ñ‚Ñ€Ñбва да Ñа по-дълги от 3 Ñимвола) Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð²Ð°Ð»Ð¸."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"ТърÑене на хора в %%site.name%% по техните име, "
+"мÑÑто или интереÑи. ОтделÑйте фразите за "
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Прати"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Изпращане на бележките по Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "ÐаÑтройки"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "ÐаÑтройките Ñа запазени."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "ÐÑкой друг вече използва този OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Случи Ñе нещо Ñтранно."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Изходен код"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "СтатиÑтики"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "ÐÑма запазен OpenID."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Ðбониране"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Ðбонати"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Ðбонаментът е одобрен"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Ðбонаментът е отказан"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Ðбонаменти"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "СиÑтемна грешка при качване на файл."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "ТърÑене на текÑÑ‚"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Този OpenID не ви принадлежи."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Този Ð°Ð´Ñ€ÐµÑ Ðµ вече потвърден."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Този код за потвърждение не е за ваÑ!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Файлът е твърде голÑм."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Това вече е вашиÑÑ‚ Jabber ID."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Това не е вашиÑÑ‚ Jabber ID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Грешен IM адреÑ."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Твърде дълга бележка. ТрÑбва да е най-много 140 знака."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "ÐдреÑÑŠÑ‚ \"%s\" е потвърден за Ñметката ви."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "ÐдреÑÑŠÑ‚ е премахнат."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Ðбонаментът е одобрен, но не е зададен "
+"callback URL. За да завършите одобрÑването, "
+"проверете инÑтрукциите на Ñайта. ВашиÑÑ‚ token за абонамент е:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Ðбонаментът е отказан, но не е зададен "
+"callback URL. За да откажете напълно абонамента, проверете инÑтрукциите на Ñайта."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Това Ñа хората, които четат бележките на %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Tова Ñа хората, които четат бележките ви."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Хора, чийто бележки %s чете."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "ÐÑма хора, чийто бележки четете."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Кодът за потвърждение е твърде Ñтар. Започнете процеÑа отново."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Формата би Ñ‚Ñ€Ñбвало да Ñе изпрати "
+"автоматично. Ðко това не Ñе Ñлучи, за да "
+"преминете към OpenID доÑтавчика Ñи натиÑнете бутона за изпращане."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"За първи път влизате в ÑиÑтемата като %s, "
+"затова Ñ‚Ñ€Ñбва да приÑъединим OpenID към "
+"Ð»Ð¾ÐºÐ°Ð»Ð½Ð¸Ñ Ð²Ð¸ профил. Можете да Ñъздадете нова Ñметка или да използвате ÑъщеÑтвуваща, ако имате такава."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Страницата не е доÑтъпна във вида медиÑ, който приемате"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"За да Ñе абонирате, можете да "
+"[влезете](%%action.login%%) или да "
+"[региÑтрирате](%%action.register%%) нова Ñметка. "
+"Ðко имате Ñметка на друга, [подобна уÑлуга за микроблогване](%%doc.openmublog%%), въведете адреÑа на профила Ñи в Ð½ÐµÑ Ð¿Ð¾-долу."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° личната ви Ñтраница, блог или профил в друг Ñайт"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° профила ви в друга, ÑъвмеÑтима уÑлуга за микроблогване"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Ðеочаквано изпращане на форма."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Ðеочаквано подновÑване на паролата."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Ðепозната верÑÐ¸Ñ Ð½Ð° протокола OMB."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"ОÑвен ако не е указано друго, "
+"Ñъдържанието на този Ñайт принадлежи на Ñъздателите му и е доÑтъпно под "
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Ðеразпознат вид Ð°Ð´Ñ€ÐµÑ %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "ОтпиÑване"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Ðеподдържана верÑÐ¸Ñ Ð½Ð° OMB"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Форматът на файла Ñ Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸ÐµÑ‚Ð¾ не Ñе поддържа."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Качване"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Качете нов \"аватар\" (потребителÑко "
+"изображение) тук. Ðе можете да го "
+"промените по-къÑно, затова Ñе уверете, че "
+"е поне приблизително квадратен. ТрÑбва да е Ñ Ð»Ð¸Ñ†ÐµÐ½Ð· като ÑÐ°Ð¼Ð¸Ñ Ñайт. Използвайте картинка, коÑто ви принадлежи и иÑкате да Ñподелите."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Използва Ñе Ñамо за промени, обÑви или възÑтановÑване на паролата"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "ПотребителÑÑ‚, когото проÑледÑвате, не ÑъщеÑтвува. "
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "ПотребителÑÑ‚ нÑма профил."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "ПотребителÑки пÑевдоним"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Какво Ñтава, %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Къде Ñе намирате (град, община, държава и Ñ‚.н.)"
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Грешен вид изображение за '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Грешен размер на изображението '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Вече използвате този OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Вече Ñте влезли!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Тук можете да Ñмените паролата Ñи. Внимавайте колко е добра новата."
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Първо Ñе региÑтрирайте, за да започнете да публикувате бележки."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Можете да премахнете OpenID от Ñметката Ñи, "
+"като натиÑнете бутона \"Премахване\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Можете да получавате ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð¾ Jabber/GTalk "
+"[instant messages](%%doc.im%%). Въведете адреÑа Ñи в наÑтройките по-долу."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr "Можете да обновите Ð»Ð¸Ñ‡Ð½Ð¸Ñ Ñи профил, за да знаÑÑ‚ хората повече за ваÑ."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Можете да ползвате локален абонамент!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Ðе можете да Ñе региÑтрате, ако не Ñте ÑъглаÑни Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð°."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Ðе Ñте ни изпратили този профил"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Разпознати Ñте. Въведете паролата Ñи по-долу. "
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "ВашиÑÑ‚ OpenID URL"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "ПÑевдонимът ви на този Ñървър или е-пощата, Ñ ÐºÐ¾Ñто Ñте региÑтрирани."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) ви позволÑва влизане в "
+"различни Ñайтове Ñ ÐµÐ´Ð¸Ð½ и Ñъщ акаунт. От тук Ñе управлÑват Ñъответните OpenID."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "преди нÑколко Ñекунди"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "преди около %d дни"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "преди около %d чаÑа"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "преди около %d минути"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "преди около %d меÑеца"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "преди около ден"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "преди около минута"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "преди около меÑец"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "преди около година"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "преди около чаÑ"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "в отговор на..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "отговор"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "Ñъщо като паролата по-горе"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« След"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr "от"
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s ви кани да ползвате заедно %2$s"
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s ви кани да Ñе приÑъедините към %2$s "
+"(%3$s).\n"
+"\n"
+"%2$s е уÑлуга за микроблогване, чрез коÑто "
+"леÑно поддържате връзка Ñ Ñ…Ð¾Ñ€Ð°Ñ‚Ð°, които "
+"познавате или които Ñа ви интереÑни.\n"
+"\n"
+"Също така можете да публикувате кратки "
+"новини за Ñебе Ñи, Ñвои размиÑли и да ги "
+"обÑъждате в мрежата Ñ Ñ…Ð¾Ñ€Ð°, които ви "
+"познават. Това Ñъщо е и добър начин за "
+"запознаване Ñ Ð½Ð¾Ð²Ð¸ хора, които ÑподелÑÑ‚ "
+"интереÑите ви.\n"
+"\n"
+"%1$s ви казва:\n\n%4$s\n\nМожете да видите профила на %1$s в %2$s тук:\n\n%5$s\n\nÐко иÑкате да опитате уÑлугата на Ñайта, проÑледете препратката по-долу, за да приемете поканата.\n\n%6$s\n\nÐко не желаете, пропуÑнете това пиÑмо. Благодарим ви за търпението и отделеното време.\n\nИÑкрено ваши, %2$s\n"
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "Общ поток на %s"
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr "СъÑтоÑние на %s"
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr "Поток на %s"
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(ТрÑбва да получите веднага електронно "
+"пиÑмо Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð·Ð° потвърждаване адреÑа на е-пощата ви.)"
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"От 1 до 64 малки букви или цифри, без "
+"Ð¿ÑƒÐ½ÐºÑ‚Ð¾Ð°Ñ†Ð¸Ñ Ð¸ интервали. Задължително поле."
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr "6 или повече знака. Задължително поле."
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Ðа адреÑа на е-поща, който Ñте въвели, беше "
+"изпратен код за потвърждение. Проверете "
+"кутиÑта (или папката за Ñпам) за кода и указаниÑта за използването му."
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Ðа Ñ‚ÐµÐ»ÐµÑ„Ð¾Ð½Ð½Ð¸Ñ Ð½Ð¾Ð¼ÐµÑ€, който Ñте въвели, "
+"беше изпратен код за потвърждение. "
+"Проверете ÑъобщениÑта (или папката за Ñпам) за кода и указаниÑта за използването му."
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr "Ðе е открит методът в API."
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr "Методът в API вÑе още Ñе разработва."
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr "ДобавÑне или премахване OpenID"
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr "ÐдреÑи на приÑтели, които каните (по един на ред)"
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr "ÐаиÑтина ли иÑкате да изтриете тази бележка?"
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+"Ðвтоматично абониране за вÑеки, който Ñе "
+"абонира за мен (подходÑщо за ботове)."
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"Очаква Ñе потвърждение за този адреÑ. "
+"Проверете кутиÑта Ñи (или папката за Ñпам) за Ñъобщение Ñ ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ."
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "Очаква Ñе потвърждение за този телефонен номер."
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Грешка при изтриване на бележката."
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr "Грешка при нормализиране адреÑа на е-пощата"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr "ПромÑна обработката на пиÑмата"
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr "СмÑна на паролата"
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "ПромÑна наÑтройките на профила"
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Код за потвърждение"
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"ПоздравлениÑ, %s! И добре дошли в %%%%site.name%%%%! "
+"от тук можете да...\n"
+"\n"
+"* Отидете в [профила Ñи](%s) и да публикувате "
+"първата Ñи бележка.\n"
+"* Добавите [Ð°Ð´Ñ€ÐµÑ Ð² Jabber/GTalk](%%%%action.imsettings%%%%), "
+"за да изпращате бележки от програмата Ñи "
+"за моментни ÑъобщениÑ.\n"
+"* [ТърÑите хора](%%%%action.peoplesearch%%%%), които "
+"познавате или Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ ÑподелÑте общи "
+"интереÑи. \n"
+"* Обновите [наÑтройките на "
+"профила(%%%%action.profilesettings%%%%) Ñи, за да кажете повече за Ñебе Ñи на другите. \n* Прочетете наличната [документациÑ](%%%%doc.help%%%%) на Ñайта, за да Ñе запознаете Ñ Ð²ÑŠÐ·Ð¼Ð¾Ð¶Ð½Ð¾Ñтите му. \n\nБлагодарим, че Ñе включихте в Ñайта и дано ползването на уÑлугата ви ноÑи Ñамо приÑтни мигове!"
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr "Грешка при проÑледÑване на потребител: %s вече е в ÑпиÑъка ви."
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr "Грешка при проÑледÑване — потребителÑÑ‚ не е намерен."
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr "Грешка при абониране на друг потребител за ваÑ."
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr "Грешка при абониране."
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr "Ðе Ñа открити бележки."
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr "Грешка при обновÑване запиÑа на потребител."
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "Текущ потвърден телефонен номер за SMS-и."
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr "Текущ потвърден Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща."
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "Изтриване на бележката"
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° е-поща"
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "ÐаÑтройки на е-поща"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr "ÐÐ´Ñ€ÐµÑ Ð½Ð° е-поща, като \"UserName@example.org\""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr "ÐдреÑи на е-поща"
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr "Въведете кода, който получихте по телефона."
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "ЕмиÑÐ¸Ñ Ð·Ð° етикета %s"
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr "ТърÑене в Ñъдържанието на бележките"
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr "ТърÑене на хора в Ñайта"
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr "ИÑкам да изпращам бележки по пощата."
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr "IM"
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+"Ðко Ñте забравили или загубили паролата "
+"Ñи, може да получите нова на е-пощата, въведена в профила ви."
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "ВходÑща поща"
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr "ВходÑщиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща е премахнат."
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr "Ðеправилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща: %s"
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr "Поканите Ñа изпратени."
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr "Изпратени Ñа покани до Ñледните хора:"
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr "Покани"
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr "Покани за нови потребители"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "Език"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr "Името на езика е твърде дълго (може да е до 50 знака)."
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr "По-дълго име, за предпочитане \"иÑтинÑкото\" ви име."
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr "Управление на пощата, идваща от %%site.name%%."
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+"Мобилен оператор за SMS. Ðко знаете "
+"оператор, поддържащ SMS от е-поща, който не фигурира тук, пишете ни на Ð°Ð´Ñ€ÐµÑ %s."
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+#, fuzzy
+msgid "New"
+msgstr "Ðово"
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr "Ðов Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща за публикщуване в %s"
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr "Добавен е нов входÑщ Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща."
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr "Ðе"
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr "Ðе е избран оператор."
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr "Ðе е въведен код."
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr "Ðе е въведена е-поща."
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr "ÐÑма входÑщ Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща."
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr "Ðе е въведен телефонен номер."
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr "Ðе е открита бележка Ñ Ñ‚Ð°ÐºÑŠÐ² идентификатор."
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr "Ðе е открита бележка Ñ Ñ‚Ð°ÐºÑŠÐ² идентификатор."
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr "ÐÑма потребител Ñ Ñ‚Ð°ÐºÐ°Ð²Ð° е-поща или потребителÑко име."
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr "Това не е региÑтриран потребител."
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr "Ðеподдържан формат на данните"
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr "Това не е правилен Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща."
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr "Ðе е открито."
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr "ТърÑене на бележки"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr "Бележки Ñ ÐµÑ‚Ð¸ÐºÐµÑ‚ %s"
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr "Може да добавите и лично Ñъобщение към поканата."
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr "Хора"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr "ТърÑене на хора"
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr "Лично Ñъобщение"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr "Телефонен номер — Ñ ÐºÐ¾Ð´, без Ð¿ÑƒÐ½ÐºÑ‚Ð¾Ð°Ñ†Ð¸Ñ Ð¸ без интервали."
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr "Предпочитан език"
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Публикуване на MicroID за адреÑа в Jabber/GTalk."
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr "Публикуване на MicroID за адреÑа на е-пощата."
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr "Скорошни етикети"
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr "ЗапиÑването не е позволено."
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr "ЗапиÑването е уÑпешно."
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr "SMS"
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "Телефонен номер за SMS"
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "ÐаÑтройки за SMS"
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr "Потвърждение за SMS"
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr "Същото като паролата по-горе. Задължително поле."
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr "Изберете оператор"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr "Изпратете пиÑмо до този Ð°Ð´Ñ€ÐµÑ Ð·Ð° публикуване като бележка."
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr "Изпращане на ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ Ð·Ð° нови абонаменти по пощата."
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+"Получаване на бележки в SMS. Имайте "
+"предвид, че може да има допълнителни такÑи от оператора."
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+"Изпращане по Jabber/GTalk на отговори от хора, "
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr "Ðай-популÑрните етикети за изминалата Ñедмица"
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr "ВходÑщата поща не е разрешена."
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr "Това не е вашиÑÑ‚ входÑщ адреÑ."
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr "Етикети"
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr "ТекÑÑ‚"
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr "Тази е-поща вече Ñе използва от друг потребител."
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr "Това и Ñега е адреÑÑŠÑ‚ на е-пощата ви."
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr "Това Ñега е номерът на телефона ви."
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr "Това не е вашиÑÑ‚ Ð°Ð´Ñ€ÐµÑ Ð½Ð° е-поща."
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr "Това не е вашиÑÑ‚ телефонен номер."
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr "Този код за потвърждение е грешен."
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr "Този телефонен номер вече Ñе използва от друг потребител."
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr "Това е твърде дълго. ТрÑбва да е най-много 255 знака."
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr "Тези хора Ñа потребители тук и автоматично Ñте абонирани за Ñ‚ÑÑ…:"
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr "Този метод изиÑква заÑвка POST или DELETE."
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr "Този метод изиÑква заÑвка POST."
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr "ЧаÑови поÑÑ"
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr "Ðе е избран чаÑови поÑÑ"
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr "Ðепознато дейÑтвие"
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr "Бележки през SMS"
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr "Бележки през меÑинджър (IM)"
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr "Качване на нова Ñнимка за профила"
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+"Използвайте това поле, за да поканите "
+"приÑтели и колеги да използват уÑлугата на Ñайта."
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr "ПотребителÑÑ‚ не е открит."
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr "Ð’ кой чаÑови поÑÑ Ñте обикновено?"
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr "Да"
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr "Ще изтриете напълно бележката. Изтриването е невъзвратимо."
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr "Вече Ñте абонирани за Ñледните потребители:"
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr "С ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ» не Ñте запиÑани като приÑтели."
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr "Ðе може да изтривате бележки на друг потребител."
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr "За да каните хора в %s, Ñ‚Ñ€Ñбва да Ñте влезли."
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+"Ще ви уведомим при приемане на покана и "
+"запиÑване в Ñайта. Благодарим ви за увеличаването на общноÑтта тук!"
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr "изтриване"
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr "неподдържан вид файл"
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr "Имаше проблем ÑÑŠÑ ÑеÑиÑта ви в Ñайта. МолÑ, опитайте отново!"
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr "Тази бележка не е отбелÑзана като любима!"
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr "Грешка при изтриване на любима бележка."
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr "Любимо"
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr "Изпращане на пиÑмо при отбелÑзване на Ð¼Ð¾Ñ Ð±ÐµÐ»ÐµÐ¶ÐºÐ° като любима."
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr "Изпращане на пиÑмо при ново лично Ñъобщение."
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr "Тази бележка вече е отбелÑзана като любима!"
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr "Грешка при отбелÑзване като любима."
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr "Ðелюбимо"
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr "%s любими бележки"
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr "ЕмиÑÐ¸Ñ Ñ Ð»ÑŽÐ±Ð¸Ð¼Ð¸Ñ‚Ðµ бележки на %s"
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr "ВходÑща ÐºÑƒÑ‚Ð¸Ñ Ð·Ð° %s — Ñтраница %d"
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr "ВходÑща ÐºÑƒÑ‚Ð¸Ñ Ð·Ð° %s"
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr "Това е входÑщата ви ÐºÑƒÑ‚Ð¸Ñ Ñ Ð»Ð¸Ñ‡Ð½Ð¸ ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚ други потребители."
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr "%1$s използва %2$s (%3$s) и ви кани да Ñе приÑъедините.\n\n"
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr "Ðвтоматично влизане за в бъдеще:"
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr "Влезте Ñ Ð¸Ð¼ÐµÑ‚Ð¾ и паролата Ñи."
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr "Твърде дълго. Може да е най-много 140 знака."
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr "Ðе е указан получател."
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr "Ðе може да изпращате ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾ този потребител."
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+"Ðе може да изпращате ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾ Ñебе Ñи. "
+"По-добре Ñи го кажете на Ñебе Ñи тихичко."
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr "ÐÑма такъв потребител"
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr "Ðово Ñъобщение"
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr "Бележка без ÑъответÑтващ профил"
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr "[OpenID](%%doc.openid%%) ви позволÑва да влизате в различни Ñайтове"
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr "Ðко премахнете единÑÑ‚Ð²ÐµÐ½Ð¸Ñ Ñи OpenID, нÑма да можете да влизате!"
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr "Можете да премахнете OpenID от профила Ñи"
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr "ИзходÑща ÐºÑƒÑ‚Ð¸Ñ Ð·Ð° %s — Ñтраница %d"
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr "ИзходÑща ÐºÑƒÑ‚Ð¸Ñ Ð·Ð° %s"
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr "Това е изходÑщата ви ÐºÑƒÑ‚Ð¸Ñ Ñ Ð»Ð¸Ñ‡Ð½Ð¸ ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾ други потребители. "
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr "ТърÑене на хора в %%site.name%% по име, меÑтоположение или интереÑи."
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr "Можете а обновите личните Ñи данни тук"
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr "Потребител без ÑъответÑтващ профил"
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr "Кодът ви за потвърждение е твърде Ñтар."
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr "ПÑевдонимът ви на този Ñървър."
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr "Упътване за възÑтановÑване на паролата"
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr "Ðовата парола е запиÑана."
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr "Паролата Ñ‚Ñ€Ñбва да е 6 или повече знака."
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+"ПоздравлениÑ, %s! И добре дошли в %%%%site.name%%%%. "
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr "ЕмиÑÐ¸Ñ Ñ Ð»ÑŽÐ±Ð¸Ð¼Ð¸Ñ‚Ðµ бележки на %s"
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr "Грешка при изтеглÑне на любимите бележки"
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr "ÐÑма такова Ñъобщение"
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr "Само подателÑÑ‚ и получателÑÑ‚ могат да четат това Ñъобщение."
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr "Съобщение до %1$s в %2$s"
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr "Съобщение от %1$s в %2$s"
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr "Изпращане на Ñъобщение"
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr "Мобилен оператор на телефона ви."
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr "Преки ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾ %s"
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr "Ð’Ñички преки ÑъобщениÑ, изпратени до %s"
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr "Изпратени от Ð²Ð°Ñ Ð¿Ñ€ÐµÐºÐ¸ ÑъобщениÑ"
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr "Ð’Ñички преки ÑъобщениÑ, изпратени от %s"
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr "ЛипÑва текÑÑ‚ на Ñъобщението"
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr "ПолучателÑÑ‚ не е открит"
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+"Ðе може да изпращате преки ÑÑŠÐ¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð¾ "
+"хора, които не Ñа в ÑпиÑъка ви Ñ Ð¿Ñ€Ð¸Ñтели."
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr "%s / ОтбелÑзани като любими от %s"
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr "%s бележки отбелÑзани като любими от %s / %s."
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr "%s отбелÑза бележката ви като любима"
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr "%1$s току-що отбелÑза като любима бележката ви от %2$s.\n\n"
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+"ДобавÑне на профила ви в Twitter за "
+"автоматично препращане на бележките ви и там."
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr "ÐаÑтройки за Twitter"
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr "Профил в Twitter"
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr "Текущ проверен профил в Twitter"
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr "Име в Twitter"
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr "Без интервали, молÑ!"
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr "Парола за Twitter"
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr "Ðвтоматично препращане на бележките ми към Twitter"
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr "Препращане на локалните отговори Ñ \"@\" и към Twitter"
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr "Ðбониране за приÑтелите ми от Twitter тук"
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+"ПотребителÑкото име може да Ñъдържа Ñамо "
+"цифри, малки и главни букви и добна черта. Може да е най-много 15 знака."
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr "Грешка при ÑверÑване на данните ви Ñ Twitter"
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr "Грешка при извличане данните на профила \"%s\" от Twitter."
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr "Грешка при запиÑване наÑтройките за Twitter"
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr "ÐаÑтройките за Twitter Ñа запазени."
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr "Това не е вашиÑÑ‚ профил в Twitter."
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr "Грешка при премахване на профила от Twitter"
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr "Профилът от Twitter е премахнат."
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr "Грешка при запиÑване наÑтройките за Twitter"
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr "ÐаÑтройките за Twitter Ñа запазени."
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr "Резултат от командата"
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr "Командата е изпълнена"
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr "Грешка при изпълнение на командата"
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr "За Ñъжаление тази команда вÑе още не Ñе поддържа."
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr "Ðбонаменти: %1$s\n"
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr "ПотребителÑÑ‚ нÑма поÑледна бележка"
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr "Бележката е отбелÑзана като любима."
+
+#: classes/Command.php:166
+#, php-format
+#, fuzzy
+msgid "%1$s (%2$s)"
+msgstr "%1$s (%2$s)"
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr "Пълно име: %s"
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr "МеÑтоположение: %s"
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr "Домашна Ñтраница: %s"
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr "ОтноÑно: %s"
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+"Съобщението е твърде дълго. Ðай-много "
+"може да е 140 знака, а Ñте въвели %d."
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr "ПрÑкото Ñъобщение до %s е изпратено."
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr "Грешка при изпращане на прÑкото Ñъобщение"
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr "Уточнете името на потребителÑ, за когото Ñе абонирате."
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr "Ðбонирани Ñте за %s."
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr "Уточнете името на потребителÑ, от когото Ñе отпиÑвате."
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr "ОтпиÑани Ñте от %s."
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr "Командата вÑе още не Ñе поддържа."
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr "Уведомлението е изключено."
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr "Грешка при изключване на уведомлението."
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr "Уведомлението е включено."
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr "Грешка при включване на уведомлението."
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr "Команди:\n"
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr "Грешка при вмъкване на Ñъобщението."
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr "Грешка при обновÑване на бележката Ñ Ð½Ð¾Ð² URL-адреÑ."
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr "Потребител без ÑъответÑтващ профил в ÑиÑтемата."
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr "Ðово лично Ñъобщение от %s"
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr "%1$s (%2$s) ви изпрати лично Ñъобщение:\n\n"
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr "Само потребителÑÑ‚ може да Ð¾Ñ‚Ð²Ð°Ñ€Ñ ÑобÑтвената Ñи кутиÑ."
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr "Тази форма Ñама ще Ñе изпрати автоматично."
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr "Любими"
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr "Любими бележки на %s"
+
+#: lib/personal.php:66
+msgid "User"
+msgstr "Потребител"
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr "ВходÑщи"
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr "Получените от Ð²Ð°Ñ ÑъобщениÑ"
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr "ИзходÑщи"
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr "Изпратените от Ð²Ð°Ñ ÑъобщениÑ"
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr "Twitter"
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr "ÐаÑтройки за Ð¸Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ð¸Ñ Ñ Twitter"
+
+#: lib/util.php:1718
+msgid "To"
+msgstr "До"
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr "Грешка при обработка на Ñъобщението"
diff --git a/locale/ca_ES/LC_MESSAGES/laconica.mo b/locale/ca_ES/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..4710aa6c5
--- /dev/null
+++ b/locale/ca_ES/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/ca_ES/LC_MESSAGES/laconica.po b/locale/ca_ES/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..7de9466c6
--- /dev/null
+++ b/locale/ca_ES/LC_MESSAGES/laconica.po
@@ -0,0 +1,2831 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Oscar Ciutat <oscarciutat@gmail.com>\n"
+"Language-Team: Catalan <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Cerca \"%s\" al flux"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"excepte les següents dades privades: contrasenya, adreça de correu "
+"electrònic, adreça de missatgeria instantània, número de telèfon."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s ara està escoltant els teus avisos a %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s ara està escoltant els teus avisos a %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Atentament,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "estat de %1$s a %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Flux públic de %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s i amics"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** és un servei de microblogging de "
+"[%%site.broughtby%%**](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** és un servei de microblogging."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+". Els col·laboradors han de ser citats pel seu nom complet o sobrenom. "
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+"1-64 lletres en minúscula o números, sense signes de puntuació o "
+"espais"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 o més caràcters"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 o més caràcters, i no te n'oblidis!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"S'ha enviat un codi de confirmació a l'adreça de missatgeria instantània "
+"que has afegit. Has d'acceptar que %s et pugui enviar missatges."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Sobre"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Acceptar"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Afegir"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Afegir OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adreça"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Totes les subscripcions"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Totes les actualitzacions per a %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Totes les actualitzacions que corresponen a la frase a cercar \"%s\" "
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Ja estàs connectat."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Ja estàs subscrit!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Autoritzar subscripció"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Iniciar sessió automàticament en el futur; no utilitzar en ordinadors "
+"compartits!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar actualitzat."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"A l'espera d'una confirmació per a aquesta adreça. Busca al teu compte "
+"Jabber/GTalk un missatge amb més instruccions. (Has afegit a %s a la teva "
+"llista d'amics?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Anterior »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Biografia"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "La biografia és massa llarga (màx. 140 caràcters)."
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "No es pot llegir l'URL de l'avatar '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "No es pot guardar la nova contrasenya."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Cancel·lar"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Impossible crear una instància de l'objecte OpenID"
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Impossible normalitzar aquest Jabber ID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Canviar"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Canviar contrasenya"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Confirmar"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Confirmar adreça"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Confirmació cancel·lada."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Codi de confirmació no trobat. "
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Connectar-se"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Connectar-se a un compte existent"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Posar-se en contacte"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "No s'ha pogut crear el formulari OpenID: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "No s'ha pogut redirigir al servidor: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "No s'ha pogut guardar la informació de l'avatar"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "No s'ha pogut guardar la informació del nou perfil"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "No s'ha pogut confirmar el correu electrònic."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "No s'han pogut convertir els senyals de petició a senyals d'accés."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "No s'ha pogut crear la subscripció."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "No s'ha pogut eliminar la confirmació de correu electrònic."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "No s'ha pogut eliminar la subscripció."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "No s'ha pogut obtenir un senyal de petició."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "No s'ha pogut inserir el codi de confirmació."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "No s'ha pogut inserir una nova subscripció."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "No s'ha pogut guardar el perfil."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "No s'ha pogut actualitzar l'usuari."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Crear"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Crear un nou usuari amb aquest sobrenom."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Crear nou compte"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Crear nou compte per a un OpenID que ja té un usuari."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Adreça actual Jabber/Gtalk confirmada."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Actualment"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Error de BD en inserir resposta: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Explica'ns alguna cosa sobre tu i els teus interessos en 140 caràcters"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Correu electrònic"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Adreça de correu electrònic"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "L'adreça de correu electrònic ja existeix."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Confirmació de l'adreça de correu electrònic"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Escriu un sobrenom o una adreça de correu electrònic."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Error en autoritzar senyal"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Error en connectar usuari a OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Error en connectar usuari."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Error en inserir avatar"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Error en inserir el nou perfil"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Error en inserir avís"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Error en inserir perfil remot"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Error en guardar confirmació de l'adreça."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Error en guardar perfil remot"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Error en guardar perfil."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Error en guardar l'usuari."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Error en guardar usuari; invàlid."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Error en configurar l'usuari."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Error en actualitzar el perfil"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Error en actualitzar el perfil remot"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Error amb el codi de confirmació."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Sobrenom ja existent."
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "Preguntes freqüents"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Error en actualitzar avatar."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed per a amics de %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed per a respostes a %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Per raons de seguretat, si us plau torna a escriure el teu nom d'usuari i "
+"contrasenya abans de canviar la teva configuració."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Nom complet"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "El teu nom és massa llarg (màx. 255 caràcters)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Ajuda"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Inici"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Pàgina personal"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "La pàgina personal no és un URL vàlid."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "Adreça de missatgeria instantània"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Configuració de missatgeria instantània"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Si ja tens un compte, inicia una sessió amb el teu nom d'usuari i "
+"contrasenya per a connectar-lo al teu OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Si vols afegir un compte OpenID, introdueix-lo en el camp de sota i clica "
+"\"Afegir\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Si has oblidat o has perdut la teva contrasenya, pots rebre una de nova a "
+"l'adreça de correu electrònic que vas utilitzar per a registrar el teu "
+"compte."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Contrasenya antiga incorrecta"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Nom d'usuari o contrasenya incorrectes."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"S'han enviat instruccions per a recuperar la teva contrasenya a l'adreça de "
+"correu electrònic registrada."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "L'URL de l'avatar '%s' és invàlid"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "La pàgina personal '%s' és invàlida"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "L'URL de la llicència '%s' és invàlid"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "El contingut de l'avís és invàlid"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "L'URI de l'avís '%s' és invàlid"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "L'URL de l'avís '%s' és invàlid"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "L'URL del perfil '%s' és invàlid."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "L'URL del perfil és invàlid (format incorrecte)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "URL del perfil retornat pel servidor invàlid."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Mida invàlida."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Nom d'usuari o contrasenya invàlids."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Utilitza el software de microblogging [Laconica](http://laconi.ca), versió "
+"%s, disponible sota la [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Aquest Jabber ID ja està sent utilitzat per un altre usuari."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Adreça Jabber o GTalk, per exemple \"NomUsuari@exemple.org\". Primer, "
+"assegura't d'afegir a %s a la teva llista d'amics en el teu client de "
+"missatgeria instantània o a GTalk."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Ubicació"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "La ubicació és massa llarga (màx. 255 caràcters)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Inici de sessió"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Inici de sessió amb un compte [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Inicia una sessió amb el teu nom d'usuari i la teva contrasenya. Encara no "
+"tens un nom d'usuari? [Crea](%%action.register%%) un nou compte o prova "
+"[OpenID] (%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Sortir"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Contrasenya oblidada o perduda?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Membre des de"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog de %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "El meu text i els meus fitxers estan disponibles sota "
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nou sobrenom"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Nou avís"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nova contrasenya"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nova contrasenya guardada correctament. Has iniciat una sessió."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Sobrenom"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Aquest sobrenom ja existeix. Prova un altre. "
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"El sobrenom ha de tenir només lletres minúscules i números i no pot tenir "
+"espais."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Sobrenom no permès."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Sobrenom de l'usuari que vols seguir"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Sobrenom o correu electrònic"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Cap Jabber ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Cap petició d'autorització!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Cap codi de confirmació."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Cap contingut!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Cap identificador."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Cap sobrenom retornat pel servidor remot."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Cap sobrenom."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Cap confirmació pendent per a cancel·lar."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Cap URL de perfil retornar pel servidor."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Cap adreça de correu electrònic registrada per aquest usuari."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Cap petició trobada!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Cap resultat"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Cap mida."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "No existeix aquest compte OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "No existeix aquest document."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "No existeix aquest avís."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "No existeix aquest codi de recuperació."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "No existeix aquesta subscripció"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "No existeix aquest usuari."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Ningú a mostrar!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "No és un codi de recuperació."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Jabber ID no vàlid"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "OpenID no vàlid."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Adreça de correu electrònic no vàlida."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Sobrenom no vàlid."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "URL de perfil no vàlid (serveis incorrectes)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "URL de perfil no vàlid (XRDS no definit)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "URL de perfil no vàlid (cap document YADIS)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "No és una imatge o és un fitxer corrupte."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "No autoritzat."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Resposta inesperada!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "No connectat."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "No estàs subscrit!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Feed d'avisos de %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Avís sense perfil"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Avisos"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Antiga contrasenya"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Configuració del compte OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Auto-enviament d'OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Accés OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Autenticació OpenID cancel·lada."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Autenticació OpenID fallida: %s."
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Error OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID eliminat."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Configuració OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Càrrega parcial."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Contrasenya"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "La contrasenya i la confirmació no coincideixen."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "La contrasenya ha de tenir 6 o més caràcters."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Recuperació de contrasenya sol·licitada"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Contrasenya guardada."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Les contrasenyes no coincideixen."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Cerca de gent"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Personal"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Si us plau, revisa aquestes dades per a estar segur que desitges "
+"subscriure't als avisos d'aquest usuari. Si no has demanat subscriure't als "
+"avisos de ningú, clica \"Cancel·lar\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Enviar un avís quan el meu estat Jabber/GTalk canvii."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Preferències"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Preferències guardades."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Privacitat"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Problema en guardar l'avís."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Perfil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL del perfil"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Configuració del perfil"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Perfil desconegut"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Públic"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Feed del flux públic"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Línia temporal pública"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Recuperar"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Recuperar contrasenya"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Codi de recuperació d'un usuari desconegut."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registrar-se"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Rebutjar"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Recorda'm"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Perfil remot sense perfil corresponent"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Subscripció remota"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Eliminar"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Eliminar OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Si elimines el teu únic OpenID no podràs tornar a entrar! Si necessites "
+"eliminar-lo, afegeix un altre abans."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Respostes"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Respostes a %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Restablir"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Restablir contrasenya"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Igual a la contrasenya de dalt"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Guardar"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Cercar"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Feed del flux de cerca"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Troba avisos a %%site.name%% per contingut. Separa els termes de cerca amb "
+"espais; han de ser majors a 3 caràcters."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Troba gent a %%site.name%% per nom, ubicació o interessos. Separa els "
+"termes de cerca amb espais; han de ser majors a 3 caràcters."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Enviar"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Enviar-me avisos per Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Configuració"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Configuració guardada."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Algú ja té aquest OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Alguna cosa estranya ha passat."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Font"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Estadístiques"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "No s'ha trobat l'OpenID emmagatzemat."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Subscriure's"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Subscriptors"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Subscripció autoritzada"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Subscripció rebutjada"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Subscripcions"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Error del sistema en pujar el fitxer."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Cerca de text"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Aquest OpenID no és teu."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Aquesta adreça ja ha estat confirmada."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Aquest codi de confirmació no és per a tu!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Aquest fitxer és massa gran."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Aquest ja és el teu Jabber ID."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Aquest no és el teu Jabber ID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Aquesta adreça de missatgeria instantània és incorrecta."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Massa llarg. La longitud màxima és de 140 caràcters."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "L'adreça \"%s\" ha estat confirmada per al teu compte."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "L'adreça ha estat eliminada."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"S'ha autoritzat la subscripció, però no s'ha enviat un URL de retorn. "
+"Llegeix de nou les instruccions per a saber com autoritzar la subscripció. "
+"El teu identificador de subscripció és:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"S'ha rebutjat la subscripció, però no s'ha enviat un URL de retorn. "
+"Llegeix de nou les instruccions per a saber com rebutjar la subscripció "
+"completament."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Aquestes són les persones que escolten els avisos de %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Aquestes són les persones que escolten els teus avisos."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Aquestes són les persones que %s escolta."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Aquestes són les persones que escoltes."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Aquest codi de confirmació és massa vell. Si us plau comença de nou."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Aquest formulari s'hauria d'enviar automàticament. En cas contrari, clica "
+"el botó d'enviament per a anar al teu proveïdor d'OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Aquesta és la primera vegada que accedeixes a %s. Per tant, hem de "
+"connectar el teu OpenID a un compte local. Pots crear-ne un de nou o "
+"connectar-te amb el teu, si el tens."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Aquesta pàgina no està disponible en un tipus de mèdia que acceptis."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Per a subscriure't, pots [iniciar una sessió](%%action.login%%), o "
+"[registrar](%%action.register%%) un nou compte. Si ja tens un en un [servei "
+"de microblogging compatible](%%doc.openmublog%%), escriu l'URL del teu "
+"perfil a sota."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL del teu web, blog o perfil en un altre lloc"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL del teu perfil en un altre servei de microblogging compatible"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Enviament de formulari inesperat."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Restabliment de contrasenya inesperat."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Versió desconeguda del protocol OMB."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Tret que s'especifiqui el contrari, el contingut d'aquest web és propietat "
+"dels seus col·laboradors i està disponible sota la"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Tipus d'adreça %s desconeguda"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Cancel·lar subscripció"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Versió OMB no suportada"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Format d'imatge no suportat."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Pujar"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Puja un nou \"avatar\" (imatge d'usuari) aquí. No pots editar la imatge una "
+"vegada carregada, per tant assegura't que sigui més o menys quadrada. A "
+"més, ha d'estar sota la llicència del lloc web. Utilitza una foto que "
+"sigui teva i que vulguis compartir."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"Utilitzat només per a actualitzacions, anuncis i recuperació de "
+"contrasenyes"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "L'usuari que vols seguir no existeix."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "L'usuari no té perfil."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Sobrenom de l'usuari"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Què tal, %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "On ets, per exemple \"Ciutat, Estat (o Regió), País\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Tipus d'imatge incorrecte per a '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Mida d'imatge incorrecta per a '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Ja tens aquest OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Ja t'has connectat!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Pots canviar la teva contrasenya aquí. Tria una de bona!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Pots crear un nou compte i començar a enviar avisos."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr "Pots eliminar un OpenID del teu compte clicant el botó \"Eliminar\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Pots enviar i rebre avisos via [missatges instantanis](%%doc.im%%) de "
+"Jabber/GTalk. Configura la teva adreça i opcions a sota."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Pots actualitzar la informació del teu perfil personal per a que la gent "
+"sàpiga més sobre tu."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Pots utilitzar la subscripció local!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "No pots registrar-te si no estàs d'acord amb la llicència."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "No ens vas enviar aquest perfil"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "T'has identificat. Escriu una nova contrasenya a continuació."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "El teu URL OpenID"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+"El teu nom d'usuari en aquest servidor, o la teva adreça de correu "
+"electrònic registrada."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) et permet accedir a molts llocs amb un mateix "
+"compte d'usuari. Administra els teus OpenID associats aquí."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "fa pocs segons"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "fa %d dies"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "fa %d hores"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "fa %d minuts"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "fa %d mesos"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "fa un dia"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "fa un minut"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "fa un mes"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "fa un any"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "fa una hora"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "en resposta a..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "resposta"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "repeteix la contrasenya anterior"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Següent"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/cs_CZ/LC_MESSAGES/laconica.mo b/locale/cs_CZ/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..8957da675
--- /dev/null
+++ b/locale/cs_CZ/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/cs_CZ/LC_MESSAGES/laconica.po b/locale/cs_CZ/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..002d9c0ab
--- /dev/null
+++ b/locale/cs_CZ/LC_MESSAGES/laconica.po
@@ -0,0 +1,2838 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+#, fuzzy
+msgid " Search Stream for \"%s\""
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Hledej ve Streamu \"%s\"\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"až na tyto privátní data: heslo, emailová adresa, IM adresa, telefonní "
+"Äíslo."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1 od teÄ naslouchá tvým sdÄ›lením v %2"
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1 naslouchá vašim sdělením na %s. \n"
+"\n"
+"\t%3\n"
+"\n"
+"S úctou váš,\n%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1 statusů na %2"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Veřejný \"Stream\""
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s a přátelé"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** je služba microblogů, kterou pro vás poskytuje "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** je služba mikroblogů."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "Přispěvatelá by měly být zmíněny přezdívkou nebo celým jménem"
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 znaků nebo Äísel, bez teÄek, Äárek a mezer"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 a více znaků"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 a více znaků, a nezapomeňte"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Ověřující kód byl poslán na vloženou IM adresu. Musíte prokázat %s "
+"pro posílání zpráv."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "O nás"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Přijmout"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Přidat"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Přidej OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adresa"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Všechny odběry"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "VÅ¡echny aktualizace pro %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Všechny položky obsahující \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Již přihlášen"
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Již přihlášeno"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Autorizovaný odběr"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "PříštÄ› automaticky pÅ™ihlásit; ne pro poÄítaÄe, které používá "
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Obrázek"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Obrázek nahrán"
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Čakám na potvrzení této adresy. Zkontrolujte zprávy na vašem "
+"Jabber/GTalk úÄtu. (PÅ™idal jste si %s do vaÅ¡ich kontaktů?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Starší »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "O mÄ›"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Text je příliš dlouhý (maximální délka je 140 zanků)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Nelze pÅ™eÄíst adresu obrázku '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Nelze uložit nové heslo"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Zrušit"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Nelze dolozit zákaznický objekt OpenID"
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Nelze normalizovat JabberID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Změnit"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Změnit heslo"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Heslo znovu"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "PotvrÄ adresu"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "PotvrÄ zruÅ¡ení"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Potvrzující kód nebyl nalezen"
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Připojit"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "ZruÅ¡ existující úÄet"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Kontakt"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Nelze vytvořit OpenID z: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Nelze přesměrovat na server: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Nelze uložin informace o obrázku"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Nelze uložit nové informace do profilu"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Nelze potvrdit email"
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Nelze konvertovat řetězec požadavku na přístupový řetězec."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Nelze vytvořit odebírat"
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Nelze smazat potvrzení emailu"
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Nelze smazat odebírání"
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Nelze získat řetězec požadavku."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Nelze vložit potvrzující kód"
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Nelze vložit odebírání"
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Nelze uložit profil"
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Nelze aktualizovat uživatele"
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Vytvořit"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Vytvořit nového uživatele s touto přezdívkou"
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "VytvoÅ™it nový úÄet"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Pro toto OpenID již uživatel existuje"
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Potvrzené Jabber/GTalk adresy"
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Nyní"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Chyba v DB při vkládání odpovědi: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Popiš sebe a své zájmy ve 140 znacích"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Email"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Emailová adresa"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Emailová adresa již existuje"
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Potvrzení emailové adresy"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Zadej přezdívku nebo emailovou adresu"
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Chyba potvrujícího řetězce"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Chyba při propojení uživatele na OpenID"
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Chyba přihlašování uživatele"
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Chyba při kládání obrázku"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Chyba při vkládání nového profilu"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Chyba při vkládání nového sdělení"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Chyba při vkládaní vzdáleného profilu"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Chyba při ukládání potvrzení adresy"
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Chyba při ukládnání vzdáleného profilu"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Chyba při ukládaní profilu"
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Chyba při ukládaní uživatele"
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Chyba při ukládaní uživatele; neplatný"
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Chyba nastavení uživatele"
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Chyba při aktualizaci profilu"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Chyba při aktualizaci vzdáleného profilu"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Chyba v ověřovacím kódu"
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Existující jméno"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "FAQ"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Nahrávání obrázku selhalo."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74 ../actions/allrss.php:64
+#: actions/all.php:61 actions/allrss.php:64
+#, php-format
+#, fuzzy
+msgid "Feed for friends of %s"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Feed přítel uživatele: %s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#: ../actions/replies.php:65 actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+#, fuzzy
+msgid "Feed for replies to %s"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Feed odpovědí na %s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr "Z bezpeÄnostních důvodů, prosím zadejte znovu své jméno a heslo."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Celé jméno"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Jméno je moc dlouhé (maximální délka je 255 znaků)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Nápověda"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Domů"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Moje stránky"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Stránka není platnou URL."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM adresa"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM nastavení"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Pokud již máte úÄet, pÅ™ihlaÅ¡te se pomocí pÅ™ezdívky a hesla. A poté "
+"propojte úÄet s OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Pokud chcete pÅ™idat OpenID k vaÅ¡emu úÄtu, zadejte OpenID do pole níže "
+"a klikněte na \"Přidat\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Pokud jste zapomněl nebo stratil heslo, můžete si nechat zaslat nové na "
+"vaší emailovou adresu uloženou u vaÅ¡eho úÄtu."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Neplatné heslo"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Neplatné jméno nebo heslo"
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Návod jak obnovit heslo byl odeslát na vaší emailovou adresu "
+"zaregistrovanou u vaÅ¡eho úÄtu."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Neplatná adresa obrázku '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Neplatná adresa '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Neplatná adresa licence '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Neplatný obsah sdělení"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Neplatná uri sdělení"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Neplatná url sdělení"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Neplatná adresa profilu '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Neplatná adresa profilu (špatný formát)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Neplatná adresa profilu, vrácená serverem"
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Neplatná velikost"
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Neplatné jméno nebo heslo"
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Běží na [Laconica](http://laconi.ca/) mikroblogovací program, verze %s, "
+"dostupná pod [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID již patří jinému uživateli"
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber nebo GTalk adresy, například \"jmeno@neco.cz\". Neprve se ujistěte "
+"že jste přidal %s do vašeho seznamu kontaktů."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Umístění"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Umístění příliš dlouhé (maximálně 255 znaků)"
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Přihlásit"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "PÅ™ihlaste se pomocí [OpenID](%%doc.openid%%) úÄtu."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Přihlaste se pomocí vaší prezdívky a hesla. Zatím nejste "
+"zaregistrován? [Registrovat](%%action.register%%) nový úÄet, nebo "
+"vyzkoušejte [OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Odhlásit"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Ztracené nebo zapomenuté heslo?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "ÄŒlenem od"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Mikroblog od %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Mé texty a soubory jsou k dispozici pod"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nová přezdívka"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Nové sdělení"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nové heslo"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nové heslo bylo uloženo. Nyní jste přihlášen."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Přezdívka"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Přezdívku již někdo používá. Zkuste jinou"
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "PÅ™ezdívka může obsahovat pouze malá písmena a Äísla bez mezer"
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Přezdívka není povolena"
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Přezdívka uživatele, kterého chcete sledovat"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Přezdívka nebo email"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Žádné Jabber ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Žádné potvrení!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Žádný potvrzující kód."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Žádný obsah!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Žádné id"
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Nebyla poskytnuta žádná přezdívka od servru."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Žádná přezdívka."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "NeÄeká žádné potvrzení na zruÅ¡ení."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Nebylo vráceno žádné URL profilu od servu."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Žádný registrovaný email pro tohoto uživatele."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Žádný požadavek nebyl nalezen!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Žádné výsledky."
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Žádná velikost"
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Žádné takové OpenID"
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Žádný takový dokument."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Žádné takové oznámení."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Žádný takový obnovující kód."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Žádné takové odebírání"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Žádný takový uživatel."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Nikdo k zobrazení!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Není obnovujícím kódem"
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Není platným Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Není platným OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Není platnou mailovou adresou."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Není platnou přezdívkou."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Neplatný adresa profilu (nesprává služba)"
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Není platnou adresou profulu (XRDS nedefinováno)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Není platnou adresou profilu (není YADIS dokumentem)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Není obrázkem, nebo jde o poškozený soubor."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Neautorizován."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "NeÄekaná odpovÄ›Ä."
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Nepřihlášen"
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Nepřihlášen!"
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+#, fuzzy
+msgid "Notice feed for %s"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Feed sdělení pro %s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Sdělení nemá profil"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Sdělení"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Staré heslo"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Nastavení OpenID úÄtu"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Přihlásit automaticky pomocí OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID přihlášení"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID adresa"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Přihlašovaní pomocí OpenID zrušeno"
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Přihlašovaní pomocí OpenID selhalo: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID selhalo: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID odstraněno"
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Nastavení OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "ČásteÄné náhrání."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Heslo"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Heslo a potvrzení nesouhlasí"
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Heslo musí být alespoň 6 znaků dlouhé"
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Žádost o obnovu hesla"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Heslo uloženo"
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Hesla nesouhlasí"
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Hledání lidí"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Osobní"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Prosím zkontrolujte tyto detailu, a ujistěte se že opravdu chcete "
+"odebírat sdělení tohoto uživatele. Pokud ne, ask to subscribe to "
+"somone's notices, klikněte na \"Zrušit\""
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Poslat oznámení, když se změní můj Jabber/Gtalk status."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Nastavení"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Nastavení uloženo"
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Soukromí"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Problém při ukládání sdělení"
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "Adresa Profilu"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Nastavené Profilu"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Neznámý profil"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Veřejné"
+
+#: ../actions/public.php:54 actions/public.php:54
+#, fuzzy
+msgid "Public Stream Feed"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Veřejný Stream Feed\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Veřejné zprávy"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Obnovit"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Obnovit"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Obnovyt kód pro neznámého uživatele"
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registrovat"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Odmítnout"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Zapamatuj si mÄ›"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Vzdálený profil s nesouhlasícím profilem"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Vzdálený odběr"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Odstranit"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Odstranit OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Odstranění jediného OpenID, bude mít za následek nemožnost dalšího "
+"přihlášení, nejprve přidejte jiné OpenID"
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Odpovědi"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Odpovědi na %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Reset"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Resetovat heslo"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Stejné jako heslo výše"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Uložit"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Hledat"
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+#, fuzzy
+msgid "Search Stream Feed"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Hledat ve Stream Feed\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Hledej sdělení na %%site.name%% podle obsahu. Minimální délka musí "
+"být alespoň 3 znaky"
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Hledej uživatele na %%site.name%% podle jejich jména, místa, nebo "
+"zájmů. Minimální délka musí být alespoň 3 znaky"
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Odeslat"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Zasílat oznámení pomocí Jabber/GTalk"
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Nastavení"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Nastavení uloženo"
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Někdo jiný již má toto OpenID"
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Něco zvláštního se stalo"
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Zdroj"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Statistiky"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Uložené OpenID nebylo nalezeno."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Odebírat"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Odběratelé"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Odběr autorizován"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Odběr odmítnut"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Odběry"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Chyba systému při nahrávání souboru"
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Vyhledávání textu"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Toto OpenID vám nepatří"
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Adresa již byla potvrzena"
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Tento potvrzující kód vám nepatří!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Soubor je příliš velký"
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Toto je již vaše Jabber"
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Toto není váš Jabber"
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Toto je špatná IM adresa"
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Je to příliš dlouhé. Maximální sdělení délka je 140 znaků"
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "Adresa \"%s\" byla potvrzena pro váš úÄet"
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Adresa byla odstraněna"
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Odběr byl potvrzen, ale neprošla žádná callback adresa. Zkontrolujte v "
+"nápovědě jak správně postupovat při potvrzování odběru. Váš "
+"řetězec odběru je:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Odebírání bylo zamítnuto, ale neprošla žádná callback adresa. "
+"Zkontrolujte v nápovědě jak správně postupovat při zamítání odběru"
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Toto jsou lidé, kteří naslouchají %s sdělením"
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Toto jsou lidé, kteří naslouchají vašim sdělením "
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Toto jsou lidé, jejiž sdělením %s naslouchá"
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Toto jsou lidé, jejiž sdělením nasloucháte"
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Tento potvrzující kód je příliš starý Prosím zkuste znovu"
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Tento formulář by se měl odeslat sám, pokud ne tak klikněte na "
+"tlašítko pro přechod k vašemu OpenID poskytovately."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Toto je poprvé co jste se přihlásil na %s proto musíme propojit vaše "
+"OpenID k naÅ¡emu úÄtu. Můžete buÄ vytvoÅ™it nový úÄet, nebo propojit "
+"OpenID k vaÅ¡emu již existujícímu úÄtu, pokud již takový máte."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Tato stránka není k dispozici v typu média která přijímáte."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Pro odebírání, se musíte [přihlásit](%%action.login%%), nebo "
+"[registrovat](%%action.register%%) nový úÄet. Pokud již máte úÄet na "
+"[kompatibilních mikroblozích](%%doc.openmublog%%), vložte níže asdresu "
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "Adresa vašich stránek, blogu nebo profilu na jiných stránkách."
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "Adresa profilu na jiných kompatibilních mikroblozích."
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "NeÄekaná forma submission."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "NeÄekané resetování hesla."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Neznámá verze OMB protokolu."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Pokud není uvedeno jinak, obsah těchto stránek chráněn autorským "
+"právem, patří přispěvatelů a je k dipozici pod"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Neznámý typ adresy %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Odhlásit"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Nepodporovaná verze OMB."
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Nepodporovaný formát obrázku."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Upload"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Zde nahrajte nový obrázek k vašemu profilu. Po tom co nahrajete obrázek "
+"ho již nemůžete editovat, proto se ujistÄ›te že jde víceménÄ› o Ätverec."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Použije se pouze pro aktualizace, oznámení a obnovu hesla."
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "Úživatel, kterému nasloucháte neexistuje."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "Uživatel nemá profil."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Přezdívka"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Co se děje %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Místo. Město, stát."
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Neplatný typ obrázku pro '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Neplatná velikost obrázku '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Již máte toto OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Již jste přihlášen"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Zde můžeze změnit heslo."
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Můžete vytvožit nový úÄet a zaÄít posílat oznámení."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Můžete odstranit OpenID z vaÅ¡eho úÄtu, kliknutím na tlaÄítko "
+"\"Odebrat\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Můžete odesílat nebo přijámat sdělení pomocí Jabber/GTalk "
+"[zpráv](%%doc.im%%).Zadejte svou adresu níže."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Zde můžete aktualizovat informace o vašem profilu, aby se lidé o vás "
+"mohli více dozvědět."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Můžete použít místní odebírání."
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Nemůžete se registrovat, pokud nesouhlasíte s licencí."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Neodeslal jste nám profil"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Byl jste identifikován. Zadejte nové heslo"
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Vaše OpenID adresa"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Vaše přezdívka na tomto servu, nebo váš email zadaný při registraci"
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) vám dovoluje použít stejný úÄet na více "
+"stránkách. Zde může spravovat vaše OpenID"
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "před pár sekundami"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "před %d dny"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "asi před %d hodinami"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "asi před %d minutami"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "asi před %d mesíci"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "asi přede dnem"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "asi před minutou"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "asi před měsícem"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "asi před rokem"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "asi před hodinou"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "odpověd na ..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "odpovÄ›Ä"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "stejné jako heslo výše"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Novější"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/de_DE/LC_MESSAGES/laconica.mo b/locale/de_DE/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..ce6144851
--- /dev/null
+++ b/locale/de_DE/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/de_DE/LC_MESSAGES/laconica.po b/locale/de_DE/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..8ed32027b
--- /dev/null
+++ b/locale/de_DE/LC_MESSAGES/laconica.po
@@ -0,0 +1,2854 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Markus Heurung <muhh@byzero.de>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Suche im Stream nach \"%s\""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"außer folgenden privaten Daten: Passwort, E-Mail, Adresse, IM Adresse, "
+"Telefonnummer."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s hat deine Nachrichten auf %2$s abonniert."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s hat deine Nachrichten auf %2$s abonniert.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Gruß,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s Status auf %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s öffentlicher Stream"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s und Freunde"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** ist ein Microbloggingdienst von "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** ist ein Microbloggingdienst."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+"Lizenz. Die ursprünglichen Autoren sollten mit ihrem vollen Namen oder "
+"Nutzernamen genannt werden."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 Kleinbuchstaben oder Ziffern, keine Sonder- oder Leerzeichen"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 oder mehr Zeichen"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 oder mehr Zeichen, und nicht vergessen!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Ein Bestätigungscode wurde an die IM Adresse geschickt, die du hinzugefügt "
+"hast. Du musst zulassen, dass %s dir Nachrichten schicken darf."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Ãœber"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Akzeptieren"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "OpenID hinzufügen"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adresse"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Alle Abonnements"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Alle Aktualisierungen für %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Alle Aktualisierungen, die den Suchbegriff \"%s\" enthalten"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Bereits eingeloggt."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Bereits abonniert!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Abonnement bestätigen"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Automatisch einloggen; Nicht bei gemeinsam genutzten PCs!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar aktualisiert."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Warte auf Bestätigung dieser Adresse. Eine Nachricht mit weiteren Anweisung "
+"sollte in deinem Jabber/GTalk Konto eingehen. (Hast du %s zu deiner "
+"Freundeliste hinzugefügt?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Früher »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Biografie"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Die Biografie ist zu lang (max. 140 Zeichen)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Konnte Avatar-URL nicht öffnen '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Konnte neues Passwort nicht speichern"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Konnte kein OpenID consumer Objekt erzeugen."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Konnte diese Jabber ID nicht normalisieren"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Ändern"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Passwort ändern"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Bestätigen"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Adresse bestätigen"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Bestätigung abgebrochen."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Bestätigungscode nicht gefunden."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Verbinden"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Verbinde bestehendes Konto"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Kontakt"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Konnte OpenID-Formular nicht erstellen: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Konnte nicht zum Server umleiten: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Konnte Avatarinfo nicht speichern"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Neue Profildaten konnten nicht gespeichert werden."
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "E-Mail konnte nicht bestätigt werden."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Konnte Anfrage-Token nicht in Zugriffs-Token umwandeln."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Konnte Abonnement nicht erstellen."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Konnte E-Mailbestätigung nicht löschen."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Konnte Abonnement nicht löschen."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Konnte keinen Anfrage-Token bekommen."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Konnte keinen Bestätigungscode einfügen."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Konnte neues Abonnement nicht eintragen."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Konnte Profil nicht speichern."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Konnte Benutzerdaten nicht aktualisieren."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Erstellen"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Neuen Nutzer mit diesem Nicknamen erstellen."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Neues Konto anlegen"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Erzeugen eines Kontos zu einer OpenID, die schon einem Nutzer gehört."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Aktuelle bestätigte Jabber/GTalk-Adresse"
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Momentan"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Datenbankfehler beim Einfügen der Antwort: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Beschreibe dich selbst in 140 Zeichen"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "E-Mail"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "E-Mail Adresse "
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Diese E-Mail Adresse existiert bereits."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Bestätigung der E-Mail Adresse"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Gib einen Spitznamen oder eine E-Mail Adresse ein."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Fehler beim Autorisieren des Tokens"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Fehler beim Verbinden des Nutzers mit der OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Fehler beim Verbinden des Nutzers."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Fehler beim Einfügen des Avatars"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Neues Profil konnte nicht angelegt werden"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Fehler beim Einfügen der Nachricht"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Fehler beim Einfügen des entfernten Profils"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Fehler beim Speichern der Adressbestätigung."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Fehler beim Speichern des entfernten Profils"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Fehler bei Speichern des Profils."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Fehler beim Speichern des Nutzers."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Fehler beim Speichern des Nutzers, ungültig."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Fehler bei den Nutzereinstellungen."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Fehler beim Update des Profils"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Fehler beim Update des entfernten Profils"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Fehler beim Bestätigungscode."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Nick wird bereits verwendet"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "FAQ"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Aktualisierung des Avatars fehlgeschlagen"
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed der Freunde von %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed der Antworten an %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Bitte geben Sie aus Sicherheitsgründen ihren Benutzernamen und ihr Passwort "
+"ein, bevor die Änderungen an ihren Einstellungen vorgenommen werden."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Vollständiger Name"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Ihr vollständiger Name ist zu lang (maximal 255 Zeichen)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Hilfe"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Startseite"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Homepage"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Homepage ist kein gültiger URL."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM Adresse"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM Einstellungen"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Wenn du schon ein Konto hast, dann melde dich mit Nutzernamen und Passwort "
+"an, um deine OpenID zu verknüpfen."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Wenn du deinem Konto eine OpenID hinzufügen möchtest, dann trage sie hier "
+"ein und klicke auf \"Hinzufügen\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Wenn du deine Passwort verlegt hast, kannst du dir ein Neues an die "
+"E-Mailadresse schicken lasssen, die für dein Konto eingetragen ist."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Altes Passwort falsch"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Falscher Benutzername oder Passwort."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Anweisungen für die Wiederherstellung deines Passworts wurden an deine "
+"hinterlegte E-Mailadresse geschickt."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Ungültiger Avatar-URL '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Ungültige Homepage '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Ungültige Lizenz-URL '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Ungültiger Nachrichteninhalt"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Ungülte Nachrichten-URI"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Ungültige Nachrichten-URL"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Ungültige Profil-URL '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Ungültige Profil-URL (falsches Format)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Server antwortete mit ungültiger Profil-URL."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Ungültige Größe."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Benutzername oder Passwort falsch."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+" Es wird mit der Microbloggingsoftware [Laconica](http://laconi.ca/) "
+"(Version %s) betrieben, die unter der [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html) erhältlich "
+"ist."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Diese Jabber ID wird bereits von einem anderen Benutzer verwendet."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber- oder GoogleTalk-Adresse, z.B. \"UserName@example.org\". Aber "
+"versichere dich zuerst, dass du %s in deine Kontaktliste in deinem IM "
+"Programm oder GTalk aufgenommen hast."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Aufenthaltsort"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Aufenthaltsort ist zu lang (maximal 255 Zeichen)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Einloggen"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Mit [OpenID](%%doc.openid%%) Konto einloggen"
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Melde dich mit Nutzernamen und Passwort an. Du hast noch keinen Nutzernamen? "
+"[Registriere](%%action.register%%) ein neues Konto oder versuche es mit "
+"[OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Abmelden"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Passwort vergessen?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Mitglied seit"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog von %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Meine Texte und Daten sind verfügbar unter"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Neuer Nutzername"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Neue Nachricht"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Neues Passwort"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Neues Passwort erfolgreich gespeichert. Du bist jetzt eingeloggt."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Nutzername"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Nutzername wird bereits verwendet. Suche dir einen anderen aus."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"Der Nutzername darf nur aus Kleinbuchstaben und Ziffern bestehen. "
+"Leerzeichen sind nicht erlaubt."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Nutzername nicht erlaubt."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Nutzername des Nutzers, dem du folgen möchtest"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Nutzername oder E-Mail"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Keine Jabber-ID"
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Keine Bestätigungsanfrage!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Kein Bestätigungs-Code."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Kein Inhalt!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Keine ID."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Der entferne Server hat keinen Nutzernamen geliefert."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Kein Nutzername."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Keine ausstehende Bestätigung, die abgebrochen werden kann."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Der entfernte Server hat keine Profil-URL geliefert."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Der Nutzer hat keine registrierte E-Mailadresse."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Keine Anfrage gefunden!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Keine Ergebnisse"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Keine Größe."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Diese OpenID ist nicht bekannt."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Unbekanntes Dokument."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Unbekannte Nachricht."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Unbekannter Wiederherstellungscode."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Unbekanntes Abonnement"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Unbekannter Benutzer."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Niemand anzuzeigen!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Kein Wiederherstellungscode."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Ungültige Jabber-ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Ungültige OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Ungültige E-Mail-Adresse."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Ungültiger Nutzername."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Ungültige Profil-URL (falsche Dienste)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Ungültige Profil-URL (XRDS nicht definiert)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Ungültige Profil-URL (kein YADIS-Dokument)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Kein Bild oder defekte Datei."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Nicht autorisiert."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Unerwartete Antwort!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Nicht eingeloggt."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Nicht abonniert!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Feed der Nachrichten von %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Nachricht hat kein Profil"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Nachrichten"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Altes Passwort"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "OpenID Account erstellen"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "OpenID Automatische Ãœbertragung"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID Anmeldung"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID-URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "OpenID-Authentifizierung abgebrochen."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID-Authentifizierung fehlgeschlagen: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID-Fehler: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID entfernt."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID-Einstellungen"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Unvollständiger Upload"
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Passwort"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Passwort und seine Bestätigung stimmen nicht überein."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Passwort muss mehr als 6 Zeichen enthalten"
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Wiederherstellung des Passworts angefordert"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Passwort gespeichert."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Passwörter stimmen nicht überein."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Suche nach anderen Nutzern"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Eigene"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Bitte überprüfe diese Angaben, um sicher zu gehen, dass du die Nachrichten "
+"dieses Nutzers abonnieren möchtest. Wenn du das nicht wolltest, klicke auf "
+"\"Abbrechen\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Schicke eine Nachricht, wenn sich mein Jabber/GTalk Status verändert."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Einstellungen"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Einstellungen gesichert."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Privatsphäre"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Problem bei Speichern der Nachricht."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "Profil-URL"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Profil Einstellungen"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Profil unbekannt"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Öffentlich"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Feed des öffentlichen Streams"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Öffentliche Zeitleiste"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Wiederherstellung"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Stelle Passwort wieder her"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Wiederherstellungscode für unbekannten Nutzer."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registrieren"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Ablehnen"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Anmeldedaten merken"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Entferntes Profil ohne ein passendes Profil"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Entferntes Abonnement"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Entfernen"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Entferne OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Wenn du deine einzige OpenID entfernst, kannst du dich nicht mehr einloggen! "
+"Falls du sie also entfernen musst, füge zuerst eine neue OpenID hinzu."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Antworten"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Antworten an %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Zurücksetzen"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Passwort zurücksetzen"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Gleiches Passwort wie zuvor"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Speichern"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Suchen"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Stream-Feed suchen"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Dursuche den Inhalt der Nachrichten auf %%site.name%%. Trenne mehrere "
+"Suchbegriffe durch Leerzeichen. Ein Suchbegriff muss aus mindestens 3 "
+"Zeichen bestehen."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Durchsuche die Namen, Orten oder Interessen der Nutzer von %%site.name%%. "
+"Trenne mehrere Suchbegriffe durch Leerzeichen. Ein Suchbegriff muss aus "
+"mindestens 3 Zeichen bestehen."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Senden"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Schicke mir Nachrichten mittels Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Einstellungen"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Einstellungen gespeichert."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Jemand anderes hat schon diese OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Etwas eigenartiges ist passiert."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Quellcode"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Statistiken"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Gespeicherte OpenID nicht gefunden."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Abonnieren"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Abonnenten"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Abonnement autorisiert"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Abonnement abgelehnt"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Abonnements"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Systemfehler beim hochladen der Datei."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Volltextsuche"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Diese OpenID gehört dir nicht."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Diese Adresse wurde bereits bestätigt."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Dieser Bestätigungscode ist nicht für dich!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Diese Datei ist zu groß."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Diese JabberID hast du schon angegeben."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Dies ist nicht deine JabberID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Das ist die falsche IM Adresse."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+"Das war zu lang. Die Länge einer Nachricht ist auf 140 Zeichen "
+"beschränkt."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "Die Adresse \"%s\" wurde für dein Konto bestätigt."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Die Adresse wurde entfernt."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Das Abonnement wurde bestätigt, aber es wurde keine Callback-URL "
+"zurückgegeben. Lies nochmal die Anweisungen der Site, wie Abonnements "
+"bestätigt werden. Dein Abonnement-Token ist:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Das Abonnement wurde abgelehnt, aber es wurde keine Callback-URL "
+"zurückgegeben. Lies nochmal die Anweisungen der Site, wie Abonnements "
+"vollständig abgelehnt werden. Dein Abonnement-Token ist:"
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Dies sind die Leute, die %ss Nachrichten lesen."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Dies sind die Leute, die deine Nachrichten lesen."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Dies sind die Leute, deren Nachrichten %s liest."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Dies sind die Leute, deren Nachrichten du liest."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Der Bestätigungscode ist zu alt. Bitte fange nochmal von vorne an."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Dieses Formular sollte automatisch übermittelt werden. Wenn nicht, dann "
+"klicke auf \"Senden\", um zu deinem OpenID-Anbieter zu gelangen."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Dies ist das erste Mail, dass du dich bei %s einloggst. Deshalb müssen wir "
+"deine OpenID mit einem lokalen Konto verbinden. Du kannst entweder ein neues "
+"Konto erstellen oder mit einem vorhandenen Konto anmelden."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Dies Seite liegt in keinem von dir akzeptierten Mediatype vor."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Für ein Abonnement kannst du dich entweder [einloggen](%%action.login%%) "
+"oder ein neues Konto [registrieren](%%action.register%%). Wenn du schon ein "
+"Konto auf einer [kompatiblen Microbloggingsite](%%doc.openmublog%%) hast, "
+"dann gib deine Profil-URL unten an."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+"URL deiner Homepage, deines Blogs, oder deines Profils auf einer anderen "
+"Site"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "Profil-URL bei einem anderen kompatiblen Microbloggingdienst"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Unerwartete Formulareingabe."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Unerwarteter Passwortreset."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Unbekannte OMB-Protokollversion."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Wenn nicht anders angegeben unterstehen die Inhalte dieser Site dem "
+"Urheberrecht der Autoren und sind unter der"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Nicht erkannter Adresstyp %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Abbestellen"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Nicht unterstützte OMB-Version"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Bildformat wird nicht unterstützt."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Hochladen"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Hier kannst du einen neuen \"Avatar\" (Nutzerbild) hochladen. Du kannst das "
+"Bild nach dem Hochladen nicht mehr verändern, also stell bitte sicher, dass "
+"es halbwegs quadratisch ist. Es muss auch unter der Lizenz der Site zur "
+"Verfügung gestellt werden. Nutze also ein Bild, das dir gehört und das du "
+"auch weitergeben möchtest."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"Wird nur für Updates, wichtige Mitteilungen und zur "
+"Passwortwiederherstellung verwendet"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "Aufgeführte Nutzer existiert nicht."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "Benutzer hat kein Profil."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Benutzername"
+
+#: ../lib/util.php:969 ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+#, fuzzy
+msgid "What's up, %s?"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Was ist los, %s?\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Wo du bist, z.B. \"Stadt, Gebiet, Land\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Falscher Bildtyp für '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Falsche Bildgröße bei '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Diese OpenID hast du schon angegeben!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Du bist bereits angemeldet!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Hier kannst du dein Passwort ändern. Wähle ein Gutes!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Du kannst ein neues Konto erstellen, um Nachrichten zu verschicken."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Du kannst eine OpenID aus deinem Konto entfernen, indem du auf \"Entfernen\" "
+"klickst."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Du kannst Nachrichten mittels [Jabber/GTalk IM](%%doc.im%%) empfangen und "
+"senden. Stelle deine Adresse und Einstellungen unten ein."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Du kannst dein Profil auf den neusten Stand bringen, damit andere Leute mehr "
+"über dich erfahren können."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Du kannst ein lokales Abonnement erstellen!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+"Du kannst dich nicht registrieren, wenn du die Lizenz nicht "
+"akzeptierst."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Dieses Profil hast du uns nicht geschickt"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Du wurdest indentifiziert. Bitte trage unten ein neues Passwort ein."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Deine OpenID URL"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Dein Benutzername oder E-Mail Adresse auf diesem Server."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"Mit [OpenID](%%doc.openid%%) kannst du dich bei mehreren Sites mit demselben "
+"Nutzerkonto anmelden. Hier kannst du deine verknüpften OpenIDs "
+"verwalten."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "vor wenigen Sekunden"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "vor %d Tagen"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "vor %d Stunden"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "vor %d Minuten"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "vor %d Monaten"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "vor einem Tag"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "vor einer Minute"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "vor einem Monat"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "vor einem Jahr"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "vor einer Stunde"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "als Antwort auf... "
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "antworten"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "Gleiches Passwort wie zuvor"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Später"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr "von"
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "%s öffentliche Zeitleiste"
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr "%s Status"
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr "%s Zeitleiste"
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(Sie sollten in Kürze eine E-Mail mit der Anleitung zur Überprüfung Ihrer "
+"Mailadresse erhalten.)"
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"1-64 kleingeschriebene Buchstaben oder Zahlen, keine Satz- oder Leerzeichen. "
+"Pflicht."
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr "6 oder mehr Buchstaben. Pflicht."
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Ein Bestätigungscode wurde an die angegebene E-Mail Adresse geschickt. "
+"Überprüfen Sie Ihren Posteingang (auch den Spamordner!) für den Code und "
+"Anweisungen, wie dieser benutzt wird."
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Ein Bestätigungscode wurde an die von Ihnen angegebene Telefonnummer "
+"gesandt. Überprüfen Sie bitte Ihren Posteingang (auch den Spamordner!) auf "
+"den Code und die Anweisungen, um ihn zu benutzen."
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr "API-Methode nicht gefunden!"
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr "API-Methode im Aufbau."
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr "Sind sie sicher, dass sie diese Nachricht löschen wollen?"
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+"Abonniere automatisch alle Kontakte, die mich abonnieren (sinnvoll für "
+"Nicht-Menschen)"
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "Warte auf die Bestätigung dieser Telefonnummer."
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Die Nachricht konnte nicht gelöscht werden."
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "Ändern der Profileinstellungen"
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Bestätigungscode"
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr "Konnte keine Statusmeldungen finden."
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "Sprache"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr "Kein Code eingegeben"
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr "Unbekannter Befehl"
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr "In welcher Zeitzone befinden Sie sich üblicherweise?"
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/el/LC_MESSAGES/laconica.mo b/locale/el/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..b40616592
--- /dev/null
+++ b/locale/el/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/el/LC_MESSAGES/laconica.po b/locale/el/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..0171b419c
--- /dev/null
+++ b/locale/el/LC_MESSAGES/laconica.po
@@ -0,0 +1,2872 @@
+# 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: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+#: actions/finishopenidlogin.php:88 actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/publicrss.php:62 actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr "Δημόσια Ïοή %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51 actions/all.php:47
+#: actions/allrss.php:60 actions/twitapistatuses.php:155 lib/personal.php:51
+#, php-format
+msgid "%s and friends"
+msgstr "%s και οι φίλοι του/της"
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: actions/finishopenidlogin.php:79 actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/password.php:42 actions/profilesettings.php:181
+msgid "6 or more characters"
+msgstr "6 ή πεÏισσότεÏοι χαÏακτήÏες"
+
+#: ../actions/recoverpassword.php:180 actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 ή πεÏισσότεÏοι χαÏακτήÏες και μην το ξεχάσετε!"
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr "ΠεÏί"
+
+#: ../actions/userauthorization.php:119 actions/userauthorization.php:126
+msgid "Accept"
+msgstr "Αποδοχή"
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+#: actions/emailsettings.php:63 actions/imsettings.php:64
+#: actions/openidsettings.php:58 actions/smssettings.php:71
+#: actions/twittersettings.php:85
+msgid "Add"
+msgstr "ΠÏοσθήκη"
+
+#: ../actions/openidsettings.php:43 actions/openidsettings.php:44
+msgid "Add OpenID"
+msgstr "ΠÏοσθήκη OpenID"
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39 actions/emailsettings.php:39
+#: actions/imsettings.php:40 actions/smssettings.php:39
+msgid "Address"
+msgstr "ΔιεÏθυνση"
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/showstream.php:273 actions/showstream.php:288
+msgid "All subscriptions"
+msgstr ""
+
+#: ../actions/publicrss.php:64 actions/publicrss.php:50
+#, php-format
+msgid "All updates for %s"
+msgstr ""
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr ""
+
+#: ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/userauthorization.php:77 actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:104 ../actions/register.php:178
+#: actions/register.php:192
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#: actions/profilesettings.php:34
+msgid "Avatar"
+msgstr ""
+
+#: ../actions/avatar.php:113 actions/profilesettings.php:350
+msgid "Avatar updated."
+msgstr ""
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/util.php:1318 lib/util.php:1452
+msgid "Before »"
+msgstr ""
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+#: actions/profilesettings.php:82 actions/register.php:184
+msgid "Bio"
+msgstr "ΒιογÏαφικό"
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr "Το βιογÏαφικό είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿ (μέγιστο 140 χαÏακτ.)."
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+#: actions/profilesettings.php:404 actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr "ΑδÏνατη η αποθήκευση του νέου κωδικοÏ"
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62 actions/emailsettings.php:58
+#: actions/imsettings.php:59 actions/smssettings.php:62
+msgid "Cancel"
+msgstr "ΑκÏÏωση"
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../actions/password.php:45 actions/profilesettings.php:184
+msgid "Change"
+msgstr "Αλλαγή"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../actions/password.php:32 actions/profilesettings.php:36
+msgid "Change password"
+msgstr "Αλλαγή κωδικοÏ"
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+#: actions/profilesettings.php:182 actions/recoverpassword.php:187
+#: actions/register.php:169 actions/smssettings.php:65
+msgid "Confirm"
+msgstr "Επιβεβαίωση"
+
+#: ../actions/confirmaddress.php:90 actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr "Επιβεβαίωση διεÏθυνσης"
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245 actions/emailsettings.php:256
+#: actions/imsettings.php:230 actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr "Η επιβεβαίωση ακυÏώθηκε."
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Ο κωδικός επιβεβαίωσης δεν βÏέθηκε."
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:91 actions/finishopenidlogin.php:97
+msgid "Connect"
+msgstr "ΣÏνδεση"
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr "ΣÏνδεση με υπάÏχων λογαÏιασμό"
+
+#: ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr "Επικοινωνία"
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:162 actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr ""
+
+#: ../actions/updateprofile.php:155 actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr "ΑδÏνατη η αποθήκευση των νέων πληÏοφοÏιών του Ï€Ïοφίλ"
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:127 actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+#: actions/profilesettings.php:299 actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr "ΑδÏνατη η αποθήκευση του Ï€Ïοφίλ."
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:84 actions/finishopenidlogin.php:90
+msgid "Create"
+msgstr "ΔημιουÏγία"
+
+#: ../actions/finishopenidlogin.php:70 actions/finishopenidlogin.php:76
+msgid "Create a new user with this nickname."
+msgstr "ΔημιουÏγία νέου χÏήστη με αυτό το ψευδώνυμο."
+
+#: ../actions/finishopenidlogin.php:68 actions/finishopenidlogin.php:74
+msgid "Create new account"
+msgstr "ΔημιουÏγία νέου λογαÏιασμοÏ"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../actions/showstream.php:356 actions/showstream.php:367
+msgid "Currently"
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+#: actions/profilesettings.php:84 actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr ""
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87 actions/register.php:172
+#: actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr "Η διεÏθυνση email υπάÏχει ήδη."
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr "Επιβεβαίωση διεÏθυνσης email"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/recoverpassword.php:191 actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr "Εισάγετε ψευδώνυμο ή διεÏθυνση email."
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:253 actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:240 actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../lib/openid.php:226 lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr ""
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr ""
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:80 actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr ""
+
+#: ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr "Συχνές εÏωτήσεις"
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:64 actions/all.php:61
+#: actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Ροή φίλων του/της %s"
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#: actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Ροή απαντήσεων Ï€Ïος τον/την %s"
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Για λόγους ασφαλείας, παÏακαλώ εισάγετε "
+"ξανά το όνομα χÏήστη και τον κωδικό σας, Ï€Ïιν αλλάξετε τις Ïυθμίσεις σας."
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr "Ονοματεπώνυμο"
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr "Το ονοματεπώνυμο είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î¿ (μέγιστο 255 χαÏακτ.)."
+
+#: ../lib/util.php:322 lib/util.php:338
+msgid "Help"
+msgstr "Βοήθεια"
+
+#: ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr "ΑÏχή"
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+#: actions/profilesettings.php:79 actions/register.php:181
+msgid "Homepage"
+msgstr "ΑÏχική σελίδα"
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+#: actions/profilesettings.php:210 actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr "Η αÏχική σελίδα δεν είναι έγκυÏο URL."
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/imsettings.php:60 actions/imsettings.php:61
+msgid "IM Address"
+msgstr ""
+
+#: ../actions/imsettings.php:33 actions/imsettings.php:33
+msgid "IM Settings"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr "Λάθος παλιός κωδικός"
+
+#: ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr "Λάθος όνομα χÏήστη ή κωδικός"
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Οδηγίες για την ανάκτηση του ÎºÏ‰Î´Î¹ÎºÎ¿Ï ÏƒÎ±Ï‚ "
+"έχουν σταλεί στην διεÏθυνση email που έχετε καταχωÏίσει στον λογαÏιασμό σας."
+
+#: ../actions/updateprofile.php:114 actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:98 actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:87 actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111 actions/finishopenidlogin.php:241
+#: actions/register.php:103 actions/register.php:121
+msgid "Invalid username or password."
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+#: actions/profilesettings.php:85 actions/register.php:187
+msgid "Location"
+msgstr "Τοποθεσία"
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr "Η τοποθεσία είναι Ï€Î¿Î»Ï Î¼ÎµÎ³Î¬Î»Î· (μέγιστο 255 χαÏακτ.)."
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310 actions/login.php:97
+#: actions/login.php:106 actions/openidlogin.php:77 lib/util.php:326
+msgid "Login"
+msgstr "ΣÏνδεση"
+
+#: ../actions/openidlogin.php:44 actions/openidlogin.php:52
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Συνδεθείτε με έναν λογαÏιασμό [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Συνδεθείτε με το όνομα χÏήστη και τον "
+"κωδικό σας. Δεν έχετε όνομα χÏήστη ακόμα; "
+"Κάντε [εγγÏαφή](%%action.register%%) για ένα νέο λογαÏιασμό ή δοκιμάστε το [OpenID](%%action.openidlogin%%). "
+
+#: ../lib/util.php:308 lib/util.php:324
+msgid "Logout"
+msgstr "ΑποσÏνδεση"
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr "Χάσατε ή ξεχάσατε τον κωδικό σας;"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/showstream.php:300 actions/showstream.php:315
+msgid "Member since"
+msgstr "Μέλος από"
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+#: actions/finishopenidlogin.php:85 actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr "Îέο ψευδώνυμο"
+
+#: ../actions/newnotice.php:87 actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+#: actions/profilesettings.php:180 actions/recoverpassword.php:185
+msgid "New password"
+msgstr "Îέος κωδικός"
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr "Ψευδώνυμο"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr "Το ψευδώνυμο είναι ήδη σε χÏήση. Δοκιμάστε κάποιο άλλο."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+#: actions/finishopenidlogin.php:171 actions/profilesettings.php:203
+#: actions/register.php:74 actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "Το ψευδώνυμο Ï€Ïέπει να έχει μόνο πεζοÏÏ‚ χαÏακτήÏες και χωÏίς κενά."
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr "Το ψευδώνυμο αυτό δεν επιτÏέπεται."
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr "Το ψευδώνυμο του χÏήστη που θέλετε να παÏακολουθήσετε"
+
+#: ../actions/recoverpassword.php:162 actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr "Ψευδώνυμο ή email"
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/imsettings.php:156 actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr ""
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr ""
+
+#: ../actions/newnotice.php:44 actions/newmessage.php:53
+#: actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/userbyid.php:32 actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr ""
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:226 actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr ""
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82 actions/all.php:34
+#: actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/imsettings.php:167 actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr ""
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr ""
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:120 actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:113 actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27 actions/disfavor.php:29 actions/favor.php:30
+#: actions/finishaddopenid.php:29 actions/logout.php:33
+#: actions/newmessage.php:28 actions/newnotice.php:29 actions/subscribe.php:28
+#: actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:316 actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/password.php:39 actions/profilesettings.php:178
+msgid "Old password"
+msgstr ""
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314 lib/settingsaction.php:90
+#: lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr "Το OpenID αφαιÏέθηκε."
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Ρυθμίσεις OpenID"
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+#: actions/finishopenidlogin.php:96 actions/login.php:102
+#: actions/register.php:167
+msgid "Password"
+msgstr "Κωδικός"
+
+#: ../actions/recoverpassword.php:288 actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr "Ο κωδικός και η επιβεβαίωση του δεν ταυτίζονται."
+
+#: ../actions/recoverpassword.php:284 actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr "Ο κωδικός Ï€Ïέπει να είναι 6 χαÏακτήÏες ή πεÏισσότεÏοι."
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+#: actions/profilesettings.php:408 actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr "Ο κωδικός αποθηκεÏτηκε."
+
+#: ../actions/password.php:61 ../actions/register.php:88
+#: actions/profilesettings.php:380 actions/register.php:98
+msgid "Passwords don't match."
+msgstr "Οι κωδικοί δεν ταυτίζονται."
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr "ΠÏοσωπικά"
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94 actions/emailsettings.php:86
+#: actions/imsettings.php:68 actions/smssettings.php:94
+#: actions/twittersettings.php:70
+msgid "Preferences"
+msgstr "ΠÏοτιμήσεις"
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr "Οι Ï€Ïοτιμήσεις αποθηκεÏτηκαν"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr ""
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60 lib/personal.php:60
+#: lib/settingsaction.php:84
+msgid "Profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/recoverpassword.php:166 actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:156 actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr ""
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr ""
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#: actions/replies.php:47 actions/repliesrss.php:62 lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:183 actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:173 actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300 lib/searchaction.php:84
+#: lib/util.php:316
+msgid "Search"
+msgstr ""
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/invite.php:137 ../lib/util.php:1172 actions/invite.php:145
+#: lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:192 actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:296 actions/showstream.php:311
+msgid "Statistics"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+#: actions/finishopenidlogin.php:188 actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+#: actions/showstream.php:328 actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:170 actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/imsettings.php:233 actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+#: actions/emailsettings.php:244 actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+#: actions/newnotice.php:49 actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:92 actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+#: actions/profilesettings.php:81 actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:276 actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:209 actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../actions/avatar.php:68 actions/profilesettings.php:161
+msgid "Upload"
+msgstr ""
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82 actions/all.php:41
+#: actions/avatarbynickname.php:48 actions/foaf.php:47 actions/replies.php:41
+#: actions/showfavorites.php:41 actions/showstream.php:44
+#: actions/twitapiaccount.php:80 actions/twitapifavorites.php:68
+#: actions/twitapistatuses.php:235 actions/twitapistatuses.php:609
+#: actions/twitapiusers.php:87 lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+#: actions/profilesettings.php:87 actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:135 actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+#: actions/finishopenidlogin.php:38 actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr ""
+
+#: ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr ""
+
+#: ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr ""
+
+#: ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117 actions/noticesearch.php:136
+#: actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124 actions/noticesearch.php:143
+#: actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/en_GB/LC_MESSAGES/laconica.mo b/locale/en_GB/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..a62774f34
--- /dev/null
+++ b/locale/en_GB/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/en_GB/LC_MESSAGES/laconica.po b/locale/en_GB/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..726a771b1
--- /dev/null
+++ b/locale/en_GB/LC_MESSAGES/laconica.po
@@ -0,0 +1,2875 @@
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.pot (laconica) #-#-#-#-#\n"
+"Project-Id-Version: laconica\n"
+"Report-Msgid-Bugs-To: john@nextraweb.com\n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: 2008-08-05 01:44+0100\n"
+"Last-Translator: John Drinkwater <john@nextraweb.com>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Search Stream for ‘%s’"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"except this private data: password, e-mail address, IM address, phone "
+"number."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s is now listening to your notices on %2$s."
+
+#: ../actions/subscribe.php:86 ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s’s status on %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s Public Stream"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s and friends"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)"
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** is a microblogging service."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ". Contributors should be attributed by full name or nickname."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 lowercase letters or numbers, no punctuation or spaces"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 or more characters"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 or more characters, and don’t forget it!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "About"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Accept"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Add"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Add OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Address"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "All subscriptions"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "All updates for %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "All updates matching search term ‘%s’"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Already logged in."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Already subscribed!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Authorise subscription"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Automatically log-in in the future; not for shared computers!"
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#: actions/profilesettings.php:34
+msgid "Avatar"
+msgstr ""
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar updated."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy "
+"list?)"
+
+# erm, not sure what to do here, » is recognised as a quotation mark.
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Before →"
+
+#: ../actions/profilesettings.php:52 ../actions/profilesettings.php:49
+#: ../actions/register.php:170 actions/profilesettings.php:82
+#: actions/register.php:184
+msgid "Bio"
+msgstr ""
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Bio is too long (max 140 chars)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Can’t read avatar URL ‘%s’"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Can’t save new password."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Cancel"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Cannot instantiate OpenID consumer object."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Cannot normalise Jabber ID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Change"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Change password"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Confirm"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Confirm Address"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Confirmation cancelled."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Confirmation code not found."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Connect"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Connect existing account"
+
+#: ../lib/util.php:304 ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr ""
+
+# Thought form was incorrect
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Could not create OpenID from: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Could not redirect to server: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Could not save avatar info"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Could not save new profile info"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Couldn’t confirm e-mail."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Couldn’t convert request tokens to access tokens."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Couldn’t create subscription."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Couldn’t delete e-mail confirmation."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Couldn’t delete subscription."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Couldn’t get a request token."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Couldn’t insert confirmation code."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Couldn’t insert new subscription."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Couldn’t save profile."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Couldn’t update user."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Create"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Create a new user with this nickname."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Create new account"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Creating new account for OpenID that already has a user."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Current confirmed Jabber/GTalk address."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Currently"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "DB error inserting reply: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Describe yourself and your interests in 140 chars"
+
+#: ../actions/register.php:181 ../actions/register.php:158
+#: ../actions/register.php:161 ../lib/settingsaction.php:87
+#: actions/register.php:172 actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr ""
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "E-mail address"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "E-mail address already exists."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "E-mail address confirmation"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Enter a nickname or e-mail address."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Error authorising token."
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Error connecting user to OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Error connecting user."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Error inserting avatar"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Error inserting new profile"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Error inserting notice"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Error inserting remote profile"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Error saving address confirmation."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Error saving remote profile"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Error saving the profile."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Error saving the user."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Error saving user; invalid."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Error setting user."
+
+# Added full stop
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Error updating profile."
+
+# Added full stop
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Error updating remote profile."
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Error with confirmation code."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Existing nickname"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "F.A.Q."
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Failed to update avatar."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed for friends of %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed for replies to %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Full name"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Full name is too long (max 255 chars)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Help"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Home"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Homepage"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Homepage is not a valid URL."
+
+# possibly use I.M.
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM Address"
+
+# requires full stops?
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM Settings"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"If you already have an account, log-in with your username and password to "
+"connect it to your OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click ‘Add’."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"If you’ve forgotten or lost your password, you can get a new one sent to "
+"the e-mail address you have stored in your account."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Incorrect old password"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Incorrect username or password."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Instructions for recovering your password have been sent to the e-mail "
+"address registered to your account."
+
+# Avatar?
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Invalid avatar URL ‘%s’"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Invalid homepage ‘%s’"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Invalid licence URL ‘%s’"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Invalid notice content"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Invalid notice URI"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Invalid notice URL"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Invalid profile URL ‘%s’."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Invalid profile URL (bad format)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Invalid profile URL returned by server."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Invalid size."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Invalid username or password."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"Licence](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID already belongs to another user."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber or GTalk address, like ‘UserName@example.org’. First, make sure "
+"to add %s to your buddy list in your IM client or on GTalk."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Location"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Location is too long (max 255 chars)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Log-in"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Log-in with an [OpenID](%%doc.openid%%) account."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Log-in with your username and password. Don’t have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Log out"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Lost or forgotten password?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Member since"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog by %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "My text and files are available under"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "New nickname"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "New notice"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "New password"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "New password successfully saved. You are now logged in."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Nickname"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Nickname already in use. Try another one."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "Nickname must have only lowercase letters and numbers and no spaces."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Nickname not allowed."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Nickname of the user you want to follow"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Nickname or e-mail"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "No Jabber ID."
+
+#: ../actions/userauthorization.php:128 ../actions/userauthorization.php:129
+#: actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "No confirmation code."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "No content!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "No id."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "No nickname provided by remote server."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "No nickname."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "No pending confirmation to cancel."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "No profile URL returned by server."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "No registered e-mail address for that user."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "No request found!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "No results"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "No size."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "No such OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "No such document."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "No such notice."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "No such recovery code."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "No such subscription"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "No such user."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Nobody to show!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Not a recovery code."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Not a valid Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Not a valid OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Not a valid e-mail address."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Not a valid nickname."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Not a valid profile URL (incorrect services)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Not a valid profile URL (no XRDS defined)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Not a valid profile URL (no YADIS document)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Not an image or corrupt file."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Not authorised."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Not expecting this response!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Not logged in."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Not subscribed!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Notice feed for %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Notice has no profile"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Notices"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Old password"
+
+#: ../lib/util.php:288 ../lib/settingsaction.php:96 ../lib/util.php:314
+#: lib/settingsaction.php:90 lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "OpenID Account Setup"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "OpenID Auto-Submit"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID Log-in"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "OpenID authentication cancelled."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID authentication failed: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID failure: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID removed."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID settings"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Partial upload."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Password"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Password and confirmation do not match."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Password must be 6 chars or more."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Password recovery requested"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+#: ../actions/recoverpassword.php:313 actions/profilesettings.php:408
+#: actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Passwords don’t match."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "People search"
+
+#: ../lib/stream.php:44 ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr ""
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Please verify the details to make sure that you want to subscribe to this "
+"user’s notices. If you didn’t just ask to subscribe to someone’s "
+"notices, click ‘Cancel’."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Post a notice when my Jabber/GTalk status changes."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Preferences"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Preferences saved."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Privacy"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Problem saving notice."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profile"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "Profile URL"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Profile settings"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Unknown profile"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr ""
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Public Stream Feed"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Public timeline"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Recover"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Recover password"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Recovery code for unknown user."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Register"
+
+#: ../actions/userauthorization.php:119 ../actions/userauthorization.php:120
+#: actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:99 ../actions/register.php:183
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr ""
+
+#: ../actions/updateprofile.php:69 ../actions/updateprofile.php:70
+#: actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr ""
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:49 ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#: ../lib/stream.php:56 actions/replies.php:47 actions/repliesrss.php:62
+#: lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:168 ../actions/recoverpassword.php:183
+#: actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:158 ../actions/recoverpassword.php:173
+#: actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277 ../lib/searchaction.php:84
+#: ../lib/util.php:300 lib/searchaction.php:84 lib/util.php:316
+msgid "Search"
+msgstr ""
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../lib/util.php:982 ../actions/invite.php:137 ../lib/util.php:1172
+#: actions/invite.php:145 lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/imsettings.php:71 ../actions/imsettings.php:70
+#: actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../lib/util.php:282 ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:183 ../actions/profilesettings.php:192
+#: actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../lib/util.php:302 ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:277 ../actions/showstream.php:296
+#: actions/showstream.php:311
+msgid "Statistics"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+#: ../actions/finishopenidlogin.php:246 actions/finishopenidlogin.php:188
+#: actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+#: ../actions/showstream.php:313 actions/showstream.php:328
+#: actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:309 ../actions/userauthorization.php:310
+#: actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Subscription rejected"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Subscriptions"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "System error uploading file."
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "That OpenID does not belong to you."
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:161 ../actions/imsettings.php:170
+#: actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:224 ../actions/imsettings.php:233
+#: actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:201 ../actions/emailsettings.php:226
+#: ../actions/imsettings.php:210 actions/emailsettings.php:244
+#: actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/newnotice.php:52 ../actions/newnotice.php:49
+#: ../actions/twitapistatuses.php:408 actions/newnotice.php:49
+#: actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:86 ../actions/confirmaddress.php:92
+#: actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/imsettings.php:241 ../actions/emailsettings.php:264
+#: ../actions/imsettings.php:250 ../actions/smssettings.php:274
+#: actions/emailsettings.php:282 actions/imsettings.php:258
+#: actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "These are the people who listen to %s’s notices."
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/recoverpassword.php:87 ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../lib/util.php:147 ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/profilesettings.php:48
+#: ../actions/register.php:169 actions/profilesettings.php:81
+#: actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/smssettings.php:135 actions/emailsettings.php:144
+#: actions/imsettings.php:118 actions/recoverpassword.php:39
+#: actions/smssettings.php:143 actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:237 ../actions/recoverpassword.php:276
+#: actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:245 ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:193 ../actions/showstream.php:209
+#: actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+#: ../actions/updateprofile.php:45 actions/postnotice.php:45
+#: actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../actions/avatar.php:68 actions/profilesettings.php:161
+msgid "Upload"
+msgstr ""
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/foaf.php:47
+#: ../actions/twitapiaccount.php:82 ../actions/twitapistatuses.php:319
+#: ../actions/twitapistatuses.php:685 ../actions/twitapiusers.php:82
+#: actions/all.php:41 actions/avatarbynickname.php:48 actions/foaf.php:47
+#: actions/replies.php:41 actions/showfavorites.php:41
+#: actions/showstream.php:44 actions/twitapiaccount.php:80
+#: actions/twitapifavorites.php:68 actions/twitapistatuses.php:235
+#: actions/twitapistatuses.php:609 actions/twitapiusers.php:87
+#: lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../lib/util.php:969 ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 ../actions/profilesettings.php:54
+#: ../actions/register.php:175 actions/profilesettings.php:87
+#: actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:127 ../actions/updateprofile.php:128
+#: actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:122 ../actions/updateprofile.php:123
+#: actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:164 ../actions/register.php:135
+#: actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+#: ../actions/register.php:61 actions/finishopenidlogin.php:38
+#: actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:62 ../actions/updateprofile.php:63
+#: actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:134 ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149 ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:814 ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:826 ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr ""
+
+#: ../lib/util.php:822 ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr ""
+
+#: ../lib/util.php:818 ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr ""
+
+#: ../lib/util.php:830 ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:824 ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:816 ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:828 ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:832 ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:820 ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101 ../actions/noticesearch.php:130
+#: ../actions/showstream.php:408 ../lib/stream.php:117
+#: actions/noticesearch.php:136 actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108 ../actions/noticesearch.php:137
+#: ../actions/showstream.php:415 ../lib/stream.php:124
+#: actions/noticesearch.php:143 actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+# erm, not sure what to do here, « is recognised as a quotation mark.
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "↠After"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/es/LC_MESSAGES/laconica.mo b/locale/es/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..fa655909f
--- /dev/null
+++ b/locale/es/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/es/LC_MESSAGES/laconica.po b/locale/es/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..4cc5a0719
--- /dev/null
+++ b/locale/es/LC_MESSAGES/laconica.po
@@ -0,0 +1,2839 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Busca \"%s\" en el Corriente"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"excepto los siguientes datos privados: contraseña, dirección de correo "
+"electrónico, dirección de mensajería instantánea, número de teléfono."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s ahora está escuchando tus avisos en %2$s"
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"\t%1$s ahora está escuchando tus avisos en %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Atentamente,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "estado de %1$s en %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Flujo público de %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s y amigos"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** es un servicio de microblogueo de "
+"[%%site.broughtby%%**](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** es un servicio de microblogueo."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ". Los colaboradores deben ser citados por su nombre completo o apodo."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+"1-64 letras en minúscula o números, sin signos de puntuación o "
+"espacios"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 o más caracteres"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 o más caracteres, ¡no te olvides!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Un código de confirmación fue enviado a la dirección de mensajería "
+"instantánea que agregaste. Debes aprobar a %s para que pueda enviarte "
+"mensajes."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Acerca de"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Aceptar"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Añadir"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Añadir OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Dirección"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Todas las suscripciones"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Todas las actualizaciones para %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Todas las actualizaciones que corresponden a la frase a buscar \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Ya estás conectado."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "¡Ya está suscrito!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Autorizar la suscripción"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Iniciar sesión automáticamente en el futuro. ¡No usar en ordenadores "
+"compartidos! "
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar actualizado"
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"A la espera de una confirmación para esta dirección. Busca en tu cuenta "
+"Jabber/GTalk un mensaje con más instrucciones. (¿Has añadido a %s a tu "
+"lista de amigos?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Antes"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Biografía"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "La biografía es demasiado larga (máx. 140 caracteres)."
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "No se puede leer el URL del avatar '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "No se puede guardar la nueva contraseña."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Imposible crear una instancia del objeto OpenID."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "No se puede normalizar este Jabber ID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Cambiar"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Cambiar contraseña"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Confirmar"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Confirmar la dirección"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Confirmación cancelada."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Código de confirmación no encontrado."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Conectarse"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Conectarse a una cuenta existente"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Ponerse en contacto"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "No se pudo crear el formulario OpenID: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "No se pudo redirigir al servidor: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "No se pudo guardar la información del avatar"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "No se pudo guardar la información del nuevo perfil"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "No se pudo confirmar el correo electrónico."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "No se pudieron convertir las señales de petición a señales de acceso."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "No se pudo crear la suscripción."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "No se pudo eliminar la confirmación de correo electrónico."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "No se pudo eliminar la suscripción."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "No se pudo obtener la señal de petición."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "No se pudo insertar el código de confirmación."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "No se pudo insertar una nueva suscripción."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "No se pudo guardar el perfil."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "No se pudo actualizar el usuario."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Crear"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Crear un nuevo usuario con este apodo."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Crear una nueva cuenta"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Crear nueva cuenta para un OpenID que ya tiene un usuario."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Dirección actual Jabber/Gtalk confirmada."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Actualmente"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Error de BD al insertar respuesta: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Cuéntanos algo sobre ti y tus intereses en 140 caracteres"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Correo electrónico"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Dirección de correo electrónico"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "La dirección de correo electrónico ya existe."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Confirmacion de correo electronico"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Ingresa un apodo o correo electronico"
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Error al autorizar señal"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Error al conectar tu usuario a OpenID"
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Error al conectar usuario"
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Error al insertar el avatar"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Error al insertar el nuevo perfil"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Error al insertar aviso"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Error al insertar perfil remoto"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Error al guardar confirmación de la dirección."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Error al guardar perfil remoto"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Error al guardar el perfil."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Error al guardar el usuario."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Error al guardar el usuario; inválido."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Error al configurar el usuario."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Error al actualizar el perfil"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Error al actualizar el perfil remoto"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Error con el código de confirmación."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Apodo existente"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "Preguntas Frecuentes"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Error al actualizar avatar."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed de los amigos de %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed de respuestas a %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Por razones de seguridad, por favor vuelve a escribir tu nombre de usuario y "
+"contraseña antes de cambiar tu configuración."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Nombre completo"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Tu nombre es demasiado largo (max. 255 carac.)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Ayuda"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Portada"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Página personal"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "La página personal no es un URL válido."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "Dirección de mensajería instantánea"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Configuración de mensajería instantánea"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Si ya tienes una cuenta, ingresa con tu nombre de usuario y contraseña para "
+"conectarla con tu OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Si quieres agregar una cuenta OpenID, ponla en el campo de abajo y haz clic "
+"en \"Añadir\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Si olvidaste o perdiste tu contraseña, puedes recibir una nueva en la "
+"dirección de correo electrónico que usaste para registrar tu cuenta."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Contraseña antigua incorrecta."
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Nombre de usuario o contraseña incorrectos."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Se enviaron instrucciones para recuperar tu contraseña a la dirección de "
+"correo registrada."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "El URL del avatar '%s' es inválido"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "El sitio '%s' es inválido"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "El URL de la licencia '%s' es inválido"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "El contenido del aviso es inválido"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "El URI del aviso es inválido"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "El URL del aviso es inválido"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "El URL del perfil '%s' es inválido"
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "El URL del perfil es inválido (formato incorrecto)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "URL del perfil devuelto por el servidor inválido."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Tamaño inválido."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Usuario o contraseña inválidos."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Usa el software de microblogueo [Laconica](http://laconi.ca), versión %s, "
+"disponible bajo la [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "El Jabber ID ya pertenece a otro usuario."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Dirección Jabber o GTalk, por ejemplo \"NombreUsuario@ejemplo.org\". "
+"Primero, asegúrate de agregar a %s a tu lista de amigos en tu cliente de "
+"mensajería instantánea o en GTalk."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Ubicación"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "La ubicación es demasiado larga (máx. 255 caracteres)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Inicio de sesión"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Inicio de sesión con una cuenta [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Inicia una sesión con tu usuario y contraseña. ¿Aún no tienes usuario? "
+"[Crea](%%action.register%%) una cuenta nueva o prueba [OpenID] "
+"(%%action.openidlogin%%). "
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Salir"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "¿Contraseña olvidada o perdida?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Miembro desde"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog por %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Mi texto y archivos están disponibles bajo"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nuevo apodo"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Nuevo aviso"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nueva contraseña"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nueva contraseña guardada correctamente. Has iniciado una sesión."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Apodo"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "El apodo ya existe. Prueba otro."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"El apodo debe tener solamente letras minúsculas y números y no puede tener "
+"espacios. "
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Apodo prohibido."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Apodo del usuario que quieres seguir"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Apodo o correo electrónico"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Ningún Jabber ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "¡Ninguna petición de autorización!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Ningún código de confirmación."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "¡Ningún contenido!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Ningún identificador."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Ningún apodo devuelto por el servidor remoto."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Ningún apodo."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Ninguna confirmación pendiente para cancelar."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Ningun URL de perfil devuelto por el servidor."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Ninguna dirección de correo electrónico registrada por este usuario."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "¡Ninguna petición encontrada!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Ningún resultado"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Ningún tamaño."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "No existe esa cuenta OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "No existe ese documento."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "No existe ese aviso."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "No existe ese código de recuperación."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "No existe esa suscripción"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "No existe ese usuario."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "¡Nadie a mostrar!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "No es un código de recuperación."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Jabber ID no válido"
+
+#: ../lib/openid.php:131 lib/openid.php:131
+#, fuzzy
+msgid "Not a valid OpenID."
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"La cuenta OpenID no es válida\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Correo electrónico no válido"
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Apodo no válido"
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "URL de perfil no válido (servicios incorrectos)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "URL de perfil no válido (XRDS no definido)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "URL de perfil no válido (ningún documento YADIS)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "No es una imagen o es un fichero corrupto."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "No autorizado."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "¡Respuesta inesperada!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "No conectado."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "¡No estás suscrito!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Feed de avisos de %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Aviso sin perfil"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Avisos"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Antigua contraseña"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "Cuenta OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Configuración de la Cuenta OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Auto-envío de OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Ingreso desde una cuenta OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL de la cuenta OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Autenticación OpenID cancelada."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Autenticación OpenID fallida: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Error OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID eliminado."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Configuración OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Carga parcial."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Contraseña"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "La contraseña y la confirmación no coinciden."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "La contraseña debe tener 6 o más caracteres."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Recuperación de contraseña solicitada"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Contraseña guardada"
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Las contraseñas no coinciden"
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Buscador de gente"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Personal"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Por favor revisa estos detalles para asegurar que deseas suscribirte a los "
+"avisos de este usuario. Si no pediste esta suscripción, haz clic en "
+"\"Cancelar\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Enviar un aviso cuando el estado de mi Jabber/GTalk cambie."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Preferencias"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Preferencias guardadas."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Privacidad"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Hubo un problema al guardar el aviso."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Perfil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL del perfil"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Configuración del perfil"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Perfil desconocido"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Público"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Feed del flujo público"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Línea temporal pública"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Recuperar"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Recuperar contraseña"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Código de recuperación para usuario desconocido."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registrarse"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Rechazar"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Recordarme"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Perfil remoto sin perfil coincidente"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Subscripción remota"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Eliminar"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Eliminar OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"¡Si eliminas tu único OpenID no podrás volver a entrar! Si necesitas "
+"eliminarlo, agrega otro antes."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Respuestas"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Respuestas a %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Restablecer"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Restablecer contraseña"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Igual a la contraseña de arriba"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Guardar"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Buscar"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Feed del flujo de búsqueda"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Buscar avisos en %%site.name%% por contenido. Separa los términos de "
+"búsqueda con espacios; deben tener una longitud mínima de 3 caracteres."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Buscar personas en %%site.name%% por nombre, ubicación o intereses. Separa "
+"los términos con espacios; deben tener una longitud mínima de 3 "
+"caracteres."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Enviar"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Enviarme avisos por Jabber/GTalk"
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Configuración"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Configuración guardada."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Alguien ya tiene este OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Algo raro pasó."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Fuente"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Estadísticas"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "No se ha encontrado el OpenID almacenado."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Suscribirse"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Suscriptores"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Suscripción autorizada"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Suscripción rechazada"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Suscripciones"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Error del sistema al cargar el archivo."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Búsqueda de texto"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Ese OpenID no es tuyo."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Esa dirección ya fue confirmada."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "¡Ese código de confirmación no es para ti!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Ese archivo es demasiado grande."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Ese ya es tu Jabber ID."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Ese no es tu Jabber ID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Esa dirección de mensajería instantánea es incorrecta."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Demasiado largo. La longitud máxima es de 140 caracteres. "
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "La dirección \"%s\" fue confirmada para tu cuenta."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "La dirección fue eliminada."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Se ha autorizado la suscripción, pero no se ha enviado un URL de retorno. "
+"Lee de nuevo las instrucciones para saber cómo autorizar la suscripción. "
+"Tu identificador de suscripción es:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Se ha rechazado la suscripción, pero no se ha enviado un URL de retorno. "
+"Lee de nuevo las instrucciones para saber cómo rechazar la suscripción "
+"completamente."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Estas son las personas que escuchan los avisos de %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Estas son las personas que escuchan tus avisos."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Estas son las personas que %s escucha."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Estas son las personas que escuchas."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+"Este código de confirmación es demasiado viejo. Por favor empieza de "
+"nuevo."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Este formulario debería enviarse automáticamente. En caso contrario, haz "
+"clic en el botón de envío para ir a tu proveedor de OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Esta es la primera vez que accedes a %s por lo que debemos conectar tu "
+"OpenID a una cuenta local. Puedes crear una nueva o conectarte con la tuya, "
+"si la tienes."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Esta página no está disponible en un tipo de media que aceptes."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Para suscribirte, puedes [iniciar una sesión](%%action.login%%), o "
+"[registrar](%%action.register%%) una nueva cuenta. Si ya tienes una en un "
+"[servicio de microblogueo compatible](%%doc.openmublog%%), escribe el URL de "
+"tu perfil debajo."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "El URL de tu página personal, blog o perfil en otro sitio"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "El URL de tu perfil en otro servicio de microblogueo compatible"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Envío de formulario inesperado."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Restablecimiento de contraseña inesperado."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Versión desconocida del protocolo OMB."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"A menos que se especifique lo contrario, el contenido de este sitio es "
+"propiedad de sus colaboradores y está disponible bajo la"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Tipo de dirección %s desconocida"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Cancelar suscripción"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Versión OMB no soportada"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Formato de imagen no soportado."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Cargar"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Carga un nuevo \"avatar\" (imagen de usuario) aquí. No puedes editar la "
+"imagen una vez cargada, por lo que antes debes asegurarte que sea más o "
+"menos cuadrada. Además, debe estar bajo la misma licencia del sitio. Usa "
+"una foto que te pertenezca y que quieras compartir."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"Usado sólo para actualizaciones, anuncios y recuperación de "
+"contraseñas"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "El usuario al que quieres seguir no existe."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "El usuario no tiene un perfil."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Apodo del usuario"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "¿Qué tal, %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Dónde estás, por ejemplo \"Ciudad, Estado (o Región), País\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Tipo de imagen incorrecto para '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Tamaño de imagen incorrecto para '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "¡Ya tienes este OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "¡Ya te has conectado!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Puedes cambiar tu contraseña aquí. ¡Elige una buena!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Puedes crear una nueva cuenta y empezar a enviar avisos."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Puedes eliminar un OpenID de tu cuenta haciendo clic en el botón "
+"\"Eliminar\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Puedes enviar y recibir avisos vía [mensajes instantáneos](%%doc.im%%) de "
+"Jabber/GTalk. Configura tu dirección y opciones debajo."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Puedes actualizar la información de tu perfil personal para que la gente "
+"sepa más sobre ti."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "¡Puedes usar la suscripción local!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "No puedes registrarte si no estás de acuerdo con la licencia."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "No nos enviaste ese perfil"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Te has identificado. Escribe una nueva contraseña a continuación."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "El URL de tu OpenID"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+"Tu nombre de usuario en este servidor, o la dirección de correo "
+"electrónico registrada."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) te permite ingresar a muchos sitios con la misma "
+"cuenta de usuario. Puedes administrar tus OpenID asociados desde aquí."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "hace unos segundos"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "hace %d días"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "hace %d horas"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "hace %d minutos"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "hace %d meses"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "hace un día"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "hace un minuto"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "hace un mes"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "hace un año"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "hace una hora"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "en respuesta a..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "responder"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "repita la contraseña anterior"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Después"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/fr_FR/LC_MESSAGES/laconica.mo b/locale/fr_FR/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..3f144d677
--- /dev/null
+++ b/locale/fr_FR/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/fr_FR/LC_MESSAGES/laconica.po b/locale/fr_FR/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..eba0e8a3c
--- /dev/null
+++ b/locale/fr_FR/LC_MESSAGES/laconica.po
@@ -0,0 +1,3207 @@
+# #-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#
+# French translations for Laconica package
+# Traductions françaises du paquet Laconica.
+# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the Laconica package.
+# Florian Birée <florian@biree.name>, 2008.
+#
+# For translation choices and other informations, please read
+# <http://dev.filyb.info/laconica/wiki/french-translation>
+#
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Project-Id-Version: 0.43\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-08-17 11:01-0400\n"
+"PO-Revision-Date: 2008-07-13 22:49+0200\n"
+"Last-Translator: Florian Birée <florian@biree.name>\n"
+"Language-Team: French\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"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr " Flux de recherche pour « %s »"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "Statut de %1$s sur %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Flux public de %s"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Ajouter un OpenID"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Toutes les mises à jour pour %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Toutes les mises à jour correspondantes aux termes cherchés « %s »"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar mis à jour."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"En attente d'une confirmation pour cette adresse. Vérifiez votre "
+"compteJabber/GTalk pour recevoir un message avec les instructions "
+"suivantes.(Avez-vous ajouté %s à votre liste de contacts ?)"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Impossible d'instancier l'objet client pour OpenID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Changer"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Changer de mot de passe"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Code de confirmation non trouvé."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Connecter"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Connecté à un compte existant"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Impossible de créer le formulaire OpenID : %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Impossible de rediriger vers le serveur : %s"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Impossible de confirmer l'email"
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Impossible de convertir les jetons de requête en jetons d'accès"
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Impossible de créer l'inscription."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Impossible d'obtenir le jeton de requête."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Impossible d'insérer une nouvelle inscription."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Créer"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Créer un nouvel utilisateur avec ce surnom."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Créer un nouveau compte"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Création d'un nouveau compte pour un OpenID qui a déjà un utilisateur."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Adresse Jabber/GTalk actuellement confirmée."
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Adresse email"
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Erreur de connexion d'utilisateur."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Erreur lors de l'insertion d'un avatar"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Erreur lors de l'insertion d'un nouveau profil"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Erreur lors de l'insertion d'un message"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Erreur lors de l'insertion d'un profil distant"
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Erreur lors de l'enregistrement de l'utilisateur."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Erreur lors de l'enregistrement de l'utilisateur ; invalide."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Erreur lors de la mise à jour du profil"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Erreur lors de la mise à jour du profil distant"
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Surnom existant"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "La mise à jour de l'avatar a échoué."
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Paramètres de messagerie instantanée"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Si vous avez déjà un compte, connectez-vous avec votre nom d'utilisateur "
+"et votre mot de passe pour les relier à votre OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Si vous voulez ajouter un OpenID à votre compte, entrez-le dans le champ "
+"ci-dessous et cliquez sur « Ajouter »."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Ancien mot de passe incorrect"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Contenu du message invalide"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "URI du message invalide"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "URL du message invalide"
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "URL du profil invalide (mauvais format)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "URL du profil retourné par le serveur invalide."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Taille invalide."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public License] "
+"(http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Il utilise le logiciel de microblog [Laconica](http://laconi.ca/), version "
+"%s, disponible sous la licence [GNU Affero General Public License] "
+"(http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Se connecter avec un compte [OpenID](%%doc.openid%%)."
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog par %s"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nouveau surnom"
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Surnom non-autorisé."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Surnom de l'utilisateur à surveiller"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Pas de code de confirmation."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Pas de surnom proposé par le serveur distant."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Pas de surnom."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Pas d'URL de profil retourné par le serveur."
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Pas de résultats"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Pas de taille."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Il n'y a pas cet OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Il n'y a pas ce document.'"
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Il n'y a pas ce code de récupération."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Il n'y a pas cette inscription'"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Pas de code de récupération."
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Ce n'est pas un OpenID valide."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Ce n'est pas un URL de profil valide (services incorrects)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Ce n'est pas un URL de profil valide (pas de XRDS défini)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Ce n'est pas un URL de profil valide (pas de document YADIS)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Ce n'est pas une image, ou c'est un fichier corrompu."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Non autorisé."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Cette réponse n'était pas attendue !"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Fil des messages de %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Le message n'a pas de profil"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Ancien mot de passe"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Mise en place du compte OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Soumission automatique OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Connexion OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Authentification OpenID annulée."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "L'authentification OpenID a échouée : %s'"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Erreur OpenID : %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID enlevé."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Paramètres OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Transfert partiel."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Rechercher des personnes"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL du profil"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Paramètres du profil"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Public"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Fil du flux public"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Évolution publique"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Code de récupération d'un utilisateur inconnu."
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Inscription distante"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Enlever l'OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Enlever votre seul OpenID vous empêcherai de vous connecter ! Si vous avez "
+"besoin de l'enlever, ajoutez un autre OpenID d'abord."
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Fil du flux de recherche"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Recherche des messages sur %%site.name%% par leur contenu. Séparez les "
+"termes de la recherches par des espaces ; ils doivent être de 3 caractères "
+"ou plus."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Recherche de personne sur %%site.name%% par leur nom, localisation ou "
+"intérêt. Séparez les termes de la recherches par des espaces ; ils "
+"doivent être de 3 caractères ou plus."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Quelqu'un d'autre a déjà cet OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Quelque chose de bizarre est arrivé."
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Erreur système en transférant le fichier."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Texte cherché"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Cet OpenID ne vous appartient pas."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Cette adresse a déjà été confirmée."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Ce code de confirmation n'est pas pour vous !"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Ce fichier est trop gros."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Ce sont les personnes qui suivent les messages de %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Ce sont les personnes qui suivent vos messages."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Ce sont les personnes dont les messages sont suivis par %s."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Ce sont les personnes dont vous suivez les messages."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Ce formulaire devrait se transmettre automatiquement. Si ce n'est pas le "
+"cas, cliquez sur le bouton de soumission pour aller chez votre fournisseur "
+"OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Ce la première fois que vous vous êtes connectés à %s, donc nous devons "
+"connecter votre OpenID à un compte local. Vous pouvez soit créer un "
+"nouveau compte, soit vous connectez avec un compte existant, si vous en avez "
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Pour s'inscrire, vous pouvez [vous connecter](%%action.login%%), ou [créer "
+"unnouveau compte](%%action.register%%). Si vous avez déjà un compte sur un "
+"[site de microblog compatible](%%doc.openmublog%%), entrez l'URL de votre "
+"profil ci-dessous."
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL de votre profil sur un autre service de microblog compatible"
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Version inconnue du protocole OMB"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Type d'adresse non reconnu : %s"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Format de fichier d'image non-supporté."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Transfert."
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Transférez ici un nouvel « avatar » (image utilisateur). Vous ne pouvez "
+"modifier l'image après l'envoi, alors faites en sorte qu'elle soit plus ou "
+"moins carrée. Elle doit aussi être soumise à la licence du site. Utilisez "
+"une image qui vous appartient et que vous voulez partager."
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "L'utilisateur à suivre n'existe pas."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Surnom de l'utilisateur"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Vous avez déjà cet OpenID !"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Vous êtes déjà connecté !"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Vous pouvez changer ici votre mot de passe. Choisissez-en un bon !"
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Vous pouvez enlever un OpenID de votre compte en cliquant sur le bouton « "
+"Enlever »."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Vous pouvez envoyer et recevoir des messages via [la messagerie "
+"instantanée](%%doc.im%%)Jabber/GTalk. Configurez votre adresse et vos "
+"paramètres ci-dessous."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Vous pouvez mettre à jour les informations de votre profil personnel pour "
+"que l'on en sache plus sur vous."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Vous pouvez utiliser l'inscription locale !"
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Votre URL OpenID"
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) vous permet de vous connecter à différents sites "
+"avec le même compte utilisateur. Gérez vos OpenID associés d'ici."
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "identique au mot de passe ci-dessus"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:105 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:470 ../actions/twitapistatuses.php:478
+#: actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:474 ../actions/twitapistatuses.php:482
+#: actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+#, fuzzy
+msgid "%s public timeline"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Évolution publique\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/mail.php:202 ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+#, fuzzy
+msgid "%s status"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Statut de %1$s sur %2$s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/twitapistatuses.php:335 ../actions/twitapistatuses.php:338
+#: actions/twitapistatuses.php:265
+#, php-format
+#, fuzzy
+msgid "%s timeline"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Évolution publique\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:199 ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:143 ../actions/register.php:152
+#: actions/register.php:166
+#, fuzzy
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"1 à 64 lettres minuscules ou chiffres, sans ponctuation ni espaces\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/register.php:145 ../actions/register.php:154
+#: actions/register.php:168
+#, fuzzy
+msgid "6 or more characters. Required."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"6 caractères ou plus\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+#, fuzzy
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Un code de confirmation a été envoyé à l'adresse que vous avez ajouté. "
+"Vousdevez approuver %s pour qu'il vous envoie des messages.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+#, fuzzy
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Un code de confirmation a été envoyé à l'adresse que vous avez ajouté. "
+"Vousdevez approuver %s pour qu'il vous envoie des messages.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+#, fuzzy
+msgid "Add or remove OpenIDs"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Enlever l'OpenID\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+#, fuzzy
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"En attente d'une confirmation pour cette adresse. Vérifiez votre "
+"compteJabber/GTalk pour recevoir un message avec les instructions "
+"suivantes.(Avez-vous ajouté %s à votre liste de contacts ?)\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+#, fuzzy
+msgid "Can't delete this notice."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Impossible de supprimer la confirmation d'email.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+#, fuzzy
+msgid "Cannot normalize that email address"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Impossible de normaliser cet identifiant Jabber\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+#, fuzzy
+msgid "Change your password"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Changer de mot de passe\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+#, fuzzy
+msgid "Change your profile settings"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Paramètres du profil\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+#, fuzzy
+msgid "Confirmation code"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Pas de code de confirmation.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/register.php:188 ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:48 ../actions/twitapifriendships.php:53
+#: actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../actions/subscribe.php:62 ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../actions/subscribe.php:54 ../lib/subs.php:46 lib/subs.php:46
+#, fuzzy
+msgid "Could not subscribe."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Non inscrit !\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+#, fuzzy
+msgid "Could not update user with confirmed email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Adresse Jabber/GTalk actuellement confirmée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+#, fuzzy
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Impossible de mettre à jour l'utilisateur.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+#, fuzzy
+msgid "Couldn't update user record."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Impossible de mettre à jour l'utilisateur.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+#, fuzzy
+msgid "Current confirmed email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Adresse Jabber/GTalk actuellement confirmée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+#, fuzzy
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Erreur de base de donnée en insérant la réponse : %s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+#, fuzzy
+msgid "Delete notice"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Nouveau message\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+#, fuzzy
+msgid "Email Address"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Adresse email\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+#, fuzzy
+msgid "Email Settings"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Paramètres\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+#, fuzzy
+msgid "Feed for tag %s"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Fil des réponses à %s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+#, fuzzy
+msgid "Incoming email address removed."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"L'adresse a été enlevée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/util.php:260 ../lib/util.php:261 lib/util.php:277
+#, php-format
+#, fuzzy
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Il utilise le logiciel de microblog [Laconica](http://laconi.ca/), version "
+"%s, disponible sous la licence [GNU Affero General Public License] "
+"(http://www.fsf.org/licensing/licenses/agpl-3.0.html).\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+#, fuzzy
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"La localisation est trop longue (255 caractères maximum).\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/register.php:152 ../actions/register.php:166
+#: actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:140 ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+#, fuzzy
+msgid "New email address for posting to %s"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Pas d'adresse email définie pour cet utilisateur.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+#, fuzzy
+msgid "New incoming email address added."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Ce n'est pas une adresse email valide.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+#, fuzzy
+msgid "No code entered"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Pas de contenu !\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+#, fuzzy
+msgid "No email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Adresse email\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+#, fuzzy
+msgid "No incoming email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Ce n'est pas une adresse email valide.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+#, fuzzy
+msgid "No phone number."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Il n'y a pas cet utilisateur.'\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+#, fuzzy
+msgid "No user with that email address or username."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Pas d'adresse email définie pour cet utilisateur.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+#, fuzzy
+msgid "Not a registered user."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Pas de code de récupération.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+#, fuzzy
+msgid "Not a valid email address"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Ce n'est pas une adresse email valide.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+#, fuzzy
+msgid "Notice Search"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Recherche\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+#, fuzzy
+msgid "Notices tagged with %s"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Fil des messages de %s\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+#, fuzzy
+msgid "People"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Rechercher des personnes\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+#, fuzzy
+msgid "People Search"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Rechercher des personnes\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+#, fuzzy
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"1 à 64 lettres minuscules ou chiffres, sans ponctuation ni espaces\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+#, fuzzy
+msgid "Preferred language"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Préférences\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+#, fuzzy
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Adresse Jabber/GTalk actuellement confirmée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+#, fuzzy
+msgid "Publish a MicroID for my email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Entrez un surnom ou une adresse email.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+#, fuzzy
+msgid "Registration not allowed."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Surnom non-autorisé.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/register.php:186 ../actions/register.php:200
+#: actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+#, fuzzy
+msgid "SMS Settings"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Paramètres de messagerie instantanée\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/mail.php:215 ../lib/mail.php:219 lib/mail.php:225
+#, fuzzy
+msgid "SMS confirmation"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Pas de code de confirmation.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/register.php:147 ../actions/register.php:156
+#: actions/register.php:170
+#, fuzzy
+msgid "Same as password above. Required."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Identique au mot de passe ci-dessus\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+#, fuzzy
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"M'envoyer des messages par Jabber/GTalk.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+#, fuzzy
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"M'envoyer des messages par Jabber/GTalk.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+#, fuzzy
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"C'est la mauvaise adresse de messagerie instantanée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/tag.php:41 ../lib/util.php:300 ../lib/util.php:301
+#: actions/tag.php:41 lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+#, fuzzy
+msgid "That email address already belongs to another user."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Cet identifiant Jabber appartient déjà à un autre utilisateur.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+#, fuzzy
+msgid "That is already your email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"C'est déjà votre identifiant Jabber.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+#, fuzzy
+msgid "That is already your phone number."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"C'est déjà votre identifiant Jabber.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+#, fuzzy
+msgid "That is not your email address."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"C'est la mauvaise adresse de messagerie instantanée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+#, fuzzy
+msgid "That is not your phone number."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Ce n'est pas votre identifiant Jabber.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+#, fuzzy
+msgid "That is the wrong confirmation number."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"C'est la mauvaise adresse de messagerie instantanée.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+#, fuzzy
+msgid "That phone number already belongs to another user."
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Cet identifiant Jabber appartient déjà à un autre utilisateur.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:150 ../actions/twitapifriendships.php:163
+#: actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/twitterapi.php:248 ../lib/twitterapi.php:269
+#, fuzzy
+msgid "Unsupported type"
+msgstr "Version OMB non-supportée"
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:238 ../actions/twitapistatuses.php:241
+#: actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 ../actions/twitapistatuses.php:341
+#: actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+#, fuzzy
+msgid "Upload a new profile image"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Impossible d'enregistrer les informations du nouveau profil\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:115 ../actions/twitapifriendships.php:128
+#: actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:143 ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:128 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../lib/twitterapi.php:354
+#, fuzzy
+msgid "not a supported data format"
+msgstr "Format de fichier d'image non-supporté."
+
+#: ../actions/twitapistatuses.php:692 ../actions/twitapistatuses.php:755
+#: actions/twitapistatuses.php:678
+#, fuzzy
+msgid "unsupported file type"
+msgstr ""
+"#-#-#-#-# laconica-no-duplicates.po (0.43) #-#-#-#-#\n"
+"Format de fichier d'image non-supporté.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+#: actions/finishopenidlogin.php:88 actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51 actions/all.php:47
+#: actions/allrss.php:60 actions/twitapistatuses.php:155 lib/personal.php:51
+#, php-format
+msgid "%s and friends"
+msgstr ""
+
+#: ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: actions/finishopenidlogin.php:79 actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+
+#: ../actions/password.php:42 actions/profilesettings.php:181
+msgid "6 or more characters"
+msgstr ""
+
+#: ../actions/recoverpassword.php:180 actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr ""
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr ""
+
+#: ../actions/userauthorization.php:119 actions/userauthorization.php:126
+msgid "Accept"
+msgstr ""
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+#: actions/emailsettings.php:63 actions/imsettings.php:64
+#: actions/openidsettings.php:58 actions/smssettings.php:71
+#: actions/twittersettings.php:85
+msgid "Add"
+msgstr ""
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39 actions/emailsettings.php:39
+#: actions/imsettings.php:40 actions/smssettings.php:39
+msgid "Address"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/showstream.php:273 actions/showstream.php:288
+msgid "All subscriptions"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr ""
+
+#: ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/userauthorization.php:77 actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:104 ../actions/register.php:178
+#: actions/register.php:192
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#: actions/profilesettings.php:34
+msgid "Avatar"
+msgstr ""
+
+#: ../lib/util.php:1318 lib/util.php:1452
+msgid "Before »"
+msgstr ""
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+#: actions/profilesettings.php:82 actions/register.php:184
+msgid "Bio"
+msgstr ""
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr ""
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+#: actions/profilesettings.php:404 actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr ""
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62 actions/emailsettings.php:58
+#: actions/imsettings.php:59 actions/smssettings.php:62
+msgid "Cancel"
+msgstr ""
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+#: actions/profilesettings.php:182 actions/recoverpassword.php:187
+#: actions/register.php:169 actions/smssettings.php:65
+msgid "Confirm"
+msgstr ""
+
+#: ../actions/confirmaddress.php:90 actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245 actions/emailsettings.php:256
+#: actions/imsettings.php:230 actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr ""
+
+#: ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/updateprofile.php:162 actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr ""
+
+#: ../actions/updateprofile.php:155 actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr ""
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+#: actions/profilesettings.php:299 actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr ""
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr ""
+
+#: ../actions/showstream.php:356 actions/showstream.php:367
+msgid "Currently"
+msgstr ""
+
+#: ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+#: actions/profilesettings.php:84 actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr ""
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87 actions/register.php:172
+#: actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr ""
+
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr ""
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/recoverpassword.php:191 actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr ""
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:253 actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/recoverpassword.php:240 actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../lib/openid.php:226 lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/recoverpassword.php:80 actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:64 actions/all.php:61
+#: actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr ""
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#: actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr ""
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr ""
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr ""
+
+#: ../lib/util.php:322 lib/util.php:338
+msgid "Help"
+msgstr "Aide"
+
+#: ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr ""
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+#: actions/profilesettings.php:79 actions/register.php:181
+msgid "Homepage"
+msgstr ""
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+#: actions/profilesettings.php:210 actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr ""
+
+#: ../actions/imsettings.php:60 actions/imsettings.php:61
+msgid "IM Address"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr ""
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+
+#: ../actions/updateprofile.php:114 actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:98 actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:87 actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111 actions/finishopenidlogin.php:241
+#: actions/register.php:103 actions/register.php:121
+msgid "Invalid username or password."
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+#: actions/profilesettings.php:85 actions/register.php:187
+msgid "Location"
+msgstr ""
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr ""
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310 actions/login.php:97
+#: actions/login.php:106 actions/openidlogin.php:77 lib/util.php:326
+msgid "Login"
+msgstr ""
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:308 lib/util.php:324
+msgid "Logout"
+msgstr ""
+
+#: ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr ""
+
+#: ../actions/showstream.php:300 actions/showstream.php:315
+msgid "Member since"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+#: actions/finishopenidlogin.php:85 actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/newnotice.php:87 actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+#: actions/profilesettings.php:180 actions/recoverpassword.php:185
+msgid "New password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+#: actions/finishopenidlogin.php:171 actions/profilesettings.php:203
+#: actions/register.php:74 actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/recoverpassword.php:162 actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr ""
+
+#: ../actions/imsettings.php:156 actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr ""
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/newnotice.php:44 actions/newmessage.php:53
+#: actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/userbyid.php:32 actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/recoverpassword.php:226 actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82 actions/all.php:34
+#: actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/imsettings.php:167 actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr ""
+
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr ""
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27 actions/disfavor.php:29 actions/favor.php:30
+#: actions/finishaddopenid.php:29 actions/logout.php:33
+#: actions/newmessage.php:28 actions/newnotice.php:29 actions/subscribe.php:28
+#: actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/showstream.php:316 actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314 lib/settingsaction.php:90
+#: lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+#: actions/finishopenidlogin.php:96 actions/login.php:102
+#: actions/register.php:167
+msgid "Password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:288 actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: ../actions/recoverpassword.php:284 actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr ""
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+#: actions/profilesettings.php:408 actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:88
+#: actions/profilesettings.php:380 actions/register.php:98
+msgid "Passwords don't match."
+msgstr ""
+
+#: ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94 actions/emailsettings.php:86
+#: actions/imsettings.php:68 actions/smssettings.php:94
+#: actions/twittersettings.php:70
+msgid "Preferences"
+msgstr ""
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr ""
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60 lib/personal.php:60
+#: lib/settingsaction.php:84
+msgid "Profile"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../actions/recoverpassword.php:166 actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:156 actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr ""
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr ""
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#: actions/replies.php:47 actions/repliesrss.php:62 lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:183 actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:173 actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300 lib/searchaction.php:84
+#: lib/util.php:316
+msgid "Search"
+msgstr ""
+
+#: ../actions/invite.php:137 ../lib/util.php:1172 actions/invite.php:145
+#: lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:192 actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:296 actions/showstream.php:311
+msgid "Statistics"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+#: actions/finishopenidlogin.php:188 actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+#: actions/showstream.php:328 actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/imsettings.php:170 actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:233 actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+#: actions/emailsettings.php:244 actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+#: actions/newnotice.php:49 actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:92 actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+#: actions/profilesettings.php:81 actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:276 actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/showstream.php:209 actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82 actions/all.php:41
+#: actions/avatarbynickname.php:48 actions/foaf.php:47 actions/replies.php:41
+#: actions/showfavorites.php:41 actions/showstream.php:44
+#: actions/twitapiaccount.php:80 actions/twitapifavorites.php:68
+#: actions/twitapistatuses.php:235 actions/twitapistatuses.php:609
+#: actions/twitapiusers.php:87 lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+#: actions/profilesettings.php:87 actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/register.php:135 actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+#: actions/finishopenidlogin.php:38 actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr ""
+
+#: ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr ""
+
+#: ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr ""
+
+#: ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117 actions/noticesearch.php:136
+#: actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124 actions/noticesearch.php:143
+#: actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/he_IL/LC_MESSAGES/laconica.po b/locale/he_IL/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..b30da8437
--- /dev/null
+++ b/locale/he_IL/LC_MESSAGES/laconica.po
@@ -0,0 +1,2819 @@
+# #-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#
+# Hebrew translation for Laconica
+# ×ª×¨×’×•× ×œ×¢×‘×¨×™×ª של ל×קוניה
+# Copyright (C) 2008 COPYRIGHT HOLDER
+# כל הזכויות שמורות, 2008
+# This file is distributed under the same license as the Laconica package.
+# קובץ ×–×” מופץ תחת רשיון ×–×”×” לזה של החבילה ל×קוניקה
+# Hezy Amiel ×—×–×™ עמי×ל <open@hezyamiel.com>, 2008.
+#
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: 2008-09-17 23:59 GMT+3\n"
+"Last-Translator: Hezy Amiel <open@hezyamiel.com>\n"
+"Language-Team: Hebrew \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "חיפוש ברצף ×חרי \"%s\""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+#: ../actions/register.php:191 actions/finishopenidlogin.php:88
+#: actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr "מלבד מידע פרטי ×–×”: סיסמה, כתובת דו×"ל, כתובת ×ž×¡×¨×™× ×ž×™×“×™×™×, מספר טלפון."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s כעת מ×זין להודעות שלך ב-%2$s"
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s מ×זין כעת להודעות שלך ב %2$s. \n"
+"\n"
+"\t%3$s\n שלך,\n %4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "הסטטוס של %1$s ב-%2$s "
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "רצף ציבורי ב-%s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s וחברי×"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** ×”×•× ×©×¨×•×ª ביקרובלוג הניתן על ידי "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** ×”×•× ×©×¨×•×ª ביקרובלוג."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "תנו קרדיט ×ž×œ× ×œ×›×•×ª×‘×™× ×‘×©×ž× ×”×ž×œ× ×ו בכינויי×."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1 עד 64 ×ותיות ×נגליות קטנות ×ו מספרי×, ×œ×œ× ×¡×™×ž× ×™ פיסוק ×ו רווחי×."
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "לפחות 6 ×ותיות"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "לפחות 6 ×ותיות, ×ל תשכח!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"קוד ×ישור נשלח ×ל כתובת ×”×ž×¡×¨×™× ×”×ž×™×“×™×™× "
+"שהוספת. עליך ל×שר ×ת %s לשליחת ×ž×¡×¨×™× ×ž×™×“×™×™× ×ליך."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "×ודות"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "קבל"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "הוסף"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "הוסף OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "כתבות"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "כל המנויי×"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "כל ×”×¢×“×›×•× ×™× ×¢×‘×•×¨ %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "כל ×”×¢×™×“×›×•× ×™× ×”×ª×•××ž×™× ×ת החיפוש ×חרי \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "כבר מחובר."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "כבר מנוי!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "×שר מנוי"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "בעתיד התחבר ×וטומטית; ×œ× ×œ×©×™×ž×•×© ×‘×ž×—×©×‘×™× ×¦×™×‘×•×¨×™×™×!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "תמונה"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "התמונה עודכנה."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"מחכה ל×ישור כתובת זו. בדוק ×ת חשבון "
+"×”-Jabber/GTalk שלך לקבלת מסר ×¢× ×”×•×¨×ות נוספותץ (×”×× ×”×•×¡×¤×ª ×ת %s לרשימת ×”×—×‘×¨×™× ×©×œ×š?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "לפני >>"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "ביוגרפיה"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "הביוגרפיה ×רוכה מידי (לכל היותר 140 ×ותיות)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "×œ× × ×™×ª×Ÿ ×œ×§×¨×•× ×ת ×”-URL '%s' של התמונה"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "×œ× × ×™×ª×Ÿ לשמור ×ת הסיסמה"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "בטל"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "×œ× × ×™×ª×Ÿ להפעיל ×ת ×ובייקט הלקוח של OpenID."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "×œ× × ×™×ª×Ÿ לנרמל ×ת זהות ×”-Jabber ×”×–×”"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "שנה"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "שנה סיסמה"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "×שר"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "×שר כתובת"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "×”×ישור בוטל."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "קוד ×”×ישור ×œ× × ×ž×¦×."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "התחבר"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "התחבר לחשבון קיי×"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "צור קשר"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "נכשלה יצירת OpenID מתוך: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "נכשלה ההפניה לשרת: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "שמירת מידע התמונה נכשל"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "שמירת מידע הפרופיל החדש נכשלה"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "×œ× × ×™×ª×Ÿ ל×שר ×ת הדו×"ל."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "המרת ×סימון הבקשה ל×סימון גישה ×œ× ×”×¦×œ×™×—×”."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "יצירת המנוי נכשלה."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr "מחיקת ×ישור הדו×"ל נכשלה."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "מחיקת המנוי ×œ× ×”×¦×œ×™×—×”."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "×סימון הבקשה ×œ× ×”×ª×§×‘×œ."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "הכנסת קוד ×”×ישור נכשלה."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "הכנסת מנוי חדש נכשלה."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "שמירת הפרופיל נכשלה."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "עידכון המשתמש נכשל."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "צור"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "צור משתמש חדש ×¢× ×”×›×™× ×•×™ ×”×–×”."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "צור חשבון חדש"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "יצירת חשבון חדש עבור OpenID שכבר משוייך למשתמש."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "כתובת מ×ושרת נוכחית של Jabber/GTalk."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "כרגע"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "שגי×ת מסד × ×ª×•× ×™× ×‘×”×›× ×¡×ª התגובה: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "ת×ר ×ת עצמך ו×ת נוש××™ העניין שלך ב-140 ×ותיות"
+
+#: ../actions/register.php:181 ../actions/register.php:158
+#: ../actions/register.php:161 ../lib/settingsaction.php:87
+#: actions/register.php:172 actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr "דו×"ל"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "כתובת דו×"ל"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr "כתובת דו×"ל זו כבר קיימת."
+
+#: ../lib/mail.php:82 ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr "×ישור כתובת הדו×"ל"
+
+#: ../actions/recoverpassword.php:176 ../actions/recoverpassword.php:191
+#: actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr "הכנס כינוי ×ו כתובת דו×"ל."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "שגי××” ב×ישור ×”×סימון"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "שגי××” בקישור המשתמש ל-OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "שגי×ת חיבור משתמש."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "שגי××” בהכנסת התמונה."
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "שגי××” בהכנסת הפרופיל"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "שגי××” בהכנסת ההוד××”"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "שגי××” בהכנסת פרופיל מרוחק"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "שגי××” בשמירת ×ישור הכתובת."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "שגי××” בשמירת פרופיל מרוחק"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "שגי××” בשמירת הפרופיל."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "שגי××” בשמירת המשתמש."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "שגי××” בשמירת ×©× ×”×ž×©×ª×ž×©, ×œ× ×¢×•×ž×“ בכללי×."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "שגי××” ביצירת ×©× ×”×ž×©×ª×ž×©."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "שגי××” בעידכון הפרופיל"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "שגי××” בעדכון פרופיל מרוחק"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "שגי××” ב×ישור הקוד."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "כינוי ×–×” כבר קיי×"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "רשימת ש×לות נפוצות"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "עדכון התמונה נכשל."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "הזנות ×”×—×‘×¨×™× ×©×œ %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "הזנת התגובות ל-%s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr "לצרכי ×בטחה, הכנס מחדש ×ת ×©× ×”×ž×©×ª×ž×© והסיסמה לפני שתשנה ×ת ההגדרות."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "×©× ×ž×œ×"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "×”×©× ×”×ž×œ× ×רוך מידי (מותרות 255 ×ותיות בלבד)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "עזרה"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "בית"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "×תר בית"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "ל×תר הבית יש כתובת ×œ× ×—×•×§×™×ª."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "כתובת ×ž×¡×¨×™× ×ž×™×“×™×™×"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "הגדרות ×ž×¡×¨×™× ×ž×™×“×™×™×"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"×× ×™×© לך כבר חשבון, התחבר ×¢× ×©× ×”×ž×©×ª×ž×© "
+"והסיסמה שלך וקשר ×ותו ×ל ×”-OpenID שלך."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"×× ×תה רוצה להוסיף OpenID לחשבון שלך, הכנס "
+"×ותו לתיבה שלמטה ולחץ \"הוסף\""
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"×× ×©×›×—×ª ×ו ×יבדת ×ת סיסמתך, תוכל לקבל חדשה "
+"שתישלח ×ליך לכתובת הדו×"ל הרשומה בחשבונך."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "הסיסמה הישנה ×œ× × ×›×•× ×”"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "×©× ×ž×©×ª×ž×© ×ו סיסמה ×œ× × ×›×•× ×™×."
+
+#: ../actions/recoverpassword.php:226 ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr "הור×ות לשיחזור הסיסמה שלך נשלחו לכתובת הדו×"ל הרשומה בחשבונך."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "כתובת התמונה '%s' ××™× ×” חוקית"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "כתובת ×תר הבית '%s' ××™× ×” חוקית"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "כתובת הרשיון '%s' ××™×” חוקית"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "תוכן ההודעה ×œ× ×—×•×§×™"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "כתובת ×œ× ×—×•×§×™×ª"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "כתובת ×œ× ×—×•×§×™×ª"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "כתובת הפרופיל '%s' ×œ× ×—×•×§×™×ª."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "כתובת פרופיל ×œ× ×—×•×§×™×ª (פורמט ×œ× ×ª×§×™×Ÿ)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "השרת החזיר כתובת פרופיל ×œ× ×—×•×§×™×ª"
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "גודל ×œ× ×—×•×§×™."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "×©× ×”×ž×©×ª×ž×© ×ו הסיסמה ×œ× ×—×•×§×™×™×"
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"×”×•× ×¤×•×¢×œ על תוכנת המיקרובלוג "
+"[](http://laconi.caל×קוניקה/) ל×קוניקה, גירסה %s, "
+"המופצת תחת רשיון [GNU Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)"
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "זיהוי ×”-Jabber כבר שייך למשתמש ×חר."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"כתובת Jabber ×ו GTalk, כגון \"UserName@example.org\". הוסף "
+"×ת %s ×ל רשימת ×”×—×‘×¨×™× ×‘×ª×•×›× ×ª ×”×”×ž×¡×¨×™× ×”×ž×™×“×™×™× ×ו GTalk שלך."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "מיקו×"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "×©× ×”×ž×™×§×•× ×רוך מידי (מותר עד 255 ×ותיות)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "היכנס"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "התחבר לחשבון [OpenID](%%doc.openid%%). "
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"היכנס בעזרת ×©× ×”×ž×©×ª×ž×© והסיסמה שלך. עדיין "
+"×ין לך ×©× ×ž×©×ª×ž×©? [הרש×](%%action.register%%) לחשבון "
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "צ×"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "שכחת ×ו ×יבדת ×ת הסיסמה?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "חבר מ××–"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "מיקרובלוג מ×ת %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "×”×˜×§×¡×˜×™× ×•×”×§×‘×¦×™× ×©×œ×™ ×ž×•×¤×¦×™× ×ª×—×ª רשיון"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "כינוי חדש"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "הודעה חדשה"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "סיסמה חדשה"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "הסיסמה החדשה נשמרה בהצלחה. ×תה מחובר למערכת."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "כינוי"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "כינוי ×–×” כבר תפוס. נסה כינוי ×חר."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "כינוי יכול להכיל רק ×ותיות ×נגליות קטנות ומספרי×, ×•×œ×œ× ×¨×•×•×—×™×."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "כינוי ×–×” ×סור."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "כינויו של המשתמש ×חריו ×תה רוצה לעקוב"
+
+#: ../actions/recoverpassword.php:147 ../actions/recoverpassword.php:162
+#: actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr "כינוי ×ו דו×"ל"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "×ין זיהוי Jabber ×›×–×”."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "×œ× ×”×ª×‘×§×© ×ישור!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "×ין קוד ×ישור."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "×ין תוכן!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "×ין זיהוי."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "השרת הרחוק ×œ× ×ž×¡×¤×§ כינוי"
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "×ין כינוי"
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "×ין ×ישור ממתין שניתן לבטל."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "השרת ×œ× ×”×—×–×™×¨ כתובת פרופיל"
+
+#: ../actions/recoverpassword.php:189 ../actions/recoverpassword.php:226
+#: actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr "×ין דו×"ל ×¨×©×•× ×¢×‘×•×¨ משתמש ×–×”."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "×œ× × ×ž×¦××” בקשה!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "×ין תוצ×ות"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "×ין גודל."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "×ין OpenID ×›×–×”."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "×ין מסמך ×›×–×”."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "×ין הודעה כזו."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "×ין קוד שיחזור ×›×–×”."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "×ין מנוי ×›×–×”"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "×ין משתמש ×›×–×”."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "×ין ×ת מי להציג!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "זהו ×œ× ×§×•×“ ×ישור."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "×œ× ×¢×•×ž×“ ×‘×›×œ×œ×™× ×œ×–×™×”×•×™ Jabber"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "×œ× ×¢×•×ž×“ ×‘×›×œ×œ×™× ×œ-OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr "כתובת דו×"ל ×œ× ×—×•×§×™×ª."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "×©× ×ž×©×ª×ž×© ×œ× ×—×•×§×™."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Not a valid profile URL (incorrect services)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Not a valid profile URL (no XRDS defined)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Not a valid profile URL (no YADIS document)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "זהו ×œ× ×§×•×‘×¥ תמונה, ×ו שחל בו שיבוש."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "×œ× ×ž×•×¨×©×”."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "זו תגובה ×œ× ×¦×¤×•×™×”!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "×œ× ×ž×—×•×‘×¨."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "×œ× ×ž× ×•×™!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "הזנת הודעות של %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "להודעה ×ין פרופיל"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "הודעות"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "סיסמה ישנה"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "הגדרת חשבון OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "הגשה ×וטומטית של OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "כניסת OpenId"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "כתובת ה-OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "×ישור ×”-OpenID בוטל."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "×ישור ×”-OpenID נכשל: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "כשל OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID הוסר."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "הגדרות OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "העל××” חלקית."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "סיסמה"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "הסיסמה ו×ישורה ×ינן תו×מות."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "הסיסמה חייבת להיות בת לפחות 6 ×ותיות."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "התבקש שיחזור סיסמה"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "הסיסמה נשמרה."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "הסיסמ×ות ×œ× ×ª×•×מות."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "חיפוש סיסמה"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "×ישי"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"בדוק ×ת ×”×¤×¨×˜×™× ×›×“×™ ×œ×•×•×“× ×©×‘×¨×¦×•× ×š ×œ×”×™×¨×©× "
+"כמנוי להודעות משתמש ×–×”. ×× ×ינך רוצה להירש×, לחץ \"בטל\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "×¤×¨×¡× ×”×•×“×¢×” כששורת הסטטוס שלי ב-Jabber/GTalk מתעדכנת."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "העדפות"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "העדפות נשמרו."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "פרטיות"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "בעיה בשמירת ההודעה."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "פרופיל"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "כתובת הפרופיל"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "הגדרות הפרופיל"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "פרופיל ×œ× ×ž×•×›×¨"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "ציבורי"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "הזנת ×–×¨× ×”×¦×™×‘×•×¨×™"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "קו זמן ציבורי"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "שיחזור"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "סיסמת שיחזור"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "קוד שיחזור למשתמש ×œ× ×™×“×•×¢."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "הירש×"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "דחה"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "זכור ×ותי"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "×ין פרופיל תו×× ×œ×¤×¨×•×¤×™×œ המרוחק "
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "הרשמה מרוחקת"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "הסר"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "הסר OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"הסרת ×”-OpenID היחיד שלך ×ª×’×¨×•× ×œ×›×š ×©×œ× ×ª×•×›×œ "
+"להתחבר למערכת. ×× ×תה צריך להסיר ×ותו, לפני כן הוסף OpenID ×חר."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "תגובות"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "תגובת עבור %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "×יפוס"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "×יפוס סיסמה"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "זהה לסיסמה למעלה"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "שמור"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "חיפוש"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "הזנת ×–×¨× ×”×—×™×¤×•×©×™×"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"חפש הודעות ב-%%site.name%% לפי תוכנן. הפרד בעזרת "
+"×¨×•×•×—×™× ×‘×™×Ÿ הביטויי×; ×¢×œ×™×”× ×œ×”×™×•×ª בני לפחות 3 ×ותיות."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"חפש ×× ×©×™× ×‘-%%site.name%% לפי ש×, מיקו×, ×ו תחומי "
+"עניין. הפרד בעזרת ×¨×•×•×—×™× ×‘×™×Ÿ הביטויי×; ×¢×œ×™×”× ×œ×”×™×•×ª בני לפחות 3 ×ותיות."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "שלח"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "שלח לי הודעות דרך Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "הגדרות"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "ההגדרות נשמרו."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "למישהו כבר יש ×ת ×”-OpenID ×”×–×”."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "קרה משהו מוזר."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "מקור"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "סטטיסטיקה"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "OpenID מ×וכסן ×œ× × ×ž×¦×."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "×”×™×¨×©× ×›×ž× ×•×™"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "מנויי×"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "ההרשמה ×ושרה"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "ההרשמה נדחתה"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "הרשמות"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "שגי×ת מערכת בהעל×ת הקובץ."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "חיפוש טקסט"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "×”-OpenID ×”×–×” ×ינו שייך לך."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "כתובת זו כבר ×ושרה."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "קוד ×”×ישור ×”×–×” ×ינו מיועד לך!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "קובץ זה גדול מידי."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "זהו כבר זיהוי ה-Jabber שלך."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "זהו ×œ× ×–×™×”×•×™ ×”-Jabber שלך."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "זוהי כתובת ×ž×¡×¨×™× ×ž×™×“×™×™× ×©×’×•×™×”."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "×–×” ×רוך מידי. ×ורך מירבי להודעה ×”×•× 140 ×ותיות."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "הכתובת \"%s\" ×ושרה עבור חשבונך."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "הכתובת הוסרה."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"המנוי ×ושר, ×בל ×œ× ×”×ª×§×‘×œ×” כתובת ×ליה ניתן "
+"לחזור. בדוק ×ת הור×ות ×”×תר וחפש כיצד ל×שר מנוי. ×סימון המנוי שלך הו×:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"המנוי נדחה, ×בל ×œ× ×”×ª×§×‘×œ×” כתובת לחזרה. "
+"בדוק ×ת הור×ות ×”×תר וחפש כיצד ×œ×”×©×œ×™× ×“×—×™×™×ª מנוי."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "×לה ×”×× ×©×™× ×‘×ž××–×™× ×™× ×œ×”×•×“×¢×•×ª של %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "×לה ×”×× ×©×™× ×”×ž××–×™× ×™× ×œ×”×•×“×¢×•×ª שלך."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "×לה ×”×× ×©×™× ×©%s מ×זין להודעות שלה×."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "×לה ×”×× ×©×™× ×©×œ×”×•×“×¢×•×ª ×©×œ×”× ×תה מ×זין."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "קוד ×ישור ×–×” ישן מידי. ×× × ×”×ª×—×œ מחדש."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr "טופס ×מור לשלוח ×ת עצמו ×וטומטית. ×× ×œ×, "
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"זו ההתחברות הר×שונה שלך ×ל %s לכן עליך "
+"להתחבר לחשבון מקומי של OpenID. ×תה יכול ליצור חשבון חדש, ×ו להתחבר לחשבון קיי×, ×× ×™×© לך."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "עמוד ×–×” ×ינו זמין בסוג מדיה ש×תה יכול לקבל"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"כדי לעשות מנוי, עליך [להיכנס "
+"למערכת](%%action.login%%), ×ו [להירש×](%%action.register%%) "
+"לחשבון חדשה. ×× ×™×© לך כבר חשבון [במערכת "
+"מיקרובלוג תו×מת](%%doc.openmublog%%), הכנס ×ת כתובת הפרופיל שלך למטה. "
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "הכתובת של ×תר הבית שלך, בלוג, ×ו פרופיל ב×תר ×חר "
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "כתובת הפרופיל שלך בשרות ביקרובלוג תו×× ×חר"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "הגשת טופס ×œ× ×¦×¤×•×™×”."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "×יפוס סיסמה ×œ× ×¦×¤×•×™."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "גירסה ×œ× ×ž×•×›×¨×ª של פרוטוקול OMB"
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr "×× ×œ× ×¤×•×¨×˜ ×חרת, כל הזכויות על התוכן של ×תר "
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "סוג ×œ× ×ž×–×•×”×” של כתובת %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "בטל מנוי"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "גירסה ×œ× × ×ª×ž×›×ª של OMB"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "פורמט התמונה ×ינו נתמך."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "ההעלה"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"העלה תמונת משתמש ×›×ן. ל×חר העל×ת התמונה ×œ× "
+"תוכל לערוך ×ותה, לכן ×•×•×“× ×©×”×™× ×¨×™×‘×•×¢×™×ª "
+"(פחות ×ו יותר). כמו כן, התמונה ×ª×¤×•×¨×¡× ×ª×—×ª רשיון השימוש של ×”×תר. השתמש בתמונה ששייכת לך וש×תה מוכן לשתף. "
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "לשימוש רק ×‘×ž×§×¨×™× ×©×œ עידכוני×, הודעות מערכת, ושיחזורי סיסמ×ות"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "המשתמש ×ליו ×תה מ×זין ×ינו קיי×."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "למשתמש ×ין פרופיל."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "כינוי משתמש"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "מה המצב %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "מיקומך, למשל \"עיר, מדינה ×ו מחוז, ×רץ\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "סוג התמונה של '%s' ×ינו מת××™×"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "גודל התמונה של: '%s' ×œ× ×ž×ª××™×."
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "כבר יש לך ×ת ×”-OpenID ×”×–×”!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "כבר נכנסת למערכת!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "×פשר לשנות ×ת הסיסמה ×›×ן. בחר סיסמה טובה!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "ניתן ליצור חשבון חדש ולהתחיל ×œ×¤×¨×¡× ×”×•×“×¢×•×ª."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr "הסיר OpenID מחשבונך על ידי לחיצה על הכפתור המסומן \"הסר\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"×פשר לשלוח ולקבל בודעות דרך Jabber/GTalk [instant "
+"messages](%%doc.im%%) הגדר ×ת כתובתך והעדפותיך למטה."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr "עדכן ×ת הפרופיל ×”×ישי שלך ×›×ן, על מנת ש×× ×©×™× ×™×•×›×œ×• לדעת עליך יותר."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "ניתן להשתמש במנוי המקומי!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "×œ× × ×™×ª×Ÿ ×œ×”×™×¨×©× ×œ×œ× ×”×¡×›×ž×” לרשיון"
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "×œ× ×©×œ×—× ×• ×לינו ×ת הפרופיל ×”×–×”"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "זוהית. הכנס סיסמה חדשה למטה."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "כתובת ה-OpenID שלך"
+
+#: ../actions/recoverpassword.php:149 ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr "כינויך על שרת ×–×”, ×ו כתובת הדו×"ל הרשומה שלך."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"מ×פשר לך להיכנס להרבה ××ª×¨×™× ×‘×¢×–×¨×ª חשבון "
+"משתמש ×חד. נהל ×ת שיוך ×”-OpenID מכ×ן. [OpenID](%%doc.openid%%)"
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "לפני מספר שניות"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "לפני ×›-%d ימי×"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "לפני כ-%d שעות"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "לפני כ-%d דקות"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "לפני ×›-%d חודשי×"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "לפני כיו×"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "לפני כדקה"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "לפני כחודש"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "לפני כשנה"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "לפני כשעה"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "בתגובה ל..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "הגב"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "זהה לסיסמה למעלה"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "<< ×חרי"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "שפה"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr "חדש"
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr "ל×"
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr "×œ× × ×ž×¦×"
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr "×נשי×"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr "חיפוש ×נשי×"
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr "סמס"
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr "טקסט"
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr "כן"
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr "מחק"
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr "הודעה חדשה"
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr "×ודות: %s"
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr "מועדפי×"
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr "מתשמש"
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr "×ל"
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/it_IT/LC_MESSAGES/laconica.mo b/locale/it_IT/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..e32264533
--- /dev/null
+++ b/locale/it_IT/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/it_IT/LC_MESSAGES/laconica.po b/locale/it_IT/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..83d13b13e
--- /dev/null
+++ b/locale/it_IT/LC_MESSAGES/laconica.po
@@ -0,0 +1,2968 @@
+# Italian translation of laconica
+# Copyright (C) 2008 THE laconica'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the laconica package.
+# Milo Casagrande <milo@ubuntu.com>, 2008
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: laconica\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: 2008-12-24 17:46+0100\n"
+"Last-Translator: Milo Casagrande <milo@ubuntu.com>\n"
+"Language-Team: Italian <tp@lists.linux.it>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Ricerca \"%s\" nel flusso"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"a eccezione di questi dati personali: password, indirizzo email, indirizzo "
+"messaggistica istantanea, numero di telefono."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s sta ora seguendo i tuoi messaggi su %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s sta ora seguendo i tuoi messaggi su %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Cordiali saluti,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "Stato di %1$s su %2$s"
+
+# Solo una prova, se non sta bene si ripristina la versione vecchia, giusto per avere l'interfaccia il più tradotta possibile
+#: ../actions/publicrss.php:60 ../actions/publicrss.php:62
+#: actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr "Attività pubblica di %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s e amici"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** è un servizio di micro-blog offerto da "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** è un servizio di micro-blog."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+". I collaboratori devono essere indicati col loro nome completo o "
+"soprannome."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+"1-64 lettere minuscole o numeri, senza spazi o simboli di "
+"punteggiatura"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 o più caratteri"
+
+# messa al femminile... dovrebbe essere la password
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 o più caratteri, e non dimenticarla!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Un codice di conferma è stato inviato all'indirizzo di messaggistica "
+"istantanea che hai aggiunto. Devi approvare %s affinché ti invii "
+"messaggi."
+
+#: ../lib/util.php:296 ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr "Informazioni"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Accetta"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Aggiungi"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Aggiungi OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Indirizzo"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Tutti gli abbonamenti"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Tutti gli aggiornamenti di %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Tutti gli aggiornamenti corrispondenti al termine di ricerca \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Accesso già effettuato."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Già abbonato!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Autorizza abbonamento"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Accedi automaticamente in futuro; non per computer condivisi!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Immagine"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Immagine aggiornata."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"In attesa di conferma per questo indirizzo. Controlla il tuo account "
+"Jabber/GTalk per un messaggio con ulteriori istruzioni (hai aggiunto %s al "
+"tuo elenco contatti?)."
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Precedenti »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Biografia"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "La biografia è troppo lunga (max 140 caratteri)."
+
+#: ../actions/updateprofile.php:118 ../actions/updateprofile.php:119
+#: actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Impossibile leggere l'URL \"%s\" dell'immagine"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Impossibile salvare la nuova password."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Annulla"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Impossibile creare l'oggetto OpenID consumer."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Impossibile normalizzare quell'ID Jabber"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Modifica"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Modifica password"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Conferma"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Conferma indirizzo"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Conferma annullata."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Codice di conferma non trovato."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Connetti"
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr "Collega a un account esistente"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Contatti"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Impossibile creare il modulo OpenID: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Impossibile redirigere al server: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Impossibile salvare le informazioni dell'immagine"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Impossibile salvare le nuove informazioni del profilo"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Impossibile confermare l'email."
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+"Impossibile convertire le credenziali di richiesta in credenziali di "
+"accesso."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Impossibile creare l'abbonamento."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Impossibile eliminare l'email di conferma."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Impossibile eliminare l'abbonamento."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Impossibile ottenere un token di richiesta."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Impossibile inserire il codice di conferma."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Impossibile inserire un nuovo abbonamento."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Impossibile salvare il profilo."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Impossibile aggiornare l'utente."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Crea"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Crea un nuovo utente con questo soprannome."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Crea un nuovo account"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Creazione nuovo account per OpenID che ha già un utente."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Indirizzo Jabber/GTalk attualmente confermato."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Attualmente"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Errore nel DB nell'inserire la risposta: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Descrivi te stesso e i tuoi interessi in 140 caratteri"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Email"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Indirizzo email"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Indirizzo email già esistente."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Conferma indirizzo email"
+
+#: ../actions/recoverpassword.php:176 ../actions/recoverpassword.php:191
+#: actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr "Inserisci un soprannome o un indirizzo email."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Errore nell'autorizzare il token"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Errore nel collegare l'utente a OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Errore nel connettere l'utente."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Errore nell'inserire l'immagine"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Errore nell'inserire il nuovo profilo"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Errore nell'inserire un messaggio"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Errore nell'inserire un profilo remoto"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Errore nel salvare la conferma dell'indirizzo."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Errore nel salvare il profilo remoto"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Errore nel salvare il profilo."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Errore nel salvare l'utente."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Errore nel salvare l'utente; non valido."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Errore nell'impostare l'utente."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Errore nell'aggiornare il profilo"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Errore nell'aggiornare il profilo remoto"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Errore con il codice di conferma."
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr "Soprannome esistente"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "FAQ"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Errore nell'aggiornare l'immagine."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed per gli amici di %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed per le risposte a %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Per motivi di sicurezza è necessario reinserire il proprio nome utente e la "
+"propria password prima di modificare le impostazioni."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Nome"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Nome troppo lungo (max 255 caratteri)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Aiuto"
+
+#: ../lib/util.php:274 ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr "Home"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Pagina web"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "L'URL della pagina web non è valido."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "Indirizzo di messaggistica istantanea"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Impostazioni messaggistica istantanea"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Se hai già un account, esegui l'accesso col tuo nome utente e la tua "
+"password per connetterlo al tuo OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Se vuoi aggiungere un OpenID al tuo account, inseriscilo nel riquadro "
+"sottostante e fai clic su \"Aggiungi\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Se hai dimenticato o perso la tua password, puoi riceverne una nuova "
+"all'indirizzo email memorizzato nel tuo account."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Vecchia password non corretta"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Nome utente o password non corretto."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Le istruzioni per recuperare la tua password sono state inviate "
+"all'indirizzo email registrato nel tuo account."
+
+#: ../actions/updateprofile.php:113 ../actions/updateprofile.php:114
+#: actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "URL \"%s\" dell'immagine non valido"
+
+#: ../actions/updateprofile.php:97 ../actions/updateprofile.php:98
+#: actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Pagina web \"%s\" non valida"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "URL \"%s\" della licenza non valido"
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr "Contenuto del messaggio non valido"
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr "URI del messaggio non valido"
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr "URL del messaggio non valido"
+
+#: ../actions/updateprofile.php:86 ../actions/updateprofile.php:87
+#: actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "URL \"%s\" del profilo non valido"
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "URL del profilo non valido (formato errato)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "URL del profilo restituito dal server non valido."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Dimensione non valida."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Nome utente o password non valido."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+" Gestito dal software di micro-blog [Laconica](http://laconi.ca/), versione "
+"%s, disponibile sotto licenza [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "ID Jabber già assegnato a un altro utente."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Indirizzo Jabber o GTalk nella forma \"NomeUtente@example.org\". Per prima "
+"cosa, assicurati di aggiungere %s all'elenco dei contatti nel tuo programma "
+"di messaggistica o su GTalk."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Ubicazione"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Ubicazione troppo lunga (max 255 caratteri)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Accedi"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Accedi con un account [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Accedi con nome utente e password. Non hai ancora un nome utente? "
+"[Registra](%%action.register%%) un nuovo account o prova "
+"[OpenID](%%action.openidlogin%%). "
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Esci"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Password persa o dimenticata?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Membro dal "
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Micro-blog di %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "I miei testi e file sono disponibili sotto"
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr "Nuovo soprannome"
+
+#: ../actions/newnotice.php:100 ../actions/newnotice.php:87
+#: actions/newnotice.php:96
+msgid "New notice"
+msgstr "Nuovo messaggio"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nuova password"
+
+#: ../actions/recoverpassword.php:275 ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nuova password salvata con successo. Hai effettuato l'accesso."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175 ../actions/login.php:101
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr "Soprannome"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr "Soprannome già in uso. Prova con un altro."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+#: ../actions/profilesettings.php:88 ../actions/register.php:67
+#: ../actions/updateprofile.php:77 actions/finishopenidlogin.php:171
+#: actions/profilesettings.php:203 actions/register.php:74
+#: actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"Il soprannome deve essere composto solo da lettere minuscole e numeri, senza "
+"spazi."
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr "Soprannome non consentito."
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr "Soprannome dell'utente che vuoi seguire"
+
+#: ../actions/recoverpassword.php:147 ../actions/recoverpassword.php:162
+#: actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr "Soprannome o email"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Nessun ID di Jabber."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Nessuna richiesta di autorizzazione!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Nessun codice di conferma."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Nessun contenuto!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Nessun id."
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr "Nessun soprannome fornito dal server remoto."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Nessun soprannome."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Nessuna conferma da annullare."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Nessun URL di profilo restituito dal server."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Nessun indirizzo email registrato per quell'utente."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Nessuna richiesta trovata!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Nessun risultato"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Nessuna dimensione."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Nessun tale OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Nessun tale documento."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+#: ../actions/shownotice.php:83 ../lib/deleteaction.php:30
+#: actions/shownotice.php:32 actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr "Nessun tale messaggio."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Nessun codice di ripristino."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Nessun tale abbonamento"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Nessun tale utente."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Nessuno da mostrare!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Non è un codice di ripristino."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Non è un ID Jabber valido"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Non è un OpenID valido."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Non è un indirizzo email valido."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr "Non è un soprannome valido."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Non è un URL di profilo valido (servizio incorretto)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Non è un URL di profilo valido (nessun XRDS definito)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Non è un URL di profilo valido (nessun documento YADIS)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Non è un'immagine o il file è danneggiato."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Non autorizzato."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Non aspettavo questa risposta!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Non connesso."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Non abbonato!"
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Feed dei messaggi per %s"
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Il messaggio non ha un profilo"
+
+#: ../actions/showstream.php:297 ../actions/showstream.php:316
+#: actions/showstream.php:331
+msgid "Notices"
+msgstr "Messaggi"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Vecchia password"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Configurazione account OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Auto-invio OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Accesso OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Autenticazione OpenID annullata."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Autenticazione OpenID non riuscita: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Errore OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID rimosso."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Impostazioni OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Caricamento parziale."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Password"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "La password e la conferma non corrispondono."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "La password dev'essere lunga almeno 6 caratteri."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Richiesta password di ripristino"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Password salvata."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Le password non corrispondono."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Ricerca persone"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Personale"
+
+#: ../actions/userauthorization.php:77 ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Controlla i dettagli seguenti per essere sicuro di volerti abbonare ai "
+"messaggi di questo utente. Se non hai richiesto ciò, fai clic su "
+"\"Annulla\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Pubblica un messaggio quando il mio stato Jabber/GTalk cambia"
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Preferenze"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Preferenze salvate."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Privacy"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Problema nel salvare il messaggio."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profilo"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL del profilo"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Impostazioni del profilo"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Profilo sconosciuto"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Pubblico"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Feed del flusso pubblico"
+
+# Solo una prova, se non sta bene si ripristina la versione vecchia, giusto per avere l'interfaccia il più tradotta possibile
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Attività pubblica"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Recupero"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Recupero password"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Codice di recupero per utente sconosciuto."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registra"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Rifiuta"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Ricordami"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Profilo remoto senza profilo corrispondente"
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr "Abbonamento remoto"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Rimuovi"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Rimuovi OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Rimuovere il tuo unico OpenID renderà impossibile effettuare l'accesso! Se "
+"vuoi rimuoverlo, prima aggiungi un altro OpenID."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Risposte"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Risposte a %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Reimposta"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Reimposta password"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Stessa password di sopra"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Salva"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Ricerca"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Ricerca nel flusso dei feed"
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Ricerca tra i messaggi su %%site.name%% in base al contenuto. Separa i "
+"termini di ricerca con degli spazi; lunghezza minima 3 caratteri."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Ricerca le persone su %%site.name%% per nome, ubicazione o interessi. Separa "
+"i termini di ricerca con degli spazi; lunghezza minima 3 caratteri."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Invia"
+
+#: ../actions/imsettings.php:71 ../actions/imsettings.php:70
+#: actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Inviami le notifiche via Jabber/GTalk"
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Impostazioni"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Impostazioni salvate."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Qualcun altro ha già questo OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "È successo qualcosa di strano."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Sorgenti"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Statistiche"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "OpenID memorizzato non trovato."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Abbonati"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+#: ../actions/showstream.php:313 actions/showstream.php:328
+#: actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Abbonati"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Abbonamento autorizzato"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Abbonamento rifiutato"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Abbonamenti"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Errore di sistema nel caricare il file."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Ricerca testo"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Quel OpenID non ti appartiene."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Quell'indirizzo è già stato confermato."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Quel codice di conferma non è per te!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Quel file è troppo grande."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Quello è già il tuo ID di Jabber."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Quello non è il tuo ID di Jabber."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Quello è l'indirizzo di messaggistica sbagliato."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Troppo lungo. Lunghezza massima 140 caratteri."
+
+#: ../actions/confirmaddress.php:86 ../actions/confirmaddress.php:92
+#: actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "L'indirizzo \"%s\" è stato confermato per il tuo account."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "L'indirizzo è stato rimosso."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"L'abbonamento è stato autorizzato, ma non è stato passato alcun URL di "
+"richiamo. Controlla le istruzioni del sito per i dettagli su come "
+"autorizzare l'abbonamento. Il tuo token per l'abbonamento è:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"L'abbonamento è stato rifiutato, ma non è stato passato alcun URL di "
+"richiamo. Controlla con le istruzioni del sito per i dettagli su come "
+"rifiutare completamente l'abbonamento."
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Queste sono le persone che seguono %s."
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Queste sono le persone che ti seguono."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Queste sono le persone seguite da %s."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Queste sono le persone che stai seguendo."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Questo codice di conferma è scaduto. Ricomincia da capo."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Questo modulo dovrebbe inviarsi automaticamente. In caso contrario, fai clic "
+"sul pulsante per inviarlo al tuo servizio OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Questa è la prima volta in cui accedi a %s quindi è necessario collegare "
+"il tuo OpenID a un account locale. Puoi crearne uno nuovo o collegarlo con "
+"il tuo account esistente, se ne hai uno."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Questa pagina non è disponibile in un tipo di supporto che tu accetti"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Per abbonarti puoi [eseguire l'accesso](%%action.login%%) oppure "
+"[registrare](%%action.register%%) un nuovo account. Se ne possiedi già uno "
+"su un [sito di micro-blog compatibile](%%doc.openmublog%%), inserisci "
+"l'indirizzo del tuo profilo qui di seguito."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL della tua pagina web, blog o profilo su un altro sito"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL del tuo profilo su un altro servizio di micro-blog compatibile"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Invio del modulo inaspettato."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Ripristino della password inaspettato."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Versione del protocollo OMB sconosciuta."
+
+#: ../lib/util.php:245 ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Se non indicato diversamente, i diritti d'autore dei contenuti di questo "
+"sito sono dei singoli utenti e sono disponibili sotto"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Tipo di indirizzo non riconosciuto %s"
+
+#: ../actions/showstream.php:193 ../actions/showstream.php:209
+#: actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr "Annulla abbonamento"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Versione OMB non supportata"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Formato file immagine non supportato."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Carica"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Carica qui una nuova immagine utente. Non puoi modificarla una volta "
+"caricata, quindi sii sicuro che sia più o meno quadrata. Deve anche essere "
+"rilasciata sotto la licenza del sito. Usa un'immagine che ti appartiene e "
+"che tu voglia condividere."
+
+# Femminile, è l'email
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Usata solo per aggiornamenti, annunci e recupero password"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "L'utente che intendi seguire non esiste."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "L'utente non ha un profilo."
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr "Soprannome dell'utente"
+
+#: ../lib/util.php:969 ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr "Cosa succede %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Dove ti trovi, tipo \"città, regione, stato\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Tipo di immagine errata per \"%s\""
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Dimensione dell'immagine sbagliata a \"%s\""
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Hai già questo OpenID!"
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Hai già effettuato l'accesso!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Qui puoi cambiare la tua password. Scegline una buona!"
+
+#: ../actions/register.php:164 ../actions/register.php:135
+#: actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr "Puoi creare un nuovo account per iniziare a inviare messaggi."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr "Puoi rimuovere un OpenID dal tuo account facendo clic su \"Rimuovi\"."
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Puoi inviare e ricevere messaggi attraverso i servizi di [messaggistica "
+"istantanea](%%doc.im%%) Jabber/GTalk. Configura il tuo indirizzo e le "
+"impostazioni qui di seguito."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Puoi aggiornare le informazioni del tuo profilo personale qui, così gli "
+"altri potranno conoscere qualcosa in più di te."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Puoi usare l'abbonamento locale!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Non puoi registrarti se non accetti la licenza."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Non hai inviato quel profilo"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Sei stato identificato. Inserisci una nuova password qui sotto."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Il tuo URL OpenID"
+
+#: ../actions/recoverpassword.php:149 ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+"Il tuo soprannome su questo server o il tuo indirizzo email "
+"registrato."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) ti permette di accedere a molti siti con lo stesso "
+"account. Gestisci i tuoi OpenID associati da qui."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "pochi secondi fa"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "circa %d giorni fa"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "circa %d ore fa"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "circa %d minuti fa"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "circa %d mesi fa"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "circa un giorno fa"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "circa un minuto fa"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "circa un mese fa"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "circa un anno fa"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "circa un'ora fa"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "in risposta a..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "rispondi"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "stessa password di sopra"
+
+#: ../lib/util.php:1127 ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr "« Successivi"
+
+# Non ne sono sicuro, ma dovrebbe essere quello che si vede come descrizione da dove si è inviato il messaggio. Mettere 'da' non suona bene ('da web', 'da api'...), 'via' mi pare la soluzione migliore ('via web', 'via api', 'via Twhirl'...).
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr "via"
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr "%1$s / Aggiornamenti in risposta a %2$s"
+
+# %USERNAME has invited you to join THEM on %site. You start with a singular and end with a plural???
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s ti invita a seguirlo su %2$s"
+
+# link -> collegamento
+# micro-blogging -> micro-blog
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s ti invita a seguirlo su %2$s (%3$s).\n"
+"\n"
+"%2$s è un servizio di micro-blog che ti consente di rimanere aggiornato "
+"sulle persone che conosci e che ti interessano.\n"
+"\n"
+"Puoi anche condividere notizie che ti riguardano, i tuoi pensieri o la tua "
+"vita in rete con le persone che ti conoscono. È anche un ottimo strumento "
+"per conoscere nuove persone che condividono i tuoi stessi interessi.\n"
+"\n"
+"%1$s ha scritto:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Puoi vedere il profilo di %1$s su %2$s qui:\n"
+"\n"
+"%5$s\n"
+"\n"
+"Se vuoi provare il servizio, fai clic sul collegamento qui sotto per "
+"accettare l'invito:\n"
+"\n"
+"%6$s\n"
+"\n"
+"Se non è ciò che vuoi, ignora semplicemente questo messaggio. Grazie per "
+"aver letto questo invito e per il tuo tempo!\n"
+"\n"
+"Cordiali saluti, %2$s\n"
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr "%1$s aggiornamenti in risposta agli aggiornamenti da %2$s / %3$s"
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+# Solo una prova, se non sta bene si ripristina la versione vecchia, giusto per avere l'interfaccia il più tradotta possibile
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "attività pubblica di %s"
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr "stato di %s"
+
+# Solo una prova, se non sta bene si ripristina la versione vecchia, giusto per avere l'interfaccia il più tradotta possibile
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr "Attività di %s"
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr "Aggiornamenti di %s da tutti!"
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(Dovresti ricevere, entro breve, un messaggio email con istruzioni su come "
+"confermare il tuo indirizzo email.)"
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"1-64 lettere minuscole o numeri, niente punteggiatura o spazi. "
+"Richiesto."
+
+# Femminile, è la password
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr "6 o più caratteri. Richiesta."
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Un codice di conferma è stato inviato all'indirizzo di posta da te "
+"aggiunto. Controlla la tua casella di posta (e anche la posta indesiderata!) "
+"per il codice e le istruzioni su come usarlo."
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Un codice di conferma è stato inviato al numero di telefono da te aggiunto. "
+"Controlla la tua casella di posta (e anche la posta indesiderata!) per il "
+"codice e le istruzioni su come usarlo."
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr "Metodo delle API non trovato!"
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr "Metodo delle API in lavorazione."
+
+# we don't use 's' in plurals even from english words.
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr "Aggiungi o rimuovi OpenID"
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr "Indirizzi email di amici da invitare (uno per riga)"
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr "Sei sicuro di voler eliminare questo messaggio?"
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+"Abbonami automaticamente a chi si abbona ai miei messaggi (utile per i "
+"non-umani)"
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"Attesa la conferma per questo indirizzo. Controlla la tua casella di posta "
+"(e anche la posta indesiderata!) per un messaggio con ulteriori "
+"istruzioni."
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "Attesa la conferma per questo numero di telefono."
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Impossibile eliminare questo messaggio."
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr "Impossibile normalizzare l'indirizzo email"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr "Modifica la gestione dell'email"
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr "Modifica la tua password"
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "Modifica le impostazioni del tuo profilo"
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Codice di conferma"
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"Congratulazioni %s! Benvenuto/a in %%%%site.name%%%%. Da qui ora "
+"puoi...\n"
+"\n"
+"* Visitare il [tuo profilo](%s) e inviare il tuo primo messaggio.\n"
+"*Aggiungere un [indirizzo Jabber/GTalk](%%%%action.imsettings%%%%) per usare "
+"quel servizio per inviare messaggi.\n"
+"*[Ricercare persone](%%%%action.peoplesearch%%%%) che potresti conoscere o "
+"che condividono i tuoi stessi interessi.\n"
+"* Aggiornare le [tue impostazioni](%%%%action.profilesettings%%%%) per "
+"fornire agli altri più informazioni su di te.\n"
+"* Leggere la [documentazione in rete](%%%%doc.help%%%%) per le "
+"caratteristiche che magari non hai ancora visto.\n"
+"\n"
+"Grazie per esserti iscritto/a e speriamo tu possa divertiti usando questo "
+"servizio."
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr "Impossibile seguire l'utente: %s è già nel tuo elenco."
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr "Impossibile seguire l'utente: utente non trovato."
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr "Impossibile abbonare altri a te."
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr "Impossibile abbonarsi."
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr "Impossibile aggiornare l'utente con l'indirizzo email confermato."
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr "Impossibile trovare un qualsiasi stato."
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr "Impossibile aggiornare l'utente per auto-abbonarsi."
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr "Impossibile aggiornare i record dell'utente."
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "Numero di telefono attualmente confermato per gli SMS."
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr "Indirizzo email attualmente confermato."
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr "Errore del DB nell'inserire un hastag: %s"
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "Elimina messaggio"
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr "Indirizzo email"
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "Impostazioni email"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr "Indirizzo email, del tipo \"NomeUtente@example.org\""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr "Indirizzi email"
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr "Inserisci il codice che hai ricevuto sul tuo telefono."
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "Feed per l'etichetta %s"
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr "Ricerca contenuto dei messaggi"
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr "Ricerca persone in questo sito"
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr "Voglio inviare i messaggi via email"
+
+# Non è il massimo (Messaggistica Istantanea), ma non lo è nemmeno in inglese, questo tipo di abbreviazioni sarebbe da evitare sempre...
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr "MI"
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+"Se hai dimenticato o perso la tua password, puoi fartene inviare una nuova "
+"per email all'indirizzo indicato nel tuo profilo."
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "Email di ricezione"
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr "Indirizzo email di ricezione rimosso."
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr "Indirizzo email non valido: %s"
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr "Inviti inviati"
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr "Inviti inviati ai seguenti indirizzi:"
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr "Invita"
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr "Invita nuovi utenti"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "Lingua"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr "La lingua è troppo lunga (max 50 caratteri)"
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr "Nome completo, preferibilmente il tuo \"vero\" nome"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+"Crea un nuovo indirizzo email a cui inviare i messaggi, rimuove quello "
+"vecchio."
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr "Gestisci la ricezione delle email da %%site.name%%."
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+"Operatore di telefonia mobile. Se conosci un operatore che accetta gli SMS "
+"via email, ma non è elencato qui, scrivici a %s."
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr "Nuovo"
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr "Nuovo indirizzo email per inviare messaggi a %s"
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr "Nuovo indirizzo email di ricezione aggiunto."
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr "No"
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr "Nessun operatore selezionato."
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr "Nessun codice inserito"
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr "Nessun indirizzo email."
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr "Nessun indirizzo email di ricezione."
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr "Nessun numero di telefono."
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr "Nessuno stato trovato con quel ID."
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr "Nessuno stato con quel ID trovato."
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr "Nessun utente con quell'email o nome utente."
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr "Non è un utente registrato."
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr "Non è un formato di dati supportato."
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr "Non è un indirizzo email valido"
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr "Non trovato"
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr "Ricerca messaggi"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr "Messaggi etichettati con %s"
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr "Puoi aggiungere un messaggio personale agli inviti."
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr "Persone"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr "Ricerca persone"
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr "Messaggio personale"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr "Numero di telefono, senza punteggiatura o spazi, con il prefisso"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr "Lingua preferita"
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Pubblica un MicroID per il mio indirizzo Jabber/GTalk"
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr "Pubblica un MicroID per il mio indirizzo email"
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr "Etichette recenti"
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr "Registrazione non consentita."
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr "Registrazione riuscita"
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr "SMS"
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "Numero di telefono per SMS"
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "Impostazioni SMS"
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr "Conferma SMS"
+
+# Femminile, è la password
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr "Stessa password di sopra. Richiesta."
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr "Seleziona un operatore"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr "Invia le email a questo indirizzo per scrivere nuovi messaggi."
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr "Inviami avvisi di nuovi abbonamenti via email"
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+"Inviami avvisi via SMS: comprendo che potrei incorrere in esorbitanti "
+"bollette da parte del mio operatore"
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr "Inviami le risposte delle persone a cui sono abbonato via Jabber/GTalk"
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr "Le etichette più popolari dell'ultima settimana"
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr "Email di ricezione non consentita."
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr "Quella non è la tua email di ricezione."
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr "Etichette"
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr "Testo"
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr "Qeull'indirizzo email appartiene già a un altro utente."
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr "Quello è già il tuo indirizzo email."
+
+# previous suggestion missed the dot
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr "Quello è già il tuo numero di telefono."
+
+# previous suggestion missed the dot
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr "Quello non è il tuo indirizzo email."
+
+# previous suggestion missed the dot
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr "Quello non è il tuo numero di telefono."
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr "Quello è il numero di conferma errato."
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr "Quel numero di telefono appartiene già a un altro utente."
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr "Troppo lungo. Massimo 255 caratteri."
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+"Queste persone sono già utenti e sei stato automaticamente abbonato a "
+"loro:"
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr "Questo metodo richiede POST o DELETE."
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr "Questo metodo richiede POST."
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr "Fuso orario"
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr "Fuso orario non selezionato"
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr "Devono essere forniti due ID utente o nominativi."
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr "Azione sconosciuta"
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr "Aggiornamenti via SMS"
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr "Aggiornamenti via messaggistica istantanea (MI)"
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr "Aggiornamenti da %1$s e amici su %2$s!"
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr "Aggiornamenti da %1$s su %2$s!"
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr "Carica una nuova immagine per il profilo"
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+"Usa questo modulo per invitare i tuoi amici e colleghi a usare questo "
+"servizio."
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr "Utente non trovato."
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr "In che fuso orario risiedi solitamente?"
+
+# "si" is the last musical note in the "do scale". "sì" is the correct Italian translation for "yes".
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr "Sì"
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+"Stai per eliminare definitivamente un messaggio. Una volta fatto non sarà "
+"possibile recuperarlo."
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr "Sei già abbonato a questi utenti:"
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr "Non sei amico dell'utente specificato."
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr "Puoi ricevere messaggi SMS attraverso l'email da %%site.name%%."
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+"Hai un nuovo indirizzo di invio su %1$s.\n"
+"\n"
+"Scrivi le email a %2$S per inviare nuovi messaggi.\n"
+"\n"
+"Puoi trovare maggiori informazioni presso %£4s.\n"
+"\n"
+"Cordiali saluti,\n"
+"%4$s"
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr "Non puoi eliminare lo stato di un altro utente."
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr "Devi eseguire l'accesso per invitare altri utenti a usare %s"
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+"Verrai avvisato quando i tuoi invitati accetteranno e si registreranno sul "
+"sito. Grazie per l'aiuto ad accrescere la comunità!"
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr "elimina"
+
+# wrong capitalization in previous suggestion
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr "tipo di file non supportato"
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr "C'è stato un problema con il tuo token di sessione. Prova di nuovo."
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr "Questo messaggio non è un preferito!"
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr "Impossibile eliminare un preferito."
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr "Preferito"
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+"Inviami un'email quando qualcuno aggiunge un mio messaggio ai "
+"preferiti"
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr "Inviami un'email quando qualcuno mi invia un messaggio privato"
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr "Questo messaggio è già un preferito!"
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr "Impossibile creare preferito."
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr "Non preferito"
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr "Messaggi preferiti di %s"
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr "Feed dei messaggi preferiti di %s"
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr "Casella posta in arrivo di %s - pagina %d"
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr "Casella posta in arrivo di %s"
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+"Questa è la casella della tua posta in arrivo, contiene i messaggi privati "
+"ricevuti."
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+"%1$s ti ha invitato a unirti a loro su %2$s (%3$s).\n"
+"\n"
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr "Accesso automatico in futuro;"
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr "Per motivi di sicurezza reinserire la propria"
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr "Accedi con il tuo nome utente e password."
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr "Troppo lungo. Il massimo per un messaggio è 140 caratteri."
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr "Nessun destinatario specificato."
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr "Non puoi inviare un messaggio a questo utente."
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr "Non inviarti un messaggio, piuttosto ripetilo a voce dolcemente."
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr "Nessun tale utente"
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr "Nuovo messaggio"
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr "Messaggi senza un profilo corrispondente"
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr "[OpenID](%%doc.openid%%) ti consente di collegarti a molti siti"
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr "Se vuoi aggiungere un OpenID al tuo account,"
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr "Rimuovere il tuo unico OpenID ti impedirebbe di eseguire l'accesso!"
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr "Puoi rimuovere un OpenID dal tuo account"
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr "Casella posta inviata di %s - pagina %d"
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr "Casella posta inviata di %s"
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+"Questa è la casella della tua posta inviata, contiene i messaggi privati "
+"che hai inviato."
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr "Ricerca persone su %%site.name%% per nome, ubicazione o interessi."
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr "Qui puoi aggiornare il tuo profilo personale"
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr "Utente senza profilo corrispondente"
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr "Questo codice di conferma è troppo vecchio."
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr "Se hai dimenticato o perso la tua"
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr "Sei ora identificato. Inserisci un"
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr "Il tuo soprannome su questo server,"
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr "Istruzioni per il recupero della password"
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr "Nuova password salvata con successo."
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr "La password deve essere di 6 o più caratteri."
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+"Congratulazioni, %s! Benvenuto su %%%%site.name%%%%. Ora potresti "
+"voler..."
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr "(Dovresti ricevere, entro breve, un messaggio email con"
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr "Per abbonarti, puoi [eseguire l'accesso](%%action.login%%),"
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr "Feed dei preferiti di %s"
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr "Impossibile recuperare i messaggi favoriti."
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr "Nessun tale messaggio."
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr "Solo mittente e destinatario possono leggere questo messaggio."
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr "Messaggio a %1$s su %2$s"
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr "Messaggio da %1$s su %2$s"
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr "Invia un messaggio"
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr "Operatore mobile del tuo telefono."
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr "Messaggi diretti a %s"
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr "Tutti i messaggi diretti inviati a %s"
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr "Messaggi diretti inviati"
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr "Tutti i messaggi diretti inviati da %s"
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr "Nessun testo nel messaggio!"
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr "Utente destinatario non trovato."
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr "Non puoi inviare messaggi diretti a utenti che non sono amici tuoi."
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr "%s / Preferiti da %s"
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr "%s aggiornamenti preferiti da %s / %s"
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr "%s ha aggiunto il tuo messaggio tra i suoi preferiti"
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+"%1$s ha appena aggiunto il tuo messaggio da %2$s tra i suoi preferiti.\n"
+"\n"
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+"Aggiungi il tuo account Twitter per inviare automaticamente i tuoi messaggi "
+"su Twitter,"
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr "Impostazioni Twitter"
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr "Account Twitter"
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr "Account Twitter attualmente verificato."
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr "Nome utente di Twitter"
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr "Niente spazi, grazie."
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr "Password di Twitter"
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr "Invia automaticamente i miei messaggi a Twitter"
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr "Invia le risposte \"@\" locali a Twitter"
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr "Abbonami ai miei amici di Twitter"
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+"Il nome utente deve contenere solo numeri, lettere maiuscole e minuscole e "
+"underscore (_) (max 15 caratteri)."
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr "Impossibile verificare le tue credenziali di Twitter!"
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr "Impossibile ottenere informazioni sull'account \"%s\" da Twitter."
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr "Impossibile salvare le tue impostazioni di Twitter!"
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr "Impostazioni di Twitter salvate."
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr "Quello non è il tuo account Twitter."
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr "Impossibile rimuovere l'utente Twitter."
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr "Account Twitter rimosso."
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr "Impossibile salvare le preferenze di Twitter."
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr "Preferenze di Twitter salvate."
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr "Controlla questi dettagli per assicurarti"
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr "L'abbonamento è stato autorizzato, ma non"
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr "L'abbonamento è stato rifiutato, ma non"
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr "Risultati comando"
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr "Comando completato"
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr "Comando non riuscito"
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr "Questo comando non è ancora implementato"
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr "Abbonamenti: %1$s\n"
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr "L'utente non ha un ultimo messaggio"
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr "Messaggio indicato come preferito."
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr "%1$s (%2$s)"
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr "Nome completo: %s"
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr "Ubicazione: %s"
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr "Pagina web: %s"
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr "Informazioni: %s"
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr "Messaggio troppo lungo - massimo 140 caratteri, inviati %d"
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr "Messaggio diretto a %s inviato"
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr "Errore nell'inviare il messaggio diretto."
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr "Specifica il nome dell'utente a cui abbonarti"
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr "Abbonato a %s"
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr "Specifica il nome dell'utente da cui annullare l'abbonamento"
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr "Abbonamento a %s annullato"
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr "Comando non ancora implementato."
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr "Notifiche disattivate."
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr "Impossibile disattivare le notifiche."
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr "Notifiche attivate."
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr "Impossibile attivare le notifiche."
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr "Comandi:\n"
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr "Impossibile inserire messaggio."
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr "Impossibile aggiornare il messaggio con il nuovo URI."
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr "Utente senza profilo corrispondente nel sistema."
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+"Hai un nuovo indirizzo di ricezione su %1$s.\n"
+"\n"
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr "Nuovo messaggio privato da %s"
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+"%1$s (%2$s) ti ha inviato un messaggio privato:\n"
+"\n"
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr "Solo l'utente può leggere la propria casella di posta."
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr "Questo modulo dovrebbe inviarsi automaticamente."
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr "Preferiti"
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr "Messaggi preferiti di %s"
+
+#: lib/personal.php:66
+msgid "User"
+msgstr "Utente"
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr "In arrivo"
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr "I tuoi messaggi in arrivo"
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr "Inviati"
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr "I tuoi messaggi inviati"
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr "Twitter"
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr "Opzioni di integrazione Twitter"
+
+#: lib/util.php:1718
+msgid "To"
+msgstr "A"
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr "Impossibile analizzare il messaggio."
diff --git a/locale/ja_JP/LC_MESSAGES/laconica.mo b/locale/ja_JP/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..8dbab6632
--- /dev/null
+++ b/locale/ja_JP/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/ja_JP/LC_MESSAGES/laconica.po b/locale/ja_JP/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..835b9941c
--- /dev/null
+++ b/locale/ja_JP/LC_MESSAGES/laconica.po
@@ -0,0 +1,2774 @@
+# #-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Toshiya TSURU <turutosiya@gmail.com>, 2008
+#
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Toshiya TSURU <turutosiya@gmail.com>\n"
+"Language-Team: Japanese <turutosiya+laconica@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 7bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "\"%s\" ã®ã‚¹ãƒˆãƒªãƒ¼ãƒ ã‚’検索"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr "個人情報を除ã:パスワードã€ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã€IMアドレスã€é›»è©±ç•ªå·"
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s 㯠%2$s ã§ã‚ãªãŸã®é€šçŸ¥ã‚’èžã„ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr "%1$s 㯠%2$s ã§ã‚ãªãŸã®é€šçŸ¥ã‚’èžã„ã¦ã„ã¾ã™ã€‚\n\n\t%3$s\n\n確ã‹ã«ã‚ãªãŸã®,\n%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$ ã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s パブリックストリーム"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s & ã¨ã‚‚ã ã¡"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** 㯠[%%site.broughtby%%](%%site.broughtbyurl%%) "
+"ãŒæä¾›ã™ã‚‹ãƒžã‚¤ã‚¯ãƒ­ãƒ–ロギングサービスã§ã™ã€‚ "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** ã¯ãƒžã‚¤ã‚¯ãƒ­ãƒ–ロギングサービスã§ã™ã€‚"
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "コントリビューターã¯ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã‹ãƒ•ãƒ«ãƒãƒ¼ãƒ ã§è¨˜è¼‰ã•ã‚Œã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1~64字以内ã§ã€å°æ–‡å­—アルファベットã€æ•°å­—ã€ã‚¹ãƒšãƒ¼ã‚¹ã€‚(å¥èª­ç‚¹ã‚’除ã)"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6文字以上"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6文字以上。ãŠå¿˜ã‚Œãªãï¼"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr "確èªç”¨ã‚³ãƒ¼ãƒ‰ã‚’入力ã•ã‚ŒãŸIMアドレスã«é€ä¿¡ã—ã¾ã—ãŸã€‚メッセージを確èªã™ã‚‹ã«ã¯ã€%sを承èªã—ã¦ä¸‹ã•ã„。"
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "About"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "承èª"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "追加"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "OpenIDを追加"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "ä½æ‰€"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "å…¨ã¦ã®ã‚µãƒ–スクリプション"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "%sã®å…¨ã¦ã®ã‚µãƒ–スクリプション"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "\"%s\" ã«ãƒ’ットã™ã‚‹ã™ã¹ã¦ã®ã‚¢ãƒƒãƒ—デート"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "æ—¢ã«ãƒ­ã‚°ã‚¤ãƒ³ã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "æ—¢ã«è³¼èª­ã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "購読を許å¯"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "å°†æ¥çš„ã«ã¯éžå…±ç”¨PCã§ã®è‡ªå‹•ãƒ­ã‚°ã‚¤ãƒ³"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "ã‚¢ãƒã‚¿ãƒ¼"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "ã‚¢ãƒã‚¿ãƒ¼ãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr "ã“ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯ç¢ºèªå¾…ã¡ã§ã™ã€‚Jabber/Gtalk ã§ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’確èªã—ã¦ä¸‹ã•ã„。(%s を追加ã—ã¦ã„ã¾ã™ã‹ï¼Ÿï¼‰"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "å‰ >>"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "ãƒã‚¤ã‚ªã‚°ãƒ©ãƒ•ã‚£"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "ãƒã‚¤ã‚ªã‚°ãƒ©ãƒ•ã‚£ãŒé•·ã™ãŽã¾ã™ã€‚(最長140字)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "ã‚¢ãƒã‚¿ãƒ¼URL を読ã¿å–ã‚Œã¾ã›ã‚“ '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "æ–°ã—ã„パスワードをä¿å­˜ã§ãã¾ã›ã‚“。"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "OpenID consumer object を生æˆã§ãã¾ã›ã‚“。"
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "ãã® Jabbar ID ã‚’æ­£è¦åŒ–ã§ãã¾ã›ã‚“"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "変更"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "パスワードã®å¤‰æ›´"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "確èª"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "アドレスã®ç¢ºèª"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "確èªä½œæ¥­ãŒã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "確èªã‚³ãƒ¼ãƒ‰ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "接続"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "既存ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨æŽ¥ç¶š"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "コンタクト"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "OpenIDを作æˆã§ãã¾ã›ã‚“ : %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "サーãƒã¸ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã§ãã¾ã›ã‚“ : %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "ã‚¢ãƒã‚¿ãƒ¼ã‚’ä¿å­˜ã§ãã¾ã›ã‚“"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "æ–°ã—ã„プロファイルをä¿å­˜ã§ãã¾ã›ã‚“"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "メールを確èªã§ãã¾ã›ã‚“"
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "リクエストトークンをアクセストークンã«å¤‰æ›ã§ãã¾ã›ã‚“"
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "サブスクリプションを作æˆã§ãã¾ã›ã‚“"
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "メール承èªã‚’削除ã§ãã¾ã›ã‚“"
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "サブスクリプションを削除ã§ãã¾ã›ã‚“"
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "リクエストトークンをå–å¾—ã§ãã¾ã›ã‚“"
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "確èªã‚³ãƒ¼ãƒ‰ã‚’追加ã§ãã¾ã›ã‚“"
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "サブスクリプションを追加ã§ãã¾ã›ã‚“"
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "プロファイルをä¿å­˜ã§ãã¾ã›ã‚“"
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "ユーザを更新ã§ãã¾ã›ã‚“"
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "作æˆ"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "ã“ã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã§æ–°ã—ãユーザを作æˆ"
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "アカウントを作æˆ"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "æ—¢ã«ãƒ¦ãƒ¼ã‚¶ã®å­˜åœ¨ã™ã‚‹OpenIDã§ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’作æˆã—ã¦ã„ã¾ã™"
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "確èªã•ã‚ŒãŸæœ€æ–°ã®ã€€Jabber/GTakk アドレス"
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "最新"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "返信を追加ã™ã‚‹éš›ã«ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã‚¨ãƒ©ãƒ¼ : %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "140字以内ã§è‡ªå·±ç´¹ä»‹"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "メール"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "メールアドレス"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "メールアドレスãŒæ—¢ã«å­˜åœ¨ã—ã¾ã™ã€‚"
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "メールアドレス確èª"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "ニックãƒãƒ¼ãƒ ã‹ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’入力ã—ã¦ãã ã•ã„。"
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "èªè¨¼ãƒˆãƒ¼ã‚¯ãƒ³ã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "ユーザã¨OpenIDã¨ã®æŽ¥ç¶šã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "ユーザ接続エラー"
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "ã‚¢ãƒã‚¿ãƒ¼è¿½åŠ ã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "プロファイル追加エラー"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "通知追加エラー"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "リモートプロファイル追加エラー"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "アドレス確èªä¿å­˜ã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "リモートプロファイルä¿å­˜ã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "プロファイルä¿å­˜ã‚¨ãƒ©ãƒ¼"
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "ユーザä¿å­˜ã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "ユーザä¿å­˜ã‚¨ãƒ©ãƒ¼; ä¸æ­£ãªãƒ¦ãƒ¼ã‚¶"
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "ユーザ設定エラー"
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "プロファイル更新エラー"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "リモートプロファイル更新エラー"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "確èªã‚³ãƒ¼ãƒ‰ã«ã‚¨ãƒ©ãƒ¼ãŒã‚ã‚Šã¾ã™ã€‚"
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "æ—¢ã«å­˜åœ¨ã™ã‚‹ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ "
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "よãã‚る質å•"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "ã‚¢ãƒã‚¿ãƒ¼ã®æ›´æ–°ã«å¤±æ•—ã—ã¾ã—ãŸã€‚"
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "%s ã®ã¨ã‚‚ã ã¡ã®ãƒ•ã‚£ãƒ¼ãƒ‰"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "%s ã®è¿”ä¿¡ã®ãƒ•ã‚£ãƒ¼ãƒ‰"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr "セキュリティー上ã®ç†ç”±ã«ã‚ˆã‚Šã€è¨­å®šã‚’変更ã™ã‚‹å‰ã«ãƒ¦ãƒ¼ã‚¶åã¨ãƒ‘スワードを入力ã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "フルãƒãƒ¼ãƒ "
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "フルãƒãƒ¼ãƒ ãŒé•·ã™ãŽã¾ã™ã€‚(255å­—ã¾ã§ï¼‰"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "ヘルプ"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "ホーム"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "ホームページ"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "ホームページã®URLãŒä¸é©åˆ‡ã§ã™ã€‚"
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IMアドレス"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM設定"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr "æ—¢ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ãŠæŒã¡ã®å ´åˆã¯ã€ãƒ¦ãƒ¼ã‚¶åã¨ãƒ‘スワードã§ãƒ­ã‚°ã‚¤ãƒ³ã—ã€OpenIDã¨é–¢é€£ä»˜ã‘ã¦ä¸‹ã•ã„。"
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr "OpenIDを追加ã™ã‚‹å ´åˆã€ä¸‹ã®ãƒœãƒƒã‚¯ã‚¹ã«ã‚ã‚‹\"Add\"ã‚’ã¤ãƒªãƒƒã‚¯ã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr "パスワードを忘れãŸå ´åˆã€ç™»éŒ²ã•ã‚ŒãŸã‚¢ãƒ‰ãƒ¬ã‚¹ã§æ–°ã—ã„ã‚‚ã®ã‚’å—ã‘å–ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚"
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "å¤ã„パスワードãŒé–“é•ã£ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "ユーザåã¾ãŸã¯ãƒ‘スワードãŒé–“é•ã£ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr "登録ã•ã‚ŒãŸãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã«ãƒ‘スワードã®å›žå¾©æ–¹æ³•ã‚’ãŠãŠãã‚Šã—ã¾ã—ãŸã€‚"
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "ä¸æ­£ãªã‚¢ãƒã‚¿ãƒ¼URL '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "ä¸æ­£ãªãƒ›ãƒ¼ãƒ ãƒšãƒ¼ã‚¸ '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "ä¸æ­£ãªãƒ©ã‚¤ã‚»ãƒ³ã‚¹URL '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "ä¸æ­£ãªé€šçŸ¥å†…容"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "ä¸æ­£ãªé€šçŸ¥uri"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "ä¸æ­£ãªé€šçŸ¥url"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "ä¸æ­£ãªãƒ—ロファイルURL '%s'。"
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "ä¸æ­£ãªãƒ—ロファイルURL。(形å¼ä¸å‚™ï¼‰"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "ä¸æ­£ãªãƒ—ロファイルURLãŒã‚µãƒ¼ãƒã‹ã‚‰è¿”ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "ä¸æ­£ãªã‚µã‚¤ã‚ºã€‚"
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "ä¸æ­£ãªãƒ¦ãƒ¼ã‚¶åã¾ãŸã¯ãƒ‘スワード。"
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"マイクロブロギングソフト [Laconica](http://laconi.ca/) , "
+"ãƒãƒ¼ã‚¸ãƒ§ãƒ³ %s ã§å‹•ã„ã¦ã„ã¾ã™ã€‚ ライセンス [GNU Affero "
+"General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)。"
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID jã¯æ—¢ã«åˆ¥ã®ãƒ¦ãƒ¼ã‚¶ãŒä½¿ç”¨ã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"\"UserName@example.org\" ã¨ã„ã£ãŸ Jabber ã¾ãŸã¯ GTalk "
+"ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã€‚ã¾ãšã€%s ã‚’IMクライアントやGTalkã«è¿½åŠ ã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "場所"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "場所ãŒé•·ã™ãŽã¾ã™ã€‚(255å­—ã¾ã§ï¼‰"
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "ログイン"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "[OpenID](%%doc.openid%%) ã§ãƒ­ã‚°ã‚¤ãƒ³ã€‚"
+
+#: ../actions/login.php:122 ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "ログアウト"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "パスワードを紛失ã€å¿˜ã‚ŒãŸï¼Ÿ"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "ã‹ã‚‰ã®ãƒ¡ãƒ³ãƒãƒ¼"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "マイクロブログ by %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "ã®ä¸‹ã§ãƒ†ã‚­ã‚¹ãƒˆåŠã³ãƒ•ã‚¡ã‚¤ãƒ«ã‚’利用å¯èƒ½"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "æ–°ã—ã„ニックãƒãƒ¼ãƒ "
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "æ–°ã—ã„通知"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "æ–°ã—ã„パスワード"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "æ–°ã—ã„パスワードã®ä¿å­˜ã«æˆåŠŸã—ã¾ã—ãŸã€‚ログインã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "ニックãƒãƒ¼ãƒ "
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "ãã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã¯æ—¢ã«ä½¿ç”¨ã•ã‚Œã¦ã„ã¾ã™ã€‚ä»–ã®ã‚‚ã®ã‚’試ã—ã¦ã¿ã¦ä¸‹ã•ã„。"
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "ニックãƒãƒ¼ãƒ ã«ã¯ã€å°æ–‡å­—アルファベットã¨æ•°å­—ã®ã¿ä½¿ç”¨ã§ãã¾ã™ã€‚スペースã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。"
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "ãã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。"
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "フォローã—ãŸã„ユーザã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ "
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "ニックãƒãƒ¼ãƒ ã¾ãŸã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Jabbar ID ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "èªè¨¼ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "確èªã‚³ãƒ¼ãƒ‰ãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "コンテンツãŒã‚ã‚Šã¾ã›ã‚“ï¼"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "id ãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "リモートユーザã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "ニックãƒãƒ¼ãƒ ãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "èªè¨¼å¾…ã¡ã®ã‚‚ã®ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "サーãƒã‹ã‚‰æä¾›ã•ã‚Œã‚‹ãƒ—ロファイルURLã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "ãã®ãƒ¦ãƒ¼ã‚¶ã«ã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã®ç™»éŒ²ãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "リクエストãŒã‚ã‚Šã¾ã›ã‚“ï¼"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "çµæžœãªã—"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "サイズãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "ãã®ã‚ˆã†ãªOpenIDã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "ãã®ã‚ˆã†ãªãƒ‰ã‚­ãƒ¥ãƒ¡ãƒ³ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "ãã®ã‚ˆã†ãªé€šçŸ¥ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "ãã®ã‚ˆã†ãªå›žå¾©ã‚³ãƒ¼ãƒ‰ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "ãã®ã‚ˆã†ãªã‚µãƒ–スクリプションã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "ãã®ã‚ˆã†ãªãƒ¦ãƒ¼ã‚¶ã¯ã„ã¾ã›ã‚“。"
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "表示ã§ãるユーザã¯ã„ã¾ã›ã‚“。"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "回復コードã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "有効㪠Jabber ID ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "有効㪠OpenID ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "有効ãªãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "有効ãªãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "有効ãªãƒ—ロファイルURLã§ã¯ã‚ã‚Šã¾ã›ã‚“。(間é•ã£ãŸã‚µãƒ¼ãƒ“ス)"
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "有効ãªãƒ—ロファイルURLã§ã¯ã‚ã‚Šã¾ã›ã‚“。(XRDSã®å®šç¾©ãŒç„¡ã„)"
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "有効ãªãƒ—ロファイルURLã§ã¯ã‚ã‚Šã¾ã›ã‚“。(XRDSドキュメントãŒç„¡ã„)"
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "ç”»åƒã§ã¯ãªã„ã‹ãƒ•ã‚¡ã‚¤ãƒ«ãŒç ´æã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "èªè¨¼ã•ã‚Œã¦ã„ã¾ã›ã‚“。"
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "想定外ã®ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã§ã™ï¼"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "ログインã—ã¦ã„ã¾ã›ã‚“。"
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "購読ã—ã¦ã„ã¾ã›ã‚“ï¼"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "%sã®é€šçŸ¥ãƒ•ã‚£ãƒ¼ãƒ‰"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "通知ã«ã¯ãƒ—ロファイルã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "通知"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "å¤ã„パスワード"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "OpenID アカウントセットアップ"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "OpenID 自動æ示"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID ログイン"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "OpenID ã§ã®èªè¨¼ãŒã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID ã§ã®èªè¨¼ã«å¤±æ•—ã—ã¾ã—㟠: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID 障害 : %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID ã¯æ¶ˆåŽ»ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID 設定"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "ä¸å®Œå…¨ãªã‚¢ãƒƒãƒ—ロード。"
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "パスワード"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "パスワードã¨ç¢ºèªãŒä¸€è‡´ã—ã¾ã›ã‚“。"
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "パスワードã¯6字以上ã§ãªã‘ã‚Œã°ã„ã‘ã¾ã›ã‚“。"
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "パスワード回復ã®ãƒªã‚¯ã‚¨ã‚¹ãƒˆã•ã‚Œã¾ã—ãŸ"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "パスワードãŒä¿å­˜ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "パスワードãŒä¸€è‡´ã—ã¾ã›ã‚“。"
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "ピープルサーãƒ"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "パーソナル"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr "ユーザã®é€šçŸ¥ã‚’購読ã™ã‚‹ã«ã¯è©³ç´°ã‚’確èªã—ã¦ä¸‹ã•ã„。購読ã—ãªã„å ´åˆã¯ã€\"Cancel\" キャンセルをクリックã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Jabber/GTalkã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ãŒå¤‰æ›´ã•ã‚ŒãŸæ™‚ã«é€šçŸ¥ã‚’é€ã‚‹ã€‚"
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "設定"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "設定ãŒä¿å­˜ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "プライãƒã‚·ãƒ¼"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "通知をä¿å­˜ã™ã‚‹éš›ã«å•é¡ŒãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "プロファイル"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "プロファイルURL"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "プロファイル設定"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "プロファイルãŒä¸æ˜Ž"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "パブリック"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "パブリックフィード"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "パブリックタイムライン"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "回復"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "パスワードを回復"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "ä¸æ˜Žãªãƒ¦ãƒ¼ã‚¶ã®ãŸã‚ã®å›žå¾©ã‚³ãƒ¼ãƒ‰ã€‚"
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "登録"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "æ‹’å¦"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "ログイン状態をä¿æŒ"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "リモートプロファイルã¨ä¸€è‡´ã™ã‚‹ã‚‚ã®ãŒã‚ã‚Šã¾ã›ã‚“"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "リモートサブスクライブ"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "削除"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "OpenID を削除"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr "最後㮠OpenID を削除ã™ã‚‹ã¨ã€ãƒ­ã‚°ã‚¤ãƒ³ã§ããªããªã‚Šã¾ã™ï¼å‰Šé™¤ã™ã‚‹ã¾ãˆã«ã€åˆ¥ã® OpenID を追加ã—ã¦ä¸‹ã•ã„。"
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "返信"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "%s ã¸ã®è¿”ä¿¡"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "リセット"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "パスワードをリセット"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "上ã¨åŒã˜ãƒ‘スワード"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "ä¿å­˜"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "検索"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "ストリームフィードを検索"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr "%%site.name%% ã®é€šçŸ¥ã‚’内容ã‹ã‚‰æ¤œç´¢ã€‚検索語ã¯ã‚¹ãƒšãƒ¼ã‚¹åŒºåˆ‡ã‚‹ã€‚3字以上"
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr "%%site.name%% ã®äººã‚’åå‰ã€å ´æ‰€ã€èˆˆå‘³ã‹ã‚‰æ¤œç´¢ã€‚検索語ã¯ã‚¹ãƒšãƒ¼ã‚¹åŒºåˆ‡ã‚‹ã€‚3字以上"
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "é€ã‚‹"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Jabber/GTalk ã§ç§ã«é€šçŸ¥ã‚’é€ã£ã¦ä¸‹ã•ã„。"
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "設定"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "設定ãŒä¿å­˜ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "æ—¢ã«ä»–ã®äººãŒã“ã® OpenID を使用ã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "ä¸æ¸¬ã®äº‹æ…‹ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚"
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "ソース"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "統計データ"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "ä¿å­˜ã•ã‚ŒãŸ OpenID ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "購読"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "購読者"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "購読ãŒè¨±å¯"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "購読ãŒæ‹’å¦"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "サブスクリプション"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "ファイルã®ã‚¢ãƒƒãƒ—ロードã§ã‚·ã‚¹ãƒ†ãƒ ã‚¨ãƒ©ãƒ¼"
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "文字検索"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "ãã® OpenID ã¯ã‚ãªãŸã®ã‚‚ã®ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "ãã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã¯æ—¢ã«æ‰¿èªã•ã‚Œã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "ãã®ç¢ºèªã‚³ãƒ¼ãƒ‰ã¯ã‚ãªãŸã®ã‚‚ã®ã§ã¯ã‚ã‚Šã¾ã›ã‚“ï¼"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "ファイルサイズãŒå¤§ãã™ãŽã¾ã™ã€‚"
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "ãã® Jabber ID ã¯æ—¢ã«ã‚ãªãŸã®ã‚‚ã®ã§ã™ã€‚"
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "ãã® Jabber ID ã¯ã‚ãªãŸã®ã‚‚ã®ã§ã¯ã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "ãã® IM アドレスã¯ä¸æ­£ã§ã™ã€‚"
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "é•·ã™ãŽã¾ã™ã€‚通知ã¯æœ€å¤§ 140 å­—ã¾ã§ã§ã™ã€‚"
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "アドレス \"%s\" ã¯ã‚ãªãŸã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨ã—ã¦æ‰¿èªã•ã‚Œã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "アドレスã¯å‰Šé™¤ã•ã‚Œã¾ã—ãŸã€‚"
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "%s ã®é€šçŸ¥ã‚’èžã„ã¦ã„る人"
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "ã‚ãªãŸã®é€šçŸ¥ã‚’èžã„ã¦ã„る人"
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "%s ãŒé€šçŸ¥ã‚’èžã„ã¦ã„る人"
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "ã‚ãªãŸãŒé€šçŸ¥ã‚’èžã„ã¦ã„る人"
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "確èªã‚³ãƒ¼ãƒ‰ãŒå¤ã™ãŽã¾ã™ã€‚ã‚‚ã†ä¸€åº¦ã‚„ã‚Šç›´ã—ã¦ãã ã•ã„。"
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr "ã“ã®ãƒ•ã‚©ãƒ¼ãƒ ã¯è‡ªå‹•çš„ã«ã‚µãƒ–ミットã•ã‚Œã¾ã™ã€‚ã•ã‚Œãªã„å ´åˆã¯ã€ã‚µãƒ–ミットボタンをクリックã—ã¦ä¸‹ã•ã„。OpenIDプロãƒã‚¤ãƒ€ã¸è»¢é€ã•ã‚Œã¾ã™ã€‚"
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"ã“れ㌠%s "
+"ã¸ã®åˆå›žã®ãƒ­ã‚°ã‚¤ãƒ³ã§ã™ã€‚OpenIDã¨ã®é–¢é€£ä»˜ã‘ãŒå¿…è¦ã§ã™ã€‚アカウントを新è¦ä½œæˆã™ã‚‹ã‹ã€æ—¢å­˜ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨ã®é–¢é€£ä»˜ã‘ã®ã©ã¡ã‚‰ã‹ã‚’下ã•ã„。"
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "ã“ã®ãƒšãƒ¼ã‚¸ã¯ã‚ãªãŸãŒæ‰¿èªã—ãŸãƒ¡ãƒ‡ã‚£ã‚¢ã‚¿ã‚¤ãƒ—ã§ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“。"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"サブスクライブã™ã‚‹ã«ã¯ã€[ログイン](%%action.login%%) "
+"ã™ã‚‹ã‹, [登録](%%action.register%%) ã‚’è¡Œã£ã¦ä¸‹ã•ã„。既㫠"
+"[compatible microblogging site](%%doc.openmublog%%) ã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’æŒã£ãŠã‚‚ã¡ã®å ´åˆã¯ã€ä¸‹ã«ãƒ—ロファイルURLを入力ã—ã¦ä¸‹ã•ã„."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "ホームページã€ãƒ–ログã€ãƒ—ロファイルã€ãã®ä»–サイト㮠URL"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "プロファイルサービスã¾ãŸã¯ãƒžã‚¤ã‚¯ãƒ­ãƒ–ロギングサービスã®URL"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "予期ã›ã¬ãƒ•ã‚©ãƒ¼ãƒ é€ä¿¡ã§ã™ã€‚"
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "予期ã›ã¬ãƒ‘スワードã®ãƒªã‚»ãƒƒãƒˆã§ã™ã€‚"
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "予期ã›ã¬ OMB プロトコルã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã§ã™ã€‚"
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr "特ã«æ–­ã‚ŠãŒç„¡ã„å ´åˆã¯ã€ã“ã®ã‚µã‚¤ãƒˆã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã®è‘—作権ã¯è‘—作者ã«å¸°å±žã—ã€ä¸‹è¨˜æ¡ä»¶ã®ä¸‹ã§ä½¿ç”¨å¯èƒ½ã§ã™ã€‚"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "ä¸æ˜Žãªã‚¢ãƒ‰ãƒ¬ã‚¹ã‚¿ã‚¤ãƒ— %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "サブスクライブ中止"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "サãƒãƒ¼ãƒˆå¤–ã® OMB ãƒãƒ¼ã‚¸ãƒ§ãƒ³"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "サãƒãƒ¼ãƒˆå¤–ã®ç”»åƒå½¢å¼ã§ã™ã€‚"
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "アップロード"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"\"ã‚¢ãƒã‚¿ãƒ¼\" (ユーザã®ç”»åƒ) をアップロード。 "
+"アップロードã—ãŸå¾Œã§ã¯ç”»åƒã®ç·¨é›†ã¯ã§ãã¾ã›ã‚“。サイズãŒå¤§ãã™ãŽãªã„ã‹ï¼ˆå°ã•ã™ãŽãªã„ã‹ï¼‰ç¢ºèªã—ã¦ä¸‹ã•ã„。サイトã®ãƒ©ã‚¤ã‚»ãƒ³ã‚¹ãŒé©ç”¨ã•ã‚Œã¾ã™ã€‚自身ã§æ‰€æœ‰ã—ã¦ã„ã¦ã€å…±æœ‰ã—ãŸã„ç”»åƒã‚’使用ã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "æ›´æ–°ã€ã‚¢ãƒŠã‚¦ãƒ³ã‚¹ã€ãƒ‘スワードリカãƒãƒªãƒ¼ã§ã®ã¿ä½¿ç”¨ã•ã‚Œã¾ã™ã€‚"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "リストã•ã‚Œã¦ã„るユーザã¯å­˜åœ¨ã—ã¾ã›ã‚“。"
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "プロファイルãŒã‚ã‚Šã¾ã›ã‚“。"
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "ユーザã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ "
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "最近ã©ã† %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "ã„る場所, 例ãˆã° \"City, State (or Region), Country\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "ä¸æ­£ãªç”»åƒå½¢å¼ã€‚'%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "ä¸æ­£ãªç”»åƒã‚µã‚¤ã‚ºã€‚'%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "æ—¢ã«ã“ã® OpenID を使用ã—ã¦ã„ã¾ã™ã€‚"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "æ—¢ã«ãƒ­ã‚°ã‚¤ãƒ³æ¸ˆã¿ã§ã™ã€‚"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "パスワードã¯ã“ã“ã§å¤‰æ›´ã§ãã¾ã™ã€‚良ã„ã‚‚ã®ã‚’é¸ã‚“ã§ä¸‹ã•ã„ï¼"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "アカウントを作æˆã—ã¦é€šçŸ¥ã®æŠ•ç¨¿ãŒå¯èƒ½ã§ã™ã€‚"
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr "\"削除\"をクリックã—㦠OpenID を削除ã§ãã¾ã™ã€‚"
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Jabber/GTalk [instant "
+"messages](%%doc.im%%) 経由ã§é€šçŸ¥ã®é€ä¿¡ã€å—ä¿¡ãŒå¯èƒ½ã§ã™ã€‚下ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’設定ã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr "ã‚ãªãŸã®ã“ã¨ã«ã¤ã„ã¦çŸ¥ã£ã¦ã‚‚らã†ãŸã‚ã«ã€ã“ã“ã§ãƒ—ロファイル情報を更新ã§ãã¾ã™ã€‚"
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "ローカルサブスクリプションを使用å¯èƒ½ã§ã™ï¼"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "ライセンスã«åŒæ„é ‚ã‘ãªã„å ´åˆã¯ç™»éŒ²ã§ãã¾ã›ã‚“。"
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "ãã®ãƒ—ロファイルã¯é€ä¿¡ã•ã‚Œã¦ã„ã¾ã›ã‚“。"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "確èªã•ã‚Œã¾ã—ãŸã€‚æ–°ã—ã„パスワードを入力ã—ã¦ä¸‹ã•ã„。"
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "ã‚ãªãŸã® OpenID URL"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "ã“ã®ã‚µãƒ¼ãƒã§ã®ãƒ‹ãƒƒã‚¯ãƒãƒ¼ãƒ ã€ã¾ãŸã¯ç™»éŒ²ã—ãŸãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ã€‚"
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) "
+"を使ã£ã¦åŒã˜ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§æ§˜ã€…ãªã‚¦ã‚§ãƒ–サイトã¸ãƒ­ã‚°ã‚¤ãƒ³ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚ã“ã“ã§é–¢é€£ä»˜ã‘ã‚‹ OpenID を管ç†ã—ã¦ä¸‹ã•ã„。"
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "数秒å‰"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "ç´„ %d æ—¥å‰"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "ç´„ %d 時間å‰"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "ç´„ %d 分å‰"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "ç´„ %d ヵ月å‰"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "ç´„ 1 æ—¥å‰"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "ç´„ 1 分å‰"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "ç´„ 1 ヵ月å‰"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "ç´„ 1 å¹´å‰"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "ç´„ 1 時間å‰"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "...ã«å¯¾ã—ã¦ã®è¿”ä¿¡"
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "返信"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "上ã®ãƒ‘スワードã¨åŒã˜"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "<< å‰"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/ko/LC_MESSAGES/laconica.mo b/locale/ko/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..c97fe45df
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/ko/LC_MESSAGES/laconica.po b/locale/ko/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..971ac3df7
--- /dev/null
+++ b/locale/ko/LC_MESSAGES/laconica.po
@@ -0,0 +1,2998 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+#: ../actions/register.php:191 actions/finishopenidlogin.php:88
+#: actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+
+#: ../actions/subscribe.php:84 ../lib/mail.php:124 lib/mail.php:124
+#: lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../actions/subscribe.php:86 ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/publicrss.php:60 ../actions/publicrss.php:62
+#: actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr ""
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#: ../actions/allrss.php:60 ../actions/twitapistatuses.php:238
+#: ../lib/stream.php:51 actions/all.php:47 actions/allrss.php:60
+#: actions/twitapistatuses.php:155 lib/personal.php:51
+#, php-format
+msgid "%s and friends"
+msgstr ""
+
+#: ../lib/util.php:233 ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:235 ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:250 ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176 actions/finishopenidlogin.php:79
+#: actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6ê¸€ìž ì´ìƒ"
+
+#: ../actions/recoverpassword.php:165 ../actions/recoverpassword.php:180
+#: actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr ""
+
+#: ../actions/imsettings.php:188 ../actions/imsettings.php:197
+#: actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../lib/util.php:296 ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr ""
+
+#: ../actions/userauthorization.php:118 ../actions/userauthorization.php:119
+#: actions/userauthorization.php:126
+msgid "Accept"
+msgstr ""
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/smssettings.php:71 actions/emailsettings.php:63
+#: actions/imsettings.php:64 actions/openidsettings.php:58
+#: actions/smssettings.php:71 actions/twittersettings.php:85
+msgid "Add"
+msgstr ""
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "OpenID 추가"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "주소"
+
+#: ../actions/showstream.php:254 ../actions/showstream.php:273
+#: actions/showstream.php:288
+msgid "All subscriptions"
+msgstr ""
+
+#: ../actions/publicrss.php:62 ../actions/publicrss.php:64
+#: actions/publicrss.php:50
+#, php-format
+msgid "All updates for %s"
+msgstr ""
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+#: ../actions/login.php:31 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr ""
+
+#: ../actions/subscribe.php:48 ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/userauthorization.php:76 ../actions/userauthorization.php:77
+#: actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:100 ../actions/register.php:184
+#: ../actions/login.php:104 ../actions/register.php:178
+#: actions/register.php:192
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "아바타"
+
+#: ../actions/avatar.php:113 actions/profilesettings.php:350
+msgid "Avatar updated."
+msgstr ""
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../lib/util.php:1136 ../lib/util.php:1318 lib/util.php:1452
+msgid "Before »"
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/profilesettings.php:49
+#: ../actions/register.php:170 actions/profilesettings.php:82
+#: actions/register.php:184
+msgid "Bio"
+msgstr ""
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr ""
+
+#: ../actions/updateprofile.php:118 ../actions/updateprofile.php:119
+#: actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+#: ../actions/recoverpassword.php:300 actions/profilesettings.php:404
+#: actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr ""
+
+#: ../actions/imsettings.php:59 ../actions/emailsettings.php:57
+#: ../actions/imsettings.php:58 ../actions/smssettings.php:62
+#: actions/emailsettings.php:58 actions/imsettings.php:59
+#: actions/smssettings.php:62
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:154 ../actions/imsettings.php:163
+#: actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/password.php:45 actions/profilesettings.php:184
+msgid "Change"
+msgstr ""
+
+#: ../actions/password.php:32 actions/profilesettings.php:36
+msgid "Change password"
+msgstr ""
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+#: actions/profilesettings.php:182 actions/recoverpassword.php:187
+#: actions/register.php:169 actions/smssettings.php:65
+msgid "Confirm"
+msgstr ""
+
+#: ../actions/confirmaddress.php:84 ../actions/confirmaddress.php:90
+#: actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr ""
+
+#: ../actions/imsettings.php:213 ../actions/emailsettings.php:238
+#: ../actions/imsettings.php:222 ../actions/smssettings.php:245
+#: actions/emailsettings.php:256 actions/imsettings.php:230
+#: actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr ""
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:91 actions/finishopenidlogin.php:97
+msgid "Connect"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr ""
+
+#: ../lib/util.php:304 ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr ""
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:161 ../actions/updateprofile.php:162
+#: actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr ""
+
+#: ../actions/updateprofile.php:154 ../actions/updateprofile.php:155
+#: actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr ""
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr ""
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../actions/unsubscribe.php:56 ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/imsettings.php:178 ../actions/emailsettings.php:205
+#: ../actions/imsettings.php:187 ../actions/smssettings.php:206
+#: actions/emailsettings.php:223 actions/imsettings.php:195
+#: actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:175 ../actions/profilesettings.php:184
+#: ../actions/twitapiaccount.php:96 actions/profilesettings.php:299
+#: actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr ""
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:84 actions/finishopenidlogin.php:90
+msgid "Create"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:70 actions/finishopenidlogin.php:76
+msgid "Create a new user with this nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:68 actions/finishopenidlogin.php:74
+msgid "Create new account"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/showstream.php:337 ../actions/showstream.php:356
+#: actions/showstream.php:367
+msgid "Currently"
+msgstr ""
+
+#: ../lib/util.php:893 ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/profilesettings.php:54 ../actions/profilesettings.php:51
+#: ../actions/register.php:172 actions/profilesettings.php:84
+#: actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr ""
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "ì´ë©”ì¼"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "ì´ë©”ì¼ ì£¼ì†Œ"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr ""
+
+#: ../lib/mail.php:82 ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr ""
+
+#: ../actions/recoverpassword.php:176 ../actions/recoverpassword.php:191
+#: actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr ""
+
+#: ../actions/userauthorization.php:136 ../actions/userauthorization.php:137
+#: actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:282 ../actions/finishopenidlogin.php:253
+#: actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr ""
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:201 ../actions/recoverpassword.php:240
+#: actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:139 ../actions/userauthorization.php:140
+#: actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+#: lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr ""
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr ""
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:79 ../actions/recoverpassword.php:80
+#: actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr ""
+
+#: ../lib/util.php:298 ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr ""
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:74 ../actions/allrss.php:64
+#: actions/all.php:61 actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr ""
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#: ../actions/replies.php:65 actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr ""
+
+#: ../actions/login.php:118 ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr ""
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr ""
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "ë„움ë§"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "홈"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "홈페ì´ì§€"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "홈페ì´ì§€ 주소형ì‹ì´ 올바르지 않습니다."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "메신저 주소"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "메신저 설정"
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr ""
+
+#: ../actions/login.php:63 ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr ""
+
+#: ../actions/recoverpassword.php:226 ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+
+#: ../actions/updateprofile.php:113 ../actions/updateprofile.php:114
+#: actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:97 ../actions/updateprofile.php:98
+#: actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:81 ../actions/updateprofile.php:82
+#: actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:86 ../actions/updateprofile.php:87
+#: actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84 ../actions/finishopenidlogin.php:235
+#: ../actions/register.php:93 ../actions/register.php:111
+#: actions/finishopenidlogin.php:241 actions/register.php:103
+#: actions/register.php:121
+msgid "Invalid username or password."
+msgstr ""
+
+#: ../lib/util.php:237 ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:164 ../actions/imsettings.php:173
+#: actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:63 ../actions/imsettings.php:62
+#: actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "위치"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr ""
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "로그ì¸"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "[OpenID](%%doc.openid%%)ë¡œ 로그ì¸í•˜ì„¸ìš”."
+
+#: ../actions/login.php:122 ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "로그아웃"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "비밀번호를 잊어버리셨어요?"
+
+#: ../actions/showstream.php:281 ../actions/showstream.php:300
+#: actions/showstream.php:315
+msgid "Member since"
+msgstr ""
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+#: ../actions/register.php:188 actions/finishopenidlogin.php:85
+#: actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr ""
+
+#: ../actions/newnotice.php:100 ../actions/newnotice.php:87
+#: actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+#: ../actions/recoverpassword.php:179 actions/profilesettings.php:180
+#: actions/recoverpassword.php:185
+msgid "New password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:275 ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "닉네임"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+#: ../actions/profilesettings.php:88 ../actions/register.php:67
+#: ../actions/updateprofile.php:77 actions/finishopenidlogin.php:171
+#: actions/profilesettings.php:203 actions/register.php:74
+#: actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr ""
+
+#: ../actions/recoverpassword.php:147 ../actions/recoverpassword.php:162
+#: actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr ""
+
+#: ../actions/imsettings.php:147 ../actions/imsettings.php:156
+#: actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr ""
+
+#: ../actions/userauthorization.php:128 ../actions/userauthorization.php:129
+#: actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr ""
+
+#: ../actions/newnotice.php:49 ../actions/newnotice.php:44
+#: actions/newmessage.php:53 actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/userbyid.php:27 ../actions/userbyid.php:32
+#: actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr ""
+
+#: ../actions/imsettings.php:197 ../actions/emailsettings.php:222
+#: ../actions/imsettings.php:206 ../actions/smssettings.php:229
+#: actions/emailsettings.php:240 actions/imsettings.php:214
+#: actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:189 ../actions/recoverpassword.php:226
+#: actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:48 ../actions/userauthorization.php:49
+#: actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr ""
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+#: ../actions/shownotice.php:83 ../lib/deleteaction.php:30
+#: actions/shownotice.php:32 actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+#: ../actions/foaf.php:40 ../actions/replies.php:57
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/xrds.php:35 ../lib/gallery.php:57 ../lib/subs.php:33
+#: ../lib/subs.php:82 actions/all.php:34 actions/allrss.php:35
+#: actions/avatarbynickname.php:43 actions/favoritesrss.php:35
+#: actions/foaf.php:40 actions/ical.php:31 actions/remotesubscribe.php:93
+#: actions/remotesubscribe.php:100 actions/replies.php:57
+#: actions/repliesrss.php:35 actions/showfavorites.php:34
+#: actions/showstream.php:110 actions/userbyid.php:36 actions/userrss.php:35
+#: actions/xrds.php:35 classes/Command.php:120 classes/Command.php:162
+#: classes/Command.php:203 classes/Command.php:237 lib/gallery.php:62
+#: lib/mailbox.php:36 lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../lib/gallery.php:76 ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../actions/imsettings.php:158 ../actions/imsettings.php:167
+#: actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr ""
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr ""
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr ""
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:118 ../actions/remotesubscribe.php:120
+#: actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:111 ../actions/remotesubscribe.php:113
+#: actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+#: ../actions/logout.php:33 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: actions/disfavor.php:29 actions/favor.php:30 actions/finishaddopenid.php:29
+#: actions/logout.php:33 actions/newmessage.php:28 actions/newnotice.php:29
+#: actions/subscribe.php:28 actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../actions/unsubscribe.php:43 ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:297 ../actions/showstream.php:316
+#: actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../actions/password.php:39 actions/profilesettings.php:178
+msgid "Old password"
+msgstr ""
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID 로그ì¸"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr ""
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr ""
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "비밀번호"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "비밀번호가 ì¼ì¹˜í•˜ì§€ 않습니다."
+
+#: ../actions/recoverpassword.php:245 ../actions/recoverpassword.php:284
+#: actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr ""
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+#: ../actions/recoverpassword.php:313 actions/profilesettings.php:408
+#: actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "비밀번호가 ì¼ì¹˜í•˜ì§€ 않습니다."
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:44 ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr ""
+
+#: ../actions/userauthorization.php:77 ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:74 ../actions/imsettings.php:73
+#: actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/imsettings.php:68 ../actions/emailsettings.php:85
+#: ../actions/imsettings.php:67 ../actions/smssettings.php:94
+#: actions/emailsettings.php:86 actions/imsettings.php:68
+#: actions/smssettings.php:94 actions/twittersettings.php:70
+msgid "Preferences"
+msgstr ""
+
+#: ../actions/imsettings.php:135 ../actions/emailsettings.php:162
+#: ../actions/imsettings.php:144 ../actions/smssettings.php:163
+#: actions/emailsettings.php:180 actions/imsettings.php:152
+#: actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "ê°œì¸ì •ë³´ë³´í˜¸"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/stream.php:54 ../lib/settingsaction.php:84 ../lib/stream.php:60
+#: lib/personal.php:60 lib/settingsaction.php:84
+msgid "Profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+#: ../actions/updateprofile.php:52 actions/postnotice.php:52
+#: actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr ""
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr ""
+
+#: ../actions/recoverpassword.php:151 ../actions/recoverpassword.php:166
+#: actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:141 ../actions/recoverpassword.php:156
+#: actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "회ì›ê°€ìž…"
+
+#: ../actions/userauthorization.php:119 ../actions/userauthorization.php:120
+#: actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "ìžë™ 로그ì¸"
+
+#: ../actions/updateprofile.php:69 ../actions/updateprofile.php:70
+#: actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "삭제"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "OpenID 삭제"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:49 ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#: ../lib/stream.php:56 actions/replies.php:47 actions/repliesrss.php:62
+#: lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:168 ../actions/recoverpassword.php:183
+#: actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:158 ../actions/recoverpassword.php:173
+#: actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "검색"
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../lib/util.php:982 ../actions/invite.php:137 ../lib/util.php:1172
+#: actions/invite.php:145 lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/imsettings.php:71 ../actions/imsettings.php:70
+#: actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../lib/util.php:282 ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:183 ../actions/profilesettings.php:192
+#: actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "소스 코드"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "통계"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+#: ../actions/finishopenidlogin.php:246 actions/finishopenidlogin.php:188
+#: actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+#: ../actions/showstream.php:313 actions/showstream.php:328
+#: actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:309 ../actions/userauthorization.php:310
+#: actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:319 ../actions/userauthorization.php:320
+#: actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27 ../actions/showstream.php:230
+#: ../actions/showstream.php:307 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:161 ../actions/imsettings.php:170
+#: actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:224 ../actions/imsettings.php:233
+#: actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:201 ../actions/emailsettings.php:226
+#: ../actions/imsettings.php:210 actions/emailsettings.php:244
+#: actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/newnotice.php:52 ../actions/newnotice.php:49
+#: ../actions/twitapistatuses.php:408 actions/newnotice.php:49
+#: actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:86 ../actions/confirmaddress.php:92
+#: actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/imsettings.php:241 ../actions/emailsettings.php:264
+#: ../actions/imsettings.php:250 ../actions/smssettings.php:274
+#: actions/emailsettings.php:282 actions/imsettings.php:258
+#: actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/recoverpassword.php:87 ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../lib/util.php:147 ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/profilesettings.php:48
+#: ../actions/register.php:169 actions/profilesettings.php:81
+#: actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/smssettings.php:135 actions/emailsettings.php:144
+#: actions/imsettings.php:118 actions/recoverpassword.php:39
+#: actions/smssettings.php:143 actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:237 ../actions/recoverpassword.php:276
+#: actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:245 ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:193 ../actions/showstream.php:209
+#: actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+#: ../actions/updateprofile.php:45 actions/postnotice.php:45
+#: actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "ì´ë¯¸ì§€ 올리기"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/foaf.php:47
+#: ../actions/twitapiaccount.php:82 ../actions/twitapistatuses.php:319
+#: ../actions/twitapistatuses.php:685 ../actions/twitapiusers.php:82
+#: actions/all.php:41 actions/avatarbynickname.php:48 actions/foaf.php:47
+#: actions/replies.php:41 actions/showfavorites.php:41
+#: actions/showstream.php:44 actions/twitapiaccount.php:80
+#: actions/twitapifavorites.php:68 actions/twitapistatuses.php:235
+#: actions/twitapistatuses.php:609 actions/twitapiusers.php:87
+#: lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../lib/util.php:969 ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 ../actions/profilesettings.php:54
+#: ../actions/register.php:175 actions/profilesettings.php:87
+#: actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:127 ../actions/updateprofile.php:128
+#: actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:122 ../actions/updateprofile.php:123
+#: actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:164 ../actions/register.php:135
+#: actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+#: ../actions/register.php:61 actions/finishopenidlogin.php:38
+#: actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:62 ../actions/updateprofile.php:63
+#: actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:134 ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149 ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:814 ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "%dì¼ì „"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "%d시간전"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "%d분전"
+
+#: ../lib/util.php:830 ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:824 ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:816 ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:828 ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:832 ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:820 ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101 ../actions/noticesearch.php:130
+#: ../actions/showstream.php:408 ../lib/stream.php:117
+#: actions/noticesearch.php:136 actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108 ../actions/noticesearch.php:137
+#: ../actions/showstream.php:415 ../lib/stream.php:124
+#: actions/noticesearch.php:143 actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+#: ../lib/util.php:1127 ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/laconica.pot b/locale/laconica.pot
new file mode 100644
index 000000000..f49d2cdad
--- /dev/null
+++ b/locale/laconica.pot
@@ -0,0 +1,2858 @@
+# 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: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+#: actions/finishopenidlogin.php:88 actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone number."
+msgstr ""
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/publicrss.php:62 actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr ""
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51 actions/all.php:47
+#: actions/allrss.php:60 actions/twitapistatuses.php:155 lib/personal.php:51
+#, php-format
+msgid "%s and friends"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by [%%site."
+"broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: actions/finishopenidlogin.php:79 actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/password.php:42 actions/profilesettings.php:181
+msgid "6 or more characters"
+msgstr ""
+
+#: ../actions/recoverpassword.php:180 actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve %"
+"s for sending messages to you."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr ""
+
+#: ../actions/userauthorization.php:119 actions/userauthorization.php:126
+msgid "Accept"
+msgstr ""
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+#: actions/emailsettings.php:63 actions/imsettings.php:64
+#: actions/openidsettings.php:58 actions/smssettings.php:71
+#: actions/twittersettings.php:85
+msgid "Add"
+msgstr ""
+
+#: ../actions/openidsettings.php:43 actions/openidsettings.php:44
+msgid "Add OpenID"
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39 actions/emailsettings.php:39
+#: actions/imsettings.php:40 actions/smssettings.php:39
+msgid "Address"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/showstream.php:273 actions/showstream.php:288
+msgid "All subscriptions"
+msgstr ""
+
+#: ../actions/publicrss.php:64 actions/publicrss.php:50
+#, php-format
+msgid "All updates for %s"
+msgstr ""
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr ""
+
+#: ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/userauthorization.php:77 actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:104 ../actions/register.php:178
+#: actions/register.php:192
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for non-humans)"
+msgstr ""
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#: actions/profilesettings.php:34
+msgid "Avatar"
+msgstr ""
+
+#: ../actions/avatar.php:113 actions/profilesettings.php:350
+msgid "Avatar updated."
+msgstr ""
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/util.php:1318 lib/util.php:1452
+msgid "Before »"
+msgstr ""
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+#: actions/profilesettings.php:82 actions/register.php:184
+msgid "Bio"
+msgstr ""
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+#: actions/profilesettings.php:404 actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr ""
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62 actions/emailsettings.php:58
+#: actions/imsettings.php:59 actions/smssettings.php:62
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../actions/password.php:45 actions/profilesettings.php:184
+msgid "Change"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../actions/password.php:32 actions/profilesettings.php:36
+msgid "Change password"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+#: actions/profilesettings.php:182 actions/recoverpassword.php:187
+#: actions/register.php:169 actions/smssettings.php:65
+msgid "Confirm"
+msgstr ""
+
+#: ../actions/confirmaddress.php:90 actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245 actions/emailsettings.php:256
+#: actions/imsettings.php:230 actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:91 actions/finishopenidlogin.php:97
+msgid "Connect"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr ""
+
+#: ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr ""
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:162 actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr ""
+
+#: ../actions/updateprofile.php:155 actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:127 actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+#: actions/profilesettings.php:299 actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:84 actions/finishopenidlogin.php:90
+msgid "Create"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:70 actions/finishopenidlogin.php:76
+msgid "Create a new user with this nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:68 actions/finishopenidlogin.php:74
+msgid "Create new account"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../actions/showstream.php:356 actions/showstream.php:367
+msgid "Currently"
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+#: actions/profilesettings.php:84 actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr ""
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87 actions/register.php:172
+#: actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr ""
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/recoverpassword.php:191 actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:253 actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:240 actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../lib/openid.php:226 lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr ""
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr ""
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:80 actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr ""
+
+#: ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr ""
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:64 actions/all.php:61
+#: actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr ""
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#: actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr ""
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr ""
+
+#: ../lib/util.php:322 lib/util.php:338
+msgid "Help"
+msgstr ""
+
+#: ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr ""
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+#: actions/profilesettings.php:79 actions/register.php:181
+msgid "Homepage"
+msgstr ""
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+#: actions/profilesettings.php:210 actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/imsettings.php:60 actions/imsettings.php:61
+msgid "IM Address"
+msgstr ""
+
+#: ../actions/imsettings.php:33 actions/imsettings.php:33
+msgid "IM Settings"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr ""
+
+#: ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr ""
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+
+#: ../actions/updateprofile.php:114 actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:98 actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:87 actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111 actions/finishopenidlogin.php:241
+#: actions/register.php:103 actions/register.php:121
+msgid "Invalid username or password."
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version %"
+"s, available under the [GNU Affero General Public License](http://www.fsf."
+"org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+#: actions/profilesettings.php:85 actions/register.php:187
+msgid "Location"
+msgstr ""
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr ""
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310 actions/login.php:97
+#: actions/login.php:106 actions/openidlogin.php:77 lib/util.php:326
+msgid "Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:44 actions/openidlogin.php:52
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr ""
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? [Register]"
+"(%%action.register%%) a new account, or try [OpenID](%%action.openidlogin%"
+"%). "
+msgstr ""
+
+#: ../lib/util.php:308 lib/util.php:324
+msgid "Logout"
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/showstream.php:300 actions/showstream.php:315
+msgid "Member since"
+msgstr ""
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+#: actions/finishopenidlogin.php:85 actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr ""
+
+#: ../actions/newnotice.php:87 actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+#: actions/profilesettings.php:180 actions/recoverpassword.php:185
+msgid "New password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+#: actions/finishopenidlogin.php:171 actions/profilesettings.php:203
+#: actions/register.php:74 actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr ""
+
+#: ../actions/recoverpassword.php:162 actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/imsettings.php:156 actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr ""
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr ""
+
+#: ../actions/newnotice.php:44 actions/newmessage.php:53
+#: actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/userbyid.php:32 actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr ""
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:226 actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr ""
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82 actions/all.php:34
+#: actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/imsettings.php:167 actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr ""
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr ""
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:120 actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:113 actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27 actions/disfavor.php:29 actions/favor.php:30
+#: actions/finishaddopenid.php:29 actions/logout.php:33
+#: actions/newmessage.php:28 actions/newnotice.php:29 actions/subscribe.php:28
+#: actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:316 actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/password.php:39 actions/profilesettings.php:178
+msgid "Old password"
+msgstr ""
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314 lib/settingsaction.php:90
+#: lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr ""
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+#: actions/finishopenidlogin.php:96 actions/login.php:102
+#: actions/register.php:167
+msgid "Password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:288 actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: ../actions/recoverpassword.php:284 actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr ""
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+#: actions/profilesettings.php:408 actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:88
+#: actions/profilesettings.php:380 actions/register.php:98
+msgid "Passwords don't match."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94 actions/emailsettings.php:86
+#: actions/imsettings.php:68 actions/smssettings.php:94
+#: actions/twittersettings.php:70
+msgid "Preferences"
+msgstr ""
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr ""
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60 lib/personal.php:60
+#: lib/settingsaction.php:84
+msgid "Profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/recoverpassword.php:166 actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:156 actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr ""
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr ""
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#: actions/replies.php:47 actions/repliesrss.php:62 lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:183 actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:173 actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300 lib/searchaction.php:84
+#: lib/util.php:316
+msgid "Search"
+msgstr ""
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/invite.php:137 ../lib/util.php:1172 actions/invite.php:145
+#: lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:192 actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:296 actions/showstream.php:311
+msgid "Statistics"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+#: actions/finishopenidlogin.php:188 actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+#: actions/showstream.php:328 actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:170 actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/imsettings.php:233 actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+#: actions/emailsettings.php:244 actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+#: actions/newnotice.php:49 actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:92 actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to them:"
+msgstr ""
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or [register](%%action."
+"register%%) a new account. If you already have an account on a [compatible "
+"microblogging site](%%doc.openmublog%%), enter your profile URL below."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+#: actions/profilesettings.php:81 actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:276 actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:209 actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../actions/avatar.php:68 actions/profilesettings.php:161
+msgid "Upload"
+msgstr ""
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this service."
+msgstr ""
+
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82 actions/all.php:41
+#: actions/avatarbynickname.php:48 actions/foaf.php:47 actions/replies.php:41
+#: actions/showfavorites.php:41 actions/showstream.php:44
+#: actions/twitapiaccount.php:80 actions/twitapifavorites.php:68
+#: actions/twitapistatuses.php:235 actions/twitapistatuses.php:609
+#: actions/twitapiusers.php:87 lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+#: actions/profilesettings.php:87 actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:135 actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant messages](%%"
+"doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+#: actions/finishopenidlogin.php:38 actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr ""
+
+#: ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr ""
+
+#: ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr ""
+
+#: ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117 actions/noticesearch.php:136
+#: actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124 actions/noticesearch.php:143
+#: actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/mk_MK/LC_MESSAGES/laconica.mo b/locale/mk_MK/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..8fa1d1997
--- /dev/null
+++ b/locale/mk_MK/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/mk_MK/LC_MESSAGES/laconica.po b/locale/mk_MK/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..67538ce60
--- /dev/null
+++ b/locale/mk_MK/LC_MESSAGES/laconica.po
@@ -0,0 +1,2827 @@
+# #-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2008 FREE SOFTWARE MACEDONIA
+# This file is distributed under the same license as the PACKAGE package.
+# IGOR STAMATOVSKI <igor@slobodensoftver.org.mk>, 2008.
+#
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: NOVICA NAKOV <novica@slobodensoftver.org.mk>\n"
+"Language-Team: Macedonian <info@slobodensoftver.org.mk>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Пребарувај го потокот за „%s“"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"оÑвен Ñледниве лични податоци: лозинка, "
+"адреÑа за е-пошта, адреÑа за ИМ, телефонÑки број."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s Ñега ги Ñледи вашите забелешки за %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s Ñега ги Ñледи вашите забелешки на "
+"%2$s.\n"
+"\n"
+"\t%3$s\n\nИÑкрено ваш,\n%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Јавниот поток на %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s и пријателите"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** е ÑÐµÑ€Ð²Ð¸Ñ Ð·Ð° микроблогирање што "
+"ви го овозможува [%%site.broughtby%%](%%site.broughtbyurl%%). "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** е ÑÐµÑ€Ð²Ð¸Ñ Ð·Ð° микроблогирање."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ". ПридонеÑувачите треба да бидат наведени Ñо цело име или прекар."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 мали букви или бројки. Без интерпукциÑки знаци и празни меÑта."
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 или повеќе знаци"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 или повеќе знаци и не ја заборавајте!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"ИÑпративме код за потврда на IM адреÑата "
+"што ја додадовте. Мора да го одобрите %S за да ви иÑпраќа пораки."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "За"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Прифати"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Додај"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Додај OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "ÐдреÑа"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Сите претплати"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Сите новини од %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Сите новини кои Ñе еднакви Ñо бараниот термин „%s“"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Веќе Ñте најавени."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Веќе Ñте претплатени!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Одобрете ја претплатата"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Следниот пат најавете Ñе автоматÑки; не "
+"за компјутери кои ги делите Ñо други!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Ðватар"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Ðватарот е ажуриран."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Чекам потвдар за оваа адреÑа. Проверете "
+"ја вашата Jabber/GTalk Ñметка за порака Ñо "
+"понатамошни инÑтрукции. (Дали го додадовте %s на вашата лиÑта Ñо пријатели?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Предходни »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Био"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Биографијата е предолга (макÑимумот е 140 знаци)."
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Ðе може да Ñе прочита URL-то на аватарот: '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Ðовата лозинка не може да Ñе Ñними"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Откажи"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Ðе е возможно да Ñе инÑтанцира OpenID објект за потрошувачка."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Ова JabberID не може да Ñе нормализира."
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Промени"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Промени ја лозинката"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Потврди"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Потврди ја адреÑата"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Потврдата е откажана"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Кодот за потврда не е пронајден."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Поврзи Ñе"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Поврзи Ñе Ñо поÑтоечка Ñметка"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Контакт"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "OpenID формуларот не може да Ñе креира:%s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Ðе може да Ñе пренаÑочи кон Ñерверот: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Информациите за аватарот не може да Ñе Ñнимат"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Информациите за новиот профил не може да Ñе Ñнимат"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "ÐдреÑата за е-пошта неможе да Ñе потврди."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Белезите за барање не може да Ñе конвертираат во белези за приÑтап."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Ðе може да Ñе креира претплатата"
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Ðе може да Ñе креира потврда за е-пошта."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Претплата не може да Ñе избрише."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Ðе може да Ñе земе белег за барање."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Кодот за потврда не може да Ñе внеÑе."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Ðе може да Ñе внеÑе нова претплата."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Профилот не може да Ñе Ñними."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "КориÑникот не може да Ñе оÑвежи/"
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Креирај"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Креирај нов кориÑник Ñо овој прекар."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Креирај нова Ñметка"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Креирање на нова Ñметка за OpenID што веќе има кориÑник."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Моментално потврдена Jabber/GTalk адреÑа."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Моментално"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Одговор од внеÑот во базата: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Опишете Ñе Ñебе Ñи и ÑопÑтвените интереÑи во 140 знаци."
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Е-пошта"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "ÐдреÑа за е-пошта"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "ÐдреÑата веќе поÑтои."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Потврдување на адреÑата"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "ВнеÑете прекар или е-пошта"
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Грешка во проверувањето на белегот"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Грешка во поврзувањето на кориÑникот Ñо OpenID"
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Грешка во поврзувањето на кориÑникот."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Грешка во внеÑувањето на аватарот"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Грешка во внеÑувањето на новиот профил"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Грешка во внеÑувањето на извеÑтувањето"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Грешка во внеÑувањето на оддалечениот профил"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Грешка во Ñнимањето на потвдата за адреÑата."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Грешка во Ñнимањето на оддалечениот профил"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Грешка во Ñнимањето на профилот."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Грешка во Ñнимањето на кориÑникот."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Грешка во Ñнимањето на кориÑникот; неправилен."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Грешка во поÑтавувањето на кориÑникот."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Грешка во оÑвежувањето на профилот"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Грешка во оÑвежувањето на оддалечениот профил"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Грешка Ñо кодот за потврдување."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "ПоÑтоечки прекар"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "ЧПП"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Товарањето на аватарот не уÑпеа."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Канал Ñо пријатели на %S"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Канал Ñо одговори на %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Поради безбедноÑни причини треба "
+"повторно да го внеÑете Вашето кориÑничко име и лозинка пред да ги Ñмените Вашите поÑтавки."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Цело име"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Целото име е предолго (макÑимум 255 знаци)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Помош"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Дома"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Домашна Ñтраница"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Домашната Ñтраница не е правилно URL."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM адреÑа"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "ПоÑтавки за IM"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Ðко веќе имае Ñметка, пријавете Ñе Ñо "
+"Вашето кориÑничко име и лозика, за иÑтата да ја поврзете Ñо Вашиот OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Ðко Ñакате да додадете OpenID на Вашата "
+"Ñметка, внеÑете го подолу и кликнете „Додај“."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Ðко Ñте ја заборавиле или загубиле "
+"лозинката, можете да добиете нова на адреÑата што ја внеÑовте во Вашата Ñметка."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Ðеточна Ñтара лозинка"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Ðеточно кориÑничко име или лозинка"
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"УпатÑтвото за пронаоѓање на Вашата "
+"лозинка е иÑпратено до адреÑата за е-пошта што е региÑтрирана Ñо Вашата Ñметка."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Ðеправилно URL за аватар: '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Ðевалидна домашна Ñтраница: '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Ðеправилно URL за лиценца: '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Ðеправилна Ñодржина за извеÑтување"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Ðеправилно uri на извеÑтување"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Ðеправилно url на извеÑтување"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Ðеправилно URL на профил: '%s'"
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Ðеправилно URL на профил (лош формат)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Ðеправилно URL на профил вратено од Ñерверот"
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Погрешна големина."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Погрешно име или лозинка."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Работи на [Laconica](http://laconi.ca/) Ñофтверот за "
+"микроблогирање, верзија %s, доÑтапен пд [GNU "
+"Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Ова Jabber ID му припаќа на друг кориÑник."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber или GTalk адреÑа како „ime@example.org“. Ðо "
+"прво додајте го %s во Вашата контакт лиÑта во Вашиот IM клиент или GTalk."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Локација"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Локацијата е предолга (макÑимумот е 255 знаци)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Пријави Ñе"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Пријавете Ñе Ñо [OpenID](%%doc.openid%%) Ñметка."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Пријавете Ñе Ñо кориÑничко име и лозинка. "
+"Ðемате? [РегиÑтрирајте](%%action.register%%) нова Ñметка или пробајте [OpenID](%%action.openidlogin%%). "
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Одјави Ñе"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Загубена или заборавена лозинка?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Член од"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Микроблог на %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Мојот текÑÑ‚ и датотеки Ñе доÑтапни под"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Ðов прекар"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Ðово извеÑтување"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Ðова лозинка"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Ðовата лозинка уÑпешно е Ñнимена. Сега Ñте пријавени."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Прекар"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Тој прекар е во употреба. Одберете друг."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "Прекарот мора да има Ñамо мали букви и бројки и да нема празни меÑта."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Тој прекар не е дозволен."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Прекар на кориÑникот што Ñакате да го Ñледите."
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Прекар или е-пошта"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Ðема JabberID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Ðема барање за проверка!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Ðема код за потврда."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Ðема Ñодржина!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Ðема id."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Серверот не доÑтави прекар."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Ðема прекар."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Ðема потврди кои може да Ñе откажат."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Серверот не доÑтави URL за профилот."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Ðема региÑтрирана адреÑа за е-пошта за тој кориÑник."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Ðе е пронаједено барање."
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Ðема резултати"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Ðема големина."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Ðема таков OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Ðема таков документ."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Ðема такво извеÑтување."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Ðема таков код за ÑпаÑување."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Ðема таква претплата"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Ðема таков кориÑник."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Ðема никој!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Ова не е код за ÑпаÑување."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Ðеправилен JabberID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Ðеправилен OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Ðеправилна адреÑа за е-пошта."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Ðеправилен прекар."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Ðеправилно URL на профил (неточен ÑервиÑ)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Ðеправилно URL на профил (нема дефиниран XRDS)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Ðеправилно URL на профил (нема YADIS документ)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Ðе е Ñлика или датотеката е корумпирана."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Ðе е одобрено."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Овој одговор не беше очекуван!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Ðе Ñте пријавени."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Ðе Ñте претплатени!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Канал Ñо извеÑтувања на %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "ИзвеÑтувањето нема профил"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "ИзвеÑтувања"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Стара лозинка"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "ПоÑтавување на Ñметка за OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "ÐвтоматÑко иÑпраќање на OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Пријавување Ñо OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Проверката на OpenID е откажана."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Проверката на OpenID не уÑпеа: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "ÐеуÑпех на OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID-то е отÑтрането"
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "ПоÑтавки за OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Парцијално товарање"
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Лозинка"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Двете лозинки не Ñе Ñовпаѓаат."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Лозинката мора да биде од најмалку 6 знаци."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Побарано е пронаоѓање на лозинката"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Лозинката е Ñнимена."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Лозинките не Ñе Ñовпаѓаат."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Пребарување на луѓе"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Личен"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Проверете ги овие детали ако Ñакате да Ñе "
+"претплатите на извеÑтувањата на овој кориÑник. Ðко не Ñакате да Ñе претплатите, кликнете на „Откажи“."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "ИÑпрати извеÑтување кога мојот ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° Jabber/GTalk ќе Ñе Ñмени."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Преференции"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Преференциите Ñе Ñнимени."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "ПриватноÑÑ‚"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Проблем во Ñнимањето на извеÑтувањето."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Профил"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL на профилот"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "ПоÑтавки на профилот"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Ðепознат профил"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Јавен"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Јавен канал"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Јавна иÑторија"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Пронајди"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Пронаоѓање на лозинка"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Код за пронаоѓање за непознат кориÑник."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "РегиÑтрирај Ñе"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Одбиј"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Запамети ме"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Оддалечениот профил нема одговарачки профил"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Оддалечена претплата"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "ОтÑтрани"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "ОтÑтрани го OpenID-то"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Ðко го оÑтраните Вашето единÑтвено OpenID, "
+"тогаш нема да можете да Ñе пријавите. Ðко треба да го отÑтраните, прво додајте друг OpenID."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Одговори"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Одговори иÑпратени до %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "РеÑетирај"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "РеÑтетирај ја лозинката"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "ИÑто како лозинката погоре"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Сними"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Барај"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Барај во каналот"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Барајте извеÑтувања на %%site.name%% Ñпоред "
+"нивната Ñодржина. Термините одделете ги Ñо празни меÑта. Ðајмала должина е 3 знаци."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Барајте луѓе на %%site.name%% Ñпоред нивното "
+"име, локација или интереÑи. Термините одделете ги Ñо празни меÑта. Ðајмала должина е 3 знаци."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "ИÑпрати"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "ИÑпраќај ми извеÑтувања преку Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "ПоÑтавки"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "ПоÑтавките Ñе Ñнимени."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Ðекој друг веќе го кориÑти ова OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Ðешто чудно Ñе Ñлучи."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Изворен код"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "СтатиÑтика"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Зачуваниот OpenID не е пронајден."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Претплати Ñе"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Претплатници"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Претплатата е одобрена"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Претплатата е одбиена"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Претплати"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "СиÑтемÑка грешка при товарањето на датотеката."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "ТекÑтуално пребарување"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Овој OpenID не Ви припаѓа Вам."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Оваа адреÑа веќе е потврдена."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Овој код за потврда не е за ВаÑ!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Датотеката е преголема."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Ова веќе е Вашиот Jabber ID."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Ова не е Вашиот Jabber ID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Ова е погрешната IM адреÑа."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Ова е предолго. МакÑималната должина е 140 знаци."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "ÐдреÑата \"%s\" е потврдена за Вашата Ñметка."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "ÐдреÑата е отÑтранета."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Претплатата е одобрена, но нема вратено "
+"URL. Проверете ги инÑтрукциите за меÑтото "
+"за да видите како да ја одобрите претплатата. Вашиот белег за претплата е:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Претплатата е одбиена, но нема вратено URL. "
+"Проверете ги инÑтрукциите за меÑтото за да видите како целоÑно да ја одбиете претплатата."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Ова Ñе луѓето што ги Ñледат извеÑтувањата на %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Ова Ñе луѓето што ги Ñледат Вашите извеÑтувања."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Ова Ñе луѓето чии извеÑтувања ги Ñледи %s."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Ова Ñе луѓето чии извеÑтувања ги Ñледите."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Овој код за потврда е премногу Ñтар. Почнете од почеток."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Овој формулар треба автоматÑки да Ñе "
+"иÑпрати. Ðко тоа не Ñе Ñлучи, кликнете на копчето „ИÑпрати“ за да одите до Вашиот OpenID Ñнабдувач."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Ова е прв пат како Ñе пријавивте на %s и "
+"затоа морам да го поврземе Вашиот OpenID Ñо "
+"локална Ñметка. Можете да креирате нова Ñметка или да Ñе поврзете Ñо тековната Ñметка - ако ја имате."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Оваа Ñтраница не е доÑтапна во форматот кој Вие го прифаќате."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"За да Ñе претплатите, може да Ñе "
+"[пријавите](%%action.login%%) или да Ñе "
+"[региÑтрирате](%%action.register%%). Ðко имате "
+"Ñметка на [компатибилно меÑто за микро блогирање](%%doc.openmublog%%), внеÑете го URL-то на Вашиот профил подолу."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL на Вашата домашна Ñтраница, блог или профил на друго меÑто."
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL на Вашиот профил на друго компатибилно меÑто за микроблогирање."
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Ðеочекувано иÑпраќање на формулар."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Ðеочекувано реÑетирање на лозинка."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Ðепозната верзија на протоколот OMB."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"ОÑвен ако не е поинаку назначено, "
+"Ñодржината на ова меÑто е авторÑко право на придонеÑувачите и е доÑтапна под"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Ðепознат тип на адреÑа %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Откажи ја претплатата"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Ðеподдржнана верзија на ОМВ"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Ðеподдржан фомрат на Ñлики."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Товари"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Тука можете да поÑтавите нов „аватар“ "
+"(коÑиничка Ñлика). Ðе можете да ја "
+"менувате Ñликата откако ќе ја товарите, "
+"па затоа погрижете Ñе да личи на квадрат. Сликата мора да биде под иÑтата лиценца како ова меÑто. КориÑтете Ñлика која е Ваша и која Ñакате да ја Ñподелите."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Се кориÑти Ñамо за надградби, објави и пронаоѓање на лозинка."
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "КориÑникот кој го Ñледите не поÑтои."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "КориÑникот нема профил."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Прекар на кориÑникот"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Што има %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Каде Ñе наоѓате, на пр. „Град, Држава“."
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Погрешен тип на Ñлика за '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Погрешна големина на Ñлика на '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Веќе го имате овој OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Веќе Ñте пријавени!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Овде можете да ја промените лозинката. Одберете добра лозинка!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Можете да креирате нова Ñметка за да иÑпраќате извеÑтувања."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Можте да отÑтраните OpenID од Вашата Ñметка "
+"Ñо кликање на копчето „ОтÑтрани“."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Можете да примате и праќате извеÑтувања "
+"преку Jabber/GTalk [брзи пораки](%%doc.im%%). Подолу "
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Во Вашиот личен профил може да дополните "
+"информации за луѓето да знаат повеќе за ВаÑ."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Може да ја кориÑтите локалната претплата."
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Ðе може да Ñе региÑтрирате ако не ја прифаќате лиценцата."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Ðе ни го иÑпративте тој профил."
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Идентификувани Ñте. Подолу можете да внеÑете нова лозинка."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Вашето URL за OpenID"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+"Вашиот прекар на овој Ñервер или адреÑата "
+"за е-пошта Ñо која Ñе региÑтриравте."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) Ви овозможува да Ñе "
+"пријавувате на многу меÑта Ñо иÑтата "
+"кориÑничка Ñметка. Овде можете да ги уредите Вашите поврзани OpenID-ја."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "пред неколку Ñекунди"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "пред %d денови"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "пред %d чаÑа"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "пред %d минути"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "пред %d меÑеци"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "пред еден ден"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "пред една минута"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "пред еден меÑец"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "пред една година"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "пред еден чаÑ"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "во одговор на..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "одговор"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "иÑто како лозинката погоре"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Следни"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/nl_NL/LC_MESSAGES/laconica.mo b/locale/nl_NL/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..d53c052e4
--- /dev/null
+++ b/locale/nl_NL/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/nl_NL/LC_MESSAGES/laconica.po b/locale/nl_NL/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..68870c232
--- /dev/null
+++ b/locale/nl_NL/LC_MESSAGES/laconica.po
@@ -0,0 +1,2850 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Doorzoek de stroom naar \"%s\""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+" behalve de volgende privégegevens: wachtwoord, e-mailadres, IM-adres, "
+"telefoonnummer."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s volgt nu je berichten op %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s volgt nu je berichten op %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Met vriendelijke groet,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s's status op %2$s"
+
+#: ../actions/publicrss.php:60 ../actions/publicrss.php:62
+#: actions/publicrss.php:48
+#, php-format
+#, fuzzy
+msgid "%s Public Stream"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"%s openbare stroom\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s en vrienden"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** is een microbloggingdienst van "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** is een microbloggingdienst. "
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+". Van de oorspronkelijke auteurs dient de volledige naam of gebruikersnaam "
+"te worden vermeld."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 kleine letters of cijfers, geen leestekens of spaties"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 of meer tekens"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 of meer tekens, en vergeet het niet!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Een bevestigingscode is verstuurd naar het opgegeven IM-adres. Je moet ermee "
+"accoord gaan dat %s boodschappen aan jou stuurt."
+
+# This would be appropriate for the footer menu link (actual context isn't clear!)
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Over ons"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Accepteer"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Voeg toe"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Voeg OpenID toe"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adres"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Alle abonnementen"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Alle updates voor %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Alle updates die overeenkomen met de zoekterm \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Al ingelogd."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Al geabonneerd!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Sta abonnement toe"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Voortaan automatisch inloggen; niet voor gemeenschappelijke computers!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar geactualiseerd."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Wacht op bevestiging van dit adres. Controleer je Jabber/GTalk account op "
+"een boodschap met nadere instructies. (Heb je %s aan je contactenlijst "
+"toegevoegd?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Eerder »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Biografie"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Biografie is te lang (maximaal 140 tekens)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Kan avatar-URL '%s' niet lezen"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Kan nieuw wachtwoord niet opslaan."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Annuleer"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Kan OpenID-consumerobject niet instantiëren."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Kan die Jabber-ID niet normaliseren"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Wijzig"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Wijzig wachtwoord"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Bevestig"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Bevestig adres"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Bevestiging geannuleerd."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Bevestigingscode niet gevonden."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Koppel"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Koppel bestaand account"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Contact"
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Kon niet omleiden naar server: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Kon avatarinformatie niet opslaan"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Kon de nieuwe profielinformatie niet opslaan"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Kon e-mail niet bevestigen."
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Kon abonnement niet aanmaken."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Kon e-mailbevestiging niet verwijderen."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Kon abonnement niet verwijderen."
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Kon bevestigingscode niet toevoegen"
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Kon nieuw abonnement niet toevoegen."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Kon profiel niet opslaan."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Kon gebruiker niet actualiseren."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Aanmaken"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Maak een nieuwe gebruiker aan met deze gebruikersnaam."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Maak een nieuw account aan"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+"Bezig nieuw account aan te maken voor OpenID waarvoor al een "
+"gebruikersaccount bestaat."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Huidig bevestigd Jabber/GTalk adres."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Op dit moment"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Database-fout bij toevoegen antwoord: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Beschrijf jezelf en je interesses in 140 tekens"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "E-mail"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "E-mailadres"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "E-mailadres bestaat al."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Bevestiging e-mailadres"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Voer een gebruikersnaam of e-mailadres in"
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Fout bij autoriseren token"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Fout bij koppelen gebruiker aan OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Fout bij koppelen gebruiker."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Fout bij toevoegen avatar"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Fout bij toevoegen nieuw profiel"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Fout bij toevoegen bericht"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Fout bij toevoegen profiel op afstand"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Fout bij opslaan adresbevestiging."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Fout bij opslaan profiel op afstand"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Fout bij opslaan van het profiel."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Fout bij opslaan van de gebruiker."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Fout bij opslaan gebruiker; ongeldig."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+#, fuzzy
+msgid "Error setting user."
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Fout bij vastleggen gebruiker.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Fout bij actualiseren profiel"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Fout bij actualiseren profiel op afstand"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Fout bij bevestigingscode."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Gebruikersnaam bestaat al"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "Veelgestelde vragen"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Actualiseren avatar niet gelukt."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed voor vrienden van %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed voor antwoorden aan %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Om veiligheidsredenen vragen we je je gebruikersnaam en wachtwoord nogmaals "
+"in te voeren alvorens je instellingen te veranderen."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Volledige naam"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Volledige naam is te lang (maximaal 255 tekens)"
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Help"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Home"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Homepage"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Homepage is geen geldige URL"
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM-adres"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM-instellingen"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Als je al een account hebt, log dan in met je gebruikersnaam en wachtwoord "
+"om het aan je OpenID te koppelen."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Als je een OpenID aan je account wilt toevoegen, vul het dan in het veld "
+"hieronder in en klik op \"Voeg toe\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Als je je wachtwoord vergeten of kwijt bent, kan een nieuw wachtwoord worden "
+"opgestuurd naar het e-mailadres dat je in je account hebt vastgelegd."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Incorrect oud wachtwoord"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Incorrecte gebruikersnaam of wachtwoord."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Instructies om je wachtwoord te herstellen zijn verstuurd naar het "
+"e-mailadres dat voor je account is geregistreerd."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Ongeldige avatar-URL '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Ongeldige homepage '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Ongeldige licentie-URL '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Ongeldige berichtinhoud"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Ongeldige bericht-URI"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Ongeldige bericht-URL"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Ongeldige profiel-URL '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Ongeldige profiel-URL (foutieve syntax)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Ongeldige profiel-URL teruggestuurd door de server."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Ongeldige afmeting."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Ongeldige gebruikersnaam of wachtwoord."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Het draait op de [Laconica](http://laconi.ca/) microbloggingsoftware versie "
+"%s, beschikbaar onder the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber-ID al in gebruik bij een andere gebruiker"
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber of GTalk adres, zoals \"JouwNaam@example.org\". Zorg ervoor dat je "
+"eerst % aan je contactenlijst in je IM-programma of in GTalk toevoegt."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Locatie"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Locatie is te lang (maximaal 255 tekens)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Inloggen"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Log in met een [OpenID](%%doc.openid%%) account."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Log in met je gebruikersnaam en wachtwoord. Heb je nog geen gebruikersnaam? "
+"[Registreer](%%action.register%%) dan een nieuw account, of probeer "
+"[OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Uitloggen"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Wachtwoord kwijt of vergeten?"
+
+# String should contain a variable token for since 'when'
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Lid sinds"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog van %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Mijn tekst en bestanden zijn beschikbaar onder"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nieuwe gebruikersnaam"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Nieuw bericht"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nieuw wachtwoord"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nieuw wachtwoord is opgeslagen. Je bent nu ingelogd."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Gebruikersnaam"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Gebruikersnaam al in gebruik. Probeer een andere."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"Gebruikersnaam moet bestaan uit alleen kleine letters en cijfers en geen "
+"spaties."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Gebruikersnaam niet toegestaan."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Gebruikersnaam van de gebruiker die je wilt volgen"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Gebruikersnaam of e-mail"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Geen Jabber-ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Geen autorisatieverzoek!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Geen bevestigingscode."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Geen inhoud!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Geen ID."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Geen gebruikersnaam verschaft door de server op afstand."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Geen gebruikersnaam."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Geen bevestiging in behandeling om te annuleren."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Geen profiel-URL teruggestuurd door de server."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Geen geregistreerd e-mailadres voor die gebruiker."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Geen verzoek gevonden!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Geen afmeting."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Die OpenID is niet bekend."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Onbekend document."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Onbekend bericht."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Onbekende herstelcode."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Onbekend abonnement"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Onbekende gebruiker."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Niemand te tonen!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Geen geldige herstelcode."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Geen geldige Jabber-ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Geen geldige OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Geen geldig e-mailadres."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Geen geldige gebruikersnaam."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Geen geldige profiel-URL (ongeldige diensten)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Geen geldige profiel-URL (geen XRDS gedefinieerd)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Geen geldige profiel-URL (geen YADIS document)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Geen afbeelding of beschadigd bestand."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Niet geautoriseerd."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Onverwacht antwoord!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Niet ingelogd."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Niet geabonneerd!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Berichten-feed voor %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Bericht heeft geen profiel"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Berichten"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Oud wachtwoord"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "OpenID-account instellen"
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Log in met OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID-URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "OpenID-authenticatie geannuleerd."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID-authenticatie niet gelukt: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID-fout: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID verwijderd."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID-instellingen."
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Onvolledige upload."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Wachtwoord"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Wachtwoord en bevestiging komen niet overeen."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Wachtwoord moet uit 6 of meer tekens bestaan."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Wachtwoordherstel aangevraagd"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Wachtwoord opgeslagen."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Wachtwoorden komen niet overeen."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Personen zoeken"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Persoonlijk"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Ga alsjeblieft deze details na om er zeker van te zijn dat je je wilt "
+"abonneren op de berichten van deze gebruiker. Als je niet zojuist hebt "
+"aangegeven je op iemand's berichten te willen abonneren, klik dan op "
+"\"Annuleer\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Plaats een bericht als mijn Jabber/GTalk-status verandert."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Voorkeuren"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Voorkeuren opgeslagen."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Privacy"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Probleem bij opslaan bericht."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profiel"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "Profiel-URL"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Profielinstellingen"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Profiel onbekend"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Openbaar"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Openbare stream-feed"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Openbare tijdlijn"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Herstellen"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Wachtwoord herstellen"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Herstelcode voor onbekende gebruiker."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registreer"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Afwijzen"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Onthoud mij"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Profiel op afstand zonder overeenkomend profiel"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Abonneren op afstand"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Verwijderen"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Verwijder OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Verwijderen van je enige OpenID zou het onmogelijk maken in te loggen! Als "
+"je het moet verwijderen, voeg dan eerst een ander OpenID toe."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Antwoorden"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Antwoorden aan %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Terugstellen"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Wachtwoord terugstellen"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Gelijk aan wachtwoord hierboven"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Opslaan"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Zoeken"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Doorzoek stroom-feed"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Zoek naar berichten op %%site.name%% op basis van hun inhoud. Scheid "
+"zoektermen met spaties; ze moeten uit 3 of meer tekens bestaan."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Zoek naar mensen op %%site.name%% op basis van hun naam, locatie of "
+"interesses. Scheid de zoektermen met spaties; ze moeten uit 3 of meer tekens "
+"bestaan."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Verstuur"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Stuur mij berichten via Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Instellingen"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Instellingen opgeslagen."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Iemand anders heeft die OpenID al."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Er is iets eigenaardigs gebeurd."
+
+#: ../lib/util.php:302 ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Statistieken"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Opgeslagen OpenID niet gevonden."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Abonneren"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Abonnees"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Abonnement geautoriseerd"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Abonnement afgewezen"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Abonnementen"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Systeemfout bij uploaden bestand."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Tekst doorzoeken"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Die OpenID is niet van jou."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Dat adres is al bevestigd."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Die bevestigingscode is niet voor jou!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Dat bestand is te groot."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Dat is al je Jabber-ID."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Dat is niet je Jabber-ID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Dat is het verkeerde IM-adres."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Dat is te lang. Maximale berichtlengte is 140 tekens."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "Het adres \"%s\" is voor je account bevestigd."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Het adres was verwijderd."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Het abonnement is geautoriseerd maar er is geen callback-URL doorgegeven. "
+"Loop de instructies van de site na voor details over hoe het abonnement te "
+"autoriseren. Je abonnement-token is:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Het abonnement is afgewezen maar er is geen callback-URL doorgegeven. Loop "
+"de instructies van de site na voor details over hoe het abonnement volledig "
+"af te wijzen."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Dit zijn de mensen die %s's berichten volgen."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Dit zijn de mensen die jouw berichten volgen."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Dit zijn de mensen van wie %s de berichten volgt."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Dit zijn de mensen van wie jij de berichten volgt."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Deze bevestigingscode is te oud. Begin alsjeblieft opnieuw."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Dit formulier zou zichzelf automatisch moeten versturen. Zoniet, klik dan op "
+"de verstuurknop om naar je OpenID-provider te gaan."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Dit is de eerste keer dat je bij %s ingelogd bent dus moeten we je OpenID "
+"koppelen aan een lokaal account. Je kunt ofwel een nieuw account aanmaken, "
+"ofwel koppelen aan je bestaande account, als je er al een hebt."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Deze pagina is niet beschikbaar in een mediatype dat jij accepteert"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Om je te abonneren kun je [inloggen](%%action.login%%), of een nieuw account "
+"[registreren](%%action.register%%). Als je al een account op een "
+"[compatibele microblogging-site](%%doc.openmublog%%) hebt, voer dan "
+"hieronder je profiel-URL in."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL van je homepage, blog, of profiel op een andere site"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL van je profiel op een andere, compatibele microbloggingdienst"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Formulier onverwacht ingezonden."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Wachtwoord onverwacht teruggesteld."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Onbekende versie van het OMB-protocol."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Tenzij anders gespecificeerd valt de inhoud van deze site onder het "
+"copyright van de auteurs en is beschikbaar onder de"
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Abonnement opheffen"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Niet ondersteunde OMB-versie"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Niet ondersteund beeldbestandsformaat."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Uploaden"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Hier kun je een nieuwe \"avatar\" (gebruikersafbeelding) uploaden. Je kunt "
+"het plaatje achteraf niet bewerken, dus zorg ervoor dat het min of meer "
+"vierkant is. Het moet ook onder site licentie vallen. Gebruik een afbeelding "
+"die je eigendom is en die je wilt delen."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Alleen gebruikt voor updates, aankondigingen en wachtwoordherstel"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "Gebruiker waarnaar geluisterd wordt betaat niet."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "Gebruiker heeft geen profiel."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Gebruikersnaam"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Hoe staat het ermee, %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Waar je bent, bijvoorbeeld \"woonplaats, land\" of \"postcode, land\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Foutief beeldbestandstype voor '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Verkeerde afmeting van afbeelding bij '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Je hebt die OpenID al!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Je bent al aangemeld!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Hier kun je je wachtwoord wijzigen. Kies een goede!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Je kunt een nieuw account aanmaken om berichten te gaan plaatsen."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Je kunt een OpenID van je account verwijderen door op de knop \"Verwijder\" "
+"te klikken."
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+#, fuzzy
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Je kunt berichten verzenden en ontvangen via Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configureer je adres en instellingen hieronder.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Je kunt hier je persoonlijke profielinformatie bijwerken zodat mensen meer "
+"over je weten."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Je kunt het lokale abonnement gebruiken!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Je kunt niet registreren als je niet met de licentie akkoord gaat."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Je hebt dat profiel niet ingezonden"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Je bent geïdentificeerd. Vul hieronder een nieuw wachtwoord in."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Je OpenID-URL"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Je gebruikersnaam op deze server, of je geregistreerde e-mailadres."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"Met [OpenID](%%doc.openid%%) kun je op vele sites inloggen met hetzelfde "
+"gebruikersaccount. Hier kun je de aan je account gekoppelde OpenID's "
+"beheren."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "een paar seconden geleden"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "ongeveer %d dagen geleden"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "ongeveer %d uur geleden"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "ongeveer %d minuten geleden"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "ongeveer %d maanden geleden"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "ongeveer een dag geleden"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "ongeveer een minuut geleden"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "ongeveer een maand geleden"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "ongeveer een jaar geleden"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "ongeveer een uur geleden"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "in antwoord op..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "antwoord"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "gelijk aan wachtwoord hierboven"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Later"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/no_NB/LC_MESSAGES/laconica.mo b/locale/no_NB/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..f7886ee2a
--- /dev/null
+++ b/locale/no_NB/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/no_NB/LC_MESSAGES/laconica.po b/locale/no_NB/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..8bdc44b68
--- /dev/null
+++ b/locale/no_NB/LC_MESSAGES/laconica.po
@@ -0,0 +1,2914 @@
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.po #-#-#-#-#\n"
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-29 20:02+1200\n"
+"PO-Revision-Date: 2008-08-01 17:38+0100\n"
+"Last-Translator: forteller <forteller@gmail.com>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Norwegian Bokmal\n"
+"X-Poedit-Country: NORWAY\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr ""
+
+#: ../actions/tag.php:139
+msgid " by "
+msgstr "av"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:255
+#: ../actions/register.php:191 actions/finishopenidlogin.php:88
+#: actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:469 ../actions/twitapistatuses.php:478
+#: actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/tag.php:127
+#, php-format
+msgid "%1$s Notices recently tagged with %2$s"
+msgstr ""
+
+#: ../lib/mail.php:120 ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../lib/mail.php:122 ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:473 ../actions/twitapistatuses.php:482
+#: actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/publicrss.php:60 ../actions/publicrss.php:62
+#: actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr ""
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:235 ../lib/stream.php:51
+#, php-format
+msgid "%s and friends"
+msgstr "%s og venner"
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:202 ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:335 ../actions/twitapistatuses.php:338
+#: actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:277 ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../lib/util.php:244 ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:246 ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:261 ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: actions/finishopenidlogin.php:79 actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+
+#: ../actions/register.php:221 ../actions/register.php:152
+#: actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/password.php:42
+msgid "6 or more characters"
+msgstr "6 eller flere tegn"
+
+#: ../actions/recoverpassword.php:165 ../actions/recoverpassword.php:180
+#: actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr ""
+
+#: ../actions/register.php:223 ../actions/register.php:154
+#: actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../lib/util.php:310
+msgid "About"
+msgstr "Om"
+
+#: ../actions/userauthorization.php:119
+msgid "Accept"
+msgstr "Aksepter"
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+msgid "Add"
+msgstr "Legg til"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Legg til OpenID"
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39
+msgid "Address"
+msgstr "Adresse"
+
+#: ../actions/showstream.php:273
+msgid "All subscriptions"
+msgstr "Alle abonnementer"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Alle oppdateringer for %s"
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+msgid "Already logged in."
+msgstr "Allerede logget inn."
+
+#: ../actions/subscribe.php:49 ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/userauthorization.php:77
+msgid "Authorize subscription"
+msgstr "Autoriser abonnementet"
+
+#: ../actions/login.php:104 ../actions/register.php:242
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Logg inn automatisk i framtiden. Ikke for datamaskiner du deler med "
+"andre!"
+
+#: ../actions/profilesettings.php:65
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr "Abonner "
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avataren har blitt oppdatert."
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "Venter på bekreftelse på dette telefonnummeret"
+
+#: ../lib/util.php:1290
+msgid "Before »"
+msgstr "Tidligere »"
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:234
+msgid "Bio"
+msgstr "Bio"
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:78
+#: ../actions/updateprofile.php:103
+msgid "Bio is too long (max 140 chars)."
+msgstr "Bioen er for lang (max 140 tegn)"
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Klarer ikke å lagre det nye passordet"
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Endre"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Endre passord"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:224 ../actions/smssettings.php:65
+msgid "Confirm"
+msgstr "Bekreft"
+
+#: ../actions/confirmaddress.php:86
+msgid "Confirm Address"
+msgstr "Bekreft Adresse"
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245
+msgid "Confirmation cancelled."
+msgstr "Bekreftelse avbrutt."
+
+#: ../actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Bekreftelseskode"
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr ""
+
+#: ../actions/register.php:266 ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Koble til"
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr ""
+
+#: ../lib/util.php:318
+msgid "Contact"
+msgstr "Kontakt"
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:48 ../actions/twitapifriendships.php:53
+#: actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:162
+msgid "Could not save avatar info"
+msgstr "Klarte ikke å lagre avatar-informasjonen"
+
+#: ../actions/updateprofile.php:155
+msgid "Could not save new profile info"
+msgstr "Klarte ikke å lagre den nye profil-informasjonen"
+
+#: ../actions/subscribe.php:62 ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../actions/subscribe.php:54 ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/confirmaddress.php:80 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: ../actions/confirmaddress.php:84 actions/confirmaddress.php:84
+#: actions/emailsettings.php:252 actions/imsettings.php:226
+#: actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../actions/unsubscribe.php:57 ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:181 ../actions/twitapiaccount.php:92
+msgid "Couldn't save profile."
+msgstr "Klarte ikke å lagre profil."
+
+#: ../actions/profilesettings.php:158 ../actions/profilesettings.php:161
+#: actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+msgid "Couldn't update user."
+msgstr "Klarte ikke å oppdatere bruker."
+
+#: ../actions/finishopenidlogin.php:84 actions/finishopenidlogin.php:90
+msgid "Create"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Lag en ny bruker med dette nicket."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Opprett en ny konto"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Nåværende bekreftede Jabber/GTak-adresse."
+
+#: ../actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "Nåværende bekreftede telefonnummer med mulighet for å motta SMS."
+
+#: ../actions/emailsettings.php:44
+msgid "Current confirmed email address."
+msgstr "Nåværende bekreftede e-postadresse"
+
+#: ../actions/showstream.php:356 actions/showstream.php:367
+msgid "Currently"
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../lib/util.php:1033 ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:236
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Beskriv degselv og dine interesser med 140 tegn"
+
+#: ../actions/register.php:226
+msgid "Email"
+msgstr "E-post"
+
+#: ../actions/emailsettings.php:59
+msgid "Email Address"
+msgstr "E-postadresse"
+
+#: ../actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "Innstillinger for e-post"
+
+#: ../actions/register.php:69 ../actions/register.php:73
+#: actions/register.php:80
+msgid "Email address already exists."
+msgstr ""
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/recoverpassword.php:176 ../actions/recoverpassword.php:191
+#: actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:282 ../actions/finishopenidlogin.php:253
+#: actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr ""
+
+#: ../actions/postnotice.php:89
+msgid "Error inserting notice"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:201 ../actions/recoverpassword.php:240
+#: actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+#: lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr ""
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr ""
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:268 ../actions/register.php:92
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:79 ../actions/recoverpassword.php:80
+#: actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Eksisterende nick"
+
+#: ../lib/util.php:312
+msgid "FAQ"
+msgstr "OSS/FAQ"
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Feed for %s sine venner"
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Feed for svar til %s"
+
+#: ../actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "Feed for taggen %s"
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:228
+msgid "Full name"
+msgstr "Fullt navn"
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:75
+#: ../actions/updateprofile.php:93
+msgid "Full name is too long (max 255 chars)."
+msgstr "Beklager, navnet er for langt (max 250 tegn)."
+
+#: ../lib/util.php:291
+msgid "Help"
+msgstr "Hjelp"
+
+#: ../lib/util.php:285
+msgid "Home"
+msgstr "Hjem"
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:231
+msgid "Homepage"
+msgstr "Hjemmesiden"
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:72
+#: ../actions/register.php:76 actions/profilesettings.php:210
+#: actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../actions/imsettings.php:60
+msgid "IM Address"
+msgstr "IM-adresse"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Innstillinger for IM"
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "innkommende e-post"
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Feil gammelt passord"
+
+#: ../actions/login.php:67
+msgid "Incorrect username or password."
+msgstr "Feil brukernavn eller passord"
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Instruksjoner om hvordan du kan gjenopprette ditt passord har blitt sendt "
+"til din registrerte e-postadresse."
+
+#: ../actions/updateprofile.php:114
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Ugyldig avatar-URL '%s'"
+
+#: ../actions/updateprofile.php:98
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Ugyldig hjemmeside '%s'"
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:87
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Ugyldig profil-URL '%s'"
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Ugyldig størrelse"
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:87
+#: ../actions/register.php:103
+msgid "Invalid username or password."
+msgstr "Ugyldig brukernavn eller passord"
+
+#: ../lib/util.php:248 ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:57
+msgid "Language"
+msgstr "Språk"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/tag.php:133
+msgid "Last message posted: "
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:237
+#: ../actions/register.php:173 actions/profilesettings.php:85
+#: actions/register.php:187
+msgid "Location"
+msgstr ""
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:81
+#: ../actions/updateprofile.php:108 ../actions/register.php:85
+#: actions/profilesettings.php:219 actions/register.php:92
+#: actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr ""
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:298
+msgid "Login"
+msgstr "Logg inn"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Logg inn med en [OpenID](%%doc.openid%%)-konto."
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:296
+msgid "Logout"
+msgstr "Logg ut"
+
+#: ../actions/register.php:230
+msgid "Longer name, preferably your \"real\" name"
+msgstr "Lengre navn, helst ditt \"ekte\" navn"
+
+#: ../actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr "Mistet eller glemt passordet?"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/showstream.php:300
+msgid "Member since"
+msgstr "Medlem siden"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Mikroblogg av %s"
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:252
+#: ../actions/register.php:188 actions/finishopenidlogin.php:85
+#: actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+msgid "New"
+msgstr "Ny"
+
+#: ../lib/mail.php:140 ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nytt nick"
+
+#: ../actions/newnotice.php:102 ../actions/newnotice.php:87
+#: actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nytt passord"
+
+#: ../actions/recoverpassword.php:275 ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:220
+msgid "Nickname"
+msgstr "Nick"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:65
+msgid "Nickname already in use. Try another one."
+msgstr "Det nicket er allerede i bruk. Prøv et annet."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:63 ../actions/updateprofile.php:77
+#: ../actions/register.php:67 actions/finishopenidlogin.php:171
+#: actions/profilesettings.php:203 actions/register.php:74
+#: actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Dette nicket er ikke tillatt"
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr ""
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Nick eller e-postadresse"
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/imsettings.php:156
+msgid "No Jabber ID."
+msgstr "Ingen Jabber ID."
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr ""
+
+#: ../actions/newnotice.php:50 ../actions/newnotice.php:44
+#: actions/newmessage.php:53 actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/emailsettings.php:174
+msgid "No email address."
+msgstr "Ingen e-postadresse."
+
+#: ../actions/userbyid.php:32
+msgid "No id."
+msgstr "Ingen id."
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr ""
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:189 ../actions/recoverpassword.php:226
+#: actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr ""
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/replies.php:57
+#: ../actions/repliesrss.php:35 ../actions/showstream.php:110
+#: ../actions/subscribe.php:44 ../actions/unsubscribe.php:39
+#: ../actions/userbyid.php:36 ../actions/userrss.php:35 ../actions/xrds.php:35
+#: ../lib/gallery.php:57 ../lib/subs.php:33 ../lib/subs.php:82
+#: actions/all.php:34 actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../actions/imsettings.php:167
+msgid "Not a valid Jabber ID"
+msgstr "Ugyldig Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Ugyldig OpenID"
+
+#: ../actions/emailsettings.php:185
+msgid "Not a valid email address"
+msgstr "Ugyldig e-postadresse"
+
+#: ../actions/register.php:59
+msgid "Not a valid email address."
+msgstr "Ugyldig e-postadresse."
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:67
+msgid "Not a valid nickname."
+msgstr "Ugyldig nick."
+
+#: ../actions/remotesubscribe.php:118 ../actions/remotesubscribe.php:120
+#: actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:111 ../actions/remotesubscribe.php:113
+#: actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Ikke autorisert."
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Ikke logget inn."
+
+#: ../actions/unsubscribe.php:44 ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:316 actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 ../actions/tag.php:124
+#: actions/tag.php:35 actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Gammelt passord"
+
+#: ../lib/util.php:302
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID-URL"
+#-#-#-#-
+# laconica.po#-#-#-#-
+# eller autentifikasjon?
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+#, fuzzy
+msgid "OpenID authentication cancelled."
+msgstr ""
+"#-#-#-#-# laconica.po #-#-#-#-#\n"
+"OpenID-autentifisering avbrutt.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID fjernet"
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Innstillinger for OpenID"
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:222
+msgid "Password"
+msgstr "Passord"
+
+#: ../actions/recoverpassword.php:249 ../actions/recoverpassword.php:288
+#: actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Passordet må bestå av 6 eller flere tegn."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Passordet ble lagret"
+
+#: ../actions/password.php:61 ../actions/register.php:84
+#: ../actions/register.php:88 actions/profilesettings.php:380
+#: actions/register.php:98
+msgid "Passwords don't match."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:50
+msgid "Personal"
+msgstr "Personlig"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94 actions/emailsettings.php:86
+#: actions/imsettings.php:68 actions/smssettings.php:94
+#: actions/twittersettings.php:70
+msgid "Preferences"
+msgstr ""
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../lib/util.php:314 ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr ""
+
+#: ../actions/newnotice.php:62 ../actions/newnotice.php:70
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/stream.php:60
+msgid "Profile"
+msgstr "Profil"
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../lib/util.php:287
+#, fuzzy
+msgid "Public"
+msgstr "Offentlig"
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr ""
+
+#: ../actions/imsettings.php:79
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Publiser en MicroID for min Jabber/Gtalk-adresse."
+
+#: ../actions/emailsettings.php:94
+msgid "Publish a MicroID for my email address."
+msgstr "Publiser en MicroID for min e-postadresse."
+
+#: ../actions/tag.php:75 ../actions/tag.php:76
+msgid "Recent Tags"
+msgstr "Nyeste Tagger"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Gjenopprett"
+
+#: ../actions/recoverpassword.php:141 ../actions/recoverpassword.php:156
+#: actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:216 ../actions/register.php:257 ../lib/util.php:300
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:264 ../actions/register.php:200
+#: actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:103 ../actions/register.php:240
+msgid "Remember me"
+msgstr "Husk meg"
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+msgid "Remove"
+msgstr "Fjern"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Fjern OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:55
+msgid "Replies"
+msgstr "Svar"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr "Svar til %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Nullstill"
+
+#: ../actions/recoverpassword.php:158 ../actions/recoverpassword.php:173
+#: actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "Telefonnummer for SMS"
+
+#: ../actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "Innstillinger for SMS"
+
+#: ../lib/mail.php:215 ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/recoverpassword.php:167 ../actions/recoverpassword.php:182
+#: actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/register.php:225 ../actions/register.php:156
+#: actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+msgid "Save"
+msgstr "Lagre"
+
+#: ../lib/searchaction.php:84 ../lib/util.php:288
+msgid "Search"
+msgstr "Søk"
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../lib/util.php:1144
+msgid "Send"
+msgstr "Send"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../lib/util.php:294 ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:189 ../actions/profilesettings.php:192
+#: actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../lib/util.php:316
+msgid "Source"
+msgstr "Kilde"
+
+#: ../actions/showstream.php:296
+msgid "Statistics"
+msgstr "Statistikk"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+#: ../actions/finishopenidlogin.php:246 actions/finishopenidlogin.php:188
+#: actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+#: actions/showstream.php:328 actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:289
+msgid "Tags"
+msgstr "Tagger"
+
+#: ../lib/searchaction.php:104
+msgid "Text"
+msgstr "Tekst"
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Tekst-søk"
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Den filen er for stor."
+
+#: ../actions/imsettings.php:170
+msgid "That is already your Jabber ID."
+msgstr "Det er allerede din Jabber ID."
+
+#: ../actions/emailsettings.php:188
+msgid "That is already your email address."
+msgstr "Det er allerede din e-postadresse."
+
+#: ../actions/smssettings.php:188
+msgid "That is already your phone number."
+msgstr "Det er allerede din ditt telefonnummer."
+
+#: ../actions/imsettings.php:233
+msgid "That is not your Jabber ID."
+msgstr "Det er ikke din Jabber ID."
+
+#: ../actions/emailsettings.php:249
+msgid "That is not your email address."
+msgstr "Det er ikke din e-postadresse."
+
+#: ../actions/smssettings.php:257
+msgid "That is not your phone number."
+msgstr "Det er ikke ditt telefonnummer."
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+msgid "That is the wrong IM address."
+msgstr "Det er feil IM-adresse."
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/newnotice.php:53 ../actions/newnotice.php:49
+#: ../actions/twitapistatuses.php:408 actions/newnotice.php:49
+#: actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:88 ../actions/confirmaddress.php:92
+#: actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/recoverpassword.php:87 ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../lib/util.php:151 ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/profilesettings.php:63
+msgid "Timezone"
+msgstr "Tidssone"
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:150 ../actions/twitapifriendships.php:163
+#: actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:233
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL til din hjemmeside, blogg, eller profil på annen nettside."
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:237 ../actions/recoverpassword.php:276
+#: actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:256 ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:209 actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../lib/twitterapi.php:257 ../lib/twitterapi.php:278
+msgid "Unsupported type"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:238 ../actions/twitapistatuses.php:241
+#: actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 ../actions/twitapistatuses.php:341
+#: actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Last opp"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../actions/register.php:227 ../actions/register.php:159
+#: ../actions/register.php:162 actions/register.php:173
+#: actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:78
+#: ../actions/twitapistatuses.php:316 ../actions/twitapistatuses.php:621
+#: ../actions/twitapiaccount.php:82 ../actions/twitapistatuses.php:319
+#: ../actions/twitapistatuses.php:685 ../actions/twitapiusers.php:82
+#: actions/all.php:41 actions/avatarbynickname.php:48 actions/foaf.php:47
+#: actions/replies.php:41 actions/showfavorites.php:41
+#: actions/showstream.php:44 actions/twitapiaccount.php:80
+#: actions/twitapifavorites.php:68 actions/twitapistatuses.php:235
+#: actions/twitapistatuses.php:609 actions/twitapiusers.php:87
+#: lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../lib/util.php:1131 ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:239
+#: ../actions/register.php:175 actions/profilesettings.php:87
+#: actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+msgid "Yes"
+msgstr "Ja"
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Du er allerede logget inn!"
+
+#: ../actions/twitapifriendships.php:115 ../actions/twitapifriendships.php:128
+#: actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:209 ../actions/register.php:135
+#: actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:57
+#: ../actions/register.php:61 actions/finishopenidlogin.php:38
+#: actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../lib/mail.php:143 ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:134 ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149 ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:919
+msgid "a few seconds ago"
+msgstr "noen få sekunder siden"
+
+#: ../lib/util.php:931
+#, php-format
+msgid "about %d days ago"
+msgstr "omtrent %d dager siden"
+
+#: ../lib/util.php:927
+#, php-format
+msgid "about %d hours ago"
+msgstr "omtrent %d timer siden"
+
+#: ../lib/util.php:923
+#, php-format
+msgid "about %d minutes ago"
+msgstr "omtrent %d minutter siden"
+
+#: ../lib/util.php:935
+#, php-format
+msgid "about %d months ago"
+msgstr "omtrent %d måneder siden"
+
+#: ../lib/util.php:929
+msgid "about a day ago"
+msgstr "omtrent én dag siden"
+
+#: ../lib/util.php:921
+msgid "about a minute ago"
+msgstr "omtrent ett minutt siden"
+
+#: ../lib/util.php:933
+msgid "about a month ago"
+msgstr "omtrent én måned siden"
+
+#: ../lib/util.php:937
+msgid "about a year ago"
+msgstr "omtrent ett år siden"
+
+#: ../lib/util.php:925
+msgid "about an hour ago"
+msgstr "omtrent én time siden"
+
+#: ../actions/showstream.php:424 ../lib/stream.php:130
+msgid "delete"
+msgstr "slett"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:408
+#: ../lib/stream.php:114 ../actions/noticesearch.php:130 ../lib/stream.php:117
+#: actions/noticesearch.php:136 actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../lib/twitterapi.php:363
+msgid "not a supported data format"
+msgstr ""
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:415
+#: ../lib/stream.php:121
+msgid "reply"
+msgstr "svar"
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:691 ../actions/twitapistatuses.php:755
+#: actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: ../lib/util.php:1281 ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/pl_PL/LC_MESSAGES/laconica.mo b/locale/pl_PL/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..38a0f543d
--- /dev/null
+++ b/locale/pl_PL/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/pl_PL/LC_MESSAGES/laconica.po b/locale/pl_PL/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..0beb8e85a
--- /dev/null
+++ b/locale/pl_PL/LC_MESSAGES/laconica.po
@@ -0,0 +1,2848 @@
+# #-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Paweł Wilk <siefca@gnu.org>, 2008.
+#
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Paweł Wilk <siefca@gnu.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+# Polish language has 3 plural forms.
+# Special case is used for one and some numbers ending in 2, 3, or 4.
+# Example:
+# 1 WINDOW -> 1 OKNO
+# x2 to x4 WINDOWS -> x2 do x4 OKNA (x != 1)
+# 5 or more WINDOWS -> 5 lub więcej OKIEN
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Szukaj strumienia dla \"%s\""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"z wyłączeniem tych prywatnych danych: e-maila, identyfikatora IM, numeru "
+"telefonu."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s obserwuje teraz Twoje wpisy na %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s obserwuje teraz Twoje wpisy na %2$s.\n"
+"\n"
+"KÅ‚aniam siÄ™,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "Status użytkownika %1$s na %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "Publiczny strumień %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s i przyjaciele"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** to serwis z mikroblogami prowadzony przez "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** to serwis do mikroblogowania."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+". Współpracownicy powinni być wymienieni z imienia i nazwiska lub "
+"pseudonimu."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "Max. 64 znaki alfanumeryczne, bez spacji i znaków przestankowych"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 lub więcej znaków"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 lub więcej znaków – nie zapomnij go!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Na Twój adres komunikatora został wysłany kod potwierdzający. Musisz "
+"zaakceptować otrzymywanie wiadomości od %s."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "O serwisie"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Akceptuj"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Dodaj"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Dodaj konto OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adres"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Wszyscy obserwowani"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Wszystkie aktualizacje od %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Wszystkie aktualizacje pasujÄ…ce do wzorca wyszukiwania \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Jesteś już zalogowany."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Już obserwujesz!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Pozwól na obserwację"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Automatycznie loguj mnie na konto. Nie używać w przypadku "
+"współdzielonych komputerów!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Awatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Awatar załadowany."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Oczekiwanie na potwierdzenie z tego adresu. Sprawdź czy na Twoje konto "
+"Jabbera/GTalka przyszła wiadomość z dalszymi instrukcjami. (Nie zapomnij "
+"dodać %s do listy znajomych.)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Wcześniej »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "O mnie"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Wpis \"O mnie\" jest za długi (maks. 140 znaków)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Nie można odczytać URL-a awatara '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Nie można zapisać nowego hasła."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Nie można stworzyć instancji obiektu OpenID."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Nie można znormalizować tego identyfikatora Jabbera"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Zmień"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Zmień hasło"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Potwierdź"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Potwierdź adres"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Anulowano potwierdzenie."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Nie znaleziono kodu potwierdzajÄ…cego."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Połącz"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Połącz z istniejącym kontem"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Kontakt"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Nie można utworzyć formularza OpenID: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Nie można przekierować do serwera: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Nie można zapisać informacji o awatarze"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Nie można zapisać informacji o nowym profilu"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Nie można potwierdzić e-maila."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Nie można przekształcić tokenów z żądaniami na tokeny dostępu."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Nie można obserwować."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Nie można skasować potwierdzenia adresu e-mail."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Nie można usunąć obserwacji."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Nie można uzyskać tokena z żądaniem."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Nie można wprowadzić kodu potwierdzającego."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Nie można wprowadzić nowej obserwacji."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Nie można zapisać profilu."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Nie można zaktualizować użytkownika."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Utwórz"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Załóż użytkownika o tym pseudonimie."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Załóż nowe konto"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Tworzenie nowego konta użytkownika na podstawie identyfikatora OpenID."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Potwierdzone adresy Jabbera/GTalka"
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Obecnie"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "BÅ‚Ä…d przy dodawaniu do bazy danych: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Opisz siÄ™ w 140 znakach"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "E-mail"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Adres e-mailowy"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Taki e-mail już istnieje"
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Potwierdzenie adresu e-mailowego"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Podaj pseudonim lub adres e-mailowy"
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "BÅ‚Ä…d podczas autoryzacji tokena"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Błąd w podłączaniu użytkownika do OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Błąd w podłączaniu użytkownika."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "BÅ‚Ä…d we wstawianiu awatara"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "BÅ‚Ä…d podczas wprowadzania nowego profilu"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "BÅ‚Ä…d przy wprowadzaniu wpisu"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "BÅ‚Ä…d podczas wprowadzania zdalnego profilu"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "BÅ‚Ä…d w zapisie potwierdzenia adresu."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "BÅ‚Ä…d w zapisie zdalnego profilu."
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "BÅ‚Ä…d w zapisie profilu."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Błąd w zapisie użytkownika."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Błąd podczas zapisywania użytkownika; niepoprawne dane."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Błąd w ustawianiu danych użytkownika."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "BÅ‚Ä…d podczas aktualizowania profilu"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "BÅ‚Ä…d podczas aktualizowania zdalnego profilu"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "BÅ‚Ä…d kodu potwierdzajÄ…cego."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Dotychczasowy pseudonim"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "FAQ"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Uaktualnianie awatara nie powiodło się."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Kanał dla znajomych użytkownika %s"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Kanał dla odpowiedzi do użytkownika %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Z powodów bezpieczeństwa wprowadź proszę ponownie nazwę użytkownika i "
+"hasło przed zmianą swoich ustawień."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Pełna nazwa"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Pełna nazwa jest zbyt długa (max. 255 znaków)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Pomoc"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "PoczÄ…tek"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Strona domowa"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Adres strony domowej nie jest poprawnym URL-em."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "Adres komunikatora"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Ustawienia komunikatora"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Jeśli już masz konto, zaloguj się używając nazwy użytkownika i hasła, "
+"aby połączyć je ze swoim identyfikatorem OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Jeśli chcesz skojarzyć konto OpenID ze swoim lokalnym kontem, wprowadź "
+"identyfikator w poniższe pole i kliknij \"Dodaj\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Jeśli Twoje hasło gdzieś się zapodziało lub zostało zapomniane to "
+"możesz wygenerować nowe. Zostanie ono wysłane na adres e-mailowy "
+"skojarzony z Twoim kontem."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Stare hasło jest niepoprawne"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Błędna nazwa użytkownika lub hasło."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Instrukcje dotyczące przywrócenia hasła zostały wysłane na adres "
+"e-mailowy skojarzony z Twoim kontem."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Błędny URL awatara '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Błędna strona domowa '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Błędny URL licencji '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Błędna zawartość wpisu"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Błędny URI wpisu"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Błędny URL wpisu"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Błędny URL profilu '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Błędny URL profilu (zły format)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Błędny URL profilu zwrócony przez serwer."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Niepoprawny rozmiar."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Błędna nazwa użytkownika lub hasło."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Działa pod kontrolą oprogramowania [Laconica](http://laconi.ca/) "
+"służącego do prowadzenia mikroblogów, w wersji %s, dostępnego na "
+"licencji [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Identyfikator Jabbera należy już do innego użytkownika."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Adres Jabbera lub GTalka w postaci \"Użytkownik@przykladowadomena.org\". "
+"Nie zapomnij dodać %s do listy znajomych w swoim komunikatorze lub panelu "
+"GTalka."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Lokalizacja"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Lokalizacja jest za długa (max. 255 znaków)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Login"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Zaloguj się używając konta [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Zaloguj się używając nazwy użytkownika i hasła. Nie masz ich jeszcze? "
+"[Zarejestruj się](%%action.register%%) i utwórz konto, albo użyj swojego "
+"[OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Wyloguj"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Zgubione hasło?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "W serwisie od"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "%s – mikroblog"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Moje teksty i pliki sÄ… widoczne pod"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nowy pseudonim"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Nowy wpis"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nowe hasło"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nowe hasło zapisano pomyślnie. Możesz się zalogować."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Pseudonim"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Ten pseudonim jest już w użyciu. Wybierz inny."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "Pseudonim musi zawierać tylko małe litery i cyfry, bez znaków spacji."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Pseudonim niedozwolony."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Pseudonim użytkownika którego chcesz obserwować"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Pseudonim lub e-mail"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Brak identyfikatora Jabbera."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Brak żądania autoryzacji!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Brak kodu potwierdzajÄ…cego."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Brak zawartości!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Brak identyfikatora."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Zdalny serwer nie wysłał pseudonimu."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Brak pseudonimu."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Brak oczekujących potwierdzeń do anulowania."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Serwer nie zwrócił żadnego URL-a."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Brak zarejestrowanych adresów e-mailowych dla tego użytkownika."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Nie znaleziono żądania!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Brak wyników"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Zerowy rozmiar."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Brak takiego identyfikatora OpenID."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Brak takiego dokumentu."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Brak takiego wpisu."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Brak takiego kodu przywracania."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Nie ma takiej obserwacji"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Brak takiego użytkownika."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Nie ma kogo pokazać!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "To nie jest kod przywracania."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Niepoprawny identyfikator Jabbera"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Niepoprawny identyfikator OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Niewłaściwy adres e-mailowy."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Niewłaściwy pseudonim."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Błędny URL profilu (niepoprawne usługi)"
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Błędny URL profilu (nie zdefiniowany XRDS)"
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Błędny URL profilu (brak dokumentu YADIS)"
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Brak obrazka lub plik uszkodzony."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Brak autoryzacji."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Nieoczekiwana odpowiedź!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Niezalogowany."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Nie obserwujesz!."
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Kanał wpisów dla %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Wpis nie ma przypisanego profilu"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Wpisy"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Stare hasło"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Ustawienia konta OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Automatyczne zatwierdzanie OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Użytkownik OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL usługi OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Uwiarygadnianie OpenID przerwane."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Uwiarygadnianie OpenID nie powiodło się: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Awaria OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "Usunięto identyfikator OpenID."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Ustawienia OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Częściowa wysyłka."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Hasło"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Hasło i jego potwierdzenie nie pasują do siebie."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Hasło musi mieć 6 lub więcej znaków."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Zażądano odzyskania hasła"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Hasło zostało zapisane."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Podane hasła nie pasują do siebie."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Szukaj ludzi"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Osobiste"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Sprawdź proszę poniższe informacje, aby upewnić się czy na pewno chcesz "
+"obserwować wpisy tego użytkownika. Jeżeli to pomyłka lub chodziło o "
+"kogoÅ› innego kliknij \"Anuluj\"."
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Utwórz wpis kiedy zmieni się status na komunikatorze."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Preferencje"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Preferencje zostały zapisane."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Prywatność"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Problem z zachowywaniem wpisu."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL profilu"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Ustawienia profilu"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Nieznany profil"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Publiczny"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Publiczny Kanał Strumieni"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Publiczna oÅ› czasu"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Przywróć"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Odzyskiwanie hasła"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Kod przywracający dla nieznanego użytkownika."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Zarejestruj"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Odrzuć"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Pamiętaj mnie"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Zdalny profil bez odpowiadajÄ…cego profilu lokalnego"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Zdalna subskrypcja"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Usuń"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Usuń konto OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Usunięcie jedynego konta OpenID uniemożliwi dalsze logowanie! Jeśli "
+"musisz je usunąć dodaj wcześniej jakieś inne."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Odpowiedzi"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Odpowiedzi na %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Wyzeruj"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Ustaw ponownie hasło"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Takie samo jak hasło wprowadzone powyżej"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Zapisz"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Szukaj"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Szukaj Kanału Strumieni"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Wyszukiwanie w treści wpisów w serwisie %%site.name%%. Użyj spacji aby "
+"oddzielić elementy wyszukiwania. Słowa muszą mieć minimum 3 znaki."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Szukaj ludzi w serwisie %%site.name%%. Kryteriami mogą być imiona i "
+"nazwiska, miejscowości lub zainteresowania. Użyj spacji aby oddzielić "
+"elementy wyszukiwania. Słowa muszą mieć minimum 3 znaki."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Wyślij"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Wysyłaj mi wpisy przez Jabbera/GTalka"
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Ustawienia"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Ustawienia zostały zapisane."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Ktoś inny posługuje się już tym identyfikatorem OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Stało się coś dziwnego."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Kod źródłowy"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Statystyki"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Nie znaleziono zapisanego konta OpenID."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Subskrybuj"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Subskrybenci"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Subskrypcja uwierzytelniona"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Subskrypcja odrzucona"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Subskrypcje"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Błąd systemowy podczas wysyłania pliku."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Szukaj tekstu"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Ten identyfikator OpenID nie należy do Ciebie."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Ten adres został już potwierdzony"
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Ten kod potwierdzajÄ…cy nie jest przeznaczony dla Ciebie!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Ten plik jest za duży."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Ten identyfikator Jabbera jest już do Ciebie przypisany."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "To nie jest Twój identyfikator Jabbera."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "To jest błędny adres komunikatora."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Wpis za długi. Maksymalna długość to 140 znaków."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "Skojarzony z Twoim kontem adres \"%s\" został potwierdzony."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Adres został usunięty."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Twoje żądanie obserwacji zostało odrzucone, ale nie przekazano żadnego "
+"URL-a do zwrotnego komunikatu. Sprawdź w instrukcjach serwisu w jaki "
+"sposób dokładnie odbywa się odrzucanie subskrypcji. Twój token "
+"subskrypcji to:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Twoje żądanie obserwacji zostało odrzucone, ale nie przekazano żadnego "
+"URL-a do zwrotnego komunikatu. Sprawdź w instrukcjach serwisu w jaki "
+"sposób dokładnie odbywa się odrzucanie subskrypcji."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Ludzie obserwujący wpisy użytkownika %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Ludzie obserwujÄ…cy Twoje wpisy."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Ludzie, których wpisy obserwuje użytkownik %s."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Ludzie których wpisy obserwujesz."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Kod potwierdzajÄ…cy jest przeterminowany. Zacznij jeszcze raz."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Ten formularz powinien wysłać się automatycznie. Jeśli tak się nie "
+"stanie kliknij Wyślij, aby przejść do Twojego dostawcy OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Jeżeli logujesz się po raz pierwszy do %s to twoje konto OpenID musi "
+"zostać skojarzone z kontem lokalnym. Możesz więc albo utworzyć nowe "
+"konto, albo połączyć je z posiadanym istniejącym."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Ta strona nie jest dostępna dla medium, którego typ akceptujesz"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Aby się zapisać możesz się [zalogować](%%action.login%%) lub "
+"[zarejestrować](%%action.register%%). Jeśli już posiadasz konto w "
+"[kompatybilnym serwisie](%%doc.openmublog%%) wprowadź poniżej "
+"identyfikator URL."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL Twojej strony domowej, bloga, lub profilu na innej stronie"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL Twojego profilu na kompatybilnym serwisie do mikroblogów"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Nieoczekiwane przesłanie formularza."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Nieoczekiwane wyzerowanie hasła."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Nieznana wersja protokołu OMB."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Prawo do kopiowania zawartości tej strony, chyba że zaznaczono inaczej, "
+"należy do tworzących jej treści i uwarunkowane zasadami"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Nierozpoznany typ adresu %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Zrezygnuj z subskrypcji"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Nieobsługiwana wersja OMB"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Nieobsługiwany format pliku obrazu."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Wyślij"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Tu możesz wysłać nowego \"awatara\" (wizerunek użytkownika). Nie da się "
+"edytować obrazu po jego umieszczeniu w serwisie, więc upewnij się, że "
+"jest w miarę kwadratowy. Wysyłając go zgadzasz się na jego publikację "
+"na warunkach podanych w licencji strony. Użyj grafiki, która należy do "
+"Ciebie i którą możesz dzielić się z innymi."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Używane tylko do aktualizacji, ogłoszeń i przywracania hasła"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "Obserwowany użytkownik nie istnieje."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "Użytkownik nie ma profilu."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Pseudonim użytkownika"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Co słychać, %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Gdzie jesteÅ›? (np. \"miasto, region, kraj\")"
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Nieprawidłowy typ obrazu dla '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Nieprawidłowy rozmiar obrazu dla '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Już masz ten identyfikator OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Nie musisz ponownie się logować!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Tu możesz zmienić hasło. Wybierz porządne!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Możesz utworzyć nowe konto, aby rozpocząć wysyłanie wpisów."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Możesz usunąć łączność z serwerem OpenID ze swojego konta klikając "
+"\"Usuń\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Możesz wysyłać i odbierać wpisy przez komunikator Jabber/GTalk "
+"(%%doc.im%%). Poniżej możesz skonfigurować swój adres i ustawienia IM."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"W tym miejscu możesz zaktualizować informacje zawarte w Twoim osobistym "
+"profilu, aby inni mogli lepiej Cię poznać."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Możesz skorzystać z lokalnej subskrypcji!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+"Nie możesz się zarejestrować, jeśli nie zgadzasz się z warunkami "
+"licencji."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Ten profil nie był wysłany przez Ciebie"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Identyfikacja pomyślna. Wprowadź poniżej nowe hasło."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "URL Twojej usługi OpenID"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+"Twój pseudonim na tym serwerze lub adres e-mailowy użyty podczas "
+"rejestracji."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) pozwala logować Ci się do wielu serwisów z "
+"wykorzystaniem jednego konta użytkownika. Tu możesz zarządzać swoimi "
+"identyfikatorami OpenID."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "kilka sekund temu"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "około %d dni temu"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "około %d godzin temu"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "około %d minut temu"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "około %d miesięcy temu"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "blisko dzień temu"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "blisko minutÄ™ temu"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "blisko miesiÄ…c temu"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "blisko rok temu"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "blisko godzinÄ™ temu"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "w odpowiedzi na…"
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "odpowiedź"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "takie samo hasło jak powyżej"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« następne"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/pt/LC_MESSAGES/laconica.mo b/locale/pt/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..bca591d88
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/pt/LC_MESSAGES/laconica.po b/locale/pt/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..6cb7250ae
--- /dev/null
+++ b/locale/pt/LC_MESSAGES/laconica.po
@@ -0,0 +1,2865 @@
+# 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: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+#: actions/finishopenidlogin.php:88 actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/publicrss.php:62 actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr ""
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51 actions/all.php:47
+#: actions/allrss.php:60 actions/twitapistatuses.php:155 lib/personal.php:51
+#, php-format
+msgid "%s and friends"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: actions/finishopenidlogin.php:79 actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/password.php:42 actions/profilesettings.php:181
+msgid "6 or more characters"
+msgstr ""
+
+#: ../actions/recoverpassword.php:180 actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr ""
+
+#: ../actions/userauthorization.php:119 actions/userauthorization.php:126
+msgid "Accept"
+msgstr ""
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+#: actions/emailsettings.php:63 actions/imsettings.php:64
+#: actions/openidsettings.php:58 actions/smssettings.php:71
+#: actions/twittersettings.php:85
+msgid "Add"
+msgstr ""
+
+#: ../actions/openidsettings.php:43 actions/openidsettings.php:44
+msgid "Add OpenID"
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39 actions/emailsettings.php:39
+#: actions/imsettings.php:40 actions/smssettings.php:39
+msgid "Address"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/showstream.php:273 actions/showstream.php:288
+msgid "All subscriptions"
+msgstr ""
+
+#: ../actions/publicrss.php:64 actions/publicrss.php:50
+#, php-format
+msgid "All updates for %s"
+msgstr ""
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr ""
+
+#: ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/userauthorization.php:77 actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:104 ../actions/register.php:178
+#: actions/register.php:192
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#: actions/profilesettings.php:34
+msgid "Avatar"
+msgstr ""
+
+#: ../actions/avatar.php:113 actions/profilesettings.php:350
+msgid "Avatar updated."
+msgstr ""
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/util.php:1318 lib/util.php:1452
+msgid "Before »"
+msgstr ""
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+#: actions/profilesettings.php:82 actions/register.php:184
+msgid "Bio"
+msgstr ""
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+#: actions/profilesettings.php:404 actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr ""
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62 actions/emailsettings.php:58
+#: actions/imsettings.php:59 actions/smssettings.php:62
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../actions/password.php:45 actions/profilesettings.php:184
+msgid "Change"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../actions/password.php:32 actions/profilesettings.php:36
+msgid "Change password"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+#: actions/profilesettings.php:182 actions/recoverpassword.php:187
+#: actions/register.php:169 actions/smssettings.php:65
+msgid "Confirm"
+msgstr ""
+
+#: ../actions/confirmaddress.php:90 actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245 actions/emailsettings.php:256
+#: actions/imsettings.php:230 actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:91 actions/finishopenidlogin.php:97
+msgid "Connect"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr ""
+
+#: ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr ""
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:162 actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr ""
+
+#: ../actions/updateprofile.php:155 actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:127 actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+#: actions/profilesettings.php:299 actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:84 actions/finishopenidlogin.php:90
+msgid "Create"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:70 actions/finishopenidlogin.php:76
+msgid "Create a new user with this nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:68 actions/finishopenidlogin.php:74
+msgid "Create new account"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../actions/showstream.php:356 actions/showstream.php:367
+msgid "Currently"
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+#: actions/profilesettings.php:84 actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr ""
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87 actions/register.php:172
+#: actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr ""
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/recoverpassword.php:191 actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:253 actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:240 actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../lib/openid.php:226 lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr ""
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr ""
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:80 actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr ""
+
+#: ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr ""
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:64 actions/all.php:61
+#: actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr ""
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#: actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr ""
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr ""
+
+#: ../lib/util.php:322 lib/util.php:338
+msgid "Help"
+msgstr ""
+
+#: ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr ""
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+#: actions/profilesettings.php:79 actions/register.php:181
+msgid "Homepage"
+msgstr ""
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+#: actions/profilesettings.php:210 actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/imsettings.php:60 actions/imsettings.php:61
+msgid "IM Address"
+msgstr ""
+
+#: ../actions/imsettings.php:33 actions/imsettings.php:33
+msgid "IM Settings"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr ""
+
+#: ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr ""
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+
+#: ../actions/updateprofile.php:114 actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:98 actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:87 actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111 actions/finishopenidlogin.php:241
+#: actions/register.php:103 actions/register.php:121
+msgid "Invalid username or password."
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+#: actions/profilesettings.php:85 actions/register.php:187
+msgid "Location"
+msgstr ""
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr ""
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310 actions/login.php:97
+#: actions/login.php:106 actions/openidlogin.php:77 lib/util.php:326
+msgid "Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:44 actions/openidlogin.php:52
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr ""
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:308 lib/util.php:324
+msgid "Logout"
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/showstream.php:300 actions/showstream.php:315
+msgid "Member since"
+msgstr ""
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+#: actions/finishopenidlogin.php:85 actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr ""
+
+#: ../actions/newnotice.php:87 actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+#: actions/profilesettings.php:180 actions/recoverpassword.php:185
+msgid "New password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+#: actions/finishopenidlogin.php:171 actions/profilesettings.php:203
+#: actions/register.php:74 actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr ""
+
+#: ../actions/recoverpassword.php:162 actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/imsettings.php:156 actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr ""
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr ""
+
+#: ../actions/newnotice.php:44 actions/newmessage.php:53
+#: actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/userbyid.php:32 actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr ""
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:226 actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr ""
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82 actions/all.php:34
+#: actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/imsettings.php:167 actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr ""
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr ""
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:120 actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:113 actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27 actions/disfavor.php:29 actions/favor.php:30
+#: actions/finishaddopenid.php:29 actions/logout.php:33
+#: actions/newmessage.php:28 actions/newnotice.php:29 actions/subscribe.php:28
+#: actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:316 actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/password.php:39 actions/profilesettings.php:178
+msgid "Old password"
+msgstr ""
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314 lib/settingsaction.php:90
+#: lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr ""
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+#: actions/finishopenidlogin.php:96 actions/login.php:102
+#: actions/register.php:167
+msgid "Password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:288 actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: ../actions/recoverpassword.php:284 actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr ""
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+#: actions/profilesettings.php:408 actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:88
+#: actions/profilesettings.php:380 actions/register.php:98
+msgid "Passwords don't match."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94 actions/emailsettings.php:86
+#: actions/imsettings.php:68 actions/smssettings.php:94
+#: actions/twittersettings.php:70
+msgid "Preferences"
+msgstr ""
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr ""
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60 lib/personal.php:60
+#: lib/settingsaction.php:84
+msgid "Profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/recoverpassword.php:166 actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:156 actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr ""
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr ""
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#: actions/replies.php:47 actions/repliesrss.php:62 lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:183 actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:173 actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300 lib/searchaction.php:84
+#: lib/util.php:316
+msgid "Search"
+msgstr ""
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/invite.php:137 ../lib/util.php:1172 actions/invite.php:145
+#: lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:192 actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:296 actions/showstream.php:311
+msgid "Statistics"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+#: actions/finishopenidlogin.php:188 actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+#: actions/showstream.php:328 actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:170 actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/imsettings.php:233 actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+#: actions/emailsettings.php:244 actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+#: actions/newnotice.php:49 actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:92 actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+#: actions/profilesettings.php:81 actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:276 actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:209 actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../actions/avatar.php:68 actions/profilesettings.php:161
+msgid "Upload"
+msgstr ""
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82 actions/all.php:41
+#: actions/avatarbynickname.php:48 actions/foaf.php:47 actions/replies.php:41
+#: actions/showfavorites.php:41 actions/showstream.php:44
+#: actions/twitapiaccount.php:80 actions/twitapifavorites.php:68
+#: actions/twitapistatuses.php:235 actions/twitapistatuses.php:609
+#: actions/twitapiusers.php:87 lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+#: actions/profilesettings.php:87 actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:135 actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+#: actions/finishopenidlogin.php:38 actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr ""
+
+#: ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr ""
+
+#: ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr ""
+
+#: ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117 actions/noticesearch.php:136
+#: actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124 actions/noticesearch.php:143
+#: actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/pt_BR/LC_MESSAGES/laconica.mo b/locale/pt_BR/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..562a91187
--- /dev/null
+++ b/locale/pt_BR/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/pt_BR/LC_MESSAGES/laconica.po b/locale/pt_BR/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..7c8b0d8e8
--- /dev/null
+++ b/locale/pt_BR/LC_MESSAGES/laconica.po
@@ -0,0 +1,2877 @@
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.po (laconica) #-#-#-#-#\n"
+"Project-Id-Version: laconica\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-08-27 14:57-0400\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Acácio Alves dos Santos <acacioas@gmail.com>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Language: Portuguese\n"
+"X-Poedit-Country: BRAZIL\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Pesquisar por \"%s\" no fluxo de mensagens"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"exceto estes dados privados: senha, endereço de e-mail, endereço de "
+"mensageiro instantâneo, número de telefone."
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+msgid " from "
+msgstr " via "
+
+#: ../actions/twitapistatuses.php:478
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr "%1$s / Atualizações respondendo à %2$s"
+
+#: ../actions/invite.php:168
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s convidou você para se juntar ao %2$s"
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s convidou você para se juntar ao %2$s (%3$s).\n"
+"\n"
+"%2$s é um serviço de micro-blogging the permite você se manter atualizado "
+"com as pessoas que você conhece e com as que interessam à você.\n"
+"\n"
+"Você também pode compartilhar notícias sobre você mesmo, seus "
+"pensamentos, ou sua vida, com pessoas que conhecem você. Também é ótimo "
+"para conhecer novas pessoas que compartilham os mesmos interesses que "
+"você.\n"
+"\n"
+"%1$s disse:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Você pode ver o perfil de %1$s's no %2$s aqui:\n"
+"\n"
+"%5$s\n"
+"\n"
+"Se você quiser testar o serviço, clique no link abaixo para aceitar o "
+"convite.\n"
+"\n"
+"%6$s\n"
+"\n"
+"Se não, você pode ignorar esta mensagem. Obrigado por sua paciência e "
+"tempo.\n"
+"\nAtenciosamente, %2$s\n"
+
+#: ../lib/mail.php:124
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s agora está acompanhando suas mensagens em %2$s. "
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s agora está acompanhando suas mensagens em %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Atenciosamente,\n"
+"%4$s.\n "
+
+#: ../actions/twitapistatuses.php:482
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr "Atualizações de %1$s que respondem a mensagens de %2$s / %3$s."
+
+# Apesar do termo em inglês ser status, na verdade ele refere-se a uma determinada mensagem.
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "Mensagem de %1$s em %2$s"
+
+#: ../actions/invite.php:84 ../actions/invite.php:92
+#, php-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "%s Public Stream"
+msgstr "Conteúdo público de %s"
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51
+#, php-format
+msgid "%s and friends"
+msgstr "%s e amigos"
+
+#: ../actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "Mensagens públicas de %s"
+
+#: ../lib/mail.php:206
+#, php-format
+msgid "%s status"
+msgstr "Status de %s"
+
+#: ../actions/twitapistatuses.php:338
+#, php-format
+msgid "%s timeline"
+msgstr "Mensagens de %s"
+
+#: ../actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr "%s atualizações de todo mundo!"
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(Você receberá uma mensagem por e-mail a qualquer momento, com "
+"instruções sobre como confirmar seu endereço.)"
+
+#: ../lib/util.php:257
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** é um serviço de microblog disponibilizado por "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+
+#: ../lib/util.php:259
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** é um serviço de microblog."
+
+#: ../lib/util.php:274
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+". Os colaboradores devem ser citados usando seu nome completo ou "
+"apelido."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 letras minúsculas ou números, sem pontuações ou espaços"
+
+#: ../actions/register.php:152
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr "1-64 letras ou números, sem pontuação ou espaços. Obrigatório."
+
+#: ../actions/password.php:42
+msgid "6 or more characters"
+msgstr "6 ou mais caracteres"
+
+#: ../actions/recoverpassword.php:180
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 ou mais caracteres. E não se esqueça dela!"
+
+#: ../actions/register.php:154
+msgid "6 or more characters. Required."
+msgstr "6 caracteres ou mais. Obrigatório."
+
+#: ../actions/imsettings.php:197
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Um código de confirmação foi enviado para o endereço de MI que você "
+"adicionou. Você deve permitir que %s envie mensagens para você."
+
+#: ../actions/emailsettings.php:213
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Um código de confirmação foi enviado para o endereço de e-mail que você "
+"informou. Verifique sua caixa de entrada (e de spam!) para o código e "
+"instruções sobre como usá-lo."
+
+#: ../actions/smssettings.php:216
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Um código de confirmação foi enviado para o número de telefone de que "
+"você informou. Verifique sua caixa de entrada (e de spam!) para o código e "
+"instruções sobre como usá-lo."
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122
+msgid "API method not found!"
+msgstr "Método da API não encontrado!"
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+msgid "API method under construction."
+msgstr "Método da API em construção."
+
+#: ../lib/util.php:324
+msgid "About"
+msgstr "Sobre"
+
+#: ../actions/userauthorization.php:119
+msgid "Accept"
+msgstr "Aceitar"
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+msgid "Add"
+msgstr "Adicionar"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Adicionar OpenID"
+
+#: ../lib/settingsaction.php:97
+msgid "Add or remove OpenIDs"
+msgstr "Adicionar/Remover OpenIDs"
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39
+msgid "Address"
+msgstr "Endereço"
+
+#: ../actions/invite.php:131
+msgid "Addresses of friends to invite (one per line)"
+msgstr "Endereços dos seus amigos que serão convidados (um por linha)"
+
+#: ../actions/showstream.php:273
+msgid "All subscriptions"
+msgstr "Todas as assinaturas"
+
+#: ../actions/publicrss.php:64
+#, php-format
+msgid "All updates for %s"
+msgstr "Todas as atualizações para %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Todas as atualizações correspondentes ao termo \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+msgid "Already logged in."
+msgstr "Já está autenticado."
+
+#: ../lib/subs.php:42
+msgid "Already subscribed!."
+msgstr "Já foi assinado!"
+
+#: ../actions/deletenotice.php:54
+msgid "Are you sure you want to delete this notice?"
+msgstr "Você tem certeza que deseja apagar esta mensagem?"
+
+#: ../actions/userauthorization.php:77
+msgid "Authorize subscription"
+msgstr "Autorizar assinatura"
+
+#: ../actions/login.php:104 ../actions/register.php:178
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Entrar automaticamente no futuro sem pedir a senha. Não use em computadores "
+"compartilhados!"
+
+#: ../actions/profilesettings.php:65
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr "Assinar automaticamente à quem me assinar (melhor para não humanos)"
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#, fuzzy
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+#, fuzzy
+msgid "Avatar updated."
+msgstr "O avatar foi atualizado"
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Aguardando a confirmação deste endereço. Procure em sua conta de "
+"Jabber/GTalk por uma mensagem com mais instruções (Você adicionou %s à "
+"sua lista de contatos?)"
+
+#: ../actions/emailsettings.php:54
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"Aguardando confirmação deste endereço. Verifique sua caixa de entrada (e "
+"de spam!) para novas instruções."
+
+#: ../actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "Aguardando a confirmação deste número de telefone."
+
+#: ../lib/util.php:1318
+msgid "Before »"
+msgstr "Antes »"
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+msgid "Bio"
+msgstr "Descrição"
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103
+msgid "Bio is too long (max 140 chars)."
+msgstr "Descrição muito extensa (máximo 140 caracteres)."
+
+#: ../lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Não é possível apagar esta mensagem."
+
+#: ../actions/updateprofile.php:119
+#, php-format
+#, fuzzy
+msgid "Can't read avatar URL '%s'"
+msgstr "Não foi possível encontrar a imagem de exibição '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+msgid "Can't save new password."
+msgstr "Não foi possível salvar a nova senha."
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Não foi possível instanciar um objeto do OpenID"
+
+#: ../actions/imsettings.php:163
+msgid "Cannot normalize that Jabber ID"
+msgstr "Não foi possível configurar essa ID do Jabber"
+
+#: ../actions/emailsettings.php:181
+msgid "Cannot normalize that email address"
+msgstr "Não é possível usar este endereço de email"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Alterar"
+
+#: ../lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr "Alterar tratamento de emails"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Alterar a senha"
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr "Alterar sua senha"
+
+#: ../lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "Alterar suas configurações de perfil"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+msgid "Confirm"
+msgstr "Confirmar"
+
+#: ../actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr "Confirmar o endereço"
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245
+msgid "Confirmation cancelled."
+msgstr "A confirmação foi cancelada."
+
+#: ../actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Código de confirmação"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "O código de confirmação não foi encontrado."
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"Parabéns, %s! E bem vindo a %%%%site.name%%%%. A partir de agora, você "
+"algumas opções...\n"
+"\n"
+"* Acessar [seu perfil](%s) e publicar sua primeira mensagem.\n"
+"* Adicionar um [Endereço de Jabber/GTalk](%%%%action.imsettings%%%%) para "
+"você possa publicar via mensageiro instantâneo.\n"
+"* [Pesquisar por pessoas](%%%%action.peoplesearch%%%%) que você conheça ou "
+"que tenha os mesmos interesses. \n"
+"* Atualizar suas [preferências de perfil](%%%%action.profilesettings%%%%) "
+"para que outras pessoas saibam mais sobre você. \n"
+"* Ler a [documentação on-line](%%%%doc.help%%%%) para conhecer os recursos "
+"disponíveis. \n"
+"\n"
+"Obrigado por se cadastrar e esperamos que você aproveite o serviço."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Conectar"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Conecta-se a uma conta já existente"
+
+#: ../lib/util.php:332
+msgid "Contact"
+msgstr "Contato"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Não foi possível criar o formulário OpenID: %s"
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr "Não foi possível seguir o usuário: %s já está na sua lista."
+
+#: ../actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr "Não é possível acompanhar o usuário: Usuário não encontrado."
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Não foi possível redirecionar para o servidor: %s"
+
+#: ../actions/updateprofile.php:162
+msgid "Could not save avatar info"
+msgstr "Não foi possível salvar as informações do avatar"
+
+#: ../actions/updateprofile.php:155
+msgid "Could not save new profile info"
+msgstr "Não foi possível salvar as novas informações do perfil"
+
+#: ../lib/subs.php:54
+msgid "Could not subscribe other to you."
+msgstr "Não é possível que outros o acompanhem."
+
+#: ../lib/subs.php:46
+msgid "Could not subscribe."
+msgstr "Não foi possível assinar."
+
+#: ../actions/recoverpassword.php:102
+msgid "Could not update user with confirmed email address."
+msgstr "Não foi possível atualizar o usuário com o endereço email confirmado."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Não foi possível encontrar a página a partir da requisição feita."
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+msgid "Couldn't delete email confirmation."
+msgstr "Não foi possível excluir a confirmação do email."
+
+#: ../lib/subs.php:103
+msgid "Couldn't delete subscription."
+msgstr "Não foi possível excluir a assinatura."
+
+#: ../actions/twitapistatuses.php:93
+msgid "Couldn't find any statuses."
+msgstr "Não foi possível encontrar nenhum status."
+
+#: ../actions/remotesubscribe.php:127
+msgid "Couldn't get a request token."
+msgstr "Não foi possível processar a requisição."
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206
+msgid "Couldn't insert confirmation code."
+msgstr "Não foi possível inserir o código de confirmação."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Não foi possível inserir a nova assinatura."
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+msgid "Couldn't save profile."
+msgstr "Não foi possível salvar o perfil."
+
+#: ../actions/profilesettings.php:161
+msgid "Couldn't update user for autosubscribe."
+msgstr "Não foi possível atualizar o usuário para assinar automaticamente."
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+msgid "Couldn't update user record."
+msgstr "Não foi possível atualizar os registros do usuário."
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+msgid "Couldn't update user."
+msgstr "Não foi possível atualizar o usuário."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Criar"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Criar um novo usuário com este apelido."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Criar uma nova conta"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Criando uma nova conta para um OpenID que já tem um usuário."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Endereço atual de Jabber/GTalk confirmado."
+
+#: ../actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "Telefone atualmente habilitado para receber SMS."
+
+#: ../actions/emailsettings.php:44
+msgid "Current confirmed email address."
+msgstr "Endereço confirmado de e-mail."
+
+#: ../actions/showstream.php:356
+msgid "Currently"
+msgstr "Nesse momento"
+
+#: ../classes/Notice.php:72
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr "Erro no banco de dados durante a inserção da etiqueta: %s"
+
+#: ../lib/util.php:1061
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Erro no banco de dados na inserção da reposta: %s"
+
+#: ../actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "Excluir mensagem"
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Descreva você mesmo e seus interesses em 140 caracteres."
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87
+msgid "Email"
+msgstr "E-mail"
+
+#: ../actions/emailsettings.php:59
+msgid "Email Address"
+msgstr "Endereço de e-mail"
+
+#: ../actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "Configurações do e-mail"
+
+#: ../actions/register.php:73
+msgid "Email address already exists."
+msgstr "O endereço de e-mail já existe."
+
+#: ../lib/mail.php:90
+msgid "Email address confirmation"
+msgstr "Confirmação do endereço de e-mail"
+
+#: ../actions/emailsettings.php:61
+msgid "Email address, like \"UserName@example.org\""
+msgstr "Endereço de email, ex: \"usuário@exemplo.org\""
+
+#: ../actions/invite.php:129
+msgid "Email addresses"
+msgstr "Endereço de email"
+
+#: ../actions/recoverpassword.php:191
+msgid "Enter a nickname or email address."
+msgstr "Informe um apelido ou endereço de e-mail."
+
+#: ../actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr "Informe o código que você recebeu no seu telefone."
+
+#: ../actions/userauthorization.php:137
+msgid "Error authorizing token"
+msgstr "Erro na autorização do token"
+
+#: ../actions/finishopenidlogin.php:253
+msgid "Error connecting user to OpenID."
+msgstr "Erro na conexão do usuário ao OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Erro na conexão do usuário."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Erro na inserção do avatar"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Erro na inserção do novo perfil"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Erro na inserção do perfil remoto"
+
+#: ../actions/recoverpassword.php:240
+msgid "Error saving address confirmation."
+msgstr "Erro no salvamento do endereço de confirmação"
+
+#: ../actions/userauthorization.php:140
+msgid "Error saving remote profile"
+msgstr "Erro no salvamento do perfil remoto"
+
+#: ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Erro no salvamento do perfil."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Erro no salvamento do usuário."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Erro no salvamento do usuário; inválido."
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+msgid "Error setting user."
+msgstr "Erro na configuração do usuário."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Erro na atualização do perfil"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Erro na atualização do perfil remoto"
+
+#: ../actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr "Erro com o código de confirmação."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Esse apelido já existe"
+
+#: ../lib/util.php:326
+msgid "FAQ"
+msgstr "FAQ"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Não foi possível atualizar o avatar."
+
+#: ../actions/all.php:61 ../actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Mensagens dos amigos de %s"
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Respostas de %s"
+
+#: ../actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "Mensagens com a etiqueta %s"
+
+#: ../lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr "Pesquisar o conteúdo das mensagens"
+
+#: ../lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr "Encontrar pessoas neste site"
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Por razões de segurança, por favor, digite novamente seu nome de usuário "
+"e senha antes de alterar suas configurações."
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+msgid "Full name"
+msgstr "Nome completo"
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93
+msgid "Full name is too long (max 255 chars)."
+msgstr "Nome completo muito longo (máx. 255 caracteres)"
+
+#: ../lib/util.php:322
+msgid "Help"
+msgstr "Ajuda"
+
+#: ../lib/util.php:298
+msgid "Home"
+msgstr "Início"
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+msgid "Homepage"
+msgstr "Sítio de Internet"
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+msgid "Homepage is not a valid URL."
+msgstr "A URL informada não é válida."
+
+#: ../actions/emailsettings.php:91
+msgid "I want to post notices by email."
+msgstr "Eu quero publicar mensagens por e-mail."
+
+#: ../lib/settingsaction.php:102
+msgid "IM"
+msgstr "IM"
+
+#: ../actions/imsettings.php:60
+msgid "IM Address"
+msgstr "Endereço do mensageiro instantâneo"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Configuração do mensageiro instantâneo"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Se você já possui uma conta, utilize seu nome de usuário e senha para "
+"conectá-la ao seu OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Se você quer adicionar uma OpenID à sua conta, informe-a na caixa abaixo e "
+"clique em \"Adicionar\"."
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+"Se você esqueceu ou perdeu sua senha, você pode ter uma nova enviada para "
+"o endereço de email informado em seu cadastro."
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "E-mail de recebimento"
+
+#: ../actions/emailsettings.php:283
+msgid "Incoming email address removed."
+msgstr "O endereço de e-mail de recebimento foi removido."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "A senha antiga está incorreta"
+
+#: ../actions/login.php:67
+msgid "Incorrect username or password."
+msgstr "Nome de usuário e/ou senha incorreto(s)"
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"As instruções para recuperar sua senha foram enviadas para o endereço de "
+"e-mail registrado no seu cadastro."
+
+#: ../actions/updateprofile.php:114
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "A URL '%s' para a imagem de exibição é inválida"
+
+#: ../actions/invite.php:55
+#, php-format
+msgid "Invalid email address: %s"
+msgstr "Não é um endereço de email válido: %s"
+
+#: ../actions/updateprofile.php:98
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "O site '%s' é inválido"
+
+#: ../actions/updateprofile.php:82
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "A URL '%s' da licença é inválida"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "O conteúdo da mensagem é inválido"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "A URI da mensagem é inválida"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "A URL da mensagem é inválida"
+
+#: ../actions/updateprofile.php:87
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "URL de perfil inválida '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "A URL do perfil é inválida (formato inválido)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "A URL do perfil retornada pelo servidor é inválida."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Tamanho inválido"
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111
+msgid "Invalid username or password."
+msgstr "Nome de usuário e/ou senha inválido(s)"
+
+#: ../actions/invite.php:79
+msgid "Invitation(s) sent"
+msgstr "Convite(s) enviado(s)"
+
+#: ../actions/invite.php:97
+msgid "Invitation(s) sent to the following people:"
+msgstr "Convite(s) enviado(s) para as seguintes pessoas:"
+
+#: ../lib/util.php:306
+msgid "Invite"
+msgstr "Convidar"
+
+#: ../actions/invite.php:123
+msgid "Invite new users"
+msgstr "Convidar novos usuários"
+
+#: ../lib/util.php:261
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Ele funciona sob o software de microblog [Laconica](http://laconi.ca/), "
+"versão %s, disponível sob a [GNU Affero General Public License] "
+"(http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:173
+msgid "Jabber ID already belongs to another user."
+msgstr "Este ID do Jabber já pertence à outro usuário."
+
+#: ../actions/imsettings.php:62
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Endereço de Jabber ou GTalk, ex: \"usuario@exemplo.org\". Primeiro, "
+"certifique-se de adicionar %s à sua lista de contatos no seu cliente de IM "
+"ou no GTalk."
+
+#: ../actions/profilesettings.php:57
+msgid "Language"
+msgstr "Idioma"
+
+#: ../actions/profilesettings.php:113
+msgid "Language is too long (max 50 chars)."
+msgstr "Nome do idioma muito extenso (máx. 50 caracteres)."
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+msgid "Location"
+msgstr "Localização"
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108
+msgid "Location is too long (max 255 chars)."
+msgstr "A localização é muito longa (máx. 255 caracteres)."
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310
+msgid "Login"
+msgstr "Autenticação"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Autentique-se com uma conta [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Autentique-se com seus nome de usuário e senha. Não tem um usuário ainda? "
+"[Registre](%%action.register%%) uma nova conta, ou use uma "
+"[OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:308
+msgid "Logout"
+msgstr "Sair"
+
+#: ../actions/register.php:166
+msgid "Longer name, preferably your \"real\" name"
+msgstr "Nome completo, de preferência seu nome \"real\""
+
+#: ../actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr "Perdeu ou esqueceu sua senha?"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr "Cria um novo endereço de email para publicar e cancela o antigo."
+
+#: ../actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr "Gerenciar como você recebe emails do %%site.name%%."
+
+#: ../actions/showstream.php:300
+msgid "Member since"
+msgstr "Membro desde"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog por %s"
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+"Operadora móvel do seu celular. Se você conhece uma operadora que aceita "
+"SMS via e-mail, mas não está listada aqui, informe-nos enviando um e-mail "
+"para %s."
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+msgid "My text and files are available under "
+msgstr "Meus textos e arquivos estão disponíveis sob"
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+msgid "New"
+msgstr "Novo"
+
+#: ../lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr "Novo endereço de email para postar no %s"
+
+#: ../actions/emailsettings.php:297
+msgid "New incoming email address added."
+msgstr ""
+"Foi adicionado um novo endereço de e-mail para recebimento de "
+"mensagens."
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Novo apelido"
+
+#: ../actions/newnotice.php:87
+msgid "New notice"
+msgstr "Nova mensagem"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+msgid "New password"
+msgstr "Nova senha"
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+"A nova senha foi salva com sucesso. A partir de agora você já está "
+"autenticado."
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151
+msgid "Nickname"
+msgstr "Apelido"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69
+msgid "Nickname already in use. Try another one."
+msgstr "Esse apelido já está em uso. Tente outro."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"O apelido deve conter apenas letras minúsculas e/ou números e não pode "
+"ter espaços."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Esse apelido não é permitido."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Apelido do usuário que você quer seguir"
+
+#: ../actions/recoverpassword.php:162
+msgid "Nickname or email"
+msgstr "Apelido ou e-mail"
+
+#: ../actions/deletenotice.php:59
+msgid "No"
+msgstr "Não"
+
+#: ../actions/imsettings.php:156
+msgid "No Jabber ID."
+msgstr "Nenhuma ID de Jabber."
+
+#: ../actions/userauthorization.php:129
+msgid "No authorization request!"
+msgstr "Nenhum pedido de autorização!"
+
+#: ../actions/smssettings.php:181
+msgid "No carrier selected."
+msgstr "Não foi selecionada nenhuma operadora."
+
+#: ../actions/smssettings.php:316
+msgid "No code entered"
+msgstr "Não foi informado nenhum código"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Nenhum código de confirmação."
+
+#: ../actions/newnotice.php:44
+msgid "No content!"
+msgstr "Nenhum conteúdo!"
+
+#: ../actions/emailsettings.php:174
+msgid "No email address."
+msgstr "Nenhum endereço de e-mail."
+
+#: ../actions/userbyid.php:32
+msgid "No id."
+msgstr "Nenhuma ID."
+
+#: ../actions/emailsettings.php:271
+msgid "No incoming email address."
+msgstr "Nenhum endereço de e-mail para recebimentos."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Nenhum apelido fornecido pelo servidor remoto."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Sem apelido."
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229
+msgid "No pending confirmation to cancel."
+msgstr "Nenhuma confirmação para cancelar."
+
+#: ../actions/smssettings.php:176
+msgid "No phone number."
+msgstr "Nenhum telefone cadastrado."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Nenhuma URL de perfil retornada pelo servidor."
+
+#: ../actions/recoverpassword.php:226
+msgid "No registered email address for that user."
+msgstr "Nenhum endereço de e-mail registrado para esse usuário."
+
+#: ../actions/userauthorization.php:49
+msgid "No request found!"
+msgstr "Não foi encontrada nenhuma requisição!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Não foi encontrado nenhum resultado"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Sem tamanho definido."
+
+#: ../actions/twitapistatuses.php:595
+msgid "No status found with that ID."
+msgstr "Nenhum status foi encontrado com esse ID."
+
+#: ../actions/twitapistatuses.php:555
+msgid "No status with that ID found."
+msgstr "Nenhum status com esse ID foi encontrado."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Essa OpenID não existe."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Esse documento não existe."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30
+msgid "No such notice."
+msgstr "Essa mensagem não existe."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Esse código de recuperação não existe."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Essa assinatura não existe."
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82
+msgid "No such user."
+msgstr "Usuário não encontrado."
+
+#: ../actions/recoverpassword.php:211
+msgid "No user with that email address or username."
+msgstr "Não foi encontrado um usuário com este login ou endereço de email."
+
+#: ../lib/gallery.php:80
+msgid "Nobody to show!"
+msgstr "Ninguém para exibir!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Não é um código de recuperação"
+
+#: ../scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr "Não é um usuário registrado."
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332
+msgid "Not a supported data format."
+msgstr "Formato de dados não suportado."
+
+#: ../actions/imsettings.php:167
+msgid "Not a valid Jabber ID"
+msgstr "Não é uma ID válida de Jabber"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Não é uma OpenID válida."
+
+#: ../actions/emailsettings.php:185
+msgid "Not a valid email address"
+msgstr "Não é um endereço de e-mail válido."
+
+#: ../actions/register.php:63
+msgid "Not a valid email address."
+msgstr "Não é um endereço de e-mail válido."
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+msgid "Not a valid nickname."
+msgstr "Não é um apelido válido."
+
+#: ../actions/remotesubscribe.php:120
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Não é uma URL de perfil válida (serviço incorreto)."
+
+#: ../actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Não é uma URL de perfil válida (nenhum XRDS definido)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Não é uma URL de perfil válida (nenhum documento YADIS)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Imagem inválida ou arquivo corrompido."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Não autorizado."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Não esperava por esta resposta!"
+
+#: ../actions/twitapistatuses.php:422
+msgid "Not found"
+msgstr "Não encontrado"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Você não está logado"
+
+#: ../lib/subs.php:91
+msgid "Not subscribed!."
+msgstr "Não é assinado!"
+
+#: ../actions/opensearch.php:35
+msgid "Notice Search"
+msgstr "Pesquisar por mensagem"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Mensagens de %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "A mensagem não está associada a nenhum perfil"
+
+#: ../actions/showstream.php:316
+msgid "Notices"
+msgstr "Mensagens"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr "Feed de mensagens com %s"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Senha antiga"
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Configuração da conta OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Submissão automática da OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Autenticar-se via OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL da OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "A autenticação pela OpenID foi cancelada."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Não foi possível autenticar via OpenID: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Falha no OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "A OpenID foi removida."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Configurações da OpenID"
+
+#: ../actions/invite.php:135
+msgid "Optionally add a personal message to the invitation."
+msgstr "Opcionalmente adicione uma mensagem pessoal ao convite."
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Envio parcial."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+msgid "Password"
+msgstr "Senha"
+
+#: ../actions/recoverpassword.php:288
+msgid "Password and confirmation do not match."
+msgstr "A senha e a confirmação não coincidem."
+
+#: ../actions/recoverpassword.php:284
+msgid "Password must be 6 chars or more."
+msgstr "A senha deve ter 6 caracteres ou mais."
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+msgid "Password recovery requested"
+msgstr "Foi solicitada a recuperação da senha"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+msgid "Password saved."
+msgstr "A senha foi salva."
+
+#: ../actions/password.php:61 ../actions/register.php:88
+msgid "Passwords don't match."
+msgstr "As senhas não coincidem."
+
+#: ../lib/searchaction.php:100
+msgid "People"
+msgstr "Pessoas"
+
+#: ../actions/opensearch.php:33
+msgid "People Search"
+msgstr "Busca por pessoas"
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Procurar pessoas"
+
+#: ../lib/stream.php:50
+msgid "Personal"
+msgstr "Pessoal"
+
+#: ../actions/invite.php:133
+msgid "Personal message"
+msgstr "Mensagem pessoal"
+
+#: ../actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr "Número de telefone, sem pontuação ou espaços, com código de área"
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Por favor, verifique estes detalhes para ter certeza que você quer assinar "
+"as mensagens deste usuário. Se você não solicitou a assinatura das "
+"mensagens de ninguém, clique em \"Cancelar\"."
+
+#: ../actions/imsettings.php:73
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Informar minhas mudanças de estado no Jabber/GTalk como uma mensagem."
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94
+msgid "Preferences"
+msgstr "Preferências"
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163
+msgid "Preferences saved."
+msgstr "As preferências foram salvas."
+
+#: ../actions/profilesettings.php:57
+msgid "Preferred language"
+msgstr "Idioma"
+
+#: ../lib/util.php:328
+msgid "Privacy"
+msgstr "Privacidade"
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106
+msgid "Problem saving notice."
+msgstr "Problema no salvamento da mensagem."
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60
+msgid "Profile"
+msgstr "Perfil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL do Perfil"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Configurações de perfil"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+msgid "Profile unknown"
+msgstr "Perfil desconhecido"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Fluxo de mensagens públicas"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Mensagens públicas"
+
+#: ../actions/imsettings.php:79
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Publique um MicroID com meu Endereço de Jabber/Gtalk."
+
+#: ../actions/emailsettings.php:94
+msgid "Publish a MicroID for my email address."
+msgstr "Publique um MicroID para meu endereço de email."
+
+#: ../actions/tag.php:75 ../actions/tag.php:76
+msgid "Recent Tags"
+msgstr "Tags recentes"
+
+#: ../actions/recoverpassword.php:166
+msgid "Recover"
+msgstr "Recuperar"
+
+#: ../actions/recoverpassword.php:156
+msgid "Recover password"
+msgstr "Recuperar a senha"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Código de confirmação para usuário desconhecido."
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+msgid "Register"
+msgstr "Registrar"
+
+#: ../actions/register.php:28
+msgid "Registration not allowed."
+msgstr "Cadastro não permitido."
+
+#: ../actions/register.php:200
+msgid "Registration successful"
+msgstr "Registro completo"
+
+#: ../actions/userauthorization.php:120
+msgid "Reject"
+msgstr "Recusar"
+
+#: ../actions/login.php:103 ../actions/register.php:176
+msgid "Remember me"
+msgstr "Lembrar minha autenticação nesse computador"
+
+#: ../actions/updateprofile.php:70
+msgid "Remote profile with no matching profile"
+msgstr "Perfil remoto sem referencia local"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Assinatura remota"
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+msgid "Remove"
+msgstr "Remover"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Remover OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Remover a sua única OpenID pode fazer com que seja impossível você se "
+"autenticar novamente! Se deseja realmente, adicione outra OpenID antes."
+
+#: ../lib/stream.php:55
+msgid "Replies"
+msgstr "Respostas"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr "Respostas para %s"
+
+#: ../actions/recoverpassword.php:183
+msgid "Reset"
+msgstr "Restaurar"
+
+#: ../actions/recoverpassword.php:173
+msgid "Reset password"
+msgstr "Restaurar a senha"
+
+#: ../lib/settingsaction.php:99
+msgid "SMS"
+msgstr "SMS"
+
+#: ../actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "Telefone para SMS"
+
+#: ../actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "Configuração de SMS"
+
+#: ../lib/mail.php:219
+msgid "SMS confirmation"
+msgstr "Confirmação de SMS"
+
+#: ../actions/recoverpassword.php:182
+msgid "Same as password above"
+msgstr "Igual à senha acima"
+
+#: ../actions/register.php:156
+msgid "Same as password above. Required."
+msgstr "Igual à senha acima. Obrigatório"
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+msgid "Save"
+msgstr "Salvar"
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300
+msgid "Search"
+msgstr "Pesquisar"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Pesquisar no fluxo de mensagens"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Pesquisar por mensagens em %%site.name%% por seu conteúdo. Separe os termos "
+"da busca com espaços; eles devem ter 3 caracteres ou mais."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Pesquisar por pessoas em %%site.name%% por seus nomes, localidade ou "
+"interesses. Separe os termos da busca com espaços; eles devem ter 3 "
+"caracteres ou mais."
+
+#: ../actions/smssettings.php:296
+msgid "Select a carrier"
+msgstr "Escolha uma operadora"
+
+#: ../actions/invite.php:137 ../lib/util.php:1172
+msgid "Send"
+msgstr "Enviar"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr "Envie emails para esse endereço para postar novas mensagens."
+
+#: ../actions/emailsettings.php:88
+msgid "Send me notices of new subscriptions through email."
+msgstr "Me envie as mensagens de novos seguidores via email."
+
+#: ../actions/imsettings.php:70
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Envie-me as mensagens via Jabber/GTalk."
+
+#: ../actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+"Me envie mensagens via SMS; Eu entendo que isso pode gerar contas "
+"exorbitantes de minha operadora."
+
+#: ../actions/imsettings.php:76
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+"Me envie respostas através do Jabber/GTalk de pessoas que eu não estou "
+"seguindo."
+
+#: ../lib/util.php:304
+msgid "Settings"
+msgstr "Configurações"
+
+#: ../actions/profilesettings.php:192
+msgid "Settings saved."
+msgstr "As configurações foram salvas."
+
+#: ../actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr "Exibindo tags mais populares da última semana"
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Alguém já está usando esta OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Aconteceu alguma coisa estranha..."
+
+#: ../scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr "Infelizmente não é permitido o recebimento de emails."
+
+#: ../scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr "Infelizmente este não é seu endeço para postagem via email."
+
+#: ../lib/util.php:330
+msgid "Source"
+msgstr "Fonte"
+
+#: ../actions/showstream.php:296
+msgid "Statistics"
+msgstr "Estatísticas"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+msgid "Stored OpenID not found."
+msgstr "O OpenID armazenado não foi encontrado."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197
+msgid "Subscribe"
+msgstr "Assinar"
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Assinantes"
+
+#: ../actions/userauthorization.php:310
+msgid "Subscription authorized"
+msgstr "A assinatura foi autorizada"
+
+#: ../actions/userauthorization.php:320
+msgid "Subscription rejected"
+msgstr "A assinatura foi recusada"
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Assinaturas"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Erro no sistema durante o envio do arquivo."
+
+#: ../actions/tag.php:41 ../lib/util.php:301
+msgid "Tags"
+msgstr "Tags"
+
+#: ../lib/searchaction.php:104
+msgid "Text"
+msgstr "Texto"
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Pesquisa no conteúdo"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Essa OpenID não pertence à você."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Esse endereço já foi confirmado."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Esse código de confirmação não é seu!"
+
+#: ../actions/emailsettings.php:191
+msgid "That email address already belongs to another user."
+msgstr "Este endereço de email já pertence à outro usuário."
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Esse arquivo é muito grande."
+
+#: ../actions/imsettings.php:170
+msgid "That is already your Jabber ID."
+msgstr "Essa já é sua ID do Jabber."
+
+#: ../actions/emailsettings.php:188
+msgid "That is already your email address."
+msgstr "Este já é seu endereço de email."
+
+#: ../actions/smssettings.php:188
+msgid "That is already your phone number."
+msgstr "Este já é seu número de telefone."
+
+#: ../actions/imsettings.php:233
+msgid "That is not your Jabber ID."
+msgstr "Essa não é sua ID do Jabber."
+
+#: ../actions/emailsettings.php:249
+msgid "That is not your email address."
+msgstr "Este não é seu endereço de email."
+
+#: ../actions/smssettings.php:257
+msgid "That is not your phone number."
+msgstr "Este não é seu número de telefone."
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+msgid "That is the wrong IM address."
+msgstr "Esse endereço do comunicador instantâneo está incorreto."
+
+#: ../actions/smssettings.php:233
+msgid "That is the wrong confirmation number."
+msgstr "O código de confirmação informado está incorreto."
+
+#: ../actions/smssettings.php:191
+msgid "That phone number already belongs to another user."
+msgstr "Este número de telefone já pertence à outro usuário."
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Está muito extenso. O tamanho máximo é de 140 caracteres."
+
+#: ../actions/twitapiaccount.php:74
+msgid "That's too long. Max notice size is 255 chars."
+msgstr "Está muito longo.. O tamanho máximo é de 255 caracteres."
+
+#: ../actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "O endereço \"%s\" foi confirmado para sua conta."
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274
+msgid "The address was removed."
+msgstr "O endereço foi removido."
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"A assinatura foi autorizada, mas não foi informada nenhuma URL de retorno "
+"válida. Verifique as instruções do site para detalhes sobre como "
+"autorizar a assinatura. Seu código de assinatura é:"
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"A assinatura foi rejeitada, mas não foi informada nenhuma URL de retorno "
+"válida. Verifique as instruções do site para maiores detalhes em como "
+"rejeitar completamente a assinatura."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Estas são as pessoas que acompanham as mensagens de %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Estas são as pessoas que acompanham as suas mensagens."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Estas são as pessoas cujas mensagens %s acompanha."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Estas são as pessoas cujas mensagens você acompanha."
+
+#: ../actions/invite.php:89
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr "Estas pessoas já são usuários e você está automaticamente seguindo a "
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr "Este código de confirmação é muito antigo. Por favor comece de novo."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Este formulário deve enviar-se automaticamente. Caso isso não ocorra, "
+"clique no botão \"Enviar\" para ir para seu provedor de OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Esta é a primeira vez que você autenticou-se em %s. Por isso nós "
+"precisamos conectar sua OpenID a uma conta local. Você pode criar uma conta "
+"nova, ou conectar a uma conta sua já existente, caso tenha uma."
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+msgid "This method requires a POST or DELETE."
+msgstr "Este método requer POST ou DELETE."
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381
+msgid "This method requires a POST."
+msgstr "Este método requer um POST."
+
+#: ../lib/util.php:164
+msgid "This page is not available in a media type you accept"
+msgstr "Esta página não está disponível em um tipo de mídia que você aceita"
+
+#: ../actions/profilesettings.php:63
+msgid "Timezone"
+msgstr "Fuso horário"
+
+#: ../actions/profilesettings.php:107
+msgid "Timezone not selected."
+msgstr "Fuso horário não selecionado."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Para assinar, você pode [autenticar-se](%%action.login%%), ou "
+"[registrar](%%action.register%%) uma nova conta. Se você já tem uma conta "
+"em um [site de microblog compatível](%%doc.openmublog%%), informe a URL do "
+"seu perfil abaixo."
+
+#: ../actions/twitapifriendships.php:163
+msgid "Two user ids or screen_names must be supplied."
+msgstr "Dois usuários com IDs ou logins devem ser informados."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL para seu site, blog ou perfil em outro site"
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL para seu perfil em outro serviço de microblog compatível"
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+msgid "Unexpected form submission."
+msgstr "Submissão inesperada de formulário."
+
+#: ../actions/recoverpassword.php:276
+msgid "Unexpected password reset."
+msgstr "Restauração inesperada de senha."
+
+#: ../index.php:57
+msgid "Unknown action"
+msgstr "Ação desconhecida"
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Versão desconhecida do protocolo OMB."
+
+#: ../lib/util.php:269
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Caso não seja especificado, os colaboradores deste site possuem direito "
+"autoral sobre seu conteúdo e ele está disponível sob"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Tipo de endereço %s desconhecido"
+
+#: ../actions/showstream.php:209
+msgid "Unsubscribe"
+msgstr "Cancelar"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+msgid "Unsupported OMB version"
+msgstr "Versão do OMB não suportada"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Formato de imagem não suportado."
+
+#: ../lib/settingsaction.php:100
+msgid "Updates by SMS"
+msgstr "Atualizações via SMS"
+
+#: ../lib/settingsaction.php:103
+msgid "Updates by instant messenger (IM)"
+msgstr "Atualizações via mensageiro instantâneo (IM)"
+
+#: ../actions/twitapistatuses.php:241
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr "Atualizações de %1$s e amigos no %2$s!"
+
+#: ../actions/twitapistatuses.php:341
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr "Atualizações de %1$s no %2$s!"
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Enviar"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Envie um novo avatar (imagem do usuário) aqui. Você não poderá editar a "
+"imagem depois que enviar, então certifique-se que o formato dela esteja "
+"mais ou menos quadrado. Ela deve estar sob a mesma licença também. Use uma "
+"imagem que pertença a você e que você queira compartilhar."
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr "Envie uma nova imagem de exibição"
+
+#: ../actions/invite.php:114
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+"Use esse formulário para convidar seus amigos e colegas para usar este "
+"serviço."
+
+#: ../actions/register.php:159 ../actions/register.php:162
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Usado apenas para atualizações, anúncios e recuperações de senha"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "O usuário que está está sendo acompanhado não existe."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82
+msgid "User has no profile."
+msgstr "O usuário não tem perfil."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Apelido do usuário"
+
+#: ../actions/twitapiusers.php:75
+msgid "User not found."
+msgstr "Usuário não encontrado."
+
+#: ../actions/profilesettings.php:63
+msgid "What timezone are you normally in?"
+msgstr "Que fuso horário você normalmente está?"
+
+#: ../lib/util.php:1159
+#, php-format
+msgid "What's up, %s?"
+msgstr "E aí, %s?"
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Onde você está, ex: \"cidade, estado, país\""
+
+#: ../actions/updateprofile.php:128
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Tipo de imagem errado para '%s'"
+
+#: ../actions/updateprofile.php:123
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Tamanho da imagem errado em '%s'"
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+msgid "Yes"
+msgstr "Sim"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Você já tem esta OpenID!"
+
+#: ../actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+"Você está prestes a apagar permanentemente uma mensagem. Uma vez feito, "
+"não poderá ser desfeito."
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Você já está autenticado!"
+
+#: ../actions/invite.php:81
+msgid "You are already subscribed to these users:"
+msgstr "Você já segue à esses usuários:"
+
+#: ../actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr "Você não é amigo do usuário especificado."
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Você pode alterar sua senha aqui. Faça uma boa escolha!"
+
+#: ../actions/register.php:135
+msgid "You can create a new account to start posting notices."
+msgstr "Você pode criar uma nova conta para começar a publicar mensagens."
+
+#: ../actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr "Você pode receber mensagens SMS do %%site.name%% através de email."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Você pode remover uma OpenID da sua conta, clicando no botão "
+"\"Remover\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Você pode enviar e receber mensagens através dos [mensageiros "
+"instantâneos](%%doc.im%%) Jabber/GTalk. Configure seu endereço e opções "
+"abaixo."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Você pode atualizar suas informações pessoais aqui, para que as pessoas "
+"saibam mais sobre você."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Você pode usar a assinatura local!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+msgid "You can't register if you don't agree to the license."
+msgstr "Você não pode se registrar se não aceitar a licença."
+
+#: ../actions/updateprofile.php:63
+msgid "You did not send us that profile"
+msgstr "Você não nos enviou esse perfil"
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+"Você tem um novo endereço para postagem no %1$s.\n"
+"\n"
+"Envie emails para %2$s a fim de postar novas mensagens.\n"
+"\n"
+"Mais instruções em %3$s.\n"
+"\n"
+"Atenciosamente,\n%4$s"
+
+#: ../actions/twitapistatuses.php:612
+msgid "You may not delete another user's status."
+msgstr "Você não pode apagar o status de outro usuário."
+
+#: ../actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr "Você deve estar logado para convidar outros usuários para o %s"
+
+#: ../actions/invite.php:103
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+"Você será notificado qunando seus convidados aceitarem o convite e se "
+"registrarem neste site. Obrigado por aumentar a comunidade!"
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr "Você foi identificado. Informe uma nova senha abaixo."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Sua URL de OpenID"
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Seu apelido neste servidor, ou seu email registrado."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"A [OpenID](%%doc.openid%%) permite que você autentique-se em vários sites "
+"com a mesma conta de usuário. Gerencie suas OpenIDs associadas aqui."
+
+#: ../lib/util.php:943
+msgid "a few seconds ago"
+msgstr "a poucos segundos atrás"
+
+#: ../lib/util.php:955
+#, php-format
+msgid "about %d days ago"
+msgstr "aproximadamente %d dias atrás"
+
+#: ../lib/util.php:951
+#, php-format
+msgid "about %d hours ago"
+msgstr "aproximadamente %d horas atrás"
+
+#: ../lib/util.php:947
+#, php-format
+msgid "about %d minutes ago"
+msgstr "aproximadamente %d minutos atrás"
+
+#: ../lib/util.php:959
+#, php-format
+msgid "about %d months ago"
+msgstr "aproximadamente %d meses atrás"
+
+#: ../lib/util.php:953
+msgid "about a day ago"
+msgstr "aproximadamente um dia atrás"
+
+#: ../lib/util.php:945
+msgid "about a minute ago"
+msgstr "aproximadamente um minuto atrás"
+
+#: ../lib/util.php:957
+msgid "about a month ago"
+msgstr "aproximadamente um mes atrás"
+
+#: ../lib/util.php:961
+msgid "about a year ago"
+msgstr "aproximadamente um ano atrás"
+
+#: ../lib/util.php:949
+msgid "about an hour ago"
+msgstr "aproximadamente uma hora atrás"
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+msgid "delete"
+msgstr "apagar"
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117
+msgid "in reply to..."
+msgstr "em resposta à..."
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124
+msgid "reply"
+msgstr "responder"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "igual à senha acima"
+
+#: ../actions/twitapistatuses.php:755
+msgid "unsupported file type"
+msgstr "Formato de imagem não suportado"
+
+#: ../lib/util.php:1309
+msgid "« After"
+msgstr "« Depois"
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/ru_RU/LC_MESSAGES/laconica.mo b/locale/ru_RU/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..1adc55b88
--- /dev/null
+++ b/locale/ru_RU/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/ru_RU/LC_MESSAGES/laconica.po b/locale/ru_RU/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..fc08785b7
--- /dev/null
+++ b/locale/ru_RU/LC_MESSAGES/laconica.po
@@ -0,0 +1,2862 @@
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.po (0.6) #-#-#-#-#\n"
+"Project-Id-Version: 0.6\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-08-27 14:57-0400\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Doktor Bro <admin@stosorok.com>\n"
+"Language-Team: Doktor Bro <info@stosorok.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Russian\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+", за иÑключением моих чаÑтных данных: "
+"пароль, почта, меÑÑенджер, телефон."
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+msgid " from "
+msgstr " из"
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s приглашает Ñ‚ÐµÐ±Ñ Ð·Ð°Ð¿Ð¸ÑатьÑÑ Ð½Ð° %2$s"
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s приглашает Ñ‚ÐµÐ±Ñ Ð·Ð°Ð¿Ð¸ÑатьÑÑ Ð½Ð° %2$s "
+"(%3$s).\n"
+"\n"
+"%2$s — Ñто открытый ÑервиÑ, который "
+"позволÑет быть в курÑе поÑледних Ñобытий "
+"из жизни людей, которых ты знаешь или "
+"которые Ñ‚ÐµÐ±Ñ Ð¸Ð½Ñ‚ÐµÑ€ÐµÑуют.\n"
+"\n"
+"Ты также можешь раÑÑказывать другим о "
+"Ñебе: о твоих делах, мыÑлÑÑ… и "
+"впечатлениÑÑ…. У Ð½Ð°Ñ Ñ‚Ñ‹ Ñможешь завеÑти "
+"новые знакомÑтва Ñ Ð»ÑŽÐ´ÑŒÐ¼Ð¸, которые "
+"разделÑÑŽÑ‚ твои интереÑÑ‹.\n"
+"\n"
+"%1$s Ñказал(а):\n\n%4$s\n\nПрофиль %1$s на %2$s можно поÑмотреть здеÑÑŒ:\n\n%5$s\n\nЕÑли Ñ‚Ñ‹ хочешь попробовать наш ÑервиÑ, нажми на нижнюю ÑÑылку, чтобы принÑÑ‚ÑŒ приглашение.\n\n%6$s\n\nЕÑли нет, проÑто проигнорируй Ñто пиÑьмо. СпаÑибо за твоё терпение и времÑ.\n\nС уважением,\n%2$s\n"
+
+#: ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s Ñлушает твои заметки на %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"С уважением,\n%4$s.\n"
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/publicrss.php:62 actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr ""
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51
+#, php-format
+msgid "%s and friends"
+msgstr "%s и друзьÑ"
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+
+#: ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr ""
+
+#: ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 латинÑких Ñтрочных букв или цифр, без "
+
+#: ../actions/register.php:152
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"1-64 латинÑких Ñтрочных букв или цифр, без "
+"пробелов и знаков препинаниÑ. ОбÑзательно."
+
+#: ../actions/password.php:42
+msgid "6 or more characters"
+msgstr "6 или больше знаков"
+
+#: ../actions/recoverpassword.php:180 actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr ""
+
+#: ../actions/register.php:154
+msgid "6 or more characters. Required."
+msgstr "6 или больше букв. ОбÑзательно."
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/util.php:324
+msgid "About"
+msgstr "О проекте"
+
+#: ../actions/userauthorization.php:119
+msgid "Accept"
+msgstr "ПринÑÑ‚ÑŒ"
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+msgid "Add"
+msgstr "Добавить"
+
+#: ../actions/openidsettings.php:43 actions/openidsettings.php:44
+msgid "Add OpenID"
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39
+msgid "Address"
+msgstr "ÐдреÑ"
+
+#: ../actions/invite.php:131
+msgid "Addresses of friends to invite (one per line)"
+msgstr "ÐдреÑа друзей, которых Ñ‚Ñ‹ хочешь приглаÑить (по одному на Ñтрочку)"
+
+#: ../actions/showstream.php:273
+msgid "All subscriptions"
+msgstr "Ð’Ñе подпиÑки"
+
+#: ../actions/publicrss.php:64 actions/publicrss.php:50
+#, php-format
+msgid "All updates for %s"
+msgstr ""
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr ""
+
+#: ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr ""
+
+#: ../actions/deletenotice.php:54
+msgid "Are you sure you want to delete this notice?"
+msgstr "Ты точно хочешь удалить Ñту заметку?"
+
+#: ../actions/userauthorization.php:77 actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:104 ../actions/register.php:178
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Ð’ Ñледующий раз зайти автоматичеÑки. Ðе Ð´Ð»Ñ Ð¾Ð±Ñ‰ÐµÑтвенных компьютеров!"
+
+#: ../actions/profilesettings.php:65
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr "ÐвтоматичеÑки подпиÑыватьÑÑ Ð½Ð° вÑех, кто "
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+msgid "Avatar"
+msgstr "Ðватар"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Ðватар обновлён."
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"Ждём Ð¿Ð¾Ð´Ñ‚Ð²ÐµÑ€Ð¶Ð´ÐµÐ½Ð¸Ñ Ñтого адреÑа. Проверь "
+"Ñвой почтовый Ñщик (и папку Ð´Ð»Ñ Ñпама!), там будут дальнейшие инÑтрукции."
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/util.php:1318
+msgid "Before »"
+msgstr "Предыдущие »"
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+msgid "Bio"
+msgstr "ИнтереÑÑ‹"
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr ""
+
+#: ../lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Заметка не удалÑетÑÑ."
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+#: actions/profilesettings.php:404 actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr ""
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62
+msgid "Cancel"
+msgstr "Отменить"
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Изменить"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Изменить пароль"
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr "Изменить пароль"
+
+#: ../lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "ÐаÑтройки профилÑ"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+msgid "Confirm"
+msgstr "Подтвердить"
+
+#: ../actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr "Подтвердить адреÑ"
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245 actions/emailsettings.php:256
+#: actions/imsettings.php:230 actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Соединить"
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr ""
+
+#: ../lib/util.php:332
+msgid "Contact"
+msgstr "СвÑзь"
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:162 actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr ""
+
+#: ../actions/updateprofile.php:155 actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr ""
+
+#: ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:127 actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+#: actions/profilesettings.php:299 actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Создать"
+
+#: ../actions/finishopenidlogin.php:70 actions/finishopenidlogin.php:76
+msgid "Create a new user with this nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Создать новый учёт"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../actions/showstream.php:356
+msgid "Currently"
+msgstr "СейчаÑ"
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "Удалить заметку"
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Опиши ÑÐµÐ±Ñ Ð¸ Ñвои ÑƒÐ²Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ 140 букв"
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87
+msgid "Email"
+msgstr "Почта"
+
+#: ../actions/emailsettings.php:59
+msgid "Email Address"
+msgstr "Почтовый адреÑ"
+
+#: ../actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "ÐаÑтройка почты"
+
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr ""
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129
+msgid "Email addresses"
+msgstr "Почтовые адреÑа"
+
+#: ../actions/recoverpassword.php:191 actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:253 actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:240 actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr ""
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr ""
+
+#: ../lib/openid.php:226 lib/openid.php:226
+msgid "Error saving the profile."
+msgstr ""
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr ""
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr ""
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:80 actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr ""
+
+#: ../lib/util.php:326
+msgid "FAQ"
+msgstr "ЧÐВО"
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr ""
+
+#: ../actions/all.php:61 ../actions/allrss.php:64 actions/all.php:61
+#: actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr ""
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#: actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+msgid "Full name"
+msgstr "Полное имÑ"
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93
+msgid "Full name is too long (max 255 chars)."
+msgstr "Полное Ð¸Ð¼Ñ Ñлишком длинное (макÑимум 255 букв)."
+
+#: ../lib/util.php:322
+msgid "Help"
+msgstr "Помощь"
+
+#: ../lib/util.php:298
+msgid "Home"
+msgstr "Моё"
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+msgid "Homepage"
+msgstr "Страница"
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+#: actions/profilesettings.php:210 actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/imsettings.php:60 actions/imsettings.php:61
+msgid "IM Address"
+msgstr ""
+
+#: ../actions/imsettings.php:33 actions/imsettings.php:33
+msgid "IM Settings"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr ""
+
+#: ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr ""
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+
+#: ../actions/updateprofile.php:114 actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:98 actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:87 actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111 actions/finishopenidlogin.php:241
+#: actions/register.php:103 actions/register.php:121
+msgid "Invalid username or password."
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306
+msgid "Invite"
+msgstr "ПриглаÑить"
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr ""
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:57
+msgid "Language"
+msgstr "Язык"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+msgid "Location"
+msgstr "МеÑто жительÑтва"
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr ""
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310
+msgid "Login"
+msgstr "Вход"
+
+#: ../actions/openidlogin.php:44 actions/openidlogin.php:52
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr ""
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:308
+msgid "Logout"
+msgstr "Выйти"
+
+#: ../actions/register.php:166
+msgid "Longer name, preferably your \"real\" name"
+msgstr "Полное имÑ, лучше вÑего наÑтоÑщее"
+
+#: ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/showstream.php:300
+msgid "Member since"
+msgstr "РегиÑтрациÑ"
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+msgid "My text and files are available under "
+msgstr "Мои текÑÑ‚Ñ‹ и файлы находÑÑ‚ÑÑ Ð¿Ð¾Ð´ лицензией"
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr ""
+
+#: ../actions/newnotice.php:87 actions/newnotice.php:96
+msgid "New notice"
+msgstr ""
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+msgid "New password"
+msgstr "Ðовый пароль"
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr ""
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151
+msgid "Nickname"
+msgstr "Кличка"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+#: actions/finishopenidlogin.php:171 actions/profilesettings.php:203
+#: actions/register.php:74 actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr ""
+
+#: ../actions/recoverpassword.php:162 actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr ""
+
+#: ../actions/deletenotice.php:59
+msgid "No"
+msgstr "Ðет"
+
+#: ../actions/imsettings.php:156 actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr ""
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr ""
+
+#: ../actions/newnotice.php:44 actions/newmessage.php:53
+#: actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/userbyid.php:32 actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr ""
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:226 actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr ""
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr ""
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr ""
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr ""
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr ""
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr ""
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82 actions/all.php:34
+#: actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/imsettings.php:167 actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr ""
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr ""
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:120 actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:113 actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422
+msgid "Not found"
+msgstr "Ðе нашли"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27 actions/disfavor.php:29 actions/favor.php:30
+#: actions/finishaddopenid.php:29 actions/logout.php:33
+#: actions/newmessage.php:28 actions/newnotice.php:29 actions/subscribe.php:28
+#: actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:316
+msgid "Notices"
+msgstr "Заметки"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Старый пароль"
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314 lib/settingsaction.php:90
+#: lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr ""
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr ""
+
+#: ../actions/invite.php:135
+msgid "Optionally add a personal message to the invitation."
+msgstr "Дополнительно добавь к приглашению личное Ñообщение."
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+msgid "Password"
+msgstr "Пароль"
+
+#: ../actions/recoverpassword.php:288 actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: ../actions/recoverpassword.php:284 actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr ""
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+#: actions/profilesettings.php:408 actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:88
+#: actions/profilesettings.php:380 actions/register.php:98
+msgid "Passwords don't match."
+msgstr ""
+
+#: ../lib/searchaction.php:100
+msgid "People"
+msgstr "Люди"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:50
+msgid "Personal"
+msgstr "Свои"
+
+#: ../actions/invite.php:133
+msgid "Personal message"
+msgstr "Личное Ñообщение"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94
+msgid "Preferences"
+msgstr "ПредпочтениÑ"
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../actions/profilesettings.php:57
+msgid "Preferred language"
+msgstr "Любимый Ñзык"
+
+#: ../lib/util.php:328
+msgid "Privacy"
+msgstr "ЧаÑтное"
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60
+msgid "Profile"
+msgstr "Профиль"
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "ÐаÑтройки профилÑ"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "ÐŸÑƒÐ±Ð»Ð¸Ñ‡Ð½Ð°Ñ Ð´Ð¾Ñ€Ð¾Ð¶ÐºÐ°"
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/recoverpassword.php:166 actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:156 actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+msgid "Register"
+msgstr "Учёт"
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:103 ../actions/register.php:176
+msgid "Remember me"
+msgstr "Запомнить менÑ"
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr ""
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:55
+msgid "Replies"
+msgstr "Ответы"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#: actions/replies.php:47 actions/repliesrss.php:62 lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:183 actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:173 actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/register.php:156
+msgid "Same as password above. Required."
+msgstr "Тот же пароль что и Ñверху. ОбÑзательно."
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+msgid "Save"
+msgstr "Сохранить"
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300
+msgid "Search"
+msgstr "ПоиÑк"
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"ПоиÑк по Ñодержанию заметок на %%site.name%%. "
+"Между терминами Ñтавь пробелы. ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° Ñлова — 3 буквы."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"ПоиÑк людей на %%site.name%% по имени, "
+"жительÑтву или интереÑам. Между "
+"терминами Ñтавь пробелы. ÐœÐ¸Ð½Ð¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð»Ð¸Ð½Ð° Ñлова — 3 буквы."
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/invite.php:137 ../lib/util.php:1172
+msgid "Send"
+msgstr "ПоÑлать"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88
+msgid "Send me notices of new subscriptions through email."
+msgstr "УведомлÑÑ‚ÑŒ Ð¼ÐµÐ½Ñ Ð¾ новых подпиÑчиках по почте."
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../lib/util.php:304
+msgid "Settings"
+msgstr "ÐаÑтройки"
+
+#: ../actions/profilesettings.php:192
+msgid "Settings saved."
+msgstr "ÐаÑтройки Ñохранены."
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../lib/util.php:330
+msgid "Source"
+msgstr "ИÑходник"
+
+#: ../actions/showstream.php:296
+msgid "Statistics"
+msgstr "СтатиÑтика"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+#: actions/finishopenidlogin.php:188 actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197
+msgid "Subscribe"
+msgstr "ПодпиÑатьÑÑ"
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "ПодпиÑчики"
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "ПодпиÑка"
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301
+msgid "Tags"
+msgstr "Метки"
+
+#: ../lib/searchaction.php:104
+msgid "Text"
+msgstr "ТекÑÑ‚"
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:170 actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/imsettings.php:233 actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+#: actions/emailsettings.php:244 actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+#: actions/newnotice.php:49 actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:92 actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/profilesettings.php:63
+msgid "Timezone"
+msgstr "ЧаÑовой поÑÑ"
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "ÐÐ´Ñ€ÐµÑ Ñ‚Ð²Ð¾ÐµÐ¹ Ñтраницы, дневника или Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ Ð½Ð° другом портале"
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:276 actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:209
+msgid "Unsubscribe"
+msgstr "Ðннулировать подпиÑку"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Загрузить"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr "Ð’ Ñтом формулÑре Ñ‚Ñ‹ можешь приглаÑить друзей и коллег на наш ÑервиÑ."
+
+#: ../actions/register.php:159 ../actions/register.php:162
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Ðужна только Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ð¹, оÑведомлений и воÑÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ."
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82 actions/all.php:41
+#: actions/avatarbynickname.php:48 actions/foaf.php:47 actions/replies.php:41
+#: actions/showfavorites.php:41 actions/showstream.php:44
+#: actions/twitapiaccount.php:80 actions/twitapifavorites.php:68
+#: actions/twitapistatuses.php:235 actions/twitapistatuses.php:609
+#: actions/twitapiusers.php:87 lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../actions/twitapiusers.php:75
+msgid "User not found."
+msgstr "Пользователь не найден."
+
+#: ../actions/profilesettings.php:63
+msgid "What timezone are you normally in?"
+msgstr "Ð’ каком чаÑовом поÑÑе Ñ‚Ñ‹ обычно находишьÑÑ?"
+
+#: ../lib/util.php:1159
+#, php-format
+msgid "What's up, %s?"
+msgstr "Что Ñлышно, %s?"
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Где Ñ‚Ñ‹ находишьÑÑ, например «Город, Страна»"
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+msgid "Yes"
+msgstr "Да"
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:135
+msgid "You can create a new account to start posting notices."
+msgstr "Ты можешь вÑтать на учёт и пиÑать Ñвои заметки."
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+msgid "You can't register if you don't agree to the license."
+msgstr "Ты не можешь вÑтать на учёт, еÑли Ñ‚Ñ‹ не ÑоглаÑен Ñ Ð»Ð¸Ñ†ÐµÐ½Ð·Ð¸ÐµÐ¹."
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:943
+msgid "a few seconds ago"
+msgstr "пару Ñекунд назад"
+
+#: ../lib/util.php:955
+#, php-format
+msgid "about %d days ago"
+msgstr "%d дней назад"
+
+#: ../lib/util.php:951
+#, php-format
+msgid "about %d hours ago"
+msgstr "%d чаÑов назад"
+
+#: ../lib/util.php:947
+#, php-format
+msgid "about %d minutes ago"
+msgstr "%d минут назад"
+
+#: ../lib/util.php:959
+#, php-format
+msgid "about %d months ago"
+msgstr "%d меÑÑцев назад"
+
+#: ../lib/util.php:953
+msgid "about a day ago"
+msgstr "день назад"
+
+#: ../lib/util.php:945
+msgid "about a minute ago"
+msgstr "минуту назад"
+
+#: ../lib/util.php:957
+msgid "about a month ago"
+msgstr "меÑÑц назад"
+
+#: ../lib/util.php:961
+msgid "about a year ago"
+msgstr "год назад"
+
+#: ../lib/util.php:949
+msgid "about an hour ago"
+msgstr "Ñ‡Ð°Ñ Ð½Ð°Ð·Ð°Ð´"
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+msgid "delete"
+msgstr "удалить"
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117
+msgid "in reply to..."
+msgstr "в ответ на..."
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124
+msgid "reply"
+msgstr "ответить"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "повторить пароль Ñверху"
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: ../lib/util.php:1309
+msgid "« After"
+msgstr "« Следующие"
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/sv_SE/LC_MESSAGES/laconica.mo b/locale/sv_SE/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..0c11e0867
--- /dev/null
+++ b/locale/sv_SE/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/sv_SE/LC_MESSAGES/laconica.po b/locale/sv_SE/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..0b73e98fa
--- /dev/null
+++ b/locale/sv_SE/LC_MESSAGES/laconica.po
@@ -0,0 +1,2914 @@
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Sök i strömmen efter \"%s\""
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"förutom det här, som är privat: lösenord, epostadress, IM-adress, "
+"telefonnummer."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s lyssnar nu på dina meddelanden i %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s lyssnar nu på dina meddelanden i %1$s.\n"
+"\n"
+"\tHälsningar,\n"
+"%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s's status den %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s Publik Ström"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s med vänner"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** är en mikroblogg service för dig ifrån "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)"
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** är en mikroblogg service."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ". Användarna är tydligt markerade med sitt fulla namn eller smeknamn."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 små bokstäver eller nummer, inga punkter eller mellanslag"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "Minst 6 tecken"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "Minst 6 tecken och glöm inte bort det!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"En bekräftelsekod har skickats till den IM-adress som du angav. Du måste "
+"godkänna att %s får skicka meddelanden till dig."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Om"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Acceptera"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Lägg till"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Lägg till OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adress"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Alla prenumerationer"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "%s alla uppdateringar"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Alla uppdateringar som matchar söksträngen \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Redan inloggad."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Det finns redan en prenumeration!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Tillåt prenumeration."
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Logga in automatiskt i framtiden; Ej för publika datorer!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Användarbild"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Användarbilden uppdaterad."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Väntar bekräftelse på denna adress. Kontrollera ditt Jabber/GTalk konto "
+"för vidare instruktioner. (La du till %s i din vännerlista?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Tidigare »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Biografi"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Biografin är för lång (max 140 tecken)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Kan inte läsa användarbild URL '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Kan inte spara nya lösenordet."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Kan inte initiera OpenID objekt."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Kan inte normalisera det Jabber ID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Ändra"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Byt lösenord"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Bekräfta"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Bekräfta adress"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Verifikation avbruten"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Verifikation koden finns ej."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Anslut"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Anslut till existerande konto"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Kontakta"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Kan inte skapa OpenID formulär: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Kunde inte skicka vidare till servern: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Kunde inte spara informationen om användarbild"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Kunde inte spara informationen om den nya profilen"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Kunde inte bekräfta epost"
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Kunde inte konvertera förfrågan tokens till Access tokens."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Kunde inte skapa prenumeration."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Kunde inte radera epost bekräftelsen."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Kunde inte radera prenumerationen. "
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr "Kunde inte få en förfrågan token."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Kunde inte lägga till bekräftelsekoden."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Kunde inte lägga till ny prenumeration."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Kunde inte spara profil."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Kunde inte uppdatera användare."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Skapa"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Skapa en ny användare med det här smeknamnet"
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Skapa ett nytt konto"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Skapar ett nytt konto för OpenID som redan har en användare"
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Aktuell bekräftad Jabber/Gtalk-adress."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Just nu"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Databasfel för svar: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Berätta om dig själv och dina intressen inom 140 tecken"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Epost"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Epostadress"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Epostadressen finns redan."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Bekräfta epostadress"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Skriv in ett smeknamn eller en epostadress."
+
+#: ../actions/userauthorization.php:136 ../actions/userauthorization.php:137
+#: actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr "Felaktig bekräftelse av token"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Lyckades inte ansluta användaren till OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Lyckades inte ansluta användaren."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Fel uppstog när användarbild skulle läggas till"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Fel uppstog när nya profilen skulle läggas till"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Fel uppstog när inlägget skulle läggas till"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Fel uppstog när fjärrprofilen skulle läggas till"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Fel uppstog när adressen skulle bekräftas."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Fel uppstog när fjärrprofil skulle sparas"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Fel uppstog när profilen skulle sparas."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Fel uppstog när användaren skulle sparas."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Fel uppstog när användare skulle sparas."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Fel uppstog i användarens inställning"
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Fel uppstog vid uppdatering av profilen"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Fel uppstog under uppdatering av fjärranvändare"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Fel uppstog med bekräftelsekoden."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Nuvarande smeknamn"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "Frågor & svar"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Uppdatering av profilbild misslyckades."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Flöden för $s vänner"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Flöde för svar till %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Av säkerhetsskäl, var vänlig skriv in ditt användarnamn och lösenord "
+"innan du ändrar dina inställningar."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Ditt fulla namn."
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Ditt namn är för långt (max 255 tecken)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Hjälp"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Hem"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Hemsida"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Hemsidan har ingen giltig URL"
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM adress"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM inställningar"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Om du redan har ett konto, logga in med ditt användarnamn och lösenord "
+"för att koppla det till ditt OpenID"
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Om du vill lägga till OpenID till ditt konto, fyll i fältet nedan och "
+"tryck på \"Add\""
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Om du har förlorat eller glömt bort ditt lösenord så kan du få ett nytt "
+"skickat till din epost, kopplat till ditt konto."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Felaktigt, gammalt lösenord"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Felaktigt användarnamn eller lösenord."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Instruktioner om hur du återställer ditt lösenord har sänts till din "
+"e-postadress "
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Ogiltig användarbild URL '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Ogiltig hemsideadress '%s'"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Ogiltig licens URL '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Ogiltig innehåll i inlägget "
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Ogiltig inlägg uri"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Ogiltig inlägg url"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "Ogiltig profil URL '%s' "
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "Nåt är fel med profil URL (Format fel)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "Felaktig profil URL skickades åter av servern."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Felaktig storlek"
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Felaktigt användarnamn eller lösenord."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Det drivs med [Laconica](http://laconi.ca/) mikroblogging software, version "
+"%s, tillgängligt under [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID används redan utav en annan användare."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber eller GTalk adress liknande \"användare@exempel.se\". Först se till "
+"att lägga till %s i din vännerlista i IM klienten eller GTalk."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Plats"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Platse är för lång (max 255 tecken)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "Logga in"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Logga in med ett [OpenID](%%doc.openid%%) konto."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Logga in med ditt användarnamn och lösenord. Har du inget användarnamn "
+"ännu? [Registrera](%%action.register%%) ett nytt konto, eller testa "
+"[OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Logga ut"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Glömt bort lösenord?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Medlem sedan"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Mikroblogg av %s"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Min text och filer finns tillgängliga under"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Nytt användarnamn"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Nytt inlägg"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Nytt lösenord"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Nya lösenordet har blivit sparat. Du är nu även inloggad."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Smeknamn"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Användarnamnet används redan, försök med ett annat."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"Smeknamnet får endast innehålla små bokstäver eller siffror, inga "
+"mellanslag."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Inget giltigt smeknamn."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Smeknamnet på användaren du vill följa"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Smeknamn eller epost"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Inget Jabber ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Ingen rättighet förfrågan!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Ingen bekräftelsekod."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Inget innehåll!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Inget id."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Inget smeknamn lämnades ut av fjärrservern."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Inget användarnamn"
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Ingen väntande bekräftelse att avbryta."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Ingen profil URL lämnades ut av servern."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Ingen registrerad epost adress för den användaren."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Ingen begäran funnen!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Inget resultat"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Ingen storlek"
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Det existerar inget sådant OpenID"
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Inget sådant dokument."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Inget sådant inlägg."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Ingen sådan återställningskod. "
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Ingen sådan prenumeration"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Ingen sådan användare"
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Finns inget att visa!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Det är ingen kod för återställning."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Det är inget giltigt Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Det är inget giltigt OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Det är ingen giltig epost adress."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Det är inget giltigt användarnamn."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Det är ingen giltig profil URL (felaktig service)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Det är ingen giltig profil URL (ingen XRDS angiven)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Det är ingen giltig profil URL (ingen YADIS angiven)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Det verkar inte vara en bildfil, annars korrupt."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Inte tillstånd ännu."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Väntade mig inte detta svar!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Inte inloggad."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Ingen prenumerant!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Inlägg flöde för %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Inlägget har ingen profil"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Inlägg"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Gammalt lösenord"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "OpenID konto setup"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "OpenID skicka automatiskt"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID inloggning"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "OpenID bekäftelse ångrad."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID bekräftelse misslyckad: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID misslyckades: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID borttagen."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID inställningar"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Bitvis uppladdad."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Lösenord"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Lösenord och bekräftelse matchar inte."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Lösenordet måste vara 6 tecken eller fler."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Förfrågan om återställning av lösenord"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Lösenord är sparat."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Lösenorden matchar inte."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Sökning personer"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Personlig"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Kontrollera dessa detajer noga så att du verkligen vet att du vill "
+"prenumerera på denna användares inlägg. Om du inte frågade efter att "
+"prenumerera på någons inlägg, klicka på \"Cancel\""
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Posta ett inlägg när min Jabber/GTalk status ändras."
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Inställningar"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Inställningar sparade."
+
+#: ../lib/util.php:300 ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr "Sekretesspolicy"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Det var ett problem när inlägget sparades."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "Profil URL"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Profil inställningar"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Okänd profil"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Publik"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Publik ström"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Publik tidslinje"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Återställ"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Återställ lösenord"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Kod för återställning av okänd användare."
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Registrera"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Avvisa"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Kom ihåg mig"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Fjärrprofil utan motsvarande profil"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Fjärrprenumerera"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Ta bort"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Ta bort OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Tar du bort din OpenID så gör du det omöjligt att logga in! Om du "
+"behöver ta bort den, lägg till en annan OpenID först."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Svar"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Svarat på %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Återställ"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Återställ lösenord"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Samma som lösenordet ovan"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Spara"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Sök"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Sök strömflöde"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Sök efter innehåll i inlägg på %%site.name%%. Skilj söktermerna åt med "
+"mellanslag; dom måste vara minst tre tecken långa."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Sök efter personer på %%site.name%% efter deras namn, plats eller "
+"intressen. Skilj söktermerna åt med mellanslag; de måste vara minst tre "
+"tecken långa. "
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Skicka"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Skicka inlägg till mig via Jabber/GTalk."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Inställningar"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Inställningar sparade."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Någon annan använder redan denna OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Någonting konstigt inträffade."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Källa"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Statistik"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Sparade OpenID kunde inte hittas."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Prenumerera"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Prenumerant"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Prenumeration accepterad"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Prenumeration avvisad"
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Prenumerationer"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Systemfel när filen laddades upp."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Text sökning"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Det OpenID du angav tillhör inte dig."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Den adressen har redan blivit bekräftad en gång."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Den bekräftelsekoden är inte för dig!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Filen är för stor."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Det är redan din Jabber ID."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Det är inte ditt Jabber ID."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Det är fel IM adress."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "För långt. Maximalt 140 tecken"
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "Adressen \"%s\" har blivit bekräftad för ditt konto."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Adressen är borttagen."
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Prenumerationen har blivit bekräftad, men ingen URL har gått igenom. Kolla "
+"med sidans instruktioner hur du bekräftar en prenumeration. Din "
+"prenumerering token är:"
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Prenumerationen har blivit avvisad, men inga URL har gått igenom. Kolla med "
+"sidans instruktioner hur du avvisar en prenumeration."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Dessa personer är dom som lyssnar på %s inlägg."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Dessa personer är dom som lyssnar på dina inlägg."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Detta är personer och inlägg som %s lyssnar på."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Detta är personer och inlägg som du lyssnar på."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Denna bekräftelsekod är för gammal. Du får starta om på nytt igen."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Detta formulär skall automatiskt skicka själv. Om den inte gör det, "
+"klicka på skicka för att gå dit där du skapade ditt OpenID."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Detta är första gången du loggade in till %s så vi måste koppla sitt "
+"OpenID till ett lokalt konto. Du kan antingen skapa ett nytt konto eller med "
+"ett existerande konto, om du har något."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Denna sida är inte tillgänglig i den mediatyp du accepterat"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"För att prenumerera så kan du [logga in](%%action.login%%) eller "
+"[registrera](%%action.register%%) ett nytt konto. Om du redan har ett konto "
+"på en [kompatibel mikroblogg sida](%%doc.openmublog%%) fyll i din profils "
+"URL nedan."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL till din hemsida, blog eller profil på en annan sida."
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL till din profil på en annan kompatibel mikroblogg"
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/smssettings.php:135 actions/emailsettings.php:144
+#: actions/imsettings.php:118 actions/recoverpassword.php:39
+#: actions/smssettings.php:143 actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr "Oväntat utskick av formuläret."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Oväntad rensning av lösenord."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Okänd version av OMB protokollet."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Om inget annat anges, innehåll på denna sida skyddas utav copyright ifrån "
+"användarna och tillgängliga under"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Adresstypen känns inte igen %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Lämnar pren."
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "OMB versionen stöds inte"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Bildfilens format stödjs inte."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Ladda upp"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Ladda upp en ny \"avatar\" (användarbild) här. Du kan inte göra "
+"ändringar i bilden efter uppladdning, försök att se till att den är så "
+"fykantig som möjligt. Den måste följa licensvillkoren, använd en bild "
+"som du äger och som du vill dela med dig utav."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"Används endast för uppdateringar, annonsering och återställning av "
+"lösenord"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "Användaren som avlyssnas existerar inte."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "Användaren har ingen profil."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Användarens smeknamn"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Vad är på gång, %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Var du håller till, såsom \"Stad, Län, Land\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Fel filtyp för bild '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Fel bildstorlek för '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Du handhar redan denna OpenID!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Du är redan inloggad!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Du kan ändra ditt lösenord här. Välj ett bra!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Du kan skapa ett nytt konto och börja skriva inlägg."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Du kan ta bort en OpenID ifrån ditt konto genom att klicka på knappen "
+"\"Remove\""
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Du kan skicka och ta emot inlägg genom Jabber/GTalk [instant "
+"messages](%%doc.im%%). Konfigurera din adress och inställningar nedan. "
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Du kan uppdatera din personliga profil här så personer får veta mer om "
+"dig."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Du kan använda lokala prenumerationer!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Du kan inte registrera dig om du inte godkänner licensvillkor."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Du skickade inte oss den profilen"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Du är identifierad. Skriv ett nytt lösenord nedan."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "Din OpenID URL"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Ditt användarnamn på denna server eller registrerad epost adress."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) låter dig logga in på flera sidor med samma "
+"konto. Ställ in ditt OpenID konto härifrån."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "ett par sekunder sedan"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "för %d dagar sedan"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "för %d timmar sedan"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "för %d minuter sedan"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "för %d månader sedan"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "för en dag sedan"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "för nån minut sedan"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "för en månad sedan"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "för ett år sedan"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "för en timma sedan"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "svar till..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "svar"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "samma som lösenordet ovan"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Nyare"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr "från"
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr "%1$s / Uppdateringar med svar till %2$s"
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s har bjudit in dig till %2$s"
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s har bjudit in dig till %2$s (%3$s).\n"
+"\n"
+"%2$s är en mikroblogg service som låter dig via sidan hålla direktkontakt "
+"med människor du känner eller intresserar dig.\n"
+"\n"
+"Du kan även dela med dig utav nyheter om dig själv, dina tankar, eller "
+"ditt liv online som känner igen dig. Det är också perfekt för att möta "
+"nya personer som delar ditt intresse.\n"
+"\n"
+"%1$s sa:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Du kan se %1$s's profilsida på %2$s här:\n"
+"\n"
+"%5$s\n"
+"\n"
+"Om du vill prova på denna service, klicka på länken nedan för att "
+"acceptera denna inbjudan.\n"
+"\n"
+"%6$s\n"
+"\n"
+"Om inte, då kan du ignorera detta meddelande. Tack för att du tog dig\n"
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr "%1$s uppdateringar med svar till uppdatering från %2$s / %3$s."
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr "%s(%s)"
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "%s publika tidslinje"
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr "%s status"
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr "%s tidslinje"
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr "%s uppdateringar ifrån allihop!"
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(Du kommer få ett meddelande med email inom kort med instruktioner hur du "
+"bekräftar din emailadress)"
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"1-64 små bokstäver eller nummer, inga punkter eller mellanslag. Måste "
+"fyllas i."
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr "6 eller fler tecken. MÃ¥ste fyllas i."
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"En bekräftelsekod har skickats ut till email adressen du fyllde i. "
+"Kontrollera din inbox (och spamlådan!) efter kod och instruktioner hur du "
+"använder den."
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"En bekräftelsekod har skickats ut till telefonnumret du fyllde i. "
+"Kontrollera din inbox (och spamlådan!) efter kod och instruktioner hur du "
+"använder den."
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr "API metoden hittades inte!"
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr "API metoden är under uppbyggnad."
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr "Lägg till eller tabort OpenIDs"
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr "Adresser till vänner att bjuda in (en rad per adress)"
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr "Är du säker på att du vill tabort detta inlägg?"
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+"Automatisk prenummeration på den som prenumererar på mig. (Bäst för icke "
+"mänsklig användare) "
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"Väntar bekräftelse på denna adress. Kontrollera din inbox (och "
+"spamlådan!) efter meddelande om vidare instruktioner."
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "Väntar bekräftelse på detta telefonnummer. "
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Kan inte tabort detta inlägg."
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr "Kan inte normalisera den emailadressen"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr "Ändra email hantering"
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr "Ändra ditt lösenord"
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "Ändra dina profil inställningar"
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Bekräftelsekod"
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"Grattis, %s! Välkommen till %%%%site.name%%%%. Härifrån, kanske du "
+"vill...\n"
+"\n"
+"* Gå till [din profil](%s) och göra ditt första inlägg.\n"
+"* Lägg till en [Jabber/GTalk adress](%%%%action.imsettings%%%%) så du kan "
+"skicka inlägg med en IM klient.\n"
+"* [Sök efter personer](%%%%action.peoplesearch%%%%) som du kanske känner "
+"eller delar dina intressen. \n"
+"* Uppdatera din [profil inställning](%%%%action.profilesettings%%%%) för "
+"att berätta lite mer om dig själv för andra här. \n"
+"* Läs igenom [online dok](%%%%doc.help%%%%) efter funktioner som du kanske "
+"missat. \n"
+"\n"
+"Tack för att du registrerade dig och vi hoppas du kommer trivas med denna service."
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr "Kunde inte följa användaren: %s finns redan i din lista."
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr "Kunde inte följa användaren: Användaren kunde inte hittas."
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr "Kunde inte prenumerera på annat åt dig."
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr "Kunde inte prenumerera."
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr "Kunde inte uppdatera användaren med bekräftad emailadress."
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr "Kunde inte få fram status."
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr "Kunde inte uppdatera användaren för automatisk prenumeration."
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr "Kunde inte uppdatera användarens inställningar."
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "Nuvarande bekäftat SMS telefonnummer"
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr "Nuvarande bekräftade emailadress."
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr "DB error vid infog av hashtag: %s"
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "Tabort inlägg"
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr "Emailadress"
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "Email inställningar"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr "Emailadress såsom \"användare@exempel.se\""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr "Emailadresser"
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr "Fyll i koden du mottog i din telefon."
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "Feed för taggar %s"
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr "Sök innehåll i inlägg"
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr "Sök personer på denna sida"
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr "Jag vill posta inlägg via min email."
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr "IM"
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+"Om du glömt eller tappat bort ditt lösenord så kan du få ett nytt "
+"skickat till den emailadress du sparat till ditt konto."
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "Inkommande email"
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr "Inkommande emailadress borttagen."
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr "Ogiltig emailadress: %s"
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr "Inbjudan(ar) skickad"
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr "Inbjudan(ar) är skickade till följande personer:"
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr "Bjud in"
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr "Bjud in nya användare"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "Språk"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr "Språket är för långt(max 50 tecken)."
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr "Långt namn, förslagsvis ditt \"riktiga\" namn"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr "Skapa en ny emailadress för att posta till, avaktiverar den gamla"
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr "Ställ in hur du tar emot email ifrån %%site.name%%"
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+"Mobiloperatör för din telefon. Vet du nån operatör som kan taemot SMS "
+"över email som inte finns med i listan, skicka ett email till oss och tala "
+"det hit %s"
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr "Ny"
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr "Ny emailadress för att skicka till %s"
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr "Ny inkommande emailadress inlagd."
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr "Nej"
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr "Ingen operatör vald."
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr "Ingen kod är ifylld"
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr "Ingen emailadress."
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr "Ingen inkommande emailadress."
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr "Inget telefonnummer."
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr "Ingen status hittad med det ID"
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr "Ingen status med det ID hittades."
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr "Ingen användare med den emailadressen eller användarnamn."
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr "Inte registrerad användare."
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr "Ingen support för det formatet."
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr "Ingen giltig emailadress"
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr "Hittades inte"
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr "Inlägg sökning"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr "Inlägg taggade med %s"
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr "Om du vill, skriv ett personligt meddelande med inbjudan."
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr "Personer"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr "Personer sökning"
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr "Personligt meddelande"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr "Telefonnummer, inga punkter eller mellanslag, med landskod"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr "Språkval"
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Publicera ett MicroID för min Jabber/GTalk adress."
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr "Publicera ett MicroID för min emailadress."
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr "Tidigare taggar"
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr "Registrering är inte möjlig."
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr "Registreringen är genomförd"
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr "SMS"
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "SMS Telefonnummer"
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "SMS Inställningar"
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr "SMS Bekräftelse"
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr "Samma som lösenordet ovan. Måste fyllas i."
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr "Välj en operatör"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr "Skicka email till denna adress för att posta ett nya inlägg."
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr "Skicka meddelande till mig via email vid nya prenumerationer."
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+"Skicka inlägg till mig via SMS; Jag är införstådd att min operatör kan "
+"debitera mig."
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+"Skicka svar till mig via Jabber/GTalk från personer som inte jag "
+"prenumererar på."
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr "Visar dom populäraste taggarna ifrån den senaste veckan."
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr "Ledsen, men inga inkommande email är tillåtna."
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr "Ledsen, men det är inte din inkommande emailadress."
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr "Taggar"
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr "Text"
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr "Den emailadressen tillhör redan en annan användare."
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr "Det är redan din emailadress."
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr "Det är redan ditt telefonnummer."
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr "Det är inte din emailadress."
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr "Det är inte ditt telefonnummer."
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr "Det är fel nummer i bekräftelsen"
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr "Det numret tillhör en annan användare."
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr "Det är för långt. Max antal tecken är 255."
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+"Dom personerna är redan registrerade användare och du blev nu automatiskt "
+"prenumerant till dom:"
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr "Denna metod kräver antingen skicka eller tabort."
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr "Denna metod kräver skicka."
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr "Tidszon"
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr "Du har inte valt tidszon"
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr "Två användarid eller namn måste läggas till."
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr "Okänd funktion"
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr "Uppdateringar via SMS"
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr "Uppdateringar via instant messenger (IM)"
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr "Uppdateringar från %1$s och vänner på %2$s!"
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr "Uppdateringar från %1$s på %2$s!"
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr "Ladda upp en ny profilbild"
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+"Använd detta formulär för att bjuda in dina vänner och kollegor till "
+"denna sida."
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr "Användare hittades inte."
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr "Vilken tidszon befinner du dig normalt?"
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr "Ja"
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+"Du håller på att tabort inlägget permanent. När det väl är gjort kan "
+"du inte ångra dig."
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr "Du prenumererar redan på dessa användare:"
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr "Du är inte vän med den användaren."
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr "Du kan ta emot SMS meddelande via email från %%site.name%%."
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+"Du har en ny adress %1$s.\n"
+"\n"
+"Skicka email till %2$s för att göra ett nytt inlägg.\n"
+"\n"
+"Mer information får du på %3$s.\n"
+"\n"
+"Mvh,\n%4$s"
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr "Du kan inte tabort nån annan användares status."
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr "du måste vara inloggad för att kunna bjuda in andra användare till %s"
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+"du kommer bli meddelad när någon du bjudit in accepterar inbjudan och "
+"registrerar sig. Tack för att du hjälper oss växa!"
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr "Tabort"
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr "okänd fil typ"
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr "Det var något problem med din session. Försök igen, tack."
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr "Det inlägget är ingen favorit!"
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr "Kunde inte tabort favoriten."
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr "Favorisera"
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr "Skicka mig ett email när någon lägger till mitt inlägg som favorit."
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr "Skicka mig ett email när någon sänder ett privat meddelande."
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr "Detta inlägg är redan en favorit!"
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr "Kunde inte skapa favorit."
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr "Avfavorisera"
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr "%s favoriter"
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr "Feed över %s favoriter"
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr "Inbox för %s - sida %d"
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr "Inbox för %s"
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr "Detta är din inbox som innehåller dina privata meddelanden."
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+"%1$s har bjudit in dig till %2$s(%3$s).\n"
+"\n"
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr "Logga in automatiskt;"
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr "För säkerhets skull, skriv in dina"
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr "Logga in med ditt användarnamn och lösenord."
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr "Det är för långt. Max är 140 tecken. "
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr "Ingen mottagare tillagd."
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr "Du kan inte skicka meddelande till den användaren."
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr "Skicka inte meddelande till dig själv, viska lite tyst istället."
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr "Ingen sådan användare"
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr "Nytt meddelande"
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr "Inlägg utan matchande profil"
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr "[OpenID](%%doc.openid%%) låter dig logga in på många sidor"
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr "Om du vill lägga till en OpenID till ditt konto,"
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr "Att tabort ditt enda OpenID skulle göra det omöjligt att logga in!"
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr "Du kan tabort ett OpenID ifrån ditt konto"
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr "Outbox för %s - sida %d"
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr "Outbox för %s"
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr "Detta är din outbox som innehåller meddelanden som du skickat."
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+"Sök efter personer på %%site.name%% efter deras namn, plats eller "
+"intressen."
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr "Du kan uppdatera din personliga profil här"
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr "Användare utan matchande profil"
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr "Denna bekräftelsekod är för gammal."
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr "Om du har glömt eller tappat bort din"
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr "Du är identifierad. Skriv in"
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr "Ditt smeknamn på denna server,"
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr "Instruktioner för att återfå ditt lösenord"
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr "Nya lösenordet är sparat."
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr "Lösenordet måste vara minst 6 tecken."
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+"Gratulerar, %s! Välkommen till %%%%site.name%%%%. Härifrån vill du "
+"kanske..."
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr "(Du kommer inom kort få ett email med"
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr "För att prenumerera så kan du [login](%%action.login%%),"
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr "Feed för %s favoriter"
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr "Kunde inte ta emot favoritinläggen."
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr "Inget sådant meddelande."
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr "Endast den som skickat och mottagaren kan läsa detta meddelande."
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr "Meddelande till %1$s på %2$s"
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr "Meddelande från %1$s på %2$s"
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr "Skicka ett meddelande"
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr "Mobiloperatören för din telefon."
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr "Direktmeddelande till %s"
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr "Alla direktmeddelanden skickade till %s"
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr "Direktmeddelanden du skickat"
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr "Alla direktmeddelanden skickade ifrån %s"
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr "Ingen meddelande text!"
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr "Mottagaren kunde inte hittas."
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+"Kan inte skicka direktmeddelanden till användare som inte är dina "
+"vänner."
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr "%s / Favoriter från %s"
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr "%s uppdaterade favoriter av %s / %s."
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr "%s la till ditt inlägg som favorit"
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr "%1$s la just in ditt inlägg ifrån %2$s som en av deras favorit.\n\n"
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+"Lägg till ditt Twitter konto för att automatiskt skicka dina inlägg till "
+"Twitter,"
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr "Twitter inställningar"
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr "Twitter konto"
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr "Verifierat Twitter konto."
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr "Twitter användarnamn"
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr "Inga mellanslag tack."
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr "Twitter lösenord"
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr "Skicka automatiskt mina inlägg till Twitter."
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr "Skicka lokala \"@\" svar till Twitter."
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr "Prenumerera på mina Twitter vänner här."
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+"Användarnamn får bara innehålla nummer, stora och små bokstäver, och "
+"underscore(_). 15 tecken max."
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr "Kunde inte verifiera din Twitter!"
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr "Kunde inte mottaga konto information för \"%s\" Twitter."
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr "Kunde inte spara dina Twitter inställningar!"
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr "Twitter inställningar sparade."
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr "Det är inte ditt Twitter konto."
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr "Kunde inte tabort Twitter användaren."
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr "Twitterkontot borttaget."
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr "Kunde inte spara Twitter inställningar."
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr "%1$s (%2$s)"
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr "Fullt namn: %s"
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr "Plats: %s"
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr "Hemsida: %s"
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr "Om: %s"
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/te_IN/LC_MESSAGES/laconica.mo b/locale/te_IN/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..cfcb68259
--- /dev/null
+++ b/locale/te_IN/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/te_IN/LC_MESSAGES/laconica.po b/locale/te_IN/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..98ee16561
--- /dev/null
+++ b/locale/te_IN/LC_MESSAGES/laconica.po
@@ -0,0 +1,2870 @@
+# #-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#
+# Laconica Telugu Translation
+# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the Laconica package.
+# Veeven <veeven@gmail.com>, 2008.
+#
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Veeven <veeven@gmail.com>\n"
+"Language-Team: Telugu <l10n@etelugu.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "\"%s\"కై à°…à°¨à±à°µà±‡à°·à°£ వాహిని"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"à°ˆ అంతరంగిక భోగటà±à°Ÿà°¾ తపà±à°ª: "
+"సంకేతపదం, ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾, IM à°šà°¿à°°à±à°¨à°¾à°®à°¾, ఫోనౠనంబరà±."
+
+#: ../actions/subscribe.php:84 ../lib/mail.php:124 lib/mail.php:124
+#: lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr ""
+
+#: ../actions/subscribe.php:86 ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr ""
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s à°ªà±à°°à°œà°¾ వాహిని"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s మరియౠమితà±à°°à±à°²à±"
+
+#: ../lib/util.php:233 ../lib/util.php:257 lib/util.php:273
+#, php-format
+#, fuzzy
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"[%%site.broughtby%%](%%site.broughtbyurl%%) వారౠ"
+"అందిసà±à°¤à±à°¨à±à°¨ à°ˆ **%%site.name%%** అనేది మైకà±à°°à±‹ à°¬à±à°²à°¾à°—ింగౠసదà±à°ªà°¾à°¯à°‚."
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** అనేది మైకà±à°°à±‹ à°¬à±à°²à°¾à°—ింగౠసదà±à°ªà°¾à°¯à°‚."
+
+#: ../lib/util.php:250 ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 à°šà°¿à°¨à±à°¨à°¬à°¡à°¿ à°…à°•à±à°·à°°à°¾à°²à± లేదా అంకెలà±, విరామచిహà±à°¨à°¾à°²à± మరియౠఖాళీలౠతపà±à°ª"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 లేదా అంతకంటే à°Žà°•à±à°•à±à°µ à°…à°•à±à°·à°°à°¾à°²à±"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 లేదా అంతకంటే à°Žà°•à±à°•à±à°µ à°…à°•à±à°·à°°à°¾à°²à±, మరà±à°šà°¿à°ªà±‹à°•à°‚à°¡à°¿!"
+
+#: ../actions/imsettings.php:188 ../actions/imsettings.php:197
+#: actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "à°—à±à°°à°¿à°‚à°šà°¿"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "అంగీకరించà±"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "చేరà±à°šà±"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "ఓపెనà±à°à°¡à±€à°¨à°¿ చేరà±à°šà±"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "à°šà°¿à°°à±à°¨à°¾à°®à°¾"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "à°…à°¨à±à°¨à°¿ చందాలà±"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "%s కోసం à°…à°¨à±à°¨à°¿ తాజాకరణలూ"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "\"%s\"తో సరిపోలే à°…à°¨à±à°¨à°¿ తాజాకరణలà±"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "ఇపà±à°ªà°Ÿà°¿à°•à±‡ లోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚చారà±."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "ఇపà±à°ªà°Ÿà°¿à°•à±‡ చేరారà±!"
+
+#: ../actions/userauthorization.php:76 ../actions/userauthorization.php:77
+#: actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr ""
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "భవిషà±à°¯à°¤à±à°¤à±à°²à±‹ ఆటోమెటిగà±à°—à°¾ లోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚à°šà±; బయటి à°•à°‚à°ªà±à°¯à±‚à°°à±à°² కొరకౠకాదà±!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "అవతారం"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "అవతారానà±à°¨à°¿ తాజాకరించాం."
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "ఇంతకà±à°°à°¿à°¤à°‚ »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "à°¸à±à°µà°ªà°°à°¿à°šà°¯à°‚"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "à°¸à±à°µà°ªà°°à°¿à°šà°¯à°‚ చాలా పెదà±à°¦à°—à°¾ ఉంది (140 à°…à°•à±à°·à°°à°¾à°²à± à°—à°°à°¿à°·à±à° à°‚)."
+
+#: ../actions/updateprofile.php:118 ../actions/updateprofile.php:119
+#: actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr ""
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "కొతà±à°¤ సంకేతపదానà±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°²à±‡à°®à±."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "à°°à°¦à±à°¦à±à°šà±‡à°¯à°¿"
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr ""
+
+#: ../actions/imsettings.php:154 ../actions/imsettings.php:163
+#: actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr ""
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "మారà±à°šà±"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "సంకేతపదం మారà±à°šà±à°•à±‹à°‚à°¡à°¿"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "నిరà±à°¥à°¾à°°à°¿à°‚à°šà±"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "à°šà°¿à°°à±à°¨à°¾à°®à°¾à°¨à°¿ నిరà±à°§à°¾à°°à°¿à°‚à°šà±"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "నిరà±à°§à°¾à°°à°£ à°°à°¦à±à°¦à°¯à°¿à°‚ది."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "నిరà±à°§à°¾à°°à°£ సంకేతం కనబడలేదà±."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "à°…à°¨à±à°¸à°‚ధానించà±"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "ఇపà±à°ªà°Ÿà°¿à°•à±‡ ఉనà±à°¨ ఖాతాతో à°…à°¨à±à°¸à°‚ధానించండి"
+
+#: ../lib/util.php:304 ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr "సంపà±à°°à°¦à°¿à°‚à°šà±"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "ఓపెనà±à°à°¡à±€ ఫారమà±à°¨à± సృషà±à°Ÿà°¿à°‚చలేకపోయాం: %s"
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr ""
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "అవతారపౠసమాచారానà±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°²à±‡à°•à±à°¨à±à°¨à°¾à°‚"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "కొతà±à°¤ à°ªà±à°°à±Šà°«à±ˆà°²à± సమాచారానà±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°²à±‡à°•à±à°¨à±à°¨à°¾à°‚"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "ఈమెయిలà±à°¨à°¿ నిరà±à°§à°¾à°°à°¿à°‚చలేకà±à°¨à±à°¨à°¾à°‚."
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "చందాని సృషà±à°Ÿà°¿à°‚చలేకపోయాం."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "ఈమెయిలౠనిరà±à°§à°¾à°°à°£à°¨à°¿ తొలగించలేకà±à°¨à±à°¨à°¾à°‚."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "చందాని తొలగించలేకపోయాం."
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "నిరà±à°§à°¾à°°à°£ సంకేతానà±à°¨à°¿ చేరà±à°šà°²à±‡à°•à°ªà±‹à°¯à°¾à°‚."
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr ""
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "à°ªà±à°°à±Šà°«à±ˆà°²à±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°²à±‡à°•à±à°¨à±à°¨à°¾à°‚."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "వాడà±à°•à°°à°¿à°¨à°¿ తాజాకరించలేకà±à°¨à±à°¨à°¾à°‚."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "సృషà±à°Ÿà°¿à°‚à°šà±"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "à°ˆ పేరà±à°¤à±‹ కొతà±à°¤ వాడà±à°•à°°à°¿à°¨à°¿ సృషà±à°Ÿà°¿à°‚à°šà±"
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "కొతà±à°¤ ఖాతా సృషà±à°Ÿà°¿à°‚à°šà±à°•à±‹à°‚à°¡à°¿"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr ""
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "à°ªà±à°°à°¸à±à°¤à±à°¤à°‚ నిరà±à°§à°¾à°°à°¿à°‚à°šà°¿à°¨ Jabber/GTalk à°šà°¿à°°à±à°¨à°¾à°®à°¾"
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "à°ªà±à°°à°¸à±à°¤à±à°¤à°‚"
+
+#: ../lib/util.php:893 ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr ""
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "మీ à°—à±à°°à°¿à°‚à°šà°¿ మరియౠమీ ఆసకà±à°¤à±à°² à°—à±à°°à°¿à°‚à°šà°¿ 140 à°…à°•à±à°·à°°à°¾à°²à±à°²à±‹ చెపà±à°ªà°‚à°¡à°¿"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "ఈమెయిలà±"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾ ఇపà±à°ªà°Ÿà°¿à°•à±‡ ఉంది."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾ నిరà±à°§à°¾à°°à°£"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "పేరౠలేదా ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾ ఇవà±à°µà°‚à°¡à°¿."
+
+#: ../actions/userauthorization.php:136 ../actions/userauthorization.php:137
+#: actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "వాడà±à°•à°°à°¿à°¨à°¿ ఓపెనà±à°à°¡à±€à°•à°¿ à°…à°¨à±à°¸à°‚ధానించటంలో పొరపాటà±."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "వాడà±à°•à°°à°¿à°¨à°¿ à°…à°¨à±à°¸à°‚ధానించడంలో పొరపాటà±."
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+#, fuzzy
+msgid "Error inserting avatar"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"అవతారానà±à°¨à°¿ పెటà±à°Ÿà°¡à°‚లో "
+"పొరపాటà±\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "కొతà±à°¤ à°ªà±à°°à±Šà°ªà±ˆà°²à±à°¨à°¿ చేరà±à°šà°Ÿà°‚లో పొరపాటà±"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "సందేశానà±à°¨à°¿ చేరà±à°šà°Ÿà°‚లో పొరపాటà±"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "దూరపౠపà±à°°à±Šà°ªà±ˆà°²à±à°¨à°¿ చేరà±à°šà°Ÿà°‚లో పొరపాటà±"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "à°šà°¿à°°à±à°¨à°¾à°®à°¾ నిరà±à°§à°¾à°°à°£à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°¡à°‚లో పొరపాటà±."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "దూరపౠపà±à°°à±Šà°«à±ˆà°²à±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°¡à°‚లో పొరపాటà±"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "à°ªà±à°°à±Šà°«à±ˆà°²à±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°¡à°‚లో పొరపాటà±."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "వాడà±à°•à°°à°¿à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°¡à°‚లో పొరపాటà±."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "వాడà±à°•à°°à°¿à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°¡à°‚లో పొరపాటà±: సరికాదà±."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "à°ªà±à°°à±Šà°ªà±ˆà°²à±à°¨à°¿ తాజాకరించటంలో పొరపాటà±"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "దూరపౠపà±à°°à±Šà°ªà±ˆà°²à±à°¨à°¿ తాజాకరించటంలో పొరపాటà±"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "నిరà±à°§à°¾à°°à°£ సంకేతంలో పొరపాటà±."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "à°ªà±à°°à°¸à±à°¤à±à°¤ పేరà±"
+
+#: ../lib/util.php:298 ../lib/util.php:326 lib/util.php:342
+#, fuzzy
+msgid "FAQ"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"తవసం\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "అవతారపౠతాజాకరణ విఫలమైంది."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "%s యొకà±à°• మితà±à°°à±à°² ఫీడà±"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "%sà°•à°¿ వచà±à°šà°¿à°¨ à°¸à±à°ªà°‚దనల ఫీడà±"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"à°­à°¦à±à°°à°¤à°¾ కారణాల దృషà±à°Ÿà±à°¯à°¾, "
+"అమరికలౠమారà±à°šà±‡ à°®à±à°‚దౠమీ వాడà±à°•à°°à°¿ పేరà±à°¨à°¿ మరియౠసంకేతపదానà±à°¨à°¿ మరోసారి ఇవà±à°µà°‚à°¡à°¿."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "పూరà±à°¤à°¿ పేరà±"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "పూరà±à°¤à°¿ పేరౠచాలా పెదà±à°¦à°—à°¾ ఉంది (à°—à°°à°¿à°·à±à° à°‚à°—à°¾ 255 à°…à°•à±à°·à°°à°¾à°²à±)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "సహాయం"
+
+#: ../lib/util.php:274 ../lib/util.php:298 lib/util.php:314
+#, fuzzy
+msgid "Home"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"వాకిలి\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:49 ../actions/profilesettings.php:46
+#: ../actions/register.php:167 actions/profilesettings.php:79
+#: actions/register.php:181
+#, fuzzy
+msgid "Homepage"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"హోమౠపేజీ\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "హోమౠపేజీ URL సరైనది కాదà±."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM à°šà°¿à°°à±à°¨à°¾à°®à°¾"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM అమరికలà±"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"మీకౠఇపà±à°ªà°Ÿà°¿à°•à±‡ ఖాతా ఉంటే, మీ "
+"వాడà±à°•à°°à°¿à°ªà±‡à°°à± మరియౠసంకేతపదంతో లోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚à°šà°¿ మీ ఓపెనà±à°à°¡à±€à°¨à°¿ మీ ఖాతాకి జతచేసà±à°•à±‹à°‚à°¡à°¿."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"మీరౠమీ సంకేతపదానà±à°¨à°¿ "
+"మరà±à°šà°¿à°ªà±‹à°¤à±‡, మీ ఖాతాలో à°­à°¦à±à°°à°ªà°°à°šà°¿à°¨ ఈమెయిలà±à°•à°¿ కొతà±à°¤à°¦à°¾à°¨à±à°¨à°¿ పంపించà±à°•à±‹à°µà°šà±à°šà±."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "పాత సంకేతపదం తపà±à°ªà±"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "వాడà±à°•à°°à°¿à°ªà±‡à°°à± లేదా సంకేతపదం తపà±à°ªà±."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"మీ సంకేతపదానà±à°¨à°¿ తిరిగి "
+"పొందడానికై అవసరమైన సూచనలని మీ ఖాతాతో నమోదైన ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾à°•à°¿ పంపించాం."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "'%s' అనే అవతారపౠURL తపà±à°ªà±"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "'%s' అనే హోమౠపేజీ సరైనదికాదà±"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "తపà±à°ªà±à°¡à± లైసెనà±à°¸à± URL '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "సందేశపౠవిషయం సరైనది కాదà±"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "సందేశపౠURI తపà±à°ªà±"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "సందేశపౠURL తపà±à°ªà±"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "à°ªà±à°°à±Šà°ªà±ˆà°²à± URL '%s' తపà±à°ªà±."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "à°ªà±à°°à±Šà°ªà±ˆà°²à± URL తపà±à°ªà± (చెడౠఫారà±à°®à°¾à°Ÿà±)"
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "తపà±à°ªà±à°¡à± పరిమాణం."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "వాడà±à°•à°°à°¿à°ªà±‡à°°à± లేదా సంకేతపదం తపà±à°ªà±."
+
+#: ../lib/util.php:237 ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID ఇపà±à°ªà°Ÿà°¿à°•à±‡ వేరొకరికి ఉంది."
+
+#: ../actions/imsettings.php:63 ../actions/imsettings.php:62
+#: actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "à°ªà±à°°à°¾à°‚తం"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "à°ªà±à°°à°¾à°‚తం పేరౠమరీ పెదà±à°¦à°—à°¾ ఉంది (255 à°…à°•à±à°·à°°à°¾à°²à± à°—à°°à°¿à°·à±à° à°‚)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "à°ªà±à°°à°µà±‡à°¶à°¿à°‚à°šà°‚à°¡à°¿"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "[ఓపెనà±à°à°¡à±€](%%doc.openid%%) ఖాతాతో à°ªà±à°°à°µà±‡à°¶à°¿à°‚à°šà°‚à°¡à°¿."
+
+#: ../actions/login.php:122 ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "నిషà±à°•à±à°°à°®à°¿à°‚à°šà±"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "మీ సంకేతపదం మరà±à°šà°¿à°ªà±‹à°¯à°¾à°°à°¾?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "సభà±à°¯à±à°²à±ˆà°¨ తేదీ"
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+#, fuzzy
+msgid "Microblog by %s"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"%s యొకà±à°• మైకà±à°°à±‹à°¬à±à°²à°¾à°—à±\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+#: ../actions/register.php:188 actions/finishopenidlogin.php:85
+#: actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "కొతà±à°¤ పేరà±"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "కొతà±à°¤ సందేశం"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "కొతà±à°¤ సంకేతపదం"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "మీ కొతà±à°¤ సంకేతపదం à°­à°¦à±à°°à°®à±ˆà°‚ది. మీరౠఇపà±à°ªà±à°¡à± లోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚చారà±."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "పేరà±"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "à°† పేరà±à°¨à°¿ ఇపà±à°ªà°Ÿà°¿à°•à±‡ వాడà±à°¤à±à°¨à±à°¨à°¾à°°à±. మరోటి à°ªà±à°°à°¯à°¤à±à°¨à°¿à°‚à°šà°‚à°¡à°¿."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+#: ../actions/profilesettings.php:88 ../actions/register.php:67
+#: ../actions/updateprofile.php:77 actions/finishopenidlogin.php:171
+#: actions/profilesettings.php:203 actions/register.php:74
+#: actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "à°† పేరà±à°¨à°¿ à°…à°¨à±à°®à°¤à°¿à°‚à°šà°®à±."
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr ""
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "పేరౠలేదా ఈమెయిలà±"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Jabber ID లేదà±."
+
+#: ../actions/userauthorization.php:128 ../actions/userauthorization.php:129
+#: actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr ""
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "నిరà±à°§à°¾à°°à°£ సంకేతం లేదà±."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "విషయం లేదà±!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "à°à°¡à±€ లేదà±."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "దూరపౠసరà±à°µà°°à± పేరà±à°¨à°¿ ఇవà±à°µà°²à±‡à°¦à±."
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+#, fuzzy
+msgid "No nickname."
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"పేరౠలేదà±.\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "à°°à°¦à±à°¦à±à°šà±‡à°¯à°¡à°¾à°¨à°¿à°•à°¿ వేచివà±à°¨à±à°¨ నిరà±à°§à°¾à°°à°£à°²à±‡à°®à±€ లేవà±."
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "à°ˆ వాడà±à°•à°°à°¿à°•à±ˆ నమోదైన ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾à°²à± à°à°®à±€ లేవà±."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "à°…à°­à±à°¯à°°à±à°¥à°¨à°²à±‡à°®à±€ కనబడలేదà±!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "ఫలితాలేమీ లేవà±"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "పరిమాణం లేదà±."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "à°…à°Ÿà±à°µà°‚à°Ÿà°¿ ఓపెనà±à°à°¡à±€ లేదà±."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "à°…à°Ÿà±à°µà°‚à°Ÿà°¿ పతà±à°°à°®à±‡à°®à±€ లేదà±."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "à°…à°Ÿà±à°µà°‚à°Ÿà°¿ సందేశమేమీ లేదà±."
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr ""
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr ""
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "à°…à°Ÿà±à°µà°‚à°Ÿà°¿ వాడà±à°•à°°à°¿ లేరà±."
+
+#: ../lib/gallery.php:76 ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr ""
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "సరైన Jabber à°à°¡à±€ కాదà±"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "సరైన ఓపెనà±à°à°¡à±€ కాదà±."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "సరైన ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾ కాదà±:"
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "సరైన పేరౠకాదà±."
+
+#: ../actions/remotesubscribe.php:118 ../actions/remotesubscribe.php:120
+#: actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:111 ../actions/remotesubscribe.php:113
+#: actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "బొమà±à°® కాదౠలేదా పాడైపోయిన ఫైలà±."
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "లోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚చలేదà±."
+
+#: ../actions/unsubscribe.php:43 ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "%s యొకà±à°• సందేశమà±à°² ఫీడà±"
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "సందేశాలà±"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "పాత సంకేతపదం"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "ఓపెనà±à°à°¡à±€"
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+#, fuzzy
+msgid "OpenID Account Setup"
+msgstr ""
+"#-#-#-#-# laconica.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"ఓపెనà±à°à°¡à±€ ఖాతా అమరà±à°ªà±\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "ఓపెనà±à°à°¡à±€ à°ªà±à°°à°µà±‡à°¶à°‚"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "ఓపెనà±à°à°¡à±€ URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "ఓపెనà±à°à°¡à±€ అధీకరణ à°°à°¦à±à°¦à± చేయబడింది."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "ఓపెనà±à°à°¡à±€ అధీకరణ విఫలమైంది: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "ఓపెనà±à°à°¡à±€ వైఫలà±à°¯à°‚: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "ఓపెనà±à°à°¡à±€à°¨à°¿ తొలగించాం."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "ఓపెనà±à°à°¡à±€ అమరికలà±"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "పాకà±à°·à°¿à°• à°Žà°—à±à°®à°¤à°¿."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "సంకేతపదం"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "సంకేతపదం మరియౠనిరà±à°§à°¾à°°à°£ సరిపోలేదà±."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "సంకేతపదం 6 లేదా అంతకంటే à°Žà°•à±à°•à°µ à°…à°•à±à°·à°°à°¾à°²à±à°‚డాలి."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "సంకేతపదం à°­à°¦à±à°°à°®à°¯à±à°¯à°¿à°‚ది."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "సంకేతపదాలౠసరిపోలలేదà±."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "à°µà±à°¯à°•à±à°¤à±à°² à°…à°¨à±à°µà±‡à°·à°£"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "à°µà±à°¯à°•à±à°¤à°¿à°—à°¤"
+
+#: ../actions/userauthorization.php:77 ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:74 ../actions/imsettings.php:73
+#: actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "à°…à°­à°¿à°°à±à°šà±à°²à±"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "à°…à°­à°¿à°°à±à°šà±à°²à± à°­à°¦à±à°°à°®à°¯à±à°¯à°¾à°¯à°¿."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "అంతరంగికత"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "సందేశానà±à°¨à°¿ à°­à°¦à±à°°à°ªà°°à°šà°¡à°‚లో పొరపాటà±."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "à°ªà±à°°à±Šà°«à±ˆà°²à±"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "à°ªà±à°°à±Šà°«à±ˆà°²à± URL"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "à°«à±à°°à±Šà°«à±ˆà°²à± అమరికలà±"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "à°…à°œà±à°žà°¾à°¤ à°ªà±à°°à±Šà°«à±ˆà°²à±"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "à°ªà±à°°à°œà°¾"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "à°ªà±à°°à°œà°¾ వాహిని ఫీడà±"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "à°ªà±à°°à°œà°¾ కాలరేఖ"
+
+#: ../actions/recoverpassword.php:151 ../actions/recoverpassword.php:166
+#: actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:141 ../actions/recoverpassword.php:156
+#: actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "నమోదà±"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "తిరసà±à°•à°°à°¿à°‚à°šà±"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "ననà±à°¨à± à°—à±à°°à±à°¤à±à°‚à°šà±à°•à±‹"
+
+#: ../actions/updateprofile.php:69 ../actions/updateprofile.php:70
+#: actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "తొలగించà±"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "ఓపెనà±à°à°¡à±€à°¨à°¿ తొలగించండి"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"మీకà±à°¨à±à°¨ ఒకే ఓపెనà±à°à°¡à±€à°¨à°¿ "
+"తొలగిసà±à°¤à±‡ మీరౠలోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚à°šà°¡à°‚ అసాధà±à°¯à°®à°µà°µà°šà±à°šà±. మీరౠదానà±à°¨à°¿ తొలగించాలనà±à°•à±à°‚టే, à°®à±à°‚దౠవేరే ఓపెనà±à°à°¡à±€à°¨à°¿ చేరà±à°šà°‚à°¡à°¿."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "à°¸à±à°ªà°‚దనలà±"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "%sà°•à°¿ à°¸à±à°ªà°‚దనలà±"
+
+#: ../actions/recoverpassword.php:168 ../actions/recoverpassword.php:183
+#: actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:158 ../actions/recoverpassword.php:173
+#: actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "పై సంకేతపదం వలెనే"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "à°­à°¦à±à°°à°ªà°°à°šà±"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "వెతà±à°•à±"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "à°…à°¨à±à°µà±‡à°·à°£ వాహిని ఫీడà±"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"%%site.name%%లోని సందేశాలనౠవెతకండి. "
+"à°…à°¨à±à°µà±‡à°·à°£ పదాలనౠఖాళీలతో వేరà±à°šà±‡à°¯à°‚à°¡à°¿; à°’à°•à±à°•à±‹ పదంలో 3 లేదా అంతకంటే à°Žà°•à±à°•à±à°µ à°…à°•à±à°·à°°à°¾à°²à± ఉండాలి."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"%%site.name%%లో à°µà±à°¯à°•à±à°¤à±à°²à°¨à± వారి "
+"పేరà±, à°ªà±à°°à°¾à°‚తం, లేదా "
+"ఆసకà±à°¤à±à°²à°¨à± బటà±à°Ÿà°¿ వెతకండి. à°…à°¨à±à°µà±‡à°·à°¿à°‚చే పదాలనౠఖాళీలతో వేరà±à°šà±‡à°¯à°‚à°¡à°¿; à°’à°•à±à°•à±‹ పదంలో 3 లేదా అంతకంటే à°Žà°•à±à°•à±à°µ à°…à°•à±à°·à°°à°¾à°²à± ఉండాలి."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "పంపించà±"
+
+#: ../actions/imsettings.php:71 ../actions/imsettings.php:70
+#: actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "అమరికలà±"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "అమరికలౠభదà±à°°à°®à°¯à±à°¯à°¾à°¯à°¿."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "à°ˆ ఓపెనà±à°à°¡à±€à°¨à°¿ ఇపà±à°ªà°Ÿà°¿à°•à±‡ ఎవరో వాడà±à°¤à±à°¨à±à°¨à°¾à°°à±."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "జరగకూడనిదేదో జరిగింది."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "మూలమà±"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "గణాంకాలà±"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+#: ../actions/finishopenidlogin.php:246 actions/finishopenidlogin.php:188
+#: actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "చందాదారà±à°²à±"
+
+#: ../actions/userauthorization.php:309 ../actions/userauthorization.php:310
+#: actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "చందాని తిరసà±à°•à°°à°¿à°‚చారà±."
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "చందాలà±"
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "పాఠà±à°¯ à°…à°¨à±à°µà±‡à°·à°£"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "à°† ఓపెనà±à°à°¡à±€ మీది కాదà±."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "à°† à°šà°¿à°°à±à°¨à°¾à°®à°¾ ఇపà±à°ªà°Ÿà°¿à°•à±‡ నిరà±à°§à°¾à°°à°¿à°¤à°®à±ˆà°‚ది."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "à°† నిరà±à°§à°¾à°°à°£à°¾ సంకేతం మీది కాదà±!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "à°† ఫైలౠచాలా పెదà±à°¦à°—à°¾ ఉంది."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "à°ˆ Jabber ID మీకౠఇపà±à°ªà°Ÿà°¿à°•à±‡ ఉంది"
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "ఇది మీ Jabber ID కాదà±"
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "à°† IM à°šà°¿à°°à±à°¨à°¾à°®à°¾ సరైనది కాదà±."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "ఇది చాలా పొడవà±à°‚ది. à°—à°°à°¿à°·à±à°  సందేశ పరిమాణం 140 à°…à°•à±à°·à°°à°¾à°²à±."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "\"%s\" అనే à°šà°¿à°°à±à°¨à°¾à°®à°¾ మీ ఖాతాకి నిరà±à°§à°¾à°°à°¿à°¤à°®à±ˆà°‚ది."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "à°† à°šà°¿à°°à±à°¨à°¾à°®à°¾à°¨à°¿ తొలగించాం."
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "à°ˆ నిరà±à°§à°¾à°°à°£ సంకేతం చాలా పాతది. మళà±à°³à±€ మొదలà±à°ªà±†à°Ÿà±à°Ÿà°‚à°¡à°¿."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../lib/util.php:147 ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "మీ హోమౠపేజీ, à°¬à±à°²à°¾à°—à±, లేదా వేరే సేటà±à°²à±‹à°¨à°¿ మీ à°ªà±à°°à±Šà°«à±ˆà°²à± యొకà±à°• à°šà°¿à°°à±à°¨à°¾à°®à°¾"
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/smssettings.php:135 actions/emailsettings.php:144
+#: actions/imsettings.php:118 actions/recoverpassword.php:39
+#: actions/smssettings.php:143 actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:237 ../actions/recoverpassword.php:276
+#: actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:245 ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "à°—à±à°°à±à°¤à±à°¤à±†à°²à°¿à°¯à°¨à°¿ à°šà°¿à°°à±à°¨à°¾à°®à°¾ à°°à°•à°‚ %s"
+
+#: ../actions/showstream.php:193 ../actions/showstream.php:209
+#: actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+#: ../actions/updateprofile.php:45 actions/postnotice.php:45
+#: actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "à°Žà°—à±à°®à°¤à°¿à°‚à°šà±"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"తాజా విశేషాలà±, à°ªà±à°°à°•à°Ÿà°¨à°²à±, "
+"మరియౠసంకేతపదం పోయినపà±à°ªà±à°¡à± మాతà±à°°à°®à±‡ ఉపయోగిసà±à°¤à°¾à°‚."
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "వాడà±à°•à°°à°¿à°•à°¿ à°ªà±à°°à±Šà°«à±ˆà°²à± లేదà±."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "వాడà±à°•à°°à°¿ పేరà±"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "%s, సంగతà±à°²à±‡à°®à°¿à°Ÿà°¿?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "మీరౠఎకà±à°•à°¡ à°¨à±à°‚à°¡à°¿, \"నగరం, రాషà±à°Ÿà±à°°à°‚ (లేదా à°ªà±à°°à°¾à°‚తం), దేశం\""
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "'%s' కొరకౠతపà±à°ªà±à°¡à± బొమà±à°® à°°à°•à°‚"
+
+#: ../actions/updateprofile.php:122 ../actions/updateprofile.php:123
+#: actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "à°ˆ ఓపెనà±à°à°¡à±€ మీకౠఇపà±à°ªà°Ÿà°¿à°•à±‡ ఉంది!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "మీరౠఇపà±à°ªà°Ÿà°¿à°•à±‡ లోనికి à°ªà±à°°à°µà±‡à°¶à°¿à°‚చారà±!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "మీ సంకేతపదానà±à°¨à°¿ ఇకà±à°•à°¡ మారà±à°šà±à°•à±‹à°µà°šà±à°šà±. మంచిది à°Žà°‚à°šà±à°•à±‹à°‚à°¡à°¿!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "సందేశాలౠపంపించడానికి మీరౠకొతà±à°¤ ఖాతా సృషà±à°Ÿà°¿à°‚à°šà±à°•à±‹à°µà°šà±à°šà±."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"\"తొలగించà±\" అనే బొతà±à°¤à°‚పై "
+"నొకà±à°•à°¡à°‚ à°¦à±à°µà°¾à°°à°¾ à°’à°• ఓపెనà±à°à°¡à±€à°¨à°¿ మీ ఖాతా à°¨à±à°‚à°¡à°¿ తొలగించà±à°•à±‹à°µà°šà±à°šà±."
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+#: ../actions/register.php:61 actions/finishopenidlogin.php:38
+#: actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:62 ../actions/updateprofile.php:63
+#: actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "మిమà±à°®à°²à±à°¨à°¿ à°—à±à°°à±à°¤à°¿à°‚చాం. కొతà±à°¤ సంకేతపదానà±à°¨à°¿ à°•à±à°°à°¿à°‚à°¦ ఇవà±à°µà°‚à°¡à°¿."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "మీ ఓపెనà±à°à°¡à±€ URL"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "à°ˆ సరà±à°µà°°à±à°²à±‹ మీ పేరà±, లేదా నమౌదైన మీ ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"ఒకే వాడà±à°•à°°à°¿ ఖాతాతో చాలా "
+"సైటà±à°²à°²à±‹à°¨à°¿à°•à°¿ à°ªà±à°°à°µà±‡à°¶à°¿à°‚చే వీలà±à°¨à°¿ [ఓపెనà±à°à°¡à±€](%%doc.openid%%) à°•à°²à±à°ªà°¿à°¸à±à°¤à±à°‚ది. మీ ఓపెనà±à°à°¡à±€à°²à°¨à± ఇకà±à°•à°¡ సంభాళించà±à°•à±‹à°‚à°¡à°¿."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "కొనà±à°¨à°¿ à°•à±à°·à°£à°¾à°² à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "%d రోజà±à°² à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "%d à°—à°‚à°Ÿà°² à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "%d నిమిషాల à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "%d నెలల à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "à°“ రోజౠకà±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "à°“ నిమిషం à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "à°“ నెల à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "à°’à°• సంవతà±à°¸à°°à°‚ à°•à±à°°à°¿à°¤à°‚"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "à°’à°• à°—à°‚à°Ÿ à°•à±à°°à°¿à°¤à°‚"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "దీనికి à°¸à±à°ªà°‚దనగా..."
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "à°¸à±à°ªà°‚దించà±"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "పై సంకేతపదం వలెనే"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« తరà±à°µà°¾à°¤"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr "ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾"
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "ఈమెయిలౠఅమరికలà±"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr "ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾, \"username@example.org\" వలె"
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr "ఆహà±à°µà°¾à°¨à°¿à°‚à°šà±"
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "భాష"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr "పాఠà±à°¯à°‚"
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr "à°† ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾ ఇపà±à°ªà°Ÿà±‡à°•à±‡ ఇతర వాడà±à°•à°°à°¿à°•à°¿ సంబంధించినది."
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr "అది ఇపà±à°ªà°Ÿà°¿à°•à±‡ మీ ఈమెయిలౠచిరà±à°¨à°¾à°®à°¾."
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr "à°…à°µà±à°¨à±"
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr "à°Ÿà±à°µà°¿à°Ÿà±à°Ÿà°°à± అమరికలà±"
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr "à°Ÿà±à°µà°¿à°Ÿà±à°Ÿà°°à± ఖాతా"
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr "పూరà±à°¤à°¿à°ªà±‡à°°à±: %s"
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr "à°ªà±à°°à°¾à°‚తం: %s"
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr "à°—à±à°°à°¿à°‚à°šà°¿: %s"
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr "వాడà±à°•à°°à°¿"
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr "మీకౠవచà±à°šà°¿à°¨ సందేశాలà±"
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr "మీరౠపంపిన సందేశాలà±"
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/tr_TR/LC_MESSAGES/laconica.mo b/locale/tr_TR/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..f825a89cb
--- /dev/null
+++ b/locale/tr_TR/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/tr_TR/LC_MESSAGES/laconica.po b/locale/tr_TR/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..275ff72b6
--- /dev/null
+++ b/locale/tr_TR/LC_MESSAGES/laconica.po
@@ -0,0 +1,2842 @@
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.po (Laconi.ca Türkçe) #-#-#-#-#\n"
+"Project-Id-Version: Laconi.ca Türkçe\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-14 21:07+1200\n"
+"PO-Revision-Date: 2008-07-29 12:33+0100\n"
+"Last-Translator: Kemal Yaylali <kemal.yaylali@gmail.com>\n"
+"Language-Team: nedurum.com\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Turkish\n"
+"X-Poedit-Country: TURKEY\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr " \"%s\" için arama sonuçları"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"bu özel veriler haricinde: parola, eposta adresi, IM adresi, telefon "
+"numarası."
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s %2$s'da durumunuzu takip ediyor"
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s %2$s durum mesajlarınızı takip etmeye başladı.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Kendisini durumsuz bırakmayın!,\n%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s'in %2$s'deki durum mesajları "
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s Genel Durum Mesajları"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s ve arkadaşları"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** [%%site.broughtby%%](%%site.broughtbyurl%%)\" tarafından "
+"hazırlanan anında mesajlaşma ağıdır. "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** bir aninda mesajlaşma sosyal ağıdır."
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "Katkı sunanlar tam ad veya takma ad ile atfedilmelidir."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+"1-64 küçük harf veya rakam, noktalama işaretlerine ve boşluklara izin "
+"verilmez"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6 veya daha fazla karakter"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "Unutmayın, 6 veya daha fazla karakter"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Eklemiş olduğunuz IM adresine bir onay kodu gönderildi. %s tarafından "
+"size mesaj yollanabilmesi için onaylamanız gerekmektedir."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Hakkında"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Kabul et"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Ekle"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "OpenID ekle"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Adres"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Bütün abonelikler"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "%s için bütün güncellemeler"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "\"%s\" kelimesinin geçtiği tüm güncellemeler"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Zaten giriş yapılmış."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Zaten abone olunmuÅŸ!."
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Takip isteÄŸini onayla"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr ""
+"Gelecekte kendiliğinden giriş yap, paylaşılan bilgisayarlar için "
+"deÄŸildir!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Avatar"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Avatar güncellendi."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Bu adresten onay bekleniyor. Jabber/Google Talk hesabınızı ayrıntılı "
+"bilgi içeren mesajı almak için kontrol edin. (%s'u arkadaş listenize "
+"eklediniz mi?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "Önce »"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Hakkında"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Hakkında bölümü çok uzun (azm 140 karakter)."
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Avatar URLi '%s' okunamıyor"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Yeni parola kaydedilemedi."
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Ä°ptal et"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "OpenID iÅŸlemlerinde hata oluÅŸtu."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Jabber iÅŸlemlerinde bir hata oluÅŸtu."
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "DeÄŸiÅŸtir"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Parolayı değiştir"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Onayla"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Adresi Onayla"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Onaylama iptal edildi."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Onay kodu bulunamadı."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "BaÄŸlan"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Varolan hesaba baÄŸlan"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Ä°letiÅŸim"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "OpenID formu yaratılamadı: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Sunucuya yönlendirme yapılamadı: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Avatar bilgisi kaydedilemedi"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Yeni profil bilgisi kaydedilemedi"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Eposta onaylanamadı."
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr ""
+
+# Note: Bu federation ozelligi nasilsa simdi kullanilmayacak
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Abonelik oluşturulamadı."
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Eposta onayı silinemedi."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Abonelik silinemedi."
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr ""
+
+# Note: Bu federation ozelligi nasilsa simdi kullanilmayacak
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Onay kodu eklenemedi."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Yeni abonelik eklenemedi."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Profil kaydedilemedi."
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Kullanıcı güncellenemedi."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Yarat"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Bu takma ad ile yeni bir kullanıcı oluştur."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Yeni hesap oluÅŸtur"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "OpenID kullanıcısı için hesap oluşturuluyor"
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Onaylanmış Jabber/Gtalk adresi."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Åžu anda"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Cevap eklenirken veritabanı hatası: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Kendinizi ve ilgi alanlarınızı 140 karakter ile anlatın"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Eposta"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Eposta adresi"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Eposta adresi zaten var."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Eposta adresi onayı"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Bir takma ad veya eposta adresi girin."
+
+#: ../actions/userauthorization.php:136 ../actions/userauthorization.php:137
+#: actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Kullanıcıyı OpenID'ye bağlarken hata oluştu."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Kullanıcı bağlamada hata oluştu."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Avatar eklemede hata oluÅŸtu"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Yeni profil eklemede hata oluÅŸtu"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Durum gönderilirken hata oluştu"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Uzak profil eklemede hata oluÅŸtu"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Adres onayını kaydetmede hata."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Uzaktaki profili kaydetmede hata oluÅŸtu"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Profili kaydetmede hata oluÅŸtu."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Kullanıcıyı kaydetmede hata oluştu."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Kullanıcıyı kaydetmede hata oluştu; geçersiz."
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Kullanıcı ayarlamada hata oluştu."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Profil güncellemede hata oluştu"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Uzaktaki profili güncellemede hata oluştu"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Onay kodu hatası."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Varolan takma ad"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "SSS"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Avatar güncellemede hata."
+
+#: ../actions/all.php:61 ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "%s için arkadaş güncellemeleri RSS beslemesi"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Cevaplar için RSS Beslemesi: %s"
+
+#: ../actions/login.php:118
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Güvenliğiniz için, ayarlarınızı değiştirmeden önce lütfen "
+"kullanıcı adınızı ve parolanızı tekrar giriniz."
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Tam Ä°sim"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Tam isim çok uzun (azm: 255 karakter)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Yardım"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Başlangıç"
+
+# Belki Durum Merkezi falan denebilir mi ki?
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Başlangıç Sayfası"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Başlangıç sayfası adresi geçerli bir URL değil."
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM adresi"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM Ayarları"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Eğer zaten bir hesabınız varsa, kullanıcı adınız ve parolanız ile "
+"giriş yapıp hesabınızı OpenID'nize bağlayabilirsiniz."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Eğer hesabınıza bir OpenID eklemek istiyorsanız, aşağıdaki kutuya "
+"girin ve \"Add\" düğmesine tıklayın."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Eğer parolanızı unuttuysanız veya kaybettiyseniz, hesabınıza eklemiş "
+"olduğunuz eposta adresine yeni bir tane gönderilmesini sağlayabilirsiniz."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Eski parola yanlış"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Yanlış kullanıcı adı veya parola."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Hesabınıza eklemiş olduğunuz eposta adresine parolanızı geri getirmek "
+"için gerekli olan talimatlar yollanmıştır."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "Geçersiz avatar URLi '%s'"
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "%s Geçersiz başlangıç sayfası"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "Geçersiz lisans URLi '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Geçersiz durum mesajı"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "Geçersiz durum adresi"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "Geçersiz durum adresi"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "'%s' geçersiz profil adresi."
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Geçersiz büyüklük."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Geçersiz kullanıcı adı veya parola."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"nedurum.com [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html) lisansı ile "
+"korunan [Laconica](http://laconi.ca/) microbloglama yazılımının %s. "
+"versiyonunu kullanmaktadır."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID başka bir kullanıcıya ait."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber veya Gtalk adresi: \"KullaniciAdi@ornek.org\" gibi. Öncelikle %s, IM "
+"istemcisi veya Gtalk arkadaşlar listenize eklenmiş olmalıdır."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Yer"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Yer bilgisi çok uzun (azm: 255 karakter)."
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286
+msgid "Login"
+msgstr "GiriÅŸ"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Bir [OpenID](%%doc.openid%%) hesabı ile giriş yapın."
+
+#: ../actions/login.php:122
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Kullanıcı adı ve parolanızla giriş yapın. Henüz bir hesabınız yok "
+"mu? Ne duruyorsunuz, hemen bir [yeni hesap oluÅŸturun](%%action.register%%) "
+"ya da [OpenID](%%action.openidlogin%%) ile giriş yapın."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Çıkış"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Parolamı unuttum veya kaybettim"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Üyelik başlangıcı"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "%s adli kullanicinin durum mesajlari"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Durum mesajlarim ve dosyalarim şu lisans ile korunmaktadır: "
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Yeni takma ad"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Yeni durum mesajı"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Yeni parola"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Yeni parola başarıyla kaydedildi. Şimdi giriş yaptınız."
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Takma ad"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Takma ad kullanımda. Başka bir tane deneyin."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"Takma ad sadece küçük harflerden ve rakamlardan oluşabilir, boşluk "
+"kullanılamaz. "
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Takma ada izin verilmiyor."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Takip etmek istediğiniz kullanıcının takma adı"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Takma ad veya eposta"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "JabberID yok."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Yetkilendirme isteÄŸi yok!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Onay kodu yok."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "İçerik yok!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Kullanıcı numarası yok"
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Uzaktaki sunucu tarafından bir takma ad sağlanmadı."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Takma ad yok"
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "İptal etmek için beklenen onay yok."
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Kullanıcı için kaydedilmiş eposta adresi yok."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "İstek bulunamadı!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Sonuç yok"
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr ""
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Böyle bir OpenID yok."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Böyle bir belge yok."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Böyle bir durum mesajı yok."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Böyle bir geri alma kodu yok."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Böyle bir abonelik yok"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Böyle bir kullanıcı yok."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Gösterecek hiç kimse yok!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Bir geri alma kodu deÄŸil."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Geçersiz bir Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Geçersiz bir OpenID."
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Geçersiz bir eposta adresi."
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Geçersiz bir takma ad."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Geçersiz profil adresi (yanlış hizmetler)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Geçersiz profil adresi (tanımlanmış XRDS yok)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Geçersiz profil adresi (YADIS belgesi yok)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Bu bir resim dosyası değil ya da dosyada hata var"
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "YetkilendirilmemiÅŸ."
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Giriş yapılmadı."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Bu kullanıcıyı zaten takip etmiyorsunuz!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "%s için durum RSS beslemesi"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Bu durum mesajının ait oldugu kullanıcı profili yok"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Durum mesajları"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Eski parola"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "OpenID Hesabı Kurulumu"
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "OpenID GiriÅŸi"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URLi"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "OpenID yetkilendirilmesi iptal edildi."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID yetkilendirmesi başarız oldu: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID hatası: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID kaldırıldı."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID ayarları"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Kısmi yükleme."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Parola"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Parola ve onaylaması birbirini tutmuyor."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Parola 6 veya daha fazla karakterden oluşmalıdır."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Parola geri alma isteÄŸi"
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Parola kaydedildi."
+
+#: ../actions/password.php:61 ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Parolalar birbirini tutmuyor."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "KiÅŸi Arama"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "KiÅŸisel"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Lütfen bu kullanıcının durumunu takip etmek istediğinizden emin olmak "
+"için detayları gözden geçirin. Kimsenin durumunu taki etme isteğinde "
+"bulunmadıysanız \"İptal\" tuşuna basın. "
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+"Jabber/GTalk durum mesajim deÄŸiÅŸtiÄŸinde nedurum.com'da durumumu "
+"güncelle"
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Tercihler"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Tercihler kaydedildi."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Gizlilik"
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Durum mesajını kaydederken hata oluştu."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Profil"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "Profil Adresi"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Profil ayarları"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Profil bilinmiyor"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Genel"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Genel Durum Akış RSS Beslemesi"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Genel zaman çizgisi"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Geri al"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Parolanı geri al"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Bilinmeye kullanıcı için geri alma kodu"
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+msgid "Register"
+msgstr "Kayıt"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Reddet"
+
+#: ../actions/login.php:99 ../actions/register.php:183
+msgid "Remember me"
+msgstr "Beni hatırla"
+
+#: ../actions/updateprofile.php:69 ../actions/updateprofile.php:70
+#: actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Uzaktan abonelik"
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Kaldır"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "OpenID'yi kaldır"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"OpenID'nizi sistemden silerseniz giriş yapamazsınız! Eğer silmek "
+"istiyorsanuz, önce yeni bir OpenID ekleyin"
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Cevaplar"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "%s için cevaplar"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Sıfırla"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Parolayı sıfırla"
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+msgid "Same as password above"
+msgstr "yukarıdaki parolanın aynısı"
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "Kaydet"
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277
+msgid "Search"
+msgstr "Ara"
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"%%site.name%%'da durum mesajlarının içeriğinde arama yap. Anahtar "
+"kelimeleri boşluk ile ayırın. Anahtar kelime 3 veya daha fazla "
+"karakterden oluşmalı."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"%%site.name%% üyeleri arasında isim, yer ya da ilgi alanları içinde "
+"arama yap. Anahtar kelimeleri boşluk ile ayırın. Anahtar kelime 3 veya "
+"daha fazla karakterden oluşmalı. "
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Gönder"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Durum mesajlarını Jabber/GTalk üzerinden gönder."
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Ayarlar"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Ayarlar kaydedildi."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Bir başkası zaten bu OpenID'ye sahip."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Garip bir ÅŸey oldu."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Kaynak"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Ä°statistikler"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Kaydedilmiş OpenID bulunamadı."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Abone ol"
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Abone olanlar"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Takip talebine izin verildi"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Abonelik reddedildi."
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Abonelikler"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Dosya yüklemede sistem hatası."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Metin arama"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Bu OpenID size ait deÄŸil."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "O adres daha önce onaylanmış."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "O onay kodu sizin için değil!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Dosya çok büyük."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Bu zaten sizin Jabber ID'niz."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Bu sizin Jabber ID'niz deÄŸil."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Yanlış IM adresi."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+"Ah, durumunuz biraz uzun kaçtı. Azami 180 karaktere sığdırmaya ne "
+"dersiniz?"
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "\"%s\" adresi hesabınız için onaylandı."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Bu adres kaldırılmıştı."
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "%s adlı kullanıcının durumunu takip edenler"
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Sizin durumunuzu takip edenler"
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "%s adlı kullanıcının durumlarını takip ettiği kullanıcılar"
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Sizin durumlarını takip ettiğiniz kullanıcılar"
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Onay kodu çok eski. Lütfen tekrar başlayınız."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Bu form kendiliğinden gönderilmeli. Eğer yönlendirilmediyseniz, OpenID "
+"servis sağlayıcınıza yönlenmek için düğmeye basın."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"%s'a İlk defa giriş yapıyorsunuz. Bu yüzden OpenID'nizi hesabınıza "
+"bağlamamız gerekli. Bunun için ya yeni bir hesap oluşturabilirsiniz ya "
+"da varolan hesabınızı kullanabilirsiniz. "
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Bu sayfa kabul ettiğiniz ortam türünde kullanılabilir değil"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+"Web Sitenizin, blogunuzun ya da varsa baÅŸka bir sitedeki profilinizin "
+"adresi"
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "BeklenmeÄŸen form girdisi."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Beklemeğen parola sıfırlaması."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "OMB protokolünün bilinmeğen sürümü."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Aksi ifade edilmedikce, bu sitedeki içerik yaratıcılarına aittir ve şu "
+"lisans ile korunmaktadır: "
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Tanınmayan adres türü %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Aboneliği sonlandır"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Desteklenmeyen OMB sürümü"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Desteklenmeyen görüntü dosyası biçemi."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Yükle"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Buradan yeni bir \"avatar\" (kullanıcı resmi) yükleyin. Resminizi "
+"yükledikten sonra düzenleyemezsiniz bu sebeple kare biçimine yakın "
+"olmasına özen gösterin. Buna ek olarak, resminiz sitenin lisansına tabi "
+"olmalıdır. Kendinize ait olan ve paylaşmak istediğiniz bir resim kullanın. "
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"Sadece sistem güncellemeleri, duyurular ve parola geri alma için "
+"kullanılır."
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "Kullanıcının profili yok."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Kullanıcı takma adı"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "N'aber %s?"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Bulunduğunuz yer, \"Şehir, Eyalet (veya Bölge), Ülke\" gibi"
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "%s için yanlış resim türü"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "%s'de yanlış resim boyutu"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Zaten bu OpenID'ye sahipsiniz!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Zaten giriş yapmış durumdasıznız!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Parolanızı burada değiştirebilirsiniz. İyi bir tane seçin!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr ""
+"Sizde bir hesap açıp durumunuzdan arkadaşlarınızı haberdar "
+"edebilirsiniz."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"\"Sil\" düğmesine basarak hesabınızdan eklediğiniz OpenID'yi "
+"silebilirsiniz."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Jabber/GTalk kullanarak durum mesaji gÖnderip alabilirsiniz. IM adres "
+"ayarlarinizi aşağıda yapın."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Burada kişisel profilinizi güncelleyebilirsiniz, böylelikle insanlar "
+"sizin hakkınızda daha fazla bilgi sahibi olur."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Yerel aboneliÄŸi kullanabilirsiniz!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Eğer lisansı kabul etmezseniz kayıt olamazsınız."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Bize o profili yollamadınız"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Harika, sizi tanıdık. Simdi yeni parolanızı girin."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "OpenID URLniz"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Bu sunucudaki takma adınız veya kaydedilmiş eposta adresiniz."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) tek hesap ile bir çok siteye giriş yapmanızı "
+"sağlar. Hesabınızla bağlantılı OpenID'lerinizi burada yönetebilirsiniz."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "birkaç saniye önce"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "yaklaşık %d gün önce"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "yaklaşık %d saat önce"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "yaklaşık %d dakika önce"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "yaklaşık %d ay önce"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "yaklaşık bir gün önce"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "yaklaşık bir dakika önce"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "yaklaşık bir ay önce"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "yaklaşık bir yıl önce"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "yaklaşık bir saat önce"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "...cevap olarak"
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "cevapla"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "yukaridaki parola ile aynı"
+
+#: ../lib/util.php:1127
+msgid "« After"
+msgstr "« Sonra"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/uk_UA/LC_MESSAGES/laconica.mo b/locale/uk_UA/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..15d92e5aa
--- /dev/null
+++ b/locale/uk_UA/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/uk_UA/LC_MESSAGES/laconica.po b/locale/uk_UA/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..329397dbb
--- /dev/null
+++ b/locale/uk_UA/LC_MESSAGES/laconica.po
@@ -0,0 +1,2898 @@
+# #-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#
+# 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 ""
+"#-#-#-#-# laconica.po #-#-#-#-#\n"
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-29 20:02+1200\n"
+"PO-Revision-Date: \n"
+"Last-Translator: Kostyantyn Bakarzhiev <boogie@e-mail.ua>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Ukrainian\n"
+"X-Poedit-Country: UKRAINE\n"
+"#-#-#-#-# laconica.new.pot (PACKAGE VERSION) #-#-#-#-#\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "Потік пошуку Ð´Ð»Ñ \"%s\""
+
+#: ../actions/tag.php:139
+msgid " by "
+msgstr "від"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:255
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr ""
+"окрім цих приватних даних: пароль, "
+"електронна адреÑа, адреÑа IM, телефонний номер."
+
+#: ../actions/twitapistatuses.php:469
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr "%1$s / Оновленні відповіді %2$s"
+
+#: ../actions/tag.php:127
+#, php-format
+msgid "%1$s Notices recently tagged with %2$s"
+msgstr "%1$s ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‰Ð¾Ð¹Ð½Ð¾ позначені з %2$s"
+
+#: ../lib/mail.php:120
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s тепер Ñлідкує за вашими повідомленÑми на %2$s."
+
+#: ../lib/mail.php:122
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s тепер Ñлідкує за вашими повідомленÑми "
+"на %2$s.\n\n\t%3$s\n\nЩиро ваші,\n%4$s.\n"
+
+#: ../actions/twitapistatuses.php:473
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr "%1$s оновив(ла) цю відповідь на Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %2$s / %3$s."
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s має ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s Спільний потік"
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:235 ../lib/stream.php:51
+#, php-format
+msgid "%s and friends"
+msgstr "%s з друзÑми"
+
+#: ../actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "%s Ñпільний чаÑовий потік"
+
+#: ../lib/mail.php:202
+#, php-format
+msgid "%s status"
+msgstr "%s ÑтатуÑ"
+
+#: ../actions/twitapistatuses.php:335
+#, php-format
+msgid "%s timeline"
+msgstr "%s чаÑовий потік"
+
+#: ../actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr "%s Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ вÑÑ–Ñ…!"
+
+#: ../actions/register.php:277
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(Ви маєте негайно отримати лиÑта "
+"електронною поштою, в Ñкому будуть інÑтрукції, Ñк підтвердити вашу електронну адреÑу.)"
+
+#: ../lib/util.php:244
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** це ÑÐµÑ€Ð²Ñ–Ñ Ð¼Ñ–ÐºÑ€Ð¾Ð±Ð»Ð¾Ð³Ñ–Ð² наданий "
+"вам [%%site.broughtby%%](%%site.broughtbyurl%%)."
+
+#: ../lib/util.php:246
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** це ÑÐµÑ€Ð²Ñ–Ñ Ð¼Ñ–ÐºÑ€Ð¾Ð±Ð»Ð¾Ð³Ñ–Ð²."
+
+#: ../lib/util.php:261
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr ""
+". Контрибутори мають бути зазначені "
+"повним ім'Ñм або ім'Ñм кориÑтувача."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64 букви нижнього регіÑтра Ñ– цифри, ніÑкої пунктуації або інтервалів"
+
+#: ../actions/register.php:221
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"1-64 букви нижнього регіÑтра Ñ– цифри, "
+"ніÑкої пунктуації або інтервалів. Ðеодмінно."
+
+#: ../actions/password.php:42
+msgid "6 or more characters"
+msgstr "6 або більше знаків"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 або більше знаків, і не забудьте їх!"
+
+#: ../actions/register.php:223
+msgid "6 or more characters. Required."
+msgstr "6 або більше знаків. Ðеодмінно."
+
+#: ../actions/imsettings.php:197
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ² відправлений на "
+"адреÑу IM, Ñку ви додали. Ви повинні Ñхвалити %s Ð´Ð»Ñ Ð²Ñ–Ð´Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ð°Ð¼ повідомлень."
+
+#: ../actions/emailsettings.php:213
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ² відправлений на "
+"електронну адреÑу, Ñку ви додали. "
+"Перевірте вхідну пошту (Ñ– теку зі Ñпамом також!), там має бути код та подальші інÑтрукції."
+
+#: ../actions/smssettings.php:216
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ² відправлений на "
+"телефонний номер, Ñкий ви додали. "
+"Перевірте вхідні повідомленнÑ, там має бути код та подальші інÑтрукції."
+
+#: ../lib/util.php:310
+msgid "About"
+msgstr "Про"
+
+#: ../actions/userauthorization.php:119
+msgid "Accept"
+msgstr "ПогодитиÑÑŒ"
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+msgid "Add"
+msgstr "Додати"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Додати OpenID"
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39
+msgid "Address"
+msgstr "ÐдреÑа"
+
+#: ../actions/showstream.php:273
+msgid "All subscriptions"
+msgstr "Ð’ÑÑ– підпиÑки"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Ð’ÑÑ– Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Ð’ÑÑ– Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð·Ð° збігом з \"%s\""
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+msgid "Already logged in."
+msgstr "Тепер ви увійшли."
+
+#: ../actions/subscribe.php:49
+msgid "Already subscribed!."
+msgstr "Тепер ви підпиÑані!"
+
+#: ../actions/deletenotice.php:54
+msgid "Are you sure you want to delete this notice?"
+msgstr "Ви впевненні, що бажаєте видалити це повідомленнÑ?"
+
+#: ../actions/userauthorization.php:77
+msgid "Authorize subscription"
+msgstr "Ðвторизувати підпиÑку"
+
+#: ../actions/login.php:104 ../actions/register.php:242
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Ðвтоматично входити у майбутньому; не Ð´Ð»Ñ "
+
+#: ../actions/profilesettings.php:65
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+"Ðвтоматично підпиÑуватиÑÑŒ до тих, хто "
+"підпиÑавÑÑ Ð´Ð¾ мене (Ñкщо ви бот, це Ñаме Ð´Ð»Ñ Ð²Ð°Ñ)"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Ðватара"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Ðватару оновлено."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— адреÑи. "
+"Перевірте Ñвій Jabber/GTalk рахунок, там має "
+"бути Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· подальшими інÑтрукціÑми. (Ви додали %s до вашого ÑпиÑку контактів?)"
+
+#: ../actions/emailsettings.php:54
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— адреÑи. "
+"Перевірте вхідну пошту (Ñ– теку зі Ñпамом також!), там має бути Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð· подальшими інÑтрукціÑми."
+
+#: ../actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ‚ÐµÐ»ÐµÑ„Ð¾Ð½Ð½Ð¾Ð³Ð¾ номера."
+
+#: ../lib/util.php:1290
+msgid "Before »"
+msgstr "Раніше"
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:234
+msgid "Bio"
+msgstr "Про Ñебе"
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:78
+#: ../actions/updateprofile.php:103
+msgid "Bio is too long (max 140 chars)."
+msgstr "Про Ñебе забагато напиÑано (140 знаків макÑимум)"
+
+#: ../lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "Ðе можна видалити це повідомленнÑ."
+
+#: ../actions/updateprofile.php:119
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Ðе можна прочитати URL аватари '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Ðе можна зберегти новий пароль."
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62
+msgid "Cancel"
+msgstr "СкаÑувати"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Ðе можна підтвердити Ñпоживчий об'єкт OpenID."
+
+#: ../actions/imsettings.php:163
+msgid "Cannot normalize that Jabber ID"
+msgstr "Ðе можна полагодити цей Jabber ID"
+
+#: ../actions/emailsettings.php:181
+msgid "Cannot normalize that email address"
+msgstr "Ðе можна полагодити цю поштову адреÑу"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Змінити"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Змінити пароль"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:224 ../actions/smssettings.php:65
+msgid "Confirm"
+msgstr "Підтвердити"
+
+#: ../actions/confirmaddress.php:86
+msgid "Confirm Address"
+msgstr "Підтвердити адреÑу"
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245
+msgid "Confirmation cancelled."
+msgstr "ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÑкаÑовано."
+
+#: ../actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "Код підтвердженнÑ"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ знайдено."
+
+#: ../actions/register.php:266
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"Вітаємо, %s! І лаÑкаво проÑимо до %%%%site.name%%%%. "
+"ЗвідÑи ви, можливо, Ñхочете...\n"
+"\n"
+"*ПодивитиÑÑŒ [ваш профіль](%s) та напиÑати "
+"Ñвоє перше повідомленнÑ.\n"
+"*Додати [адреÑу Jabber/GTalk](%%%%action.imsettings%%%%), так "
+"щоб мати змогу надÑилати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ "
+"через Ñлужбу миттєвих повідомлень.\n"
+"*[Розшукати людей](%%%%action.peoplesearch%%%%), Ñкі "
+"мають Ñпільні з вами інтереÑи. \n*Прочитати [додаткову інформацію](%%%%doc.help%%%%), аби переконатиÑÑŒ, що ви нічого не пропуÑтили. \n\nДÑкуємо, що зареєÑтрувалиÑÑŒ у наÑ, Ñ–, ÑподіваємоÑÑŒ, вам ÑподобаєтьÑÑ Ð½Ð°Ñˆ ÑервіÑ."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "З'єднатиÑÑŒ"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "З'єднатиÑÑŒ викориÑтовуючи Ñ–Ñнуючий рахунок"
+
+#: ../lib/util.php:318
+msgid "Contact"
+msgstr "Контакт"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Ðе вдалоÑÑ Ñтворити форму OpenID: %s"
+
+#: ../actions/twitapifriendships.php:48
+msgid "Could not follow user: User not found."
+msgstr "Ðе вдалоÑÑ Ñлідувати за кориÑтувачем: кориÑтувача не знайдено."
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Ðевдале Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ð° Ñервер: %s"
+
+#: ../actions/updateprofile.php:162
+msgid "Could not save avatar info"
+msgstr "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ інформацію про аватару"
+
+#: ../actions/updateprofile.php:155
+msgid "Could not save new profile info"
+msgstr "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ інформацію про новий профіль"
+
+#: ../actions/subscribe.php:62
+msgid "Could not subscribe other to you."
+msgstr "Ðе вдалоÑÑ Ð¿Ñ–Ð´Ð¿Ð¸Ñати іншого до ваÑ."
+
+#: ../actions/subscribe.php:54
+msgid "Could not subscribe."
+msgstr "Ðевдала підпиÑка."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚Ð¸ токени запиту на токени зверненнÑ."
+
+#: ../actions/confirmaddress.php:80 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+msgid "Couldn't delete email confirmation."
+msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¾Ð²Ð¾Ñ— адреÑи."
+
+#: ../actions/unsubscribe.php:57
+msgid "Couldn't delete subscription."
+msgstr "Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ підпиÑку."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ токен запиту."
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206
+msgid "Couldn't insert confirmation code."
+msgstr "Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ код підтвердженнÑ."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ нову підпиÑку."
+
+#: ../actions/profilesettings.php:181 ../actions/twitapiaccount.php:92
+msgid "Couldn't save profile."
+msgstr "Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ профіль."
+
+#: ../actions/profilesettings.php:158
+msgid "Couldn't update user for autosubscribe."
+msgstr "Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ кориÑтувача Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¿Ñ–Ð´Ð¿Ð¸Ñки."
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+msgid "Couldn't update user record."
+msgstr "Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ Ð·Ð°Ð¿Ð¸Ñ ÐºÐ¾Ñ€Ð¸Ñтувача."
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+msgid "Couldn't update user."
+msgstr "Ðе вдалоÑÑ Ð¾Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ кориÑтувача."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Створити"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Створити нового кориÑтувача з цим ім'Ñм."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Створити новий рахунок"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Створити новий рахунок з OpenID, Ñким ви вже кориÑтуєтеÑÑŒ."
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Поточна підтверджена адреÑа Jabber/GTalk."
+
+#: ../actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "Поточний підтверджений телефонний номер."
+
+#: ../actions/emailsettings.php:44
+msgid "Current confirmed email address."
+msgstr "Поточна підтверджена поштова адреÑа."
+
+#: ../actions/showstream.php:356
+msgid "Currently"
+msgstr "Поточне"
+
+#: ../classes/Notice.php:72
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr "Помилка бази даних при додаванні мітки: %s"
+
+#: ../lib/util.php:1033
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Помилка бази даних при додаванні відповіді: %s"
+
+#: ../actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "Видалити повідомленнÑ"
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:236
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Опишіть Ñебе та Ñвої інтереÑи (140 знаків)"
+
+#: ../actions/register.php:226
+msgid "Email"
+msgstr "Пошта"
+
+#: ../actions/emailsettings.php:59
+msgid "Email Address"
+msgstr "Електронна адреÑа"
+
+#: ../actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸"
+
+#: ../actions/register.php:69
+msgid "Email address already exists."
+msgstr "Ð¦Ñ Ð°Ð´Ñ€ÐµÑа вже викориÑтовуєтьÑÑ."
+
+#: ../lib/mail.php:90
+msgid "Email address confirmation"
+msgstr "ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— адреÑи"
+
+#: ../actions/emailsettings.php:61
+msgid "Email address, like \"UserName@example.org\""
+msgstr "Електронна адреÑа, на зразок \"UserName@example.org\""
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Введіть ім'Ñ Ð°Ð±Ð¾ електронну адреÑу."
+
+#: ../actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr "Введіть код, Ñкий ви отримали телефоном."
+
+#: ../actions/userauthorization.php:137
+msgid "Error authorizing token"
+msgstr "Помилка токена авторизації"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Помилка при підключенні кориÑтувача до OpenID."
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Помилка при підключенні кориÑтувача."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Помилка при додаванні аватари"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Помилка при додаванні нового профілю"
+
+#: ../actions/postnotice.php:89
+msgid "Error inserting notice"
+msgstr "Помилка при додаванні повідомленнÑ"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Помилка при додаванні віддаленого профілю"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Помилка при збереженні Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð°Ð´Ñ€ÐµÑи."
+
+#: ../actions/userauthorization.php:140
+msgid "Error saving remote profile"
+msgstr "Помилка при збереженні віддаленого профілю"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Помилка при збереженні профілю."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Помилка при збереженні кориÑтувача."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Помилка при збереженні кориÑтувача; недійÑний."
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:268 ../actions/register.php:92
+msgid "Error setting user."
+msgstr "Помилка в налаштуваннÑÑ… кориÑтувача."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Помилка при оновленні профілю"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Помилка при оновленні віддаленого профілю"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Помилка з кодом підтвердженнÑ."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "ІÑнуюче ім'Ñ"
+
+#: ../lib/util.php:312
+msgid "FAQ"
+msgstr "Пширені запитаннÑ"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð°Ð²Ð°Ñ‚Ð°Ñ€Ð¸ невдале."
+
+#: ../actions/all.php:61 ../actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Ð–Ð¸Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð´Ñ€ÑƒÐ·Ñ–Ð² %s"
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Ð–Ð¸Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´ÐµÐ¹ %s"
+
+#: ../actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "Ð–Ð¸Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð¼Ñ–Ñ‚Ð¾Ðº %s"
+
+#: ../lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr "Знайти зміÑÑ‚ повідомленнÑ"
+
+#: ../lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr "Знайти людей на цьому Ñайті"
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"З міркувань безпеки, будь лаÑка введіть "
+"ще раз ім'Ñ Ñ‚Ð° пароль, перед тим Ñк змінювати налатуваннÑ."
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:228
+msgid "Full name"
+msgstr "Повне ім'Ñ"
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:75
+#: ../actions/updateprofile.php:93
+msgid "Full name is too long (max 255 chars)."
+msgstr "Повне ім'Ñ Ð·Ð°Ð´Ð¾Ð²Ð³Ðµ (255 знаків макÑимум)"
+
+#: ../lib/util.php:291
+msgid "Help"
+msgstr "Допомога"
+
+#: ../lib/util.php:285
+msgid "Home"
+msgstr "Дім"
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:231
+msgid "Homepage"
+msgstr "Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка"
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:72
+msgid "Homepage is not a valid URL."
+msgstr "Ð”Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка має недійÑну URL-адреÑу."
+
+#: ../actions/emailsettings.php:91
+msgid "I want to post notices by email."
+msgstr "Я хочу надÑилати Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¾ÑŽ."
+
+#: ../actions/imsettings.php:60
+msgid "IM Address"
+msgstr "ÐдреÑа IM"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ IM"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Якщо ви вже маєте рахунок, увійдіть "
+"викориÑтовуючи ім'Ñ Ñ‚Ð° пароль, щоб приєднати Ñ—Ñ… до вашого OpenID."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Якщо ви бажаєте додати OpenID до вашого "
+"рахунку, введіть адреÑу в поле нижче Ñ– натиÑніть \"Add\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr ""
+"Якщо ви забули, або загубили Ñвій пароль, "
+"ви можете отримати новий на електронну адреÑу, Ñку ви закріпили за Ñвоїм рахунком."
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "Вхідна пошта"
+
+#: ../actions/emailsettings.php:283
+msgid "Incoming email address removed."
+msgstr "ÐдреÑу вхідної пошти видалено."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Старий пароль неточний"
+
+#: ../actions/login.php:67
+msgid "Incorrect username or password."
+msgstr "Ðеточне ім'Ñ Ð°Ð±Ð¾ пароль."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"ІнÑтрукції з Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ було "
+"надіÑлано на електронну адреÑу, Ñку ви закріпили за Ñвоїм ранком."
+
+#: ../actions/updateprofile.php:114
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "ÐедійÑна URL-адреÑа аватари '%s'"
+
+#: ../actions/updateprofile.php:98
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "ÐедейÑна Ð´Ð¾Ð¼Ð°ÑˆÐ½Ñ Ñторінка '%s'"
+
+#: ../actions/updateprofile.php:82
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "ÐедійÑна Ð»Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ URL '%s'"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "ÐедійÑний зміÑÑ‚ повідомленнÑ"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "ÐедійÑне URI повідомленнÑ"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "ÐедійÑна URL-адреÑа повідомленнÑ"
+
+#: ../actions/updateprofile.php:87
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "ÐедійÑна URL-адреÑа профілю '%s'."
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "ÐедійÑна URL-адреÑа профілю (неправильний формат)"
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "ÐедійÑну URL-адреÑу профілю було повернуто Ñервером."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "ÐедійÑний розмір."
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:87
+#: ../actions/register.php:103
+msgid "Invalid username or password."
+msgstr "ÐедійÑне ім'Ñ Ð°Ð±Ð¾ пароль."
+
+#: ../lib/util.php:248
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Ð¡ÐµÑ€Ð²Ñ–Ñ Ð¿Ñ€Ð°Ñ†ÑŽÑ” на [Laconica](http://laconi.ca/) - "
+"програмному забезпеченні Ð´Ð»Ñ "
+"мікроблогів, верÑÑ–Ñ %s, доÑтупному під [GNU "
+"Affero General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:173
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID вже належить іншому кориÑтувачу."
+
+#: ../actions/imsettings.php:62
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber або GTalk адреÑа, на зразок "
+"\"UserName@example.org\". Ðле Ñпершу переконайтеÑÑ, "
+"що додали %s до ÑпиÑку контактів в Ñвоєму IM-клієнті або в GTalk."
+
+#: ../actions/profilesettings.php:57
+msgid "Language"
+msgstr "Мова"
+
+#: ../actions/profilesettings.php:113
+msgid "Language is too long (max 50 chars)."
+msgstr "Мова задовга (50 знаків макÑимум)"
+
+#: ../actions/tag.php:133
+msgid "Last message posted: "
+msgstr "ОÑтаннє повідомленнÑ:"
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:237
+msgid "Location"
+msgstr "МіÑцезнаходженнÑ"
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:81
+#: ../actions/updateprofile.php:108
+msgid "Location is too long (max 255 chars)."
+msgstr "МіÑÑ†ÐµÐ·Ð½Ð°Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð¾Ð²Ð³Ðµ (255 знаків макÑимум)"
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:298
+msgid "Login"
+msgstr "Увійти"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Увійти з [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Увійти викриÑтовуючи ім'Ñ Ñ‚Ð° пароль. Ще не "
+"маєте імені кориÑтувача? "
+"[ЗареєÑтрувати](%%action.register%%) новий акаунт, або Ñпробувати [OpenID](%%action.openidlogin%%)."
+
+#: ../lib/util.php:296
+msgid "Logout"
+msgstr "Вийти"
+
+#: ../actions/register.php:230
+msgid "Longer name, preferably your \"real\" name"
+msgstr "Довше ім'Ñ, переважно це ваше \"real\" ім'Ñ"
+
+#: ../actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr "Загубили або забули пароль?"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr "Створити нову адреÑу Ð´Ð»Ñ Ð½Ð°Ð´ÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ; видалити Ñтару."
+
+#: ../actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr "Зазначте, Ñк Ñамо ви бажаєте отримувати лиÑти з %%site.name%%."
+
+#: ../actions/showstream.php:300
+msgid "Member since"
+msgstr "Разом з нами з"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Мікроблог від %s"
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+"Оператор мобільного зв'Ñзку. Якщо вам "
+"відомий оператор, що підтримує "
+"надÑÐ¸Ð»Ð°Ð½Ð½Ñ SMS через електронну пошту, але він тут не вказаний, напишіть нам Ñ– ми внеÑемо його до ÑпиÑку."
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:252
+msgid "My text and files are available under "
+msgstr "Мої Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° файли доÑтупні під"
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+msgid "New"
+msgstr "Ðове"
+
+#: ../lib/mail.php:140
+#, php-format
+msgid "New email address for posting to %s"
+msgstr "Ðова електронна адреÑа Ð´Ð»Ñ Ð½Ð°Ð´ÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ на %s"
+
+#: ../actions/emailsettings.php:297
+msgid "New incoming email address added."
+msgstr "Ðову адреÑу Ð´Ð»Ñ Ð²Ñ…Ñ–Ð´Ð½Ð¸Ñ… повідомлень додано."
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Ðове ім'Ñ"
+
+#: ../actions/newnotice.php:102
+msgid "New notice"
+msgstr "Ðове повідомленнÑ"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Ðовий пароль"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Ðовий пароль уÑпішно збережено. Тепер ви увійшли."
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:220
+msgid "Nickname"
+msgstr "Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:65
+msgid "Nickname already in use. Try another one."
+msgstr "Це ім'Ñ Ð²Ð¶Ðµ викориÑтовуєтьÑÑ. Спробуйте інше."
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:63 ../actions/updateprofile.php:77
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr ""
+"Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача повинно ÑкладатиÑÑŒ з "
+"літер нижнього регіÑтру Ñ– цифр, ніÑких інтервалів."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Таке ім'Ñ Ð½ÐµÐ¿Ñ€Ð¸Ð¿ÑƒÑтиме."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача, за Ñким ви бажаєте Ñлідувати"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Ім'Ñ Ð°Ð±Ð¾ електронна адреÑа"
+
+#: ../actions/deletenotice.php:59
+msgid "No"
+msgstr "ÐÑ–"
+
+#: ../actions/imsettings.php:156
+msgid "No Jabber ID."
+msgstr "Ðемає Jabber ID."
+
+#: ../actions/userauthorization.php:129
+msgid "No authorization request!"
+msgstr "Ðемає запиту на авторизацію!"
+
+#: ../actions/smssettings.php:181
+msgid "No carrier selected."
+msgstr "Оператора не обрано."
+
+#: ../actions/smssettings.php:316
+msgid "No code entered"
+msgstr "Код не введено"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Ðемає коду підтвердженнÑ."
+
+#: ../actions/newnotice.php:50
+msgid "No content!"
+msgstr "Ðемає зміÑту!"
+
+#: ../actions/emailsettings.php:174
+msgid "No email address."
+msgstr "Ðемає електронної адреÑи."
+
+#: ../actions/userbyid.php:32
+msgid "No id."
+msgstr "Ðемає ID."
+
+#: ../actions/emailsettings.php:271
+msgid "No incoming email address."
+msgstr "Ðемає адреÑи Ð´Ð»Ñ Ð²Ñ…Ñ–Ð´Ð½Ð¾Ñ— пошти."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Ðа віддаленому Ñервері такого імені немає."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Ðемає імені."
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229
+msgid "No pending confirmation to cancel."
+msgstr "Ðе очікуєтьÑÑ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ ÑкаÑуваннÑ."
+
+#: ../actions/smssettings.php:176
+msgid "No phone number."
+msgstr "Ðемає телефонного номера."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Ðемає URL-адреÑи профілю повернітої Ñервером."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача немає зареєÑтрованої електронної адреÑи."
+
+#: ../actions/userauthorization.php:49
+msgid "No request found!"
+msgstr "Ðемає знайденого запиту!"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Ðемає результатів"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Ðемає розміру."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Такого OpenID немає."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Такого документа немає."
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30
+msgid "No such notice."
+msgstr "Такого Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½ÐµÐ¼Ð°Ñ”."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Ðемає такого коду відновленнÑ."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Ðемає такої підпиÑки"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/replies.php:57
+#: ../actions/repliesrss.php:35 ../actions/showstream.php:110
+#: ../actions/subscribe.php:44 ../actions/unsubscribe.php:39
+#: ../actions/userbyid.php:36 ../actions/userrss.php:35 ../actions/xrds.php:35
+#: ../lib/gallery.php:57
+msgid "No such user."
+msgstr "Такого кориÑтувача немає."
+
+#: ../lib/gallery.php:80
+msgid "Nobody to show!"
+msgstr "Об'єктів Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ñƒ немає!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Це не код оновленнÑ."
+
+#: ../actions/imsettings.php:167
+msgid "Not a valid Jabber ID"
+msgstr "Це не дійÑний Jabber ID"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "Це не дійÑний OpenID."
+
+#: ../actions/emailsettings.php:185
+msgid "Not a valid email address"
+msgstr "Це не дійÑна електронна адреÑа"
+
+#: ../actions/register.php:59
+msgid "Not a valid email address."
+msgstr "Це не дійÑна електронна адреÑа."
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:67
+msgid "Not a valid nickname."
+msgstr "Це не дійÑне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Це не дійÑна URL-адреÑа профілю (некоректні поÑлуги)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Це не дійÑна URL-адреÑа профілю (немає певного XRDS)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "Це не дійÑна URL-адреÑа профілю (немає документа YADIS)."
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "Це не зображеннÑ, або файл зіпÑовано."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Ðе авторизовано."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Ð¦Ñ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´ÑŒ не очікуєтьÑÑ!"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Ðе увійшли."
+
+#: ../actions/unsubscribe.php:44
+msgid "Not subscribed!."
+msgstr "Ðе підпиÑано!."
+
+#: ../actions/opensearch.php:35
+msgid "Notice Search"
+msgstr "Пошук повідомлень"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Ð–Ð¸Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½ÑŒ Ð´Ð»Ñ %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ðµ має профілю"
+
+#: ../actions/showstream.php:316
+msgid "Notices"
+msgstr "ПовідомленнÑ"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 ../actions/tag.php:124
+#, php-format
+msgid "Notices tagged with %s"
+msgstr "ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ñ– з %s"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Старий пароль"
+
+#: ../lib/util.php:302
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "УÑтановки рахунку OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "ÐÐ²Ñ‚Ð¾Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ OpenID"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Вхід OpenID"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "URL-адреÑа OpenID"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Ідентифікацію OpenID ÑкаÑовано."
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Ð†Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ OpenID невдала: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "Ðевдача OpenID: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID видалено."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "ЧаÑткове завантаженнÑ."
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:222
+msgid "Password"
+msgstr "Пароль"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Пароль та Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ Ñпівпадають."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Пароль міÑтить 6 або більше знаків."
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Запит на Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ відправлено."
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Пароль збережено."
+
+#: ../actions/password.php:61 ../actions/register.php:84
+msgid "Passwords don't match."
+msgstr "Паролі не Ñпівпадають."
+
+#: ../lib/searchaction.php:100
+msgid "People"
+msgstr "Люди"
+
+#: ../actions/opensearch.php:33
+msgid "People Search"
+msgstr "Пошук людей"
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Пошук людей"
+
+#: ../lib/stream.php:50
+msgid "Personal"
+msgstr "ОÑобиÑте"
+
+#: ../actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr "Телефонний номер та регіональний код, ніÑкої пунктуації чи інтервалів"
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Будь лаÑка, перевірте вÑÑ– деталі, щоб "
+"упевнитиÑÑŒ, що ви дійÑно бажаєте "
+"підпиÑатиÑÑŒ на Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¾Ð³Ð¾ "
+"кориÑтувача. Якщо ви не збиралиÑÑŒ підпиÑуватиÑÑŒ ні на чиї повідомленнÑ, проÑто натиÑніть \"Cancel\"."
+
+#: ../actions/imsettings.php:73
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "Друкувати повідомленнÑ, коли мій мій ÑÑ‚Ð°Ñ‚ÑƒÑ Jabber/GTalk змінюєтьÑÑ."
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94
+msgid "Preferences"
+msgstr "Переваги"
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163
+msgid "Preferences saved."
+msgstr "Переваги збережно."
+
+#: ../actions/profilesettings.php:57
+msgid "Preferred language"
+msgstr "Мова, Ñкій надаєте перевагу"
+
+#: ../lib/util.php:314
+msgid "Privacy"
+msgstr "КонфіденційніÑÑ‚ÑŒ"
+
+#: ../actions/newnotice.php:62 ../actions/newnotice.php:70
+msgid "Problem saving notice."
+msgstr "Проблема при збереженні повідомленнÑ."
+
+#: ../lib/stream.php:60
+msgid "Profile"
+msgstr "Профіль"
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL-адреÑа профілю"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ„Ñ–Ð»ÑŽ"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+msgid "Profile unknown"
+msgstr "Ðевідомий профіль"
+
+#: ../lib/util.php:287
+msgid "Public"
+msgstr "Загал"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Ð–Ð¸Ð²Ð»ÐµÐ½Ð½Ñ Ñпільного потоку"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Спільний чаÑовий потік"
+
+#: ../actions/imsettings.php:79
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Позначати MicroID мого рахунку Jabber/GTalk"
+
+#: ../actions/emailsettings.php:94
+msgid "Publish a MicroID for my email address."
+msgstr "Позначати MicroID моєї електронної адреÑи."
+
+#: ../actions/tag.php:75 ../actions/tag.php:76
+msgid "Recent Tags"
+msgstr "Ðедавні мітки"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Відновити"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Відновити пароль"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Код Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½ÐµÐ²Ñ–Ð´Ð¾Ð¼Ð¾Ð³Ð¾ кориÑтувача."
+
+#: ../actions/register.php:216 ../actions/register.php:257 ../lib/util.php:300
+msgid "Register"
+msgstr "ЗареєÑтруватиÑÑŒ"
+
+#: ../actions/register.php:28
+msgid "Registration not allowed."
+msgstr "РеєÑтрацію не дозволено."
+
+#: ../actions/register.php:264
+msgid "Registration successful"
+msgstr "РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ ÑƒÑпішна"
+
+#: ../actions/userauthorization.php:120
+msgid "Reject"
+msgstr "Забраковано"
+
+#: ../actions/login.php:103 ../actions/register.php:240
+msgid "Remember me"
+msgstr "Пам'Ñтати мене"
+
+#: ../actions/updateprofile.php:70
+msgid "Remote profile with no matching profile"
+msgstr "Віддалений профіль без відповідного профілю"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Віддалена підпиÑка"
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+msgid "Remove"
+msgstr "Видалити"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Видалити OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Ви входите лише з OpenID, Ñкщо ви його "
+"видалите, то не зможете увійти знову! Перед тим Ñк видалити його, з початку додайте інший."
+
+#: ../lib/stream.php:55
+msgid "Replies"
+msgstr "Відповіді"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr "Відповіді %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Скинути"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Скинути пароль"
+
+#: ../actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "Телефонний номер"
+
+#: ../actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ SMS"
+
+#: ../lib/mail.php:215
+msgid "SMS confirmation"
+msgstr "ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ SMS"
+
+#: ../actions/recoverpassword.php:167
+msgid "Same as password above"
+msgstr "Такий же, Ñк пароль вище"
+
+#: ../actions/register.php:225
+msgid "Same as password above. Required."
+msgstr "Такий же, Ñк пароль вище. Ðеодмінно."
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+msgid "Save"
+msgstr "Зберегти"
+
+#: ../lib/searchaction.php:84 ../lib/util.php:288
+msgid "Search"
+msgstr "Пошук"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Ð–Ð¸Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ñ‚Ð¾ÐºÑƒ пошуку"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Пошук повідомлень на %%site.name%% за їх "
+"зміÑтом. Відокремлюйте пошукові умови "
+"інтервалами; вони повинні ÑкладатиÑÑŒ з 3 знаків або більше."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Пошук людей на %%site.name%% за Ñ—Ñ… ім'Ñм, міÑцем "
+"Ð·Ð½Ð°Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð°Ð±Ð¾ інтереÑами. "
+"Відокремлюйте пошукові умови інтервалами; вони повинні ÑкладатиÑÑŒ з 3 знаків або більше."
+
+#: ../actions/smssettings.php:296
+msgid "Select a carrier"
+msgstr "Оберіть оператора"
+
+#: ../lib/util.php:1144
+msgid "Send"
+msgstr "ÐадіÑлати"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr "ÐадÑилайте лиÑти на цю адреÑу, щоб друкувати нові повідомленнÑ."
+
+#: ../actions/emailsettings.php:88
+msgid "Send me notices of new subscriptions through email."
+msgstr "ПоівдомлÑти мене поштою про нові підпиÑки."
+
+#: ../actions/imsettings.php:70
+msgid "Send me notices through Jabber/GTalk."
+msgstr "ПовідомлÑти мене через Jabber/GTalk."
+
+#: ../actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+"ПовідомлÑти мене за допомогою SMS; Я "
+"розімію, що, можливо, понеÑу надмірні витрати від мого мобільного оператора."
+
+#: ../actions/imsettings.php:76
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+"ÐадÑилати також мені відповіді через "
+"Jabber/GTalk від людей, до Ñких Ñ Ð½Ðµ підпиÑаний."
+
+#: ../lib/util.php:294
+msgid "Settings"
+msgstr "ÐалаштуваннÑ"
+
+#: ../actions/profilesettings.php:189
+msgid "Settings saved."
+msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð¾."
+
+#: ../actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr "ПредÑтавлено найбільш популÑрні мітки за минулий тиждень"
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "ХтоÑÑŒ вже кориÑтуєтьÑÑ Ñ†Ð¸Ð¼ OpenID."
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "СталоÑÑ Ñ‰Ð¾ÑÑŒ погане."
+
+#: ../lib/util.php:316
+msgid "Source"
+msgstr "Джерело"
+
+#: ../actions/showstream.php:296
+msgid "Statistics"
+msgstr "СтатиÑтика"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Збережений OpenID не знайдено."
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197
+msgid "Subscribe"
+msgstr "ПідпиÑки"
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "ПідпиÑчики"
+
+#: ../actions/userauthorization.php:310
+msgid "Subscription authorized"
+msgstr "ПідпиÑку авторизовано"
+
+#: ../actions/userauthorization.php:320
+msgid "Subscription rejected"
+msgstr "ПідпиÑку Ñкинуто"
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "ПідпиÑки"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "СиÑтема відповіла помилкою при завантаженні цього файла."
+
+#: ../actions/tag.php:41 ../lib/util.php:289
+msgid "Tags"
+msgstr "Мітки"
+
+#: ../lib/searchaction.php:104
+msgid "Text"
+msgstr "ТекÑÑ‚"
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Пошук теÑкту"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "Цей OpenID вам не належить."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Цю адреÑу вже було підтверджено."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Цей код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ Ð´Ð»Ñ Ð²Ð°Ñ!"
+
+#: ../actions/emailsettings.php:191
+msgid "That email address already belongs to another user."
+msgstr "Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа належить іншому кориÑтувачу."
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "Цей файл завеликий."
+
+#: ../actions/imsettings.php:170
+msgid "That is already your Jabber ID."
+msgstr "Це і так вже ваш Jabber ID."
+
+#: ../actions/emailsettings.php:188
+msgid "That is already your email address."
+msgstr "Це Ñ– так вже ваша адреÑа."
+
+#: ../actions/smssettings.php:188
+msgid "That is already your phone number."
+msgstr "Це і так вже ваш телефонний номер."
+
+#: ../actions/imsettings.php:233
+msgid "That is not your Jabber ID."
+msgstr "Це не ваш Jabber ID."
+
+#: ../actions/emailsettings.php:249
+msgid "That is not your email address."
+msgstr "Це не ваша адреÑа."
+
+#: ../actions/smssettings.php:257
+msgid "That is not your phone number."
+msgstr "Це не ваш телефонний номер."
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+msgid "That is the wrong IM address."
+msgstr "Це помилкова адреÑа IM."
+
+#: ../actions/smssettings.php:233
+msgid "That is the wrong confirmation number."
+msgstr "Це помилковий код підтвердженнÑ."
+
+#: ../actions/smssettings.php:191
+msgid "That phone number already belongs to another user."
+msgstr "Цей телефонний номер належить іншому кориÑтувачу."
+
+#: ../actions/newnotice.php:53
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Задовге. МакÑимальний розмір Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ - 140 знаків."
+
+#: ../actions/confirmaddress.php:88
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "ÐдреÑу \"%s\" було підтверджено Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ рахунку."
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274
+msgid "The address was removed."
+msgstr "ÐдреÑу було видалено."
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"ПідпиÑку було авторизовано, але URL-адреÑа "
+"у відповідь не передавалаÑÑ. ЗвіртеÑÑŒ з "
+"інÑтрукціÑми на Ñайті Ð´Ð»Ñ Ð±Ñ–Ð»ÑŒÑˆ конкретної інформації про те, Ñк авторизувати підпиÑку. Ваш підпиÑний токен:"
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"ПідпиÑку було Ñкинуно, але URL-адреÑа у "
+"відповідь не передавалаÑÑ. ЗвіртеÑÑŒ з "
+"інÑтрукціÑми на Ñайті Ð´Ð»Ñ Ð±Ñ–Ð»ÑŒÑˆ конкретної інформації про те, Ñк Ñкинути підпиÑку."
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Тут предÑтавлені Ñ‚Ñ–, хто Ñлідкує за повідомленнÑми від %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Тут предÑтавлені Ñ‚Ñ–, хто Ñлідкує за вашими повідомленнÑми."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Тут предÑтавлені Ñ‚Ñ–, за чиїми повідомленнÑми Ñлідкує %s."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Тут предÑтавлені Ñ‚Ñ–, за чиїми повідомленнÑми ви Ñлідкуєте."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Цей код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð·Ð°Ñтарий. Будь лаÑка, розпочніть з початку."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Ð¦Ñ Ñ„Ð¾Ñ€Ð¼Ð° повинна автоматично Ñебе "
+"предÑтавити. Якщо цього не ÑталоÑÑ, "
+"натиÑніть на кнопку предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ñ– ви будете перенаправлені до вашого OpenID провайдера."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Ви вперше увійшли до %s, так що ми повинні "
+"приєднати ваш OpenID до локального рахунку. "
+"Ви можете також Ñтворити новий рахунок, або приєднати OpenID до вашого вже Ñ–Ñнуючого рахунку, Ñкщо ви його маєте."
+
+#: ../lib/util.php:151
+msgid "This page is not available in a media type you accept"
+msgstr "Ð¦Ñ Ñторінка не доÑтупна в медіа-типі, Ñкий ви приймаєте"
+
+#: ../actions/profilesettings.php:63
+msgid "Timezone"
+msgstr "ЧаÑовий поÑÑ"
+
+#: ../actions/profilesettings.php:107
+msgid "Timezone not selected."
+msgstr "ЧаÑовий поÑÑ Ð½Ðµ обрано."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Щоб підпиÑатиÑÑŒ, ви можете "
+"[увійти](%%action.login%%), або "
+"[зареєÑтрувати](%%action.register%%) новий рахунок. "
+"Якщо ви вже маєте рахунок на [ÑуміÑному Ñайті](%%doc.openmublog%%), введіть URL-адреÑу вашого профілю."
+
+#: ../actions/twitapifriendships.php:150
+msgid "Two user ids or screen_names must be supplied."
+msgstr "Два ID або імені_у_мережі повинні підтримуватиÑÑŒ."
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:233
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "URL-адреÑа вашої домашньої Ñторінки, блогу, "
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL-адреÑа вашого профілю на іншому ÑуміÑному Ñайті"
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+msgid "Unexpected form submission."
+msgstr "ÐеÑподіване предÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ñ„Ð¾Ñ€Ð¼Ð¸."
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "ÐеÑподіване ÑÐºÐ¸Ð´Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Ðевідома верÑÑ–Ñ Ð¿Ñ€Ð¾Ñ‚Ð¾ÐºÐ¾Ð»Ñƒ OMB."
+
+#: ../lib/util.php:256
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Якщо не зазначено інше, авторÑьке право "
+"на вміÑÑ‚ цього Ñайту належить контрибуторам Ñ– доÑтупний під"
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Ðевизнаний тип адреÑи %s"
+
+#: ../actions/showstream.php:209
+msgid "Unsubscribe"
+msgstr "ВідпиÑатиÑÑŒ"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+msgid "Unsupported OMB version"
+msgstr "ВерÑÑ–Ñ OMB не підтримуєтьÑÑ"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Формат Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ підтримуєтьÑÑ."
+
+#: ../lib/twitterapi.php:257 ../lib/twitterapi.php:278
+msgid "Unsupported type"
+msgstr "Тип не підтримуєтьÑÑ"
+
+#: ../actions/twitapistatuses.php:238
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %1$s та друзів на %2$s!"
+
+#: ../actions/twitapistatuses.php:338
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr "ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ %1$s на %2$s!"
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Завантажити"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Завантажити нову \"avatar\" (Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ "
+"кориÑтувача) можна тут. Ви не зможете "
+"відредагувати Ñвою аватару піÑÐ»Ñ "
+"завантаженнÑ, так що з початку "
+"переконайтеÑÑ, що вона має більш-менш "
+"квадратну форму. Ваше Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ñ‚Ð¸Ð¼ÐµÑ‚ÑŒÑÑ Ð¿Ñ–Ð´ ліцензією Ñайту, також. ВикориÑтовуйте зображеннÑ, Ñкі належать вам, Ñ– Ñкі ви можете вільно демонÑтрувати."
+
+#: ../actions/register.php:227
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+"ВикориÑтовуєтьÑÑ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½ÑŒ, "
+"оголошень та переуÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»ÑŽ"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "КориÑтувача, Ñкий Ñлідкував за вашими повідомленнÑми, більше не Ñ–Ñнує."
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:78
+#: ../actions/twitapistatuses.php:316 ../actions/twitapistatuses.php:621
+msgid "User has no profile."
+msgstr "КориÑтувач не має профілю."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача"
+
+#: ../actions/profilesettings.php:63
+msgid "What timezone are you normally in?"
+msgstr "За Ñким чаÑовим поÑÑом ви живете?"
+
+#: ../lib/util.php:1131
+#, php-format
+msgid "What's up, %s?"
+msgstr "Як ÑÑ Ð¼Ð°Ñ”Ñˆ, %s?"
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:239
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Де ви живете, на зразок \"City, State (or Region), Country\""
+
+#: ../actions/updateprofile.php:128
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Ðеправильний тип Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ '%s'"
+
+#: ../actions/updateprofile.php:123
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Ðеправильний розмір Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ '%s'"
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+msgid "Yes"
+msgstr "Так"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Ви вже маєте цей OpenID!"
+
+#: ../actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+"Ви видалÑєте Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð½Ð°Ð·Ð°Ð²Ð¶Ð´Ð¸. Якщо "
+"ви так зробите, це не матиме зворотньої дії."
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Ви вже в ÑиÑтемі!"
+
+#: ../actions/twitapifriendships.php:115
+msgid "You are not friends with the specified user."
+msgstr "Ви не Ñ” друзÑми із вказаним кориÑтувачем."
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Тут ви можете замінити пароль. Оберіть Ñобі ÑкийÑÑŒ гарний!"
+
+#: ../actions/register.php:209
+msgid "You can create a new account to start posting notices."
+msgstr "Ви можете Ñтворити новий рахунок, щоб почати пиÑати повідомленнÑ."
+
+#: ../actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr "Ви можете отримувати SMS через електронну пошту від %%site.name%%."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Ви можете видалити OpenID із Ñвого рахунку, "
+"Ñкщо натиÑнете кнопку \"Remove\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Ви можете надÑилати на отримувати "
+"Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· Jabber/GTalk [Ñлужбу "
+"миттевих повідомлень](%%doc.im%%). Вкажить Ñвою адреÑу Ñ– налаштуйте це нижче."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Ви можете доповнити Ñвій оÑобиÑтий "
+"профіль, так що люди знатимуть про Ð²Ð°Ñ Ð±Ñ–Ð»ÑŒÑˆÐµ."
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Ви можете кориÑтуватиÑÑŒ локальними підпиÑками!"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:57
+msgid "You can't register if you don't agree to the license."
+msgstr "Ви не зможете зареєÑтруватиÑÑŒ, Ñкщо не погодитеÑÑŒ з умовами ліцензії."
+
+#: ../actions/updateprofile.php:63
+msgid "You did not send us that profile"
+msgstr "Ви не надÑилали нам цього профілю"
+
+#: ../lib/mail.php:143
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+"Ви маєте нову поштову адреÑу на %1$s.\n"
+"\n"
+"ÐадÑилайте лиÑти на %2$s, щоб друкувати "
+"нові повідомленнÑ.\n"
+"\n"
+"Більше інформації про викориÑÑ‚Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти на %3$s.\n\nЩиро ваші,\n%4$s"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Ð’Ð°Ñ Ñ–Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ¾Ð²Ð°Ð½Ð¾. Введіть новий пароль нижче."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "URL-адреÑа вашого OpenID"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+"Ваше ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача на цьому Ñервері, "
+"або зареєÑтрована електронна адреÑа."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) дає вам можливіÑÑ‚ÑŒ "
+"реєÑтруватиÑÑ Ð½Ð° багатьох Ñайтах, "
+"кориÑтуючиÑÑŒ єдиним рахунком. Керувати вашими OpenID можна звідÑи."
+
+#: ../lib/util.php:919
+msgid "a few seconds ago"
+msgstr "кілька Ñекунд тому"
+
+#: ../lib/util.php:931
+#, php-format
+msgid "about %d days ago"
+msgstr "близько %d днів тому"
+
+#: ../lib/util.php:927
+#, php-format
+msgid "about %d hours ago"
+msgstr "близько %d годин тому"
+
+#: ../lib/util.php:923
+#, php-format
+msgid "about %d minutes ago"
+msgstr "близько %d хвилин тому"
+
+#: ../lib/util.php:935
+#, php-format
+msgid "about %d months ago"
+msgstr "близько %d міÑÑців тому"
+
+#: ../lib/util.php:929
+msgid "about a day ago"
+msgstr "день тому"
+
+#: ../lib/util.php:921
+msgid "about a minute ago"
+msgstr "хвилину тому"
+
+#: ../lib/util.php:933
+msgid "about a month ago"
+msgstr "міÑÑць тому"
+
+#: ../lib/util.php:937
+msgid "about a year ago"
+msgstr "рік тому"
+
+#: ../lib/util.php:925
+msgid "about an hour ago"
+msgstr "годину тому"
+
+#: ../actions/showstream.php:424 ../lib/stream.php:130
+msgid "delete"
+msgstr "видалити"
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:408
+#: ../lib/stream.php:114
+msgid "in reply to..."
+msgstr "у відповідь на..."
+
+#: ../lib/twitterapi.php:363
+msgid "not a supported data format"
+msgstr "формат даних не підтримуєтьÑÑ"
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:415
+#: ../lib/stream.php:121
+msgid "reply"
+msgstr "відповіÑти"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "такий же, Ñк пароль вище"
+
+#: ../actions/twitapistatuses.php:691
+msgid "unsupported file type"
+msgstr "тип файлу не підтримуєтьÑÑ"
+
+#: ../lib/util.php:1281
+msgid "« After"
+msgstr "« Вперед"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/locale/vi_VN/LC_MESSAGES/laconica.mo b/locale/vi_VN/LC_MESSAGES/laconica.mo
new file mode 100644
index 000000000..aecd4454d
--- /dev/null
+++ b/locale/vi_VN/LC_MESSAGES/laconica.mo
Binary files differ
diff --git a/locale/vi_VN/LC_MESSAGES/laconica.po b/locale/vi_VN/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..3ab63f8c4
--- /dev/null
+++ b/locale/vi_VN/LC_MESSAGES/laconica.po
@@ -0,0 +1,3157 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: laconica\n"
+"Report-Msgid-Bugs-To: mikec@resnet.net.nz\n"
+"POT-Creation-Date: 2008-07-13 17:15+1200\n"
+"PO-Revision-Date: \n"
+"Last-Translator: LienNguyen <lien.nguyen@anhone.vn>\n"
+"Language-Team: <support@saigonica.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Language: English\n"
+"X-Poedit-Country: NEW ZEALAND\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr " Tìm dòng thông tin cho \"%s\""
+
+#: ../actions/finishopenidlogin.php:82
+#: ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr " ngoại trừ thông tin riêng: mật khẩu, email, địa chỉ IM, số điện thoại"
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s dang theo doi tin nhan cua ban tren %2$s."
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s đang theo dõi các tin nhắn của bạn trên %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"NgÆ°á»i bạn trung thành của bạn,\n%4$s.\n"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "Trạng thái của %1$s vào %2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s Dòng tin công cộng"
+
+#: ../actions/all.php:47
+#: ../actions/allrss.php:70
+#: ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s và bạn bè"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** là dịch vụ gửi tin nhắn được cung cấp từ "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** là dịch vụ gửi tin nhắn. "
+
+#: ../lib/util.php:250
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "NgÆ°á»i đăng ký nên được phân theo tên hoặc nickname"
+
+#: ../actions/finishopenidlogin.php:73
+#: ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr ""
+"1-64 chữ cái thÆ°á»ng hoặc là chữ số, không có dấu chấm hay "
+
+#: ../actions/password.php:42
+#: ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "Nhiá»u hÆ¡n 6 ký tá»±"
+
+#: ../actions/register.php:178
+msgid " Required."
+msgstr " Bắt buộc."
+
+#: ../actions/register.php:178
+msgid "Longer name, preferably your \"real\" name"
+msgstr "HỠtên đầy đủ của bạn, tốt nhất là tên thật của bạn."
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "Nhiá»u hÆ¡n 6 ký tá»±, đừng quên nó!"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr ""
+"Mã xác nhận đã được gửi đến địa chỉ IM. Bạn phải "
+"chấp nhận %s để có thể gửi tin nhắn đến bạn."
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "Giới thiệu"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "Chấp nhận"
+
+#: ../actions/imsettings.php:64
+#: ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "Thêm"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "Thêm OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "Äịa chỉ"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "Tất cả đăng nhận"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "Tất cả các cập nhật của %s"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "Các thay đổi phù hợp với từ \"%s\""
+
+#: ../actions/finishopenidlogin.php:29
+#: ../actions/login.php:27
+#: ../actions/openidlogin.php:29
+#: ../actions/register.php:28
+msgid "Already logged in."
+msgstr "Äã đăng nhập."
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "Äã đăng nhận rồi!"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "Äăng nhận cho phép"
+
+#: ../actions/login.php:100
+#: ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "Sẽ tự động đăng nhập, không dành cho các máy sử dụng chung!"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "Hình đại diện"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "Hình đại diện đã được cập nhật."
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"Äang đợi xác nhận đến địa chỉ này. Hãy kiểm tra tài "
+"khoản Jabber/GTalk để nhận tin nhắn và lá»i hÆ°á»›ng dẫn. "
+"(Bạn đã thêm %s vào danh sách bạn thân chưa?)"
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+"Äang đợi xác nhận đến địa chỉ này. Hãy kiểm tra há»™p "
+"thÆ° đến (hoặc thÆ° rác) để nhận tin nhắn và lá»i hÆ°á»›ng dẫn."
+
+#: ../lib/util.php:1136
+msgid "Before"
+msgstr "TrÆ°á»›c"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "Lý lịch"
+
+#: ../actions/profilesettings.php:93
+#: ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "Lý lịch quá dài (không quá 140 ký tự)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "Không thể Ä‘á»c URL cho hình đại diện '%s'"
+
+#: ../actions/password.php:85
+#: ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "Không thể lưu mật khẩu mới"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "Hủy"
+
+#: ../lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "Không thể thiết lập đối tượng OpenID."
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "Không thể bình thÆ°á»ng hóa Jabber ID"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "Thay đổi"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "Äổi mật khẩu"
+
+#: ../actions/password.php:43
+#: ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "Xác nhận"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "Xác nhận địa chỉ"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "Sá»± xác nhận đã bị hủy bá»."
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "Không tìm thấy mã xác nhận."
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "Kết nối"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "Kết nối đến tài khoản hiện hữu"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "Liên hệ"
+
+#: ../lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "Không thể tạo OpenID mẫu: %s"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "Không thể chuyển đến máy chủ: %s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "Không thể lưu hình đại diện"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "Không thể lưu thông tin vỠhồ sơ cá nhân"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "Không thể xác nhận địa chỉ email."
+
+#: ../actions/finishremotesubscribe.php:99
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "Không thể chuyển các token yêu cầu đến token truy cập."
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "Không thể tạo đăng nhận."
+
+#: ../actions/confirmaddress.php:78
+#: ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "Không thể xóa email xác nhận."
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "Không thể xóa đăng nhận."
+
+#: ../actions/remotesubscribe.php:125
+msgid "Couldn't get a request token."
+msgstr "Không thể lấy token yêu cầu."
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "Không thể chèn mã xác nhận."
+
+#: ../actions/finishremotesubscribe.php:180
+msgid "Couldn't insert new subscription."
+msgstr "Không thể chèn thêm vào đăng nhận."
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "Không thể lưu hồ sơ cá nhân."
+
+#: ../actions/confirmaddress.php:70
+#: ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234
+#: ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "Không thể cập nhật thành viên."
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "Tạo"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "Tạo ngÆ°á»i dùng má»›i vá»›i tên đăng nhập này."
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "Tạo tài khoản mới"
+
+#: ../actions/finishopenidlogin.php:191
+msgid "Creating new account for OpenID that already has a user."
+msgstr "Tạo tài khoản mới hoặc dùng OpenID"
+
+#: ../actions/imsettings.php:45
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "Äịa chỉ Jabber/GTalk vừa được xác nhận."
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "Hiện tại"
+
+#: ../lib/util.php:893
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "Lá»—i cÆ¡ sở dữ liệu khi chèn trả lá»i: %s"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe your group's interest in 140 characters"
+msgstr "Nói vỠnhững sở thích của nhóm trong vòng 140 ký tự"
+
+#: ../actions/register.php:181
+msgid "Email"
+msgstr "Email"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "Äịa chỉ email"
+
+#: ../actions/profilesettings.php:102
+#: ../actions/register.php:63
+msgid "Email address already exists."
+msgstr "Äịa chỉ email đã tồn tại."
+
+#: ../lib/mail.php:82
+msgid "Email address confirmation"
+msgstr "Xac nhan dia chi email"
+
+#: ../actions/recoverpassword.php:176
+msgid "Enter a nickname or email address."
+msgstr "Nhập biệt hiệu hoặc email."
+
+#: ../actions/userauthorization.php:136
+msgid "Error authorizing token"
+msgstr "Lỗi cho phép token"
+
+#: ../actions/finishopenidlogin.php:282
+msgid "Error connecting user to OpenID."
+msgstr "Lỗi xảy ra khi kết nối với OpenId"
+
+#: ../actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "Lá»—i khi kết nối ngÆ°á»i dùng."
+
+#: ../actions/finishremotesubscribe.php:151
+msgid "Error inserting avatar"
+msgstr "Lỗi xảy ra khi thêm mới hình đại diện"
+
+#: ../actions/finishremotesubscribe.php:143
+msgid "Error inserting new profile"
+msgstr "Lỗi xảy ra khi thêm mới hồ sơ cá nhân"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "Lỗi xảy ra khi thêm mới tin nhắn"
+
+#: ../actions/finishremotesubscribe.php:167
+msgid "Error inserting remote profile"
+msgstr "Lỗi xảy ra khi thêm mới hồ sơ cá nhân"
+
+#: ../actions/recoverpassword.php:201
+msgid "Error saving address confirmation."
+msgstr "Lỗi xảy ra khi lưu địa chỉ đã được xác nhận."
+
+#: ../actions/userauthorization.php:139
+msgid "Error saving remote profile"
+msgstr "Lỗi xảy ra khi lưu hồ sơ cá nhân"
+
+#: ../actions/finishopenidlogin.php:222
+#: ../lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "Lỗi xảy ra khi lưu hồ sơ cá nhân."
+
+#: ../lib/openid.php:237
+msgid "Error saving the user."
+msgstr "Lỗi xảy ra khi lưu thành viên."
+
+#: ../actions/password.php:80
+msgid "Error saving user; invalid."
+msgstr "Lỗi xảy ra khi lưu thành viên; không hợp lệ."
+
+#: ../actions/login.php:43
+#: ../actions/login.php:69
+#: ../actions/recoverpassword.php:268
+#: ../actions/register.php:73
+msgid "Error setting user."
+msgstr "Lỗi xảy ra khi tạo thành viên."
+
+#: ../actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "Lỗi xảy ra khi cập nhật hồ sơ cá nhân"
+
+#: ../actions/finishremotesubscribe.php:161
+msgid "Error updating remote profile"
+msgstr "Lỗi xảy ra khi cập nhật hồ sơ cá nhân"
+
+#: ../actions/recoverpassword.php:79
+msgid "Error with confirmation code."
+msgstr "Lỗi xảy ra với mã xác nhận."
+
+#: ../actions/finishopenidlogin.php:89
+msgid "Existing nickname"
+msgstr "Biệt hiệu này đã tồn tại"
+
+#: ../lib/util.php:298
+msgid "FAQ"
+msgstr "FAQ"
+
+msgid "FAQ "
+msgstr "FAQ - Há»i đáp"
+
+#: ../actions/avatar.php:115
+msgid "Failed updating avatar."
+msgstr "Cập nhật hình đại diện không thành công."
+
+#: ../actions/all.php:61
+#: ../actions/allrss.php:74
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "Chá»n những ngÆ°á»i bạn của %s"
+
+#: ../actions/replies.php:61
+#: ../actions/repliesrss.php:80
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "Chá»n các phản hồi đến %s"
+
+#: ../actions/profilesettings.php:44
+msgid "Full name"
+msgstr "Tên đầy đủ"
+
+#: ../actions/profilesettings.php:90
+#: ../actions/updateprofile.php:92
+msgid "Full name is too long (max 255 chars)."
+msgstr "Tên đầy đủ quá dài (tối đa là 255 ký tự)."
+
+#: ../lib/util.php:279
+msgid "Help"
+msgstr "Hướng dẫn"
+
+#: ../lib/util.php:274
+msgid "Home"
+msgstr "Trang chủ"
+
+#: ../actions/profilesettings.php:49
+msgid "Homepage"
+msgstr "Trang chủ hoặc Blog"
+
+#: ../actions/profilesettings.php:87
+msgid "Homepage is not a valid URL."
+msgstr "Trang chủ không phải là URL"
+
+#: ../actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM"
+
+#: ../actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "Cấu hình IM"
+
+#: ../actions/finishopenidlogin.php:88
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr ""
+"Bạn đã có tài khoản rồi, hãy đăng nhập bằng tên đăng "
+"nhập và mật khẩu để kết nối với OpenId của bạn."
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr ""
+"Bạn muốn thêm một OpenId vào tài khoản của bạn, đánh nó "
+"vào hộp dưới đây và nhấn\" Thêm\"."
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+"Nếu bạn đã quên hoặc mất mật khẩu, bạn có thể tạo "
+"mới và được gửi đến địa chỉ email lưu trong tài khoản của bạn."
+
+#: ../actions/password.php:69
+msgid "Incorrect old password"
+msgstr "Mật khẩu cũ sai"
+
+#: ../actions/login.php:63
+msgid "Incorrect username or password."
+msgstr "Sai tên đăng nhập hoặc mật khẩu."
+
+#: ../actions/recoverpassword.php:226
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr ""
+"Hướng dẫn cách khôi phục mật khẩu đã được gửi đến "
+"địa chỉ email đăng ký trong tài khoản của bạn."
+
+#: ../actions/updateprofile.php:113
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "URL cho hình đại diện '%s' không hợp lệ "
+
+#: ../actions/updateprofile.php:97
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "Trang chủ '%s' không hợp lệ"
+
+#: ../actions/updateprofile.php:81
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "URL cấp phép '%s' không hợp lệ"
+
+#: ../actions/postnotice.php:61
+msgid "Invalid notice content"
+msgstr "Nội dung tin nhắn không hợp lệ"
+
+#: ../actions/postnotice.php:67
+msgid "Invalid notice uri"
+msgstr "URI tin nhắn không hợp lệ"
+
+#: ../actions/postnotice.php:72
+msgid "Invalid notice url"
+msgstr "URL tin nhắn không hợp lệ"
+
+#: ../actions/updateprofile.php:86
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "URL hồ sơ cá nhân của '%s' không hợp lệ"
+
+#: ../actions/remotesubscribe.php:96
+msgid "Invalid profile URL (bad format)"
+msgstr "URL hồ sơ cá nhân không đúng định dạng."
+
+#: ../actions/finishremotesubscribe.php:77
+msgid "Invalid profile URL returned by server."
+msgstr "URL hồ sơ cá nhân không hợp lệ."
+
+#: ../actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "Kích thước không hợp lệ."
+
+#: ../actions/finishopenidlogin.php:264
+#: ../actions/register.php:68
+#: ../actions/register.php:84
+msgid "Invalid username or password."
+msgstr "Tên đăng nhập hoặc mật khẩu không hợp lệ."
+
+#: ../lib/util.php:237
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public License] "
+"(http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Microblogging [Laconica](http://laconi.ca/), version %s đã có ở [GNU "
+"Affero General Public License] "
+"(http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+#: ../actions/imsettings.php:164
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID này đã thuá»™c vá» ngÆ°á»i khác rồi."
+
+#: ../actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Äịa chỉ Jabber hoặc GTalk, giống nhÆ° \"UserName@example.org\". "
+"Äầu tiên, hãy tạo thêm %s vào danh sách buddy trên IM client hoặc GTalk của bạn."
+
+#: ../actions/profilesettings.php:55
+msgid "Location"
+msgstr "Thành phố"
+
+#: ../actions/profilesettings.php:96
+#: ../actions/updateprofile.php:107
+msgid "Location is too long (max 255 chars)."
+msgstr "Tên khu vực quá dài (không quá 255 ký tự)."
+
+#: ../actions/login.php:93
+#: ../actions/login.php:102
+#: ../actions/openidlogin.php:68
+#: ../lib/util.php:286
+msgid "Login"
+msgstr "Äăng nhập"
+
+#: ../actions/openidlogin.php:44
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "Äăng nhập bằng tài khoản [OpenID](%%doc.openid%%)."
+
+#: ../actions/login.php:112
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"Hãy đăng nhập với tên đăng nhập và mật khẩu của bạn. "
+"Nếu bạn chưa có tài khoản, [hãy đăng ký](%%action.register%%) "
+"tài khoản mới, hoặc thử đăng nhập bằng [OpenID](%%action.openidlogin%%). "
+
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account."
+msgstr ""
+"Hãy đăng nhập với tên đăng nhập và mật khẩu của bạn. "
+"Nếu bạn chưa có tài khoản, [hãy đăng ký](%%action.register%%) tài khoản mới."
+
+#: ../lib/util.php:284
+msgid "Logout"
+msgstr "Thoát"
+
+#: ../actions/login.php:106
+msgid "Lost or forgotten password?"
+msgstr "Mất hoặc quên mật khẩu?"
+
+#: ../actions/showstream.php:281
+msgid "Member since"
+msgstr "Gia nhập từ"
+
+#: ../actions/userrss.php:70
+#, php-format
+msgid "Microblog by %s"
+msgstr "Microblog bởi %s"
+
+#: ../actions/finishopenidlogin.php:79
+#: ../actions/register.php:190
+msgid "My text and files are available under "
+msgstr "Ghi chú và các file của tôi đã có ở phía dưới"
+
+#: ../actions/finishopenidlogin.php:71
+msgid "New nickname"
+msgstr "Biệt hiệu mới"
+
+#: ../actions/newnotice.php:100
+msgid "New notice"
+msgstr "Thông báo mới"
+
+#: ../actions/password.php:41
+#: ../actions/recoverpassword.php:164
+msgid "New password"
+msgstr "Mật khẩu mới"
+
+#: ../actions/recoverpassword.php:275
+msgid "New password successfully saved. You are now logged in."
+msgstr "Mật khẩu má»›i đã được lÆ°u. Bạn có thể đăng nhập ngay bây giá»."
+
+#: ../actions/login.php:97
+#: ../actions/profilesettings.php:41
+#: ../actions/register.php:175
+msgid "Nickname"
+msgstr "Biệt danh"
+
+#: ../actions/finishopenidlogin.php:175
+#: ../actions/profilesettings.php:99
+#: ../actions/register.php:59
+msgid "Nickname already in use. Try another one."
+msgstr "Biệt hiệu này đã dùng rồi. Hãy nhập biệt hiệu khác."
+
+#: ../actions/finishopenidlogin.php:165
+#: ../actions/profilesettings.php:80
+#: ../actions/register.php:57
+#: ../actions/updateprofile.php:76
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "Biệt hiệu phải là chữ viết thÆ°á»ng hoặc số và không có khoảng trắng."
+
+#: ../actions/finishopenidlogin.php:170
+msgid "Nickname not allowed."
+msgstr "Biệt hiệu không được cho phép."
+
+#: ../actions/remotesubscribe.php:72
+msgid "Nickname of the user you want to follow"
+msgstr "Biệt hiệu của thành viên mà bạn muốn theo"
+
+#: ../actions/recoverpassword.php:147
+msgid "Nickname or email"
+msgstr "Biệt hiệu hoặc email"
+
+#: ../actions/imsettings.php:147
+msgid "No Jabber ID."
+msgstr "Không có Jabber ID."
+
+#: ../actions/userauthorization.php:128
+msgid "No authorization request!"
+msgstr "Không có yêu cầu!"
+
+#: ../actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "Không có mã số xác nhận."
+
+#: ../actions/newnotice.php:49
+msgid "No content!"
+msgstr "Không có nội dung!"
+
+#: ../actions/userbyid.php:27
+msgid "No id."
+msgstr "Không có id."
+
+#: ../actions/finishremotesubscribe.php:65
+msgid "No nickname provided by remote server."
+msgstr "Không có biệt hiệu được cung cấp."
+
+#: ../actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "Không có biệt hiệu."
+
+#: ../actions/imsettings.php:197
+msgid "No pending confirmation to cancel."
+msgstr "Sá»± xác nhận chÆ°a được hủy bá»."
+
+#: ../actions/finishremotesubscribe.php:72
+msgid "No profile URL returned by server."
+msgstr "Không có URL cho hồ sÆ¡ để quay vá»."
+
+#: ../actions/recoverpassword.php:189
+msgid "No registered email address for that user."
+msgstr "Thành viên này đã không đăng ký địa chỉ email."
+
+#: ../actions/userauthorization.php:48
+msgid "No request found!"
+msgstr "Không tìm thấy yêu cầu nào!"
+
+#: ../actions/noticesearch.php:64
+#: ../actions/peoplesearch.php:64
+msgid "No results"
+msgstr "Không có kết quả nào"
+
+#: ../actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "Không có kích thước."
+
+#: ../actions/openidsettings.php:135
+msgid "No such OpenID."
+msgstr "Không có OpenID nào."
+
+#: ../actions/doc.php:29
+msgid "No such document."
+msgstr "Không có tài liệu nào."
+
+#: ../actions/shownotice.php:32
+#: ../actions/shownotice.php:65
+msgid "No such notice."
+msgstr "Không có tin nhắn nào."
+
+#: ../actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "Không có mã khôi phục nào."
+
+#: ../actions/postnotice.php:56
+msgid "No such subscription"
+msgstr "Không có đăng ký nào."
+
+#: ../actions/all.php:34
+#: ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43
+#: ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185
+#: ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91
+#: ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95
+#: ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38
+#: ../actions/userbyid.php:31
+#: ../actions/userrss.php:35
+#: ../actions/xrds.php:31
+#: ../lib/gallery.php:53
+msgid "No such user."
+msgstr "Không có user nào."
+
+#: ../lib/gallery.php:76
+msgid "Nobody to show!"
+msgstr "Không có ai!"
+
+#: ../actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "Mã khôi phục không đúng."
+
+#: ../actions/imsettings.php:158
+msgid "Not a valid Jabber ID"
+msgstr "Jabber ID không hợp lệ"
+
+#: ../lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "OpenID không hợp lệ."
+
+#: ../actions/profilesettings.php:75
+#: ../actions/register.php:53
+msgid "Not a valid email address."
+msgstr "Äịa chỉ email không hợp lệ."
+
+#: ../actions/profilesettings.php:83
+#: ../actions/register.php:61
+msgid "Not a valid nickname."
+msgstr "Biệt hiệu không hợp lệ."
+
+#: ../actions/remotesubscribe.php:118
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "Không phải là URL vỠhồ sơ cá nhân hợp lệ (dịch vụ không xác định)."
+
+#: ../actions/remotesubscribe.php:111
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "Không phải là URL vỠhồ sơ cá nhân hợp lệ (chưa định nghĩa XRDS)."
+
+#: ../actions/remotesubscribe.php:104
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+"Không phải là URL vỠhồ sơ cá nhân hợp lệ (không phải là "
+
+#: ../actions/avatar.php:95
+msgid "Not an image or corrupt file."
+msgstr "File há»ng hoặc không phải là file ảnh."
+
+#: ../actions/finishremotesubscribe.php:51
+msgid "Not authorized."
+msgstr "Chưa được phép."
+
+#: ../actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "Không mong đợi trả lá»i lại!"
+
+#: ../actions/finishaddopenid.php:29
+#: ../actions/logout.php:28
+#: ../actions/newnotice.php:29
+#: ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24
+#: ../lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "Chưa đăng nhập."
+
+#: ../actions/unsubscribe.php:43
+msgid "Not subscribed!."
+msgstr "Chưa đăng nhận!"
+
+#: ../actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "Dòng tin nhắn cho %s"
+
+#: ../actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "Tin nhắn không có hồ sơ cá nhân"
+
+#: ../actions/showstream.php:297
+msgid "Notices"
+msgstr "Tin nhắn"
+
+#: ../actions/password.php:39
+msgid "Old password"
+msgstr "Mật khẩu cũ"
+
+#: ../lib/util.php:288
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61
+msgid "OpenID Account Setup"
+msgstr "Tạo tài khoản OpenID"
+
+#: ../lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "Tự động nhập OpenID"
+
+#: ../actions/finishaddopenid.php:99
+#: ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60
+msgid "OpenID Login"
+msgstr "Äăng nhập OpenID"
+
+#: ../actions/openidlogin.php:65
+#: ../actions/openidsettings.php:49
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42
+#: ../actions/finishopenidlogin.php:103
+msgid "OpenID authentication cancelled."
+msgstr "Xác thực OpenID bị hủy."
+
+#: ../actions/finishaddopenid.php:46
+#: ../actions/finishopenidlogin.php:107
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "Xác thực OpendID bị lỗi: %s"
+
+#: ../lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID lá»—i: %s"
+
+#: ../actions/openidsettings.php:144
+msgid "OpenID removed."
+msgstr "OpenID đã xóa."
+
+#: ../actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "Cấu hình OpenID"
+
+#: ../actions/avatar.php:84
+msgid "Partial upload."
+msgstr "Upload từng phần."
+
+#: ../actions/finishopenidlogin.php:90
+#: ../actions/login.php:98
+#: ../actions/register.php:177
+msgid "Password"
+msgstr "Mật khẩu"
+
+#: ../actions/recoverpassword.php:249
+msgid "Password and confirmation do not match."
+msgstr "Mật khẩu và mật khẩu xác nhận không khớp nhau."
+
+#: ../actions/recoverpassword.php:245
+msgid "Password must be 6 chars or more."
+msgstr "Mật khẩu phải nhiá»u hÆ¡n 6 ký tá»±."
+
+#: ../actions/recoverpassword.php:222
+#: ../actions/recoverpassword.php:224
+msgid "Password recovery requested"
+msgstr "Yêu cầu khôi phục lại mật khẩu đã được gửi"
+
+msgid "Password recovery requested "
+msgstr "Yeu cau khoi phuc lai mat khau da duoc gui"
+
+#: ../actions/password.php:89
+#: ../actions/recoverpassword.php:274
+msgid "Password saved."
+msgstr "Äã lÆ°u mật khẩu."
+
+#: ../actions/password.php:61
+#: ../actions/register.php:65
+msgid "Passwords don't match."
+msgstr "Mật khẩu không khớp."
+
+#: ../actions/peoplesearch.php:33
+msgid "People search"
+msgstr "Tìm kiếm nhiá»u ngÆ°á»i"
+
+#: ../lib/stream.php:44
+msgid "Personal"
+msgstr "Cá nhân"
+
+#: ../actions/userauthorization.php:77
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+"Vui lòng kiểm tra các chi tiết để chắc chắn rằng bạn muốn "
+"đăng nhận xem tin nhắn của các thành viên này. Nếu bạn "
+"không yêu cầu đăng nhận xem tin nhắn của há», hãy nhấn \"Hủy bá»\""
+
+#: ../actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+"Gửi một tin nhắn khi trạng thái của tôi trên Jabber hay GTalk "
+
+#: ../actions/imsettings.php:68
+msgid "Preferences"
+msgstr "Tính năng"
+
+#: ../actions/imsettings.php:135
+msgid "Preferences saved."
+msgstr "Các tính năng đã được lưu."
+
+#: ../lib/util.php:300
+msgid "Privacy"
+msgstr "Riêng tư"
+
+#: ../actions/newnotice.php:61
+#: ../actions/newnotice.php:69
+msgid "Problem saving notice."
+msgstr "Có lỗi xảy ra khi lưu tin nhắn."
+
+#: ../lib/stream.php:54
+msgid "Profile"
+msgstr "Hồ sơ "
+
+#: ../actions/remotesubscribe.php:73
+msgid "Profile URL"
+msgstr "URL của Hồ sơ cá nhân"
+
+#: ../actions/profilesettings.php:34
+msgid "Profile settings"
+msgstr "Các thiết lập cho Hồ sơ cá nhân"
+
+#: ../actions/postnotice.php:51
+#: ../actions/updateprofile.php:51
+msgid "Profile unknown"
+msgstr "Hồ sơ này không biết"
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr "Công cộng"
+
+#: ../actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "Dòng tin công cộng"
+
+#: ../actions/public.php:33
+msgid "Public timeline"
+msgstr "Dòng tin công cộng"
+
+#: ../actions/recoverpassword.php:151
+msgid "Recover"
+msgstr "Khôi phục"
+
+#: ../actions/recoverpassword.php:141
+msgid "Recover password"
+msgstr "Khôi phục mật khẩu"
+
+#: ../actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "Khôi phục lại code cho user không đăng ký."
+
+#: ../actions/register.php:171
+#: ../actions/register.php:195
+#: ../lib/util.php:287
+msgid "Register"
+msgstr "Äăng ký"
+
+#: ../actions/userauthorization.php:119
+msgid "Reject"
+msgstr "Từ chối"
+
+#: ../actions/login.php:99
+#: ../actions/register.php:183
+msgid "Remember me"
+msgstr "Nhớ tôi"
+
+#: ../actions/updateprofile.php:69
+msgid "Remote profile with no matching profile"
+msgstr "Hồ sơ ở nơi khác không khớp với hồ sơ này của bạn"
+
+#: ../actions/remotesubscribe.php:65
+msgid "Remote subscribe"
+msgstr "Äăng nhận từ xa"
+
+#: ../actions/imsettings.php:48
+#: ../actions/openidsettings.php:106
+msgid "Remove"
+msgstr "Xóa"
+
+#: ../actions/openidsettings.php:68
+msgid "Remove OpenID"
+msgstr "Xóa OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+"Việc xóa OpenID của bạn có thể sẽ không đăng nhập được! "
+"Nếu bạn cần xóa nó, hãy tạo một OpenID khác trước đã."
+
+#: ../lib/stream.php:49
+msgid "Replies"
+msgstr "Trả lá»i"
+
+#: ../actions/replies.php:47
+#: ../actions/repliesrss.php:76
+#: ../lib/stream.php:50
+#, php-format
+msgid "Replies to %s"
+msgstr "Trả lá»i cho %s"
+
+#: ../actions/recoverpassword.php:168
+msgid "Reset"
+msgstr "Khởi tạo"
+
+#: ../actions/recoverpassword.php:158
+msgid "Reset password"
+msgstr "Khởi tạo lại mật khẩu"
+
+#: ../actions/recoverpassword.php:167
+#: ../actions/register.php:180
+msgid "Same as password above"
+msgstr "Cùng mật khẩu ở trên"
+
+#: ../actions/imsettings.php:76
+#: ../actions/profilesettings.php:58
+msgid "Save"
+msgstr "LÆ°u"
+
+#: ../lib/searchaction.php:73
+#: ../lib/util.php:277
+msgid "Search"
+msgstr "Tìm kiếm"
+
+#: ../actions/noticesearch.php:80
+msgid "Search Stream Feed"
+msgstr "Tìm kiếm dòng thông tin"
+
+#: ../actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+"Tìm kiếm những tin nhắn trên %%site.name%% bằng nội dung. Chia "
+"các cụm từ cần tìm bởi khoảng trắng; và phải là 3 ký tự trở lên."
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+"Tìm kiếm những ngÆ°á»i trên %%site.name%% bằng tên, vị trí, "
+"hoặc sở thích của há». Chia các cụm từ bởi khoảng trắng; và phải là 3 ký tá»± trở lên."
+
+#: ../lib/util.php:982
+msgid "Send"
+msgstr "Gá»­i"
+
+#: ../actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "Hãy gửi tin nhắn đến tôi qua Jabber hay GTalk"
+
+#: ../lib/util.php:282
+msgid "Settings"
+msgstr "Äiá»u chỉnh"
+
+#: ../actions/profilesettings.php:183
+msgid "Settings saved."
+msgstr "Äã lÆ°u các Ä‘iá»u chỉnh."
+
+#: ../actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "Äã có ngÆ°á»i sá»­ dụng OpenID này rồi."
+
+#: ../actions/finishopenidlogin.php:42
+#: ../actions/openidsettings.php:126
+msgid "Something weird happened."
+msgstr "Vài Ä‘iá»u bất thÆ°á»ng xảy ra."
+
+#: ../lib/util.php:302
+msgid "Source"
+msgstr "Nguồn"
+
+#: ../actions/showstream.php:277
+msgid "Statistics"
+msgstr "Số liệu thống kê"
+
+#: ../actions/finishopenidlogin.php:182
+#: ../actions/finishopenidlogin.php:275
+msgid "Stored OpenID not found."
+msgstr "Không tìm thấy OpenID."
+
+#: ../actions/remotesubscribe.php:75
+#: ../actions/showstream.php:172
+#: ../actions/showstream.php:181
+msgid "Subscribe"
+msgstr "Theo bạn này"
+
+#: ../actions/showstream.php:294
+#: ../actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "Bạn này theo tôi"
+
+#: ../actions/showstream.php:294
+#: ../actions/subscribers.php:27
+msgid "Subscribers "
+msgstr "Theo tôi"
+
+#: ../actions/userauthorization.php:309
+msgid "Subscription authorized"
+msgstr "Äăng nhận được phép"
+
+#: ../actions/userauthorization.php:319
+msgid "Subscription rejected"
+msgstr "Äăng nhận từ chối"
+
+#: ../actions/showstream.php:212
+#: ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "Tôi theo bạn này"
+
+#: ../actions/showstream.php:212
+#: ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27
+msgid "Subscriptions "
+msgstr "Tôi theo"
+
+#: ../actions/avatar.php:87
+msgid "System error uploading file."
+msgstr "Hệ thống xảy ra lỗi trong khi tải file."
+
+#: ../actions/noticesearch.php:34
+msgid "Text search"
+msgstr "Chuỗi cần tìm"
+
+#: ../actions/openidsettings.php:140
+msgid "That OpenID does not belong to you."
+msgstr "OpenID này không phải của bạn."
+
+#: ../actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "Äịa chỉ đó đã được xác nhận rồi."
+
+#: ../actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "Mã xác nhận này không phải của bạn!"
+
+#: ../actions/avatar.php:80
+msgid "That file is too big."
+msgstr "File quá lớn."
+
+#: ../actions/imsettings.php:161
+msgid "That is already your Jabber ID."
+msgstr "Tài khoản đó đã là tên tài khoản Jabber của bạn rồi."
+
+#: ../actions/imsettings.php:224
+msgid "That is not your Jabber ID."
+msgstr "Äây không phải Jabber ID của bạn."
+
+#: ../actions/imsettings.php:201
+msgid "That is the wrong IM address."
+msgstr "Sai IM."
+
+#: ../actions/newnotice.php:52
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "Quá dài. Tối đa là 140 ký tự."
+
+#: ../actions/confirmaddress.php:86
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "Äịa chỉ \"%s\" đã được xác nhận từ tài khoản của bạn."
+
+#: ../actions/imsettings.php:241
+msgid "The address was removed."
+msgstr "Äã xóa địa chỉ."
+
+#: ../actions/userauthorization.php:311
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+"Äăng nhận được phép, nhÆ°ng URL trả lại không được gởi "
+"trả. Hãy kiểm tra các hướng dẫn chi tiết trên site để "
+"biết cách cho phép đăng ký. Äăng nhận token của bạn là:"
+
+#: ../actions/userauthorization.php:321
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+"Äăng nhận này đã bị từ chối, nhÆ°ng không có URL nào để "
+"quay vá». Hãy kiểm tra các hÆ°á»›ng dẫn chi tiết trên site để "
+
+#: ../actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "Có nhiá»u ngÆ°á»i nghe theo lá»i nhắn của %s."
+
+#: ../actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "Có nhiá»u ngÆ°á»i nghe theo lá»i nhắn của bạn."
+
+#: ../actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "Có nhiá»u ngÆ°á»i gá»­i lá»i nhắn để %s nghe theo."
+
+#: ../actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "Có nhiá»u ngÆ°á»i gá»­i lá»i nhắn để bạn nghe theo."
+
+#: ../actions/recoverpassword.php:87
+msgid "This confirmation code is too old. Please start again."
+msgstr "Mã xác nhận quá cũ. Hãy thử lại cái khác."
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+"Trang này sẽ tự động gửi đi. Nếu không, hãy click lên nút "
+"Gửi để gửi đến nhà cung cấp OpenID của bạn."
+
+#: ../actions/finishopenidlogin.php:56
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+"Äây là lần đầu tiên bạn đăng nhập vào %s, vì vậy chúng "
+"tôi phải kết nối tài khoản OpenID của bạn với tài khoản "
+"trên site này. Bạn có thể tạo một tài khoản mới, hoặc "
+"kết nối với tài khoản đã có của bạn, nếu như bạn đã có rồi."
+
+#: ../lib/util.php:147
+msgid "This page is not available in a media type you accept"
+msgstr "Trang này không phải là phÆ°Æ¡ng tiện truyá»n thông mà bạn chấp nhận."
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"Äể đăng ký, bạn cần [Äăng nhập](%%action.login%%), hoặc "
+"[Äăng ký](%%action.register%%) tài khoản má»›i. Nếu bạn đã có "
+"một tài khoản khác trên [site microblogging tương "
+"ứng](%%doc.openmublog%%), hãy nhập URL vỠhồ sơ cá nhân của bạn dưới đây."
+
+#: ../actions/profilesettings.php:51
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+"URL vỠTrang chính, Blog, hoặc hồ sơ cá nhân của bạn trên "
+
+#: ../actions/remotesubscribe.php:74
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "URL trong hồ sơ cá nhân của bạn ở trên các trang microblogging khác"
+
+#: ../actions/imsettings.php:105
+#: ../actions/recoverpassword.php:39
+msgid "Unexpected form submission."
+msgstr "Bất ngỠgửi mẫu thông tin. "
+
+#: ../actions/recoverpassword.php:237
+msgid "Unexpected password reset."
+msgstr "Bất ngỠreset mật khẩu."
+
+#: ../actions/finishremotesubscribe.php:58
+msgid "Unknown version of OMB protocol."
+msgstr "Không biết phiên bản của giao thức OMB."
+
+#: ../lib/util.php:245
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+"Nếu không có các quy định khác, nội dung của trang web này có "
+"bản quyá»n của ngÆ°á»i đóng góp và theo bản quyá»n "
+
+#: ../actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "Không nhận dạng kiểu địa chỉ %s"
+
+#: ../actions/showstream.php:193
+msgid "Unsubscribe"
+msgstr "Hết theo"
+
+#: ../actions/postnotice.php:44
+#: ../actions/updateprofile.php:44
+msgid "Unsupported OMB version"
+msgstr "Không hỗ trợ cho version OMB"
+
+#: ../actions/avatar.php:105
+msgid "Unsupported image file format."
+msgstr "Không hỗ trợ kiểu file ảnh này."
+
+#: ../actions/avatar.php:68
+msgid "Upload"
+msgstr "Tải file"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+"Tải \"hình đại diện\" mới (hình cá nhân) tại đây. Bạn "
+"không thể chỉnh sửa lại hình sau khi tải nó lên, vì thế "
+"nếu muốn hãy sửa trước khi tải lên. Hình phải phù hợp "
+
+#: ../actions/profilesettings.php:48
+#: ../actions/register.php:182
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "Chỉ dùng để cập nhật, thông báo, và hồi phục mật khẩu"
+
+#: ../actions/finishremotesubscribe.php:86
+msgid "User being listened to doesn't exist."
+msgstr "NgÆ°á»i dùng Ä‘ang lắng nghe để không thoát khá»i."
+
+#: ../actions/all.php:41
+#: ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43
+#: ../actions/replies.php:41
+#: ../actions/showstream.php:44
+msgid "User has no profile."
+msgstr "NgÆ°á»i dùng không có thông tin."
+
+#: ../actions/remotesubscribe.php:71
+msgid "User nickname"
+msgstr "Biệt hiệu của ngÆ°á»i dùng"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "What's up, %s?"
+msgstr "Bạn đang làm gì thế, %s?"
+
+#: ../lib/util.php:969
+#, php-format
+msgid "%s, Tell the world what's on your mind?"
+msgstr "%s, bạn đang làm gì?"
+
+#: ../actions/profilesettings.php:57
+msgid "City, State/Region, Country"
+msgstr "Thành phố, Tỉnh thành, Quốc gia"
+
+#: ../actions/updateprofile.php:127
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "Kiểu file ảnh không phù hợp với '%s'"
+
+#: ../actions/updateprofile.php:122
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "Kích thước file ảnh không phù hợp đối với '%s'"
+
+#: ../actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "Bạn đã có được tài khoản OpenID này rồi!"
+
+#: ../actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "Bạn đã đăng nhập!"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "Bạn có thể thay đổi mật khẩu tại đây. Hãy chá»n mật khẩu má»›i!"
+
+#: ../actions/register.php:164
+msgid "You can create a new account to start posting notices."
+msgstr "Bạn có thể tạo tài khoản mới để có thể gửi ý kiến của mình."
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+"Bạn có thể loại bá» OpenID khá»i tài khoản của bạn bằng "
+"cách nhấn vào nút \"Xóa\"."
+
+#: ../actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+"Bạn có thể gửi và nhận những tin nhắn qua Jabber hoặc GTalk "
+"[tin nhắn nhanh](%%doc.im%%). Äịnh dạng địa chỉ của bạn và các thiết lập sau."
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+"Bạn có thể cập nhật hồ sÆ¡ cá nhân tại đây để má»i "
+"ngÆ°á»i có thể biết thông tin vá» bạn."
+
+#: ../actions/finishremotesubscribe.php:31
+#: ../actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "Bạn có thể đăng ký tại nơi bạn ở!"
+
+#: ../actions/finishopenidlogin.php:33
+#: ../actions/register.php:51
+msgid "You can't register if you don't agree to the license."
+msgstr "Bạn không thể đăng ký nếu không đồng ý các Ä‘iá»u khoản."
+
+#: ../actions/updateprofile.php:62
+msgid "You did not send us that profile"
+msgstr "Bạn chưa cập nhật thông tin riêng"
+
+#: ../actions/recoverpassword.php:134
+msgid "You've been identified. Enter a new password below. "
+msgstr "Bạn đã được xác định. Hãy nhập mật khẩu mới ở dưới."
+
+#: ../actions/openidlogin.php:67
+msgid "Your OpenID URL"
+msgstr "OpenID URL của bạn"
+
+#: ../actions/recoverpassword.php:149
+msgid "Your nickname on this server, or your registered email address."
+msgstr "Biệt hiệu của bạn đã tồn tại hoặc bạn đã đăng ký bằng email này rồi."
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+"[OpenID](%%doc.openid%%) đăng nhập nhiá»u website vá»›i cùng 1 tài "
+"khoản. Quản lý các OpenID của bạn ở đây."
+
+#: ../lib/util.php:814
+msgid "a few seconds ago"
+msgstr "vài giây trước"
+
+#: ../lib/util.php:826
+#, php-format
+msgid "about %d days ago"
+msgstr "%d ngày trước"
+
+#: ../lib/util.php:822
+#, php-format
+msgid "about %d hours ago"
+msgstr "%d giá» trÆ°á»›c"
+
+#: ../lib/util.php:818
+#, php-format
+msgid "about %d minutes ago"
+msgstr "%d phút trước"
+
+#: ../lib/util.php:830
+#, php-format
+msgid "about %d months ago"
+msgstr "%d tháng trước"
+
+#: ../lib/util.php:824
+msgid "about a day ago"
+msgstr "1 ngày trước"
+
+#: ../lib/util.php:816
+msgid "about a minute ago"
+msgstr "1 phút trước"
+
+#: ../lib/util.php:828
+msgid "about a month ago"
+msgstr "1 tháng trước"
+
+#: ../lib/util.php:832
+msgid "about a year ago"
+msgstr "1 năm trước"
+
+#: ../lib/util.php:820
+msgid "about an hour ago"
+msgstr "1 giá» trÆ°á»›c"
+
+#: ../actions/noticesearch.php:126
+#: ../actions/showstream.php:383
+#: ../lib/stream.php:101
+msgid "in reply to..."
+msgstr "còn nữa..."
+
+#: ../actions/noticesearch.php:133
+#: ../actions/showstream.php:390
+#: ../lib/stream.php:108
+msgid "reply"
+msgstr "trả lá»i"
+
+#: ../actions/password.php:44
+msgid "same as password above"
+msgstr "cùng mật khẩu ở trên"
+
+#: ../lib/util.php:1127
+msgid "After"
+msgstr "Sau"
+
+#: ../lib/util.php:
+msgid "Tags"
+msgstr "Từ khóa"
+
+#: ../lib/util.php:
+msgid "Register for a free account!"
+msgstr "Äăng ký tài khoản miá»…n phí!"
+
+#: ../lib/util.php:
+msgid "Everyone"
+msgstr "Tất cả má»i ngÆ°á»i"
+
+#: ../lib/util.php:
+msgid "Welcome to %s"
+msgstr "%s chào mừng bạn "
+
+#: ../lib/util.php:
+msgid "Blog + Chat = Fun"
+msgstr "Vừa Blog vừa Chat = Vui Lắm"
+
+#: ../lib/util.php:
+msgid ""
+"%s is a new way to send short messages to your friends, family, and "
+"co-workers."
+msgstr ""
+"%s là dịch vụ cho bạn bè, gia đình và đồng nghiệp liên "
+"lạc và kết nối qua những trao đổi ngắn."
+
+#: ../lib/util.php:
+msgid ""
+"Starting a conversation is fast and easy: You can shout messages to your "
+"followers, or whisper to your friends. Just think of something to say, and "
+"get started!"
+msgstr ""
+"Äể bắt đầu cuá»™c đàm luận: Bạn có thể gá»­i tin nhắn "
+"đến những ngÆ°á»i mà bạn theo, hoặc gá»­i những mong Æ°á»›c "
+"của bạn đến bạn bè, những câu trả lá»i thông thÆ°á»ng cho má»™t câu há»i Ä‘Æ¡n giản: Bạn Ä‘ang làm gì vậy? "
+
+#: ../lib/util.php:
+msgid "Register now!"
+msgstr "Hãy đăng ký ngay!"
+
+#: ../lib/util.php:
+msgid "What's the latest?"
+msgstr "Má»i ngÆ°á»i Ä‘ang nói gì?"
+
+#: ../lib/util.php:
+msgid "What Everyone Is Talking About"
+msgstr "Má»i ngÆ°á»i Ä‘ang nói gì?"
+
+#: ../lib/searchaction.php:
+msgid "People"
+msgstr "Tên tài khoản"
+
+#: ../lib/searchaction.php:
+msgid "Find people on this site"
+msgstr "Tìm kiếm má»i ngÆ°á»i trên trang web này"
+
+#: ../lib/searchaction.php:
+msgid "Text"
+msgstr "Chuỗi bất kỳ"
+
+#: ../lib/util.php:
+msgid "Find content of notices"
+msgstr "Tìm theo nội dung của tin nhắn"
+
+#: ../actions/tag.php:
+msgid "Showing most popular tags from the last week"
+msgstr "Các từ khóa phổ biến."
+
+#: ../actions/tag.php:
+msgid "Recent Tags"
+msgstr "Các từ khóa hiện tại"
+
+#: ../actions/profileseting.php:
+msgid "What timezone are you normally in?"
+msgstr "Khu vá»±c nào bạn thÆ°á»ng ở?"
+
+#: ../actions/profileseting.php:
+msgid "Timezone"
+msgstr "Khu vá»±c"
+
+#: ../actions/profileseting.php:
+msgid "Language"
+msgstr "Ngôn ngữ"
+
+#: ../actions/profileseting.php:
+msgid "Preferred language"
+msgstr "Ngôn ngữ bạn thích"
+
+#: ../actions/profileseting.php:
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr "Tá»± Ä‘á»™ng theo những ngÆ°á»i nào đăng ký theo tôi"
+
+#: ../actions/profileseting.php:
+msgid "Could not save profile."
+msgstr "Không thể lưu hồ sơ cá nhân."
+
+#: ../lib/setingactions.php:
+msgid "Change your profile settings"
+msgstr "Thay đổi các thiết lập trong hồ sơ cá nhân của bạn"
+
+#: ../lib/setingactions.php:
+msgid "Change email handling"
+msgstr "Äang thá»±c hiện việc thay đổi email"
+
+#: ../lib/setingactions.php:
+msgid "Upload a new profile image"
+msgstr "Tải lên một file ảnh mới cho hồ sơ cá nhân"
+
+#: ../lib/setingactions.php:
+msgid "Change your password"
+msgstr "Thay đổi mật khẩu của bạn"
+
+#: ../lib/setingactions.php:
+msgid "Add or remove OpenIDs"
+msgstr "Thêm mới hoặc xóa OpenIDs"
+
+#: ../lib/setingactions.php:
+msgid "Updates by SMS"
+msgstr "Thay đổi bởi SMS"
+
+#: ../lib/setingactions.php:
+msgid "Updates by instant messenger (IM)"
+msgstr "Thay đổi bởi tin nhắn nhanh (IM)"
+
+#: ../lib/smsseting.php:
+msgid "SMS Phone number"
+msgstr "Số điện thoại để nhắn SMS "
+
+#: ../lib/smsseting.php:
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+"Số điện thoại, không cho phép nhập dấu chấm và ký tự "
+
+#: ../lib/smsseting.php:
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr "Bạn có thể nhận tin nhắn SMS qua email từ %%site.name%%."
+
+#: ../lib/smsseting.php:
+msgid "SMS Settings"
+msgstr "Thiết lập SMS"
+
+#: ../lib/smsseting.php:
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "SMS xác nhận ngay - đã cho phép gửi qua điện thoại. "
+
+#: ../lib/smsseting.php:
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+"Nhà cung cấp dịch vụ điện thoại di động của bạn. Nếu "
+"bạn biết nhà cung cấp dịch vụ điện thoại nào cho phép "
+"nhận SMS qua email mà chưa có trong danh sách này, vui lòng gửi mail cho chúng tôi đến địa chỉ %s."
+
+#: ../lib/smsseting.php:
+msgid "No code entered"
+msgstr "Không có mã nào được nhập"
+
+#: ../lib/smsseting.php:
+msgid "Select a carrier"
+msgstr "Chá»n nhà cung cấp Mobile"
+
+#: ../lib/smsseting.php:
+msgid "That is not your phone number."
+msgstr "Äó không phải là số Ä‘iện thoại của bạn."
+
+#: ../lib/smsseting.php:
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+"Hãy gửi thông báo đến tôi qua SMS; Tôi biết là bạn đang "
+"phải trả giá cao cho dịch vụ của chúng tôi. "
+
+#: ../lib/imseting.php:
+msgid "Send me replies through Jabber/GTalk from people I’m not subscribed to."
+msgstr ""
+"Gá»­i tin nhắn trả lá»i những ngÆ°á»i mà tôi không đăng ký qua "
+
+#: ../lib/imseting.php:
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "Gửi MicroID đến địa chỉ Jabber/GTalk của tôi. "
+
+#: ../lib/emailseting.php:
+msgid "Manage how you get email from %%site.name%%."
+msgstr "Bạn nhận email từ %%site.name%% như thế nào."
+
+#: ../lib/emailseting.php:
+msgid "Send me notices of new subscriptions through email."
+msgstr "Hãy gửi email cho tôi thông báo vỠcác đăng nhận mới."
+
+#: ../lib/emailseting.php:
+msgid "I want to post notices by email."
+msgstr "Tôi muốn đưa tin nhắn lên bằng email."
+
+#: ../lib/emailseting.php:
+msgid "Publish a MicroID for my email address."
+msgstr "Xuất bản một MicroID đến địa chỉ email của tôi."
+
+#: ../lib/emailseting.php:
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+"Tạo một địa chỉ email mới để đưa tin nhắn lên; và xóa "
+
+#: ../lib/emailseting.php:
+msgid "Send email to this address to post new notices."
+msgstr "Gửi email đến địa chỉ này để đưa tin nhắn mới lên."
+
+#: ../actions/choosetheme.php:
+msgid ""
+"You can change the theme as you want by clicking a thumbnail image and then "
+"click Save button to active new theme."
+msgstr ""
+"Bạn có thể thay đổi theme bằng cách chá»n vào má»™t hình ảnh "
+"theme bạn thích. Sau đó nhấn nút Lưu để thay đổi theme mới."
+
+#: ../actions/choosetheme.php:
+msgid "Choose a theme"
+msgstr "Chá»n theme"
+
+#: ../actions/choosetheme.php:
+msgid "Theme Error"
+msgstr "Lá»—i Theme"
+
+#: ../actions/choosetheme.php:
+msgid ""
+"Tired of the same old colors on your timeline? Change is good so spice up "
+"your %s page."
+msgstr ""
+"Nếu cảm thấy nhàm chán với màu sắc cũ trên timeline của "
+"bạn, hãy thay đổi màu sắc để trang %s của bạn hấp dẫn hơn."
+
+#: ../actions/choosetheme.php:
+msgid "Preview"
+msgstr "Xem trÆ°á»›c"
+
+#: ../actions/choosetheme.php:
+msgid "Background Theme:"
+msgstr "Background Theme:"
+
+#: ../actions/choosetheme.php:
+msgid "Customize your theme"
+msgstr "Tùy chỉnh theme "
+
+#: ../actions/choosetheme.php:
+msgid "Timeline Theme:"
+msgstr "Timeline Theme:"
+
+#: ../actions/choosetheme.php:
+msgid "Dashboard Theme:"
+msgstr "Dashboard Theme:"
+
+#: ../actions/choosetheme.php:
+msgid "Theme"
+msgstr "Theme"
+
+#: ../actions/public.php:
+msgid "Activity"
+msgstr "Các hoạt động"
+
+#: ../actions/public.php:
+msgid "Me & My Friends"
+msgstr "Tôi & bạn"
+
+#: ../actions/public.php:
+msgid "User & Friends"
+msgstr "Nhóm bạn"
+
+#: ../actions/public.php:
+msgid "My Profile"
+msgstr "Cá nhân"
+
+#: ../actions/public.php:
+msgid "User Profile"
+msgstr "Hồ sơ"
+
+#: ../actions/css.php:
+msgid "Customize your page even more with CSS"
+msgstr "Tùy chỉnh trang của bạn bằng CSS"
+
+#: ../actions/css.php:
+msgid "Save Changes"
+msgstr "LÆ°u"
+
+#: ../actions/css.php:
+msgid "Change css code"
+msgstr "Thay đổi code css"
+
+#: ../actions/css.php:
+msgid "You can change your css code here."
+msgstr "Bạn có thể thay đổi code css"
+
+#: ../actions/css.php:
+msgid "New css code saved."
+msgstr "CSS đã được lưu."
+
+#: ../lib/util.php:
+msgid "Reply"
+msgstr "Trả lá»i"
+
+#: ../actions/avatar.php:
+msgid "Upload from your computer"
+msgstr "Tải từ máy tính cá nhân "
+
+#: ../actions/avatar.php:
+msgid "Get from library"
+msgstr "Lấy từ tập ảnh có sẵn"
+
+#: ../actions/avatar.php:
+msgid "Male"
+msgstr "Nam"
+
+#: ../actions/avatar.php:
+msgid "Female"
+msgstr "Nữ"
+
+#: ../dasboard.php:
+msgid "Last message"
+msgstr "Tin mới nhất"
+
+#: ../stream.php:
+msgid "No reply yet"
+msgstr "chÆ°a có trả lá»i"
+
+msgid "replies"
+msgstr "trả lá»i"
+
+#: ../actions/showstream.php:400
+#: ../lib/stream.php:109
+msgid " from "
+msgstr " từ"
+
+#: ../actions/invite.php:168
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s moi ban tham gia vao %2$s"
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s má»i bạn tham gia vào %2$s (%3$s).\n"
+"\n"
+"%2$s là dịch vụ tin nhắn nhanh giúp bạn liên kết vá»›i ngÆ°á»i "
+"quen và những ngÆ°á»i yêu thích bạn.\n"
+"\n"
+"Bạn cũng có thể chia sẻ những suy nghĩ, thông tin vỠbạn, "
+"hoặc Ä‘á»i sống của bạn lên trên mạng cho những ngÆ°á»i quen "
+"của bạn biết. Dịch vụ này cũng giúp bạn gặp gỡ những "
+"ngÆ°á»i chÆ°a quen biết nhÆ°ng có cùng sở thích.\n"
+"\n"
+"%1$s nói:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Bạn có thể tham khảo trang thông tin cá nhân của %1$s trên "
+"%2$s tại đây:\n"
+"\n"
+"%5$s\n"
+"\n"
+"Nếu bạn muốn sử dụng dịch vụ này, hãy nhấn chuột vào "
+"liên kết dÆ°á»›i đây để chấp nhận lá»i má»i.\n\n%6$s\n\nNếu không thích tham gia, bạn có thể bá» qua tin nhắn này. Rất cảm Æ¡n sá»± kiên nhẫn vì đã làm mất thá»i gian của bạn.\n\nThân, %2$s\n"
+
+#: ../actions/invite.php:84
+#: ../actions/invite.php:92
+#, php-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../actions/invite.php:131
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+"Các địa chỉ email của những ngÆ°á»i bạn muốn má»i (má»—i "
+"địa chỉ nằm trên 1 dòng)"
+
+#: ../actions/twitapifriendships.php:60
+#: ../actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr "Không thể theo bạn này: %s đã có trong danh sách bạn bè của bạn rồi."
+
+#: ../actions/recoverpassword.php:102
+msgid "Could not update user with confirmed email address."
+msgstr "Không thể cập nhật thông tin user với địa chỉ email đã được xác nhận."
+
+#: ../actions/twitapistatuses.php:93
+msgid "Couldn't find any statuses."
+msgstr "Không tìm thấy bất kỳ trạng thái nào."
+
+#: ../actions/invite.php:129
+msgid "Email addresses"
+msgstr "Äịa chỉ email"
+
+#: ../lib/settingsaction.php:102
+msgid "IM"
+msgstr "IM"
+
+#: ../actions/invite.php:55
+#, php-format
+msgid "Invalid email address: %s"
+msgstr "Äịa chỉ email không đúng:%s"
+
+#: ../actions/invite.php:79
+msgid "Invitation(s) sent"
+msgstr "ThÆ° má»i đã gá»­i"
+
+#: ../actions/invite.php:97
+msgid "Invitation(s) sent to the following people:"
+msgstr "ThÆ° má»i đã gá»­i đến:"
+
+#: ../lib/util.php:306
+msgid "Invite"
+msgstr "ThÆ° má»i"
+
+#: ../actions/invite.php:123
+msgid "Invite new users"
+msgstr "Gá»­i thÆ° má»i đến những ngÆ°á»i chÆ°a có tài khoản"
+
+#: ../actions/twitapistatuses.php:595
+msgid "No status found with that ID."
+msgstr "Không tìm thấy trạng thái nào tương ứng với ID đó."
+
+#: ../actions/twitapistatuses.php:555
+msgid "No status with that ID found."
+msgstr "Không tìm thấy trạng thái nào tương ứng với ID đó."
+
+#: ../actions/recoverpassword.php:211
+msgid "No user with that email address or username."
+msgstr ""
+"Không tìm thấy ngÆ°á»i dùng nào tÆ°Æ¡ng ứng vá»›i địa chỉ "
+"email hoặc username đó."
+
+#: ../scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr "Không có ngÆ°á»i dùng nào đăng ký"
+
+#: ../lib/twitterapi.php:226
+#: ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332
+msgid "Not a supported data format."
+msgstr "Không hỗ trợ định dạng dữ liệu này."
+
+#: ../actions/twitapistatuses.php:422
+msgid "Not found"
+msgstr "Không tìm thấy"
+
+#: ../actions/invite.php:135
+msgid "Optionally add a personal message to the invitation."
+msgstr "Không bắt buá»™c phải thêm thông Ä‘iệp vào thÆ° má»i."
+
+#: ../actions/invite.php:133
+msgid "Personal message"
+msgstr "Tin nhắn cá nhân"
+
+#: ../lib/settingsaction.php:99
+msgid "SMS"
+msgstr "SMS"
+
+#: ../scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr "Xin lỗi, không có địa chỉ email cho phép."
+
+#: ../scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr "Xin lỗi, đó không phải là địa chỉ email mà bạn nhập vào."
+
+#: ../actions/twitapiaccount.php:74
+msgid "That's too long. Max notice size is 255 chars."
+msgstr "Tin nhắn quá dài. Chỉ được phép tối đa 255 ký tự."
+
+#: ../actions/invite.php:89
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+"Những ngÆ°á»i này đã là thành viên rồi và bạn chỉ cần "
+"nhấn nút \"Tôi theo ngÆ°á»i này\" để theo há»:"
+
+#: ../actions/twitapifriendships.php:108
+#: ../actions/twitapistatuses.php:586
+msgid "This method requires a POST or DELETE."
+msgstr "Phương thức này yêu cầu là POST hoặc DELETE"
+
+#: ../actions/twitapiaccount.php:65
+#: ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381
+msgid "This method requires a POST."
+msgstr "Phương thức này yêu cầu là POST."
+
+#: ../index.php:57
+msgid "Unknown action"
+msgstr "Không tìm thấy action"
+
+#: ../actions/invite.php:114
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+"Äiá»n địa chỉ email và ná»™i dung tin nhắn để gá»­i thÆ° má»i "
+"bạn bè và đồng nghiệp của bạn tham gia vào dịch vụ này."
+
+#: ../actions/twitapiusers.php:75
+msgid "User not found."
+msgstr "Không tìm thấy user."
+
+#: ../actions/invite.php:81
+msgid "You are already subscribed to these users:"
+msgstr "Bạn đã theo những ngÆ°á»i này:"
+
+#: ../actions/twitapistatuses.php:612
+msgid "You may not delete another user's status."
+msgstr "Bạn đã không xóa trạng thái của những ngÆ°á»i khác."
+
+#: ../actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+"Bạn phải đăng nhập vào má»›i có thể gá»­i thÆ° má»i những "
+
+#: ../actions/invite.php:103
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+"Bạn sẽ nhận được thông báo khi những ngÆ°á»i được bạn "
+"má»i nhận lá»i má»i và đăng ký vào trang web này. Cảm Æ¡n bạn "
+
+#: ..lib/personal.php:
+msgid "Favorites"
+msgstr "Ưa thích"
+
+#: ..lib/personal.php:
+msgid "%s's favorite notices"
+msgstr "Những tin nhắn ưa thích của %s"
+
+#: ..lib/personal.php:
+msgid "Inbox"
+msgstr "Hộp thư đến"
+
+#: ..lib/personal.php:
+msgid "Your incoming messages"
+msgstr "Thư đến của bạn"
+
+#: ..lib/personal.php:
+msgid "Outbox"
+msgstr "Há»™p thÆ° Ä‘i"
+
+#: ..lib/personal.php:
+msgid "Your sent messages"
+msgstr "Thư bạn đã gửi"
+
+#: ..actions/inbox.php:
+msgid "Inbox for %s - page %d"
+msgstr "Hộp thư đến của %s - trang %d"
+
+#: ..actions/inbox.php:
+msgid "Inbox for %s"
+msgstr "Hộp thư đến của %s"
+
+#: ..actions/inbox.php:
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr "Äây là há»™p thÆ° đến của bạn, bao gồm các tin nhắn gá»­i đến riêng cho bạn"
+
+#: ..actions/outbox.php:
+msgid "Outbox for %s - page %d"
+msgstr "Hộp thư gửi đi của %s - trang %d"
+
+#: ..actions/outbox.php:
+msgid "Outbox for %s"
+msgstr "Hộp thư đi của %s"
+
+#: ..actions/outbox.php:
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+"Hộp thư gửi đi của bạn, bao gồm danh sách các tin nhắn gửi "
+"trực tiếp mà bạn đã gửi."
+
+#: ..actions/showfavorites.php:
+msgid "%s favorite notices"
+msgstr "%s ưa thích các tin nhắn"
+
+#: ..actions/showfavorites.php:
+msgid "Feed for favorites of %s"
+msgstr "Tìm kiếm các tin nhắn ưa thích của %s"
+
+#: ..actions/showfavorites.php:
+msgid "Could not retrieve favorite notices."
+msgstr "Không thể lấy lại các tin nhắn ưa thích"
+
+#: ..actions/favor.php:
+msgid "There was a problem with your session token. Try again, please."
+msgstr "Có lỗi xảy ra khi thao tác. Hãy thử lại lần nữa."
+
+#: ..actions/favor.php:
+msgid "Could not create favorite."
+msgstr "Không thể tạo favorite."
+
+#: ..actions/favor.php:
+msgid "Disfavor"
+msgstr "Không thích"
+
+#: ..actions/favor.php:
+msgid "This notice is already a favorite!"
+msgstr "Tin nhắn này đã có trong danh sách tin nhắn ưa thích của bạn rồi!"
+
+#: ..actions/inbox.php:
+msgid "%s added your notice as a favorite"
+msgstr "%s da them tin nhan cua ban vao danh sach tin nhan ua thich"
+
+#: ..actions/inbox.php:
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+"In case you forgot, you can see the text of your notice here:\n"
+"\n"
+"%3$s\n"
+"\n"
+"You can see the list of %1$s's favorites here:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Faithfully yours,\n"
+"%5$s\n"
+msgstr ""
+"%1$s vừa thêm tin nhắn của bạn trên %2$s vào danh sách tin "
+"nhắn ưa thích của mình.\n"
+"\n"
+"Bạn có thể xem lại nội dung tin nhắn của bạn tại "
+"đây:\n"
+"\n"
+"%3$s\n"
+"\n"
+"Bạn có thể xem danh sách tin nhắn Æ°a thích của %1$s tại đây: \n\n%4$s\n\nChúc sức khá»e,\n%5$s\n"
+
+#: ../actions/twitapiaccount.php:49
+#: ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88
+#: ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370
+#: ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122
+msgid "API method not found!"
+msgstr "Phương thức API không tìm thấy!"
+
+#: ../actions/twitapiaccount.php:57
+#: ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119
+#: ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34
+#: ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62
+#: ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47
+#: ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52
+#: ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35
+#: ../actions/twitapistatuses.php:768
+msgid "API method under construction."
+msgstr "Phương thức API dưới cấu trúc có sẵn."
+
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+"1-64 chữ cái thÆ°á»ng hoặc là chữ số, không có dấu chấm hay "
+"khoảng trắng. Bắt buộc."
+
+#: ../lib/Mailbox.php
+msgid "Direct Messages"
+msgstr "Tin nhắn riêng"
+
+#: ../.php
+msgid "Notices sent"
+msgstr "Tin đã gửi"
+
+#: ../action/deletenotice.php
+msgid "Delete notice"
+msgstr "Xóa tin nhắn"
+
+#: ../action/deletenotice.php
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr "Bạn muốn xóa tin nhắn này? Sau khi xóa, bạn không thể lấy lại được."
+
+#: ../action/deletenotice.php
+msgid "No"
+msgstr "Không"
+
+#: ../action/deletenotice.php
+msgid "Yes"
+msgstr "Có"
+
+#: ../action/deletenotice.php
+msgid "Are you sure you want to delete this notice?"
+msgstr "Bạn có chắc chắn là muốn xóa tin nhắn này không?"
+
+#: ..//.php
+msgid "Flagged notices"
+msgstr "Các tin nhắn bị cảnh báo"
+
+#: ..//.php
+msgid "flag this notice"
+msgstr "cảnh báo tin nhắn"
+
+#: ..//.php
+msgid "Flag a notice"
+msgstr "Cảnh báo tin nhắn"
+
+#: ..//.php
+msgid "Flag this notice?"
+msgstr "Bạn muốn cảnh báo tin nhắn này?"
+
+#: ..//.php
+msgid ""
+"If you think this notice is an offensive message, please notify to the "
+"administrator. Thank you."
+msgstr ""
+"Nếu bạn nghĩ tin nhắn này có nội dung xấu không chấp nhận "
+"được, hãy báo cho ban quản trị web site. Cảm Æ¡n rất nhiá»u."
+
+#. ./lib/util.php
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"Saigonica là bản sửa đổi của phiên bản [Laconica "
+"0.6.0](http://laconi.ca/), phát hành theo bản quyá»n [GNU Affero "
+"General Public License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+
+msgid ""
+"To use this [Saigonica version](%%doc.source%%), you must write the license "
+"is of Laconica and has contributions from Saigonica."
+msgstr ""
+"Äể sá»­ dụng [phiên bản Saigonica](%%doc.source%%) này, cần ghi "
+"rõ bản quyá»n của Laconia cá»™ng thêm sá»± đóng góp của Saigonica."
+
+#. ./.php
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+"Gửi email thông báo tôi khi có ai đó lưu tin nhắn của tôi vào "
+"danh sách Æ°a thích của há»."
+
+#. ./.php
+msgid "Send me email when someone sends me a private message."
+msgstr "Gửi email báo cho tôi biết khi có ai đó gửi tin nhắn riêng cho tôi."
+
+#. ./.php
+msgid "Current verified Twitter account."
+msgstr "Tài khoản Twitter đã được xác nhận."
+
+#. ./.php
+msgid "Subscribe to my Twitter friends here."
+msgstr "Äăng ký theo những bạn trên Twitter tại đây."
+
+#. ./.php
+msgid "Twitter Account"
+msgstr "Tài khoản Twitter"
+
+#. ./.php
+msgid "Twitter settings"
+msgstr "Thiết lập tài khoản Twitter"
+
+#. ./.php
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, and "
+"subscribe to Twitter friends already here."
+msgstr ""
+"Hãy đăng ký tài khoản Twitter của bạn để có thể gửi tin "
+"nhắn đến Twitter, bạn cũng có thể đăng ký theo các bạn của mình trên Twitter tại đây."
+
+#. ./.php
+msgid "No spaces, please."
+msgstr "Không nhập ký tự trắng."
+
+#. ./.php
+msgid "Twitter Password"
+msgstr "Mật khẩu Twitter"
+
+#. ./.php
+msgid "That is not your Twitter account."
+msgstr "Không phải là tài khoản Twitter của bạn."
+
+#. ./.php
+msgid "Couldn't remove Twitter user."
+msgstr "Không thể xóa tài khoản Twitter."
+
+#. ./.php
+msgid "Twitter account removed."
+msgstr "Tài khoản Twitter đã xóa."
+
+#. ./.php
+msgid "Couldn't save Twitter preferences."
+msgstr "Không thể lưu các yêu cầu cho tài khoản Twitter. "
+
+#. ./.php
+msgid "Twitter preferences saved."
+msgstr "Các yêu cầu cho tài khoản Twitter đã lưu"
+
+#. ./.php
+msgid "Twitter Username"
+msgstr "Tên tài khoản Twitter"
+
+#. ./.php
+msgid "Could not verify your Twitter credentials!"
+msgstr "Không thể xác nhận tài khoản Twitter của bạn!"
+
+#. ./.php
+msgid "Unable to retrieve account information for '%s' from Twitter."
+msgstr "Không thể lấy thông tin tài khoản của '%s' từ Twitter."
+
+#. ./.php
+msgid "Unable to save your Twitter settings!"
+msgstr "Không thể lưu thông tin Twitter của bạn!"
+
+#. ./.php
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_)."
+msgstr ""
+"Tên tài khoản phải là các chữ số, chữ cái thÆ°á»ng hoặc "
+"viết hoa, hoặc là dấu gạch dưới (_)."
+
+msgid "Automatically send my notices to Twitter."
+msgstr "Tự động gửi tin nhắn của tôi đến Twitter."
+
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+"Gá»­i những tin nhắn trả lá»i của tôi từ những ngÆ°á»i mà "
+"tôi không theo qua Jabber/GTalk."
+
+msgid "New email address for posting to %s"
+msgstr "Dia chi email moi de gui tin nhan den %s"
+
+msgid "Registration successful"
+msgstr "Äăng ký thành công"
+
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"Chúc mừng, %s! Chào mừng bạn đến với %%%%site.name%%%%. Bây "
+"giỠbạn có thể...\n"
+"\n"
+"* Vào trang [Hồ sơ cá nhân](%s) của bạn và gửi tin nhắn "
+"đầu tiên. \n"
+"* Thêm [địa chỉ Jabber/GTalk](%%%%action.imsettings%%%%) để có "
+"thể gửi tin nhắn nhanh.\n"
+"* [Tìm kiếm ngÆ°á»i quen](%%%%action.peoplesearch%%%%) mà bạn nghÄ© "
+"là có thể chia sẻ niá»m vui.\n"
+"* Äá»c xuyên suốt [hÆ°á»›ng dẫn](%%%%doc.help%%%%) để hiểu thêm "
+"vỠdịch vụ của chúng tôi.\n\nCảm ơn bạn đã đăng ký để là thành viên và rất mong bạn sẽ thích dịch vụ này."
+
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+"(Bạn sẽ nhận email thông báo, hãy Ä‘á»c hÆ°á»›ng dẫn để xác "
+"nhận địa chỉ email của bạn.)"
+
+msgid "6 or more characters. Required."
+msgstr "Nhiá»u hÆ¡n 6 ký tá»±. Bắt buá»™c"
+
+msgid "Same as password above. Required."
+msgstr "Cùng mật khẩu ở trên. Bắt buộc."
+
+msgid "Email Settings"
+msgstr "Thiết lập địa chỉ email"
+
+msgid "Change theme"
+msgstr "Äổi theme"
+
+msgid "Customize theme"
+msgstr "Tùy chỉnh theme"
+
+msgid ""
+"You can change the theme as you want by choosing a thumbnail and then click "
+"Save button to active new theme."
+msgstr ""
+"Bạn có thể thay đổi theme bằng cách chá»n má»™t hình mẫu "
+"của các theme rồi nhấn nút \"Lưu\" để đổi theme. "
+
+msgid ""
+"Hey, %1$s .\n"
+"\n"
+"Someone just entered this email address on %2$s.\n"
+"\n"
+"If it was you, and you want to confirm your entry, use the URL below: \n"
+"\n"
+"\t%3$s\n"
+"\n"
+"If not, just ignore this message.\n"
+"\n"
+"Thanks for your time,\n"
+"\n"
+"%4$s\n"
+"\n"
+msgstr ""
+"Chào, %1$s .\n"
+"\n"
+"Không biết có phải bạn là ngÆ°á»i vừa nhập địa chỉ email "
+"này trên %2$s.\n"
+"\n"
+"Nếu bạn là ngÆ°á»i nhập, và bạn muốn xác nhận lại, hãy "
+"nhấn chuá»™t vào Ä‘Æ°á»ng dẫn dÆ°á»›i đây: \n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Nếu không phải bạn, hãy bá» qua tin nhắn này.\n\nCảm Æ¡n bạn đã bá» thá»i gian để Ä‘á»c thÆ°,\n\n%4$s\n\n"
+
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+"Bạn có địa chỉ mới để gửi tin nhắn trên %1$s.\n"
+"\n"
+"Hãy gửi email đến %2$s để có thể nhắn tin.\n"
+"\n"
+"Bạn có thể Ä‘á»c hÆ°á»›ng dẫn tại %3$s.\n\nChúc sức khá»e,\n%4$s"
+
+msgid "SMS confirmation"
+msgstr "Xác nhận SMS"
+
+msgid ""
+"%1$s: confirm you own this phone number with this code:\n"
+"\n"
+"%2$s\n"
+"\n"
+msgstr "%1$s: Hãy xác nhận bạn là chủ nhân số điện thoại :\n\n%2$s\n\n"
+
+msgid "New private message from %s"
+msgstr "Bạn có tin nhắn riêng từ %s"
+
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+"------------------------------------------------------\n"
+"%3$s\n"
+"------------------------------------------------------\n"
+"\n"
+"You can reply to their message here:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Don't reply to this email; it won't get to them.\n"
+"\n"
+"With kind regards,\n"
+"%5$s\n"
+msgstr ""
+"%1$s (%2$s) đã gửi đến bạn tin nhắn riêng:\n"
+"\n"
+"------------------------------------------------------\n"
+"%3$s\n"
+"------------------------------------------------------\n"
+"\n"
+"Bạn có thể trả lá»i tại:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Äừng trả lá»i lại thÆ° này; sẽ không có ai nhận thÆ°.\n\nChúc sức khá»e,\n%5$s\n"
+
+msgid "Terms of use"
+msgstr "TTSD"
+
+msgid " Terms of use"
+msgstr "Thá»a thuận sá»­ dụng"
+
+msgid "Termsofuse"
+msgstr "Thá»a thuận sá»­ dụng"
+
+msgid "Click here to view emotion icon"
+msgstr "Nhấn chuá»™t vào đây để chá»n biểu tượng cảm xúc"
+
+msgid "Import from Web Address Book"
+msgstr "Nhập địa chỉ từ Yahoo hoặc Gmail"
+
+msgid "Incorrect email address or password."
+msgstr "Äịa chỉ email hoặc mật khẩu không đúng."
+
+msgid "Select All/None"
+msgstr "Chá»n tất cả/Không chá»n"
+
+msgid "Contacts not found."
+msgstr "Không tìm thấy kết nối."
+
+msgid ""
+"Please input your yahoo or gmail account. List of contacts will be loaded "
+"from your address book.\n"
+"\n"
+"Don't worry, we won't save your password and you'll get a chance to choose "
+"which friends to invite."
+msgstr ""
+"Hãy nhập tài khoản yahoo hoặc tài khoản gmail của bạn. Danh "
+"sách bạn bè, ngÆ°á»i thân sẽ được lấy từ sổ địa chỉ "
+"của bạn.\n"
+"\n"
+"Äừng lo, chúng tôi sẽ không lÆ°u password của bạn và bạn sẽ có danh sách những ngÆ°á»i bạn để má»i."
+
+msgid "Import Address Book"
+msgstr "Thêm vào danh sách địa chỉ"
+
+msgid "Import Contacts Address Book from Yahoo, Gmail"
+msgstr "Nhập sổ địa chỉ liên lạc từ Yahoo, Gmail"
+
+msgid "Your email address"
+msgstr "Äịa chỉ email của bạn"
+
+msgid "Import Web Address Book from"
+msgstr "Nhập sổ địa chỉ web từ"
+
+msgid ""
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+msgstr ""
+"%2$s là một dịch vụ gửi tin nhắn mà nó cho phép bạn giữ "
+"liên lạc, cập nhật thông tin vá»›i bạn bè, ngÆ°á»i thân và "
+"những ngÆ°á»i quan tâm đến bạn.\n"
+"\n"
+"Ban cũng có thể chia sẻ thông tin vỠbản thân, những suy "
+"nghÄ©, cuá»™c sống của bạn vá»›i những ngÆ°á»i biết bạn. Là "
+"má»™t công cụ hữu hiệu cho việc gặp gỡ ngÆ°á»i má»›i, những ngÆ°á»i có cùng sở thích vá»›i bạn.\n\n"
+
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s đã má»i bạn tham gia vào %2$s (%3$s).\n"
+"\n"
+"%1$s đã nói:\n"
+"\n"
+"%4$s\n"
+"\n"
+"Bạn có thể thấy trang hồ sơ của %1$s trên %2$s ở đây:\n"
+"\n"
+"%5$s\n"
+"\n"
+"Nếu bạn muốn thá»­ dịch vụ này, hãy bấm vào Ä‘Æ°á»ng liên "
+"kết bên dÆ°á»›i để chấp nhận lá»i má»i.\n"
+"\n"
+"%6$s\n"
+"\n"
+"Nếu không, bạn có thể bá» qua tin nhắn này. Cảm Æ¡n sá»± kiên nhẫn và thá»i gian của bạn dành cho chúng tôi.\n\nXin chân thành cảm Æ¡n, %2$s\n"
+
+msgid ""
+"Hi, I recently created an account at %1$s and want to share it with "
+"you.\n"
+"\n"
+"%1$s is a free, micro-blogging service, which combines the best of blog and "
+"chat. You can use it to write short notices (less than 140 characters) about "
+"your daily life: where you are, what you are doing, and how you are "
+"feeling.\n"
+"\n"
+"It is a tool that allows you to communicate, share, and stay in touch with "
+"friends and to follow other people that interest you through the exchange of "
+"short frequent messages (micro-blogging).\n"
+"\n"
+msgstr ""
+"Chào bạn, mình đã tạo tài khoản trên %1$s và muốn giới "
+"thiệu cho bạn cùng biết vỠdịch vụ này.\n"
+"\n"
+"%1$s là dịch vụ gửi tin nhắn ngắn miễn phí, một sự kết "
+"hợp tuyệt vá»i giữa blog và chat. Bạn có thể viết tin nhắn "
+"khoảng 140 kí tá»± vá» Ä‘á»i sống thÆ°á»ng nhật của bạn nhÆ°: "
+"bạn ở đâu, bạn đang làm gì, và bạn cảm thấy thế "
+"nào.\n"
+"\n"
+"Äây là công cụ để bạn giao tiếp, chia sẻ, kết nối vá»›i bạn bè và theo những ngÆ°á»i có cùng sở thích vá»›i bạn bằng cách gá»­i cho nhau những tin nhắn ngắn.\n\n"
+
+msgid "Copy and paste the embed code to your website or blog"
+msgstr "Copy và dán mã trên vào website hay blog của bạn"
+
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr ""
+"Vì lý do bảo mật, bạn hãy nhập lại tên đăng nhập và mật "
+"khẩu trÆ°á»›c khi thay đổi trong Ä‘iá»u chỉnh."
+
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+"Mã xác nhận đã được gửi tới địa chỉ email của bạn. "
+"Hãy kiểm tra hộp thư và làm theo hướng dẫn."
+
+msgid ""
+"Hey, %1$s\n"
+"\n"
+"Someone just asked for a new password for this account on %2$s.\n"
+"\n"
+"If it was you, and you want to confirm, use the URL below:\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"If not, just ignore this message.\n"
+"\n"
+"Thanks for your time, \n"
+"%1$s\n"
+msgstr ""
+"Xin chào, %1$s\n"
+"\n"
+"Không biết có phải bạn là ngÆ°á»i yêu cầu mật khẩu má»›i "
+"cho tài khoản này trên %2$s.\n"
+"\n"
+"Nếu chính là bạn và nếu bạn muốn xác nhận, hãy nhấn "
+"chuá»™t vào Ä‘Æ°á»ng dẫn dÆ°á»›i đây:\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Nếu không phải bạn, hãy bá» qua tin nhắn này.\n\nCảm Æ¡n bạn đã bá» thá»i gian để Ä‘á»c thÆ°, \n%1$s\n"
+
+msgid "Create a group"
+msgstr "Tạo nhóm"
+
+msgid "No such message."
+msgstr "Không có tin nhắn nào."
+
+msgid "Only the sender and recipient may read this message"
+msgstr "Chỉ có ngÆ°á»i gá»­i hoặc ngÆ°á»i nhận má»›i có thể xem tin nhắn này"
+
+msgid "Are you sure you want to delete this message?"
+msgstr "Bạn có chắc chắn là muốn xóa tin nhắn này không?"
+
+msgid ""
+"You are about to permanently delete a message. Once this is done, it cannot "
+"be undone."
+msgstr "Bạn muốn xóa tin nhắn này? Sau khi xóa, bạn không thể lấy lại được."
+
+msgid "Can't delete this message."
+msgstr "Không thể xóa tin nhắn này."
+
+msgid "Can't delete this notice."
+msgstr "Không thể xóa tin nhắn này."
+
+msgid "%s and group"
+msgstr "%s và nhóm"
+
+msgid "My Groups"
+msgstr "Nhóm"
+
+msgid "Group Profile"
+msgstr "Thông tin nhóm"
+
+msgid "Timeline"
+msgstr "Dòng tin"
+
+msgid "%s's timeline"
+msgstr "Dòng tin nhắn của %s"
+
+msgid "Share a thought? Vas-y, ton tour..."
+msgstr "Muốn chia sẽ ý nghĩ của bạn?"
+
+msgid "Group Members"
+msgstr "Thành viên"
+
+msgid "Group name"
+msgstr "Tên nhóm"
+
+msgid "Create date"
+msgstr "Ngày thành lập"
+
+msgid "Member count"
+msgstr "Số thành viên"
+
+msgid ""
+"By clicking on 'I accept' above, you confirm that you are over 13 years of "
+"age and accept the Terms of Service"
+msgstr ""
+"Nhấn vào 'Tôi đồng ý' ở trên để xác nhận là bạn trên 13 "
+"tuổi và chấp nhận những Ä‘iá»u khoản của dịch vụ"
+
+msgid "I accept"
+msgstr "Tôi đồng ý"
+
+msgid "Group code invalid."
+msgstr "Mã số của nhóm không đúng."
+
+msgid "Register a Group"
+msgstr "Äăng ký nhóm"
+
+msgid "Group code"
+msgstr "Mã nhóm"
+
+msgid "Only people who know this code can join the group. Required."
+msgstr "Chỉ những ai biết mã này mới có thể gia nhập nhóm. Bắt buộc."
+
+msgid "Enter the code if you want to join this group"
+msgstr "Nhập vào mã của nhóm nếu bạn muốn tham gia."
+
+msgid "Manage"
+msgstr "Quản lý"
+
+msgid "You have no authentication to access this page."
+msgstr "Bạn không có quyá»n truy cập trang này."
+
+msgid "Check the flagged notice and then delete or ignore it."
+msgstr ""
+"Kiểm tra lại tin bị cảnh báo, sau đó bạn hãy quyết định "
+"xóa tin đó hoặc xác nhận nó là hợp lệ."
+
+msgid "Flagged"
+msgstr "Kiểm tin"
+
+msgid "Description"
+msgstr "Mô tả"
+
+msgid "Short description of the group"
+msgstr "Mô tả ngắn gá»n vá» nhóm"
+
+msgid "Group"
+msgstr "Nhóm"
+
+msgid "Check notice flagged by user"
+msgstr "Kiểm tra các tin nhắn do ngÆ°á»i dùng cảnh báo"
+
+msgid "is more fun with friends."
+msgstr "vui hơn với bạn bè"
+
+msgid "Invite your friends"
+msgstr "Má»i bạn bè"
+
+msgid "Hide recommendations"
+msgstr "Tắt"
+
+msgid ""
+"Sorry, but you need to activate your account first. We sent the activation "
+"email, so please check your email for details. Don't forget to look in your "
+"spam folder as well."
+msgstr ""
+"Xin lỗi, bạn cần phải kích hoạt tài khoản của bạn "
+"trước. Chúng tôi đã gửi email tới bạn, vì vậy hãy mở "
+"há»™p mail để xem hÆ°á»›ng dẫn chi tiết. Äừng quên tìm cả trong spam."
+
+msgid "Current confirmed email address."
+msgstr "Äã xác nhận địa chỉ này."
+
+msgid "Chat vs T8M"
+msgstr "Chat và T8M"
+
+msgid "Prev"
+msgstr "TrÆ°á»›c"
+
+msgid "Next"
+msgstr "Kế"
+
+#: ../actions/profilesettings.php:54
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "Nói vỠbạn và những sở thích của bạn khoảng 140 ký tự"
+
+#: ../actions/profilesettings.php:57
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "Bạn ở đâu, \"Thành phố, Tỉnh thành, Quốc gia\""
+
+msgid "Members"
+msgstr "Thành viên"
+
+msgid "Subscribe "
+msgstr "Theo nhóm này"
+
+msgid "Group View"
+msgstr "Xem theo nhóm"
+
+msgid "Linear View"
+msgstr "Xem tuần tự"
+
+msgid " and friends"
+msgstr " và bạn bè"
+
+msgid "Channels"
+msgstr "Kênh"
+
+msgid "Channel already exist. Try another one."
+msgstr "Kênh này đã có rồi. Hãy chá»n tên khác."
+
+msgid "Can't save this chanel"
+msgstr "Lỗi khi lưu kênh"
+
+msgid "You can create a new channel here."
+msgstr "Bạn có thể tạo kênh tại đây."
+
+msgid "Create Channel"
+msgstr "Tạo Kênh"
+
+msgid "Channel name"
+msgstr "Tên kênh"
+
+msgid "Name of channel. Required."
+msgstr "Tên kênh. Bắt buộc"
+
+msgid "Channel saved successfully"
+msgstr "Tạo kênh thành công."
+
+msgid " You have just created channel %s successfuly."
+msgstr "Bạn vừa tạo thành công kênh %s."
+
+msgid "Channel"
+msgstr "Kênh"
+
+msgid "No such chanel."
+msgstr "Không tìm thấy kênh nào."
+
+msgid "Channels View"
+msgstr "Hệ thống các kênh"
+
+msgid "Create a channel"
+msgstr "Tạo kênh"
+
+msgid "Register user by channel"
+msgstr "Äăng ký theo kênh"
+
+msgid "The channel this user in"
+msgstr "Kênh mà bạn tham gia"
+
+msgid " Subscribe"
+msgstr "Theo kênh này"
+
+msgid "Your incoming and sent messages"
+msgstr "Hộp thư của bạn"
+
+msgid "Send me all replies through Yahoo Messenger."
+msgstr "Hãy gửi tin nhắn đến tôi qua Yahoo Messenger"
+
+msgid "Yahoo Messenger Settings"
+msgstr "Thiết lập địa chỉ Yahoo"
+
+msgid ""
+"You can send and receive notices through Yahoo Messenger [instant "
+"messages](%%doc.im%%). Configure your yahoo address and settings below."
+msgstr ""
+"Bạn có thể gửi và nhận những tin nhắn qua Yahoo Messenger [tin "
+"nhắn nhanh](%%doc.im%%). Hãy định dạng địa chỉ của bạn và các thiết lập sau."
+
+msgid ""
+"Awaiting confirmation on this address. Check your Yahoo inbox (and spam "
+"box!) for a message with further instructions."
+msgstr ""
+"Äang đợi xác nhận đến địa chỉ này. Hãy kiểm tra há»™p "
+"thÆ° đến(và thÆ° rác) để nhận tin nhắn và lá»i hÆ°á»›ng dẫn. "
+
+msgid "Yahoo Address"
+msgstr "Äịa chỉ email Yahoo"
+
+msgid "Yahoo address, like \"UserName@yahoo.com\""
+msgstr "Äịa chỉ email Yahoo, Ví dụ: \"UserName@yahoo.com\""
+
+msgid "That yahoo address already belongs to another user."
+msgstr "Äịa chỉ email Yahoo này đã có ngÆ°á»i khác sá»­ dụng rồi."
+
+msgid "That is already your yahoo address."
+msgstr "Bạn đã dùng địa chỉ email này rồi"
+
+msgid "Cannot normalize that Yahoo address"
+msgstr "Không thể bình thÆ°á»ng hóa địa chỉ Yahoo này"
+
+msgid "No yahoo address."
+msgstr "Chưa nhập địa chỉ email Yahoo"
+
+msgid ""
+"A confirmation code was sent to the yahoo email address you added. Check "
+"your inbox (and spam box!) for the code and instructions on how to use "
+"it."
+msgstr ""
+"Mã xác nhận đã được gửi đến địa chỉ email Yahoo của "
+"bạn. Hãy kiểm tra hộp thư đến (và thư rác) và làm theo hướng dẫn trong thư."
+
+msgid "says"
+msgstr "nói"
+
+msgid "Your message is posted successful."
+msgstr "Tin nhắn đã gửi đi"
+
+msgid "To use this service, You must register your YM with Saigonica."
+msgstr "Äể được sá»­ dụng dịch vụ này, bạn phải đăng ký YM trên Saigonica"
+
+msgid "To use this service, You must register your Gtalk with Saigonica."
+msgstr "Äể được sá»­ dụng dịch vụ này, bạn phải đăng ký GTalk trên Saigonica"
+
+msgid "Send me all replies through Jabber/GTalk."
+msgstr "Hãy gửi tin nhắn đến tôi qua Jabber/GTalk."
+
+msgid "GTalk Settings"
+msgstr "Thiết lập địa chỉ GTalk"
+
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your yahoo address and settings below."
+msgstr ""
+"Bạn có thể gửi và nhận những tin nhắn qua Jabber/GTalk [tin "
+"nhắn nhanh](%%doc.im%%). Hãy định dạng địa chỉ của bạn và các thiết lập sau."
+
+msgid ""
+"Awaiting confirmation on this address. Check your GTalk inbox (and spam "
+"box!) for a message with further instructions."
+msgstr ""
+"Äang đợi xác nhận đến địa chỉ này. Hãy kiểm tra há»™p "
+"thÆ° đến(và thÆ° rác) để nhận tin nhắn và lá»i hÆ°á»›ng dẫn. "
+
+msgid "GTalk Address"
+msgstr "Äịa chỉ email GTalk"
+
+msgid "GTalk address, like \"UserName@gmail.com\""
+msgstr "Äịa chỉ email GTalk, Ví dụ: \"UserName@gmail.com\""
+
+msgid "That GTalk address already belongs to another user."
+msgstr "Äịa chỉ email GTalk này đã có ngÆ°á»i khác sá»­ dụng rồi."
+
+msgid "That is already your GTalk address."
+msgstr "Bạn đã dùng địa chỉ email này rồi"
+
+msgid "Cannot normalize that GTalk address"
+msgstr "Không thể bình thÆ°á»ng hóa địa chỉ GTalk này"
+
+msgid "No GTalk address."
+msgstr "Chưa nhập địa chỉ email GTalk"
+
+msgid ""
+"A confirmation code was sent to the GTalk email address you added. Check "
+"your inbox (and spam box!) for the code and instructions on how to use "
+"it."
+msgstr ""
+"Mã xác nhận đã được gửi đến địa chỉ email GTalk của "
+"bạn. Hãy kiểm tra hộp thư đến (và thư rác) và làm theo hướng dẫn trong thư."
+
+msgid "Check virus"
+msgstr "Kiểm tra virus"
+
+msgid "Error"
+msgstr "Lá»—i"
+
+msgid "Avatar "
+msgstr "Hình"
+
+msgid "Avatar settings"
+msgstr "Thay đổi hình đại diện"
+
+msgid "No avatar is selected."
+msgstr "Bạn chÆ°a chá»n hình để Ä‘Æ°a lên."
+
+msgid "Change your avatar"
+msgstr "Thay đổi hình đại diện của bạn"
+
+msgid "Updates by Yahoo Messenger (YM)"
+msgstr "Cập nhật địa chỉ Yahoo để gửi tin qua Yahoo Mesenger(YM)"
+
+msgid "Twitter integration options"
+msgstr "Các lá»±a chá»n để tích hợp vá»›i Twitter "
+
+msgid "Ban from network"
+msgstr "Ban user ra khá»i mạng"
+
+msgid "Ban users violate rules of saigonica"
+msgstr "Ban user vi phạm nội quy của saigonica"
+
+msgid "Ban user"
+msgstr "Ban user"
+
+msgid "Are you sure to ban this user?"
+msgstr "Bạn có chắc muốn ban ngÆ°á»i này không?"
+
+msgid "Your account was banned."
+msgstr "Tài khoản của bạn đã bị ban."
+
+msgid "User has been baned."
+msgstr "User đã bị ban."
diff --git a/locale/zh_CN/LC_MESSAGES/laconica.po b/locale/zh_CN/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..df5b877a8
--- /dev/null
+++ b/locale/zh_CN/LC_MESSAGES/laconica.po
@@ -0,0 +1,3026 @@
+# Messages of identi.ca
+# Copyright (C) 2008 Gouki <gouki@goukihq.org>
+# This file is distributed under the same license as the identi.ca package.
+# Gouki <gouki@goukihq.org>, 2008
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: identi.ca\n"
+"Report-Msgid-Bugs-To: gouki@goukihq.org\n"
+"POT-Creation-Date: 2008-10-13 21:07+0100\n"
+"PO-Revision-Date: 2008-12-16 21:31+0800\n"
+"Last-Translator: Yuan Yijun <bbbush.yuan@gmail.com>\n"
+"Language-Team: i18n-zh <i18n-zh@googlegroups.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64 actions/noticesearchrss.php:68
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "æœç´¢æœ‰å…³\"%s\"çš„èšåˆ"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:191
+#: actions/finishopenidlogin.php:88 actions/register.php:205
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr "除了éšç§å†…容:密ç ï¼Œç”µå­é‚®ä»¶ï¼Œå³æ—¶é€šè®¯å¸å·ï¼Œç”µè¯å·ç ã€‚"
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr " 从 "
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr "%1$s / å›žå¤ %2$s 的消æ¯"
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr "%1$s 邀请您加入 %2$s"
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+"%1$s 邀请您加入 %2$s (%3$s)。\n"
+"\n"
+"%2$s "
+"æ"
+"˜"
+"¯"
+"ä"
+"¸"
+"€"
+"ä"
+"¸"
+"ªå¾®åšå®¢æœåŠ¡ï¼Œæ‚¨å¯ä»¥å‘朋å‹æˆ–关注您的人公布信æ¯ã€‚\n"
+"\n"
+"æ"
+"‚"
+"¨"
+"ä"
+"¹"
+"Ÿ"
+"å"
+""
+"¯"
+"ä"
+"»"
+"¥"
+"ä"
+"¸"
+"Ž"
+"ç"
+"º"
+"¿"
+"ä"
+"¸"
+"Š"
+"ç"
+"š"
+"„"
+"æ"
+"œ"
+"‹"
+"å"
+""
+"‹"
+"å"
+"…"
+"±"
+"ä"
+"º"
+"«è‡ªå·±çš„新知新闻。您还å¯ä»¥ä¸Žæœ‰å…±åŒçˆ±å¥½çš„人交æµã€‚\n"
+"\n"
+"%1$s çš„è¯ï¼š\n\n%4$s\n\n您å¯ä»¥åœ¨è¿™é‡Œçœ‹åˆ° %1$s çš„ %2$s 个人信æ¯ï¼š\n\n%5$s\n\n如果您希望å°è¯•è¿™ä¸ªæœåŠ¡ï¼Œç‚¹å‡»ä¸‹é¢çš„链接,接å—邀请。\n\n%6$s\n\n如果ä¸å¸Œæœ›ï¼Œå¯ä»¥å¿½ç•¥è¿™æ¡æ¶ˆæ¯ã€‚谢谢您的è€å¿ƒå’Œæ—¶é—´ã€‚\n\n为您效力的 %2$s\n"
+
+#: ../lib/mail.php:124 lib/mail.php:124 lib/mail.php:126
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "%1$s 开始关注您的 %2$s ä¿¡æ¯ã€‚"
+
+#: ../lib/mail.php:126
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"%1$s 开始关注您的 %2$s ä¿¡æ¯ã€‚\n"
+"\n"
+"\t%3$s\n\n为您效力的 %4$s\n"
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr "å›žå¤ %2$s / %3$s çš„ %1$s 更新。"
+
+#: ../actions/shownotice.php:45 actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s çš„ %2$s 状æ€"
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../actions/publicrss.php:62 actions/publicrss.php:48
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s 公开èšåˆ"
+
+#: ../actions/all.php:47 ../actions/allrss.php:60
+#: ../actions/twitapistatuses.php:238 ../lib/stream.php:51 actions/all.php:47
+#: actions/allrss.php:60 actions/twitapistatuses.php:155 lib/personal.php:51
+#, php-format
+msgid "%s and friends"
+msgstr "%s åŠå¥½å‹"
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr "%s 公众时间表"
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr "%s 状æ€"
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr "%s 时间表"
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr "æ¥è‡ªæ‰€æœ‰äººçš„ %s 消æ¯ï¼"
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr "(您将收到一å°é‚®ä»¶ï¼ŒåŒ…å«äº†å¦‚何确认邮件地å€çš„说明。)"
+
+#: ../lib/util.php:257 lib/util.php:273
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"**%%site.name%%** 是一个微åšå®¢æœåŠ¡ï¼Œæ供者为 "
+"[%%site.broughtby%%](%%site.broughtbyurl%%)。"
+
+#: ../lib/util.php:259 lib/util.php:275
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%** 是一个微åšå®¢æœåŠ¡ã€‚"
+
+#: ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "。贡献者应当有全å或昵称。"
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: actions/finishopenidlogin.php:79 actions/profilesettings.php:76
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1 到 64 个å°å†™å­—æ¯æˆ–数字,ä¸åŒ…å«æ ‡ç‚¹åŠç©ºç™½"
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr "1 到 64 个å°å†™å­—æ¯æˆ–数字,ä¸åŒ…å«æ ‡ç‚¹åŠç©ºç™½ã€‚此项必填。"
+
+#: ../actions/password.php:42 actions/profilesettings.php:181
+msgid "6 or more characters"
+msgstr "6 个或更多字符"
+
+#: ../actions/recoverpassword.php:180 actions/recoverpassword.php:186
+msgid "6 or more characters, and don't forget it!"
+msgstr "6 个或更多字符,ä¸èƒ½å¿˜è®°ï¼"
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr "6 个或更多字符。此项必填。"
+
+#: ../actions/imsettings.php:197 actions/imsettings.php:205
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr "验è¯ç å·²è¢«å‘é€åˆ°æ‚¨æ–°å¢žçš„å³æ—¶é€šè®¯å¸å·ã€‚您必须å…许 %s å‘您å‘é€ä¿¡æ¯ã€‚"
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr "验è¯ç å·²è¢«å‘é€åˆ°æ‚¨æ–°å¢žçš„电å­é‚®ä»¶ã€‚请检查收件箱(和垃圾箱),找到验è¯ç å¹¶æŒ‰è¦æ±‚使用它。"
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr "验è¯ç å·²è¢«å‘é€åˆ°æ‚¨æ–°å¢žçš„电è¯å·ç ã€‚请检查收件箱(和垃圾箱),找到验è¯ç å¹¶æŒ‰è¦æ±‚使用它。"
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr "API 方法未实现ï¼"
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr "API 方法尚未实现。"
+
+#: ../lib/util.php:324 lib/util.php:340
+msgid "About"
+msgstr "关于"
+
+#: ../actions/userauthorization.php:119 actions/userauthorization.php:126
+msgid "Accept"
+msgstr "接å—"
+
+#: ../actions/emailsettings.php:62 ../actions/imsettings.php:63
+#: ../actions/openidsettings.php:57 ../actions/smssettings.php:71
+#: actions/emailsettings.php:63 actions/imsettings.php:64
+#: actions/openidsettings.php:58 actions/smssettings.php:71
+#: actions/twittersettings.php:85
+msgid "Add"
+msgstr "添加"
+
+#: ../actions/openidsettings.php:43 actions/openidsettings.php:44
+msgid "Add OpenID"
+msgstr "添加 OpenID"
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr "添加或移除 OpenID"
+
+#: ../actions/emailsettings.php:38 ../actions/imsettings.php:39
+#: ../actions/smssettings.php:39 actions/emailsettings.php:39
+#: actions/imsettings.php:40 actions/smssettings.php:39
+msgid "Address"
+msgstr "地å€"
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr "è¦é‚€è¯·çš„好å‹åœ°å€(æ¯è¡Œä¸€ä¸ª)"
+
+#: ../actions/showstream.php:273 actions/showstream.php:288
+msgid "All subscriptions"
+msgstr "所有订阅"
+
+#: ../actions/publicrss.php:64 actions/publicrss.php:50
+#, php-format
+msgid "All updates for %s"
+msgstr "所有 %s 消æ¯"
+
+#: ../actions/noticesearchrss.php:66 actions/noticesearchrss.php:70
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "所有匹é…æœç´¢æ¡ä»¶\"%s\"的消æ¯"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:31
+#: ../actions/openidlogin.php:29 ../actions/register.php:30
+#: actions/finishopenidlogin.php:29 actions/login.php:31
+#: actions/openidlogin.php:29 actions/register.php:30
+msgid "Already logged in."
+msgstr "已登录。"
+
+#: ../lib/subs.php:42 lib/subs.php:42
+msgid "Already subscribed!."
+msgstr "已订阅ï¼"
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr "确定è¦åˆ é™¤è¿™æ¡é€šå‘Šå—?"
+
+#: ../actions/userauthorization.php:77 actions/userauthorization.php:83
+msgid "Authorize subscription"
+msgstr "确认订阅"
+
+#: ../actions/login.php:104 ../actions/register.php:178
+#: actions/register.php:192
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "ä¿æŒè¿™å°æœºå™¨ä¸Šçš„登录状æ€ã€‚ä¸è¦åœ¨å…±ç”¨çš„机器上ä¿æŒç™»å½•ï¼"
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr "自动订阅任何订阅我的更新的人(这个选项最适åˆæœºå™¨äºº)"
+
+#: ../actions/avatar.php:32 ../lib/settingsaction.php:90
+#: actions/profilesettings.php:34
+msgid "Avatar"
+msgstr "头åƒ"
+
+#: ../actions/avatar.php:113 actions/profilesettings.php:350
+msgid "Avatar updated."
+msgstr "头åƒå·²æ›´æ–°ã€‚"
+
+#: ../actions/imsettings.php:55 actions/imsettings.php:56
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr ""
+"ç"
+"­"
+"‰"
+"å"
+"¾…确认此地å€ã€‚请查看您的Jabber/GTalkå¸å·æ˜¯å¦æ”¶åˆ°äº†ä¿¡æ¯ï¼Œå…¶ä¸­åŒ…å«äº†è¿›ä¸€æ­¥çš„指示。(您必须预先将 %s 添加为好å‹ï¼Œæ‰èƒ½æ”¶åˆ°ç¡®è®¤ä¿¡æ¯ï¼)"
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr "等待确认此地å€ã€‚请查看您的收件箱(和垃圾箱)是å¦æ”¶åˆ°äº†é‚®ä»¶ï¼Œå…¶ä¸­åŒ…å«äº†è¿›ä¸€æ­¥çš„指示。"
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr "等待确认此电è¯å·ç ã€‚"
+
+#: ../lib/util.php:1318 lib/util.php:1452
+msgid "Before »"
+msgstr "ä¹‹å‰ Â»"
+
+#: ../actions/profilesettings.php:49 ../actions/register.php:170
+#: actions/profilesettings.php:82 actions/register.php:184
+msgid "Bio"
+msgstr "个人å°ä¼ "
+
+#: ../actions/profilesettings.php:101 ../actions/register.php:82
+#: ../actions/updateprofile.php:103 actions/profilesettings.php:216
+#: actions/register.php:89 actions/updateprofile.php:104
+msgid "Bio is too long (max 140 chars)."
+msgstr "个人å°ä¼ è¿‡é•¿(ä¸èƒ½è¶…过140字符)。"
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr "无法删除通告。"
+
+#: ../actions/updateprofile.php:119 actions/updateprofile.php:120
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "无法访问头åƒURL '%s'"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:300
+#: actions/profilesettings.php:404 actions/recoverpassword.php:313
+msgid "Can't save new password."
+msgstr "无法ä¿å­˜æ–°å¯†ç ã€‚"
+
+#: ../actions/emailsettings.php:57 ../actions/imsettings.php:58
+#: ../actions/smssettings.php:62 actions/emailsettings.php:58
+#: actions/imsettings.php:59 actions/smssettings.php:62
+msgid "Cancel"
+msgstr "å–消"
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "无法创建 OpenID 用户对象。"
+
+#: ../actions/imsettings.php:163 actions/imsettings.php:171
+msgid "Cannot normalize that Jabber ID"
+msgstr "无法识别此Jabber ID"
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr "无法识别此电å­é‚®ä»¶"
+
+#: ../actions/password.php:45 actions/profilesettings.php:184
+msgid "Change"
+msgstr "修改"
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr "修改电å­é‚®ä»¶"
+
+#: ../actions/password.php:32 actions/profilesettings.php:36
+msgid "Change password"
+msgstr "修改密ç "
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr "修改密ç "
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr "修改您的个人信æ¯"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:181
+#: ../actions/register.php:155 ../actions/smssettings.php:65
+#: actions/profilesettings.php:182 actions/recoverpassword.php:187
+#: actions/register.php:169 actions/smssettings.php:65
+msgid "Confirm"
+msgstr "确认"
+
+#: ../actions/confirmaddress.php:90 actions/confirmaddress.php:90
+msgid "Confirm Address"
+msgstr "确认地å€"
+
+#: ../actions/emailsettings.php:238 ../actions/imsettings.php:222
+#: ../actions/smssettings.php:245 actions/emailsettings.php:256
+#: actions/imsettings.php:230 actions/smssettings.php:253
+msgid "Confirmation cancelled."
+msgstr "å·²å–消确认。"
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr "确认ç "
+
+#: ../actions/confirmaddress.php:38 actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "未找到确认ç ã€‚"
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+"ç¥è´ºæ‚¨ï¼Œ%sï¼æ¬¢è¿Žæ¥åˆ° "
+"%%%%site.name%%%%。在这里,您å¯ä»¥ï¼š\n"
+"\n"
+"* 转到[个人信æ¯](%s)页é¢ï¼Œå‘表第一æ¡æ¶ˆæ¯ã€‚\n"
+"* "
+"æ"
+"·"
+"»"
+"å"
+"Š"
+" "
+"ä"
+"¸"
+"€"
+"ä"
+"¸"
+"ª"
+"["
+"J"
+"a"
+"b"
+"b"
+"e"
+"r"
+"/"
+"G"
+"T"
+"a"
+"l"
+"k"
+"å"
+"¸"
+""
+"å"
+""
+"·"
+"]"
+"("
+"%"
+"%%%action.imsettings%%%%),以åŽé€šè¿‡å³æ—¶é€šè®¯ç¨‹åºå‘布消æ¯ã€‚\n* "
+"[æœç´¢å¥½å‹](%%%%action.peoplesearch%%%%)å¯ä»¥æœåˆ°æ‚¨è¿‡åŽ»çš„好å‹æˆ–与您有共åŒçˆ±å¥½çš„人。\n* 更新您的[个人信æ¯](%%%%action.profilesettings%%%%),添加更多内容。\n* 阅读[在线文档](%%%%doc.help%%%%),了解更多特性。\n\n感谢您注册å¸å·ï¼Œå¸Œæœ›æ‚¨ä¼šå–œæ¬¢è¿™é¡¹æœåŠ¡ã€‚"
+
+#: ../actions/finishopenidlogin.php:91 actions/finishopenidlogin.php:97
+msgid "Connect"
+msgstr "连接"
+
+#: ../actions/finishopenidlogin.php:86 actions/finishopenidlogin.php:92
+msgid "Connect existing account"
+msgstr "连接现有å¸å·"
+
+#: ../lib/util.php:332 lib/util.php:348
+msgid "Contact"
+msgstr "è”系人"
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "无法创建 OpenID 表å•ï¼š%s"
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr "无法订阅用户:%s 已在订阅列表中。"
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr "无法订阅用户:未找到。"
+
+#: ../lib/openid.php:160 lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "无法é‡å®šå‘到æœåŠ¡å™¨ï¼š%s"
+
+#: ../actions/updateprofile.php:162 actions/updateprofile.php:163
+msgid "Could not save avatar info"
+msgstr "无法ä¿å­˜å¤´åƒ"
+
+#: ../actions/updateprofile.php:155 actions/updateprofile.php:156
+msgid "Could not save new profile info"
+msgstr "无法ä¿å­˜ä¸ªäººä¿¡æ¯"
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr "无法订阅他人更新。"
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr "无法订阅。"
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr "无法更新已确认的电å­é‚®ä»¶ã€‚"
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "无法将请求标记转æ¢ä¸ºè®¿é—®ä»¤ç‰Œã€‚"
+
+#: ../actions/confirmaddress.php:84 ../actions/emailsettings.php:234
+#: ../actions/imsettings.php:218 ../actions/smssettings.php:241
+#: actions/confirmaddress.php:84 actions/emailsettings.php:252
+#: actions/imsettings.php:226 actions/smssettings.php:249
+msgid "Couldn't delete email confirmation."
+msgstr "无法删除电å­é‚®ä»¶ç¡®è®¤ã€‚"
+
+#: ../lib/subs.php:103 lib/subs.php:116
+msgid "Couldn't delete subscription."
+msgstr "无法删除订阅。"
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr "找ä¸åˆ°ä»»ä½•ä¿¡æ¯ã€‚"
+
+#: ../actions/remotesubscribe.php:127 actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr "无法获得一份请求标记。"
+
+#: ../actions/emailsettings.php:205 ../actions/imsettings.php:187
+#: ../actions/smssettings.php:206 actions/emailsettings.php:223
+#: actions/imsettings.php:195 actions/smssettings.php:214
+msgid "Couldn't insert confirmation code."
+msgstr "无法æ’入验è¯ç ã€‚"
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr "无法添加新的订阅。"
+
+#: ../actions/profilesettings.php:184 ../actions/twitapiaccount.php:96
+#: actions/profilesettings.php:299 actions/twitapiaccount.php:94
+msgid "Couldn't save profile."
+msgstr "无法ä¿å­˜ä¸ªäººä¿¡æ¯ã€‚"
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr "无法更新用户的自动订阅选项。"
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr "无法更新用户记录。"
+
+#: ../actions/confirmaddress.php:72 ../actions/emailsettings.php:156
+#: ../actions/emailsettings.php:259 ../actions/imsettings.php:138
+#: ../actions/imsettings.php:243 ../actions/profilesettings.php:141
+#: ../actions/smssettings.php:157 ../actions/smssettings.php:269
+#: actions/confirmaddress.php:72 actions/emailsettings.php:174
+#: actions/emailsettings.php:277 actions/imsettings.php:146
+#: actions/imsettings.php:251 actions/profilesettings.php:256
+#: actions/smssettings.php:165 actions/smssettings.php:277
+msgid "Couldn't update user."
+msgstr "无法更新用户。"
+
+#: ../actions/finishopenidlogin.php:84 actions/finishopenidlogin.php:90
+msgid "Create"
+msgstr "创建"
+
+#: ../actions/finishopenidlogin.php:70 actions/finishopenidlogin.php:76
+msgid "Create a new user with this nickname."
+msgstr "创建使用此昵称的新用户。"
+
+#: ../actions/finishopenidlogin.php:68 actions/finishopenidlogin.php:74
+msgid "Create new account"
+msgstr "创建新å¸å·"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr "为 OpenID 用户产生新的å¸å·ã€‚"
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "已确认的Jabber/GTalkå¸å·ã€‚"
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr "已确认的å¯ä»¥å‘é€SMS短消æ¯çš„电è¯å·ç ã€‚"
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr "已确认的电å­é‚®ä»¶ã€‚"
+
+#: ../actions/showstream.php:356 actions/showstream.php:367
+msgid "Currently"
+msgstr "ç›®å‰"
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr "添加标签时数æ®åº“出错:%s"
+
+#: ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "添加回å¤æ—¶æ•°æ®åº“出错:%s"
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr "删除通告"
+
+#: ../actions/profilesettings.php:51 ../actions/register.php:172
+#: actions/profilesettings.php:84 actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "用ä¸è¶…过140个字符æ述您自己和您的爱好"
+
+#: ../actions/register.php:158 ../actions/register.php:161
+#: ../lib/settingsaction.php:87 actions/register.php:172
+#: actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr "电å­é‚®ä»¶"
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr "电å­é‚®ä»¶åœ°å€"
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr "电å­é‚®ä»¶è®¾ç½®"
+
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr "电å­é‚®ä»¶åœ°å€å·²å­˜åœ¨ã€‚"
+
+#: ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr "电å­é‚®ä»¶åœ°å€ç¡®è®¤"
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr "电å­é‚®ä»¶ï¼Œç±»ä¼¼ \"UserName@example.org\""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr "电å­é‚®ä»¶åœ°å€"
+
+#: ../actions/recoverpassword.php:191 actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr "输入昵称或电å­é‚®ä»¶ã€‚"
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr "输入手机收到的验è¯ç ã€‚"
+
+#: ../actions/userauthorization.php:137 actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr "无法认è¯ä»¤ç‰Œ"
+
+#: ../actions/finishopenidlogin.php:253 actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr "无法将用户与 OpenID 连接。"
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "无法连接用户。"
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr "添加头åƒå‡ºé”™"
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr "添加个人信æ¯å‡ºé”™"
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr "添加远程的个人信æ¯å‡ºé”™"
+
+#: ../actions/recoverpassword.php:240 actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr "ä¿å­˜åœ°å€ç¡®è®¤æ—¶å‡ºé”™ã€‚"
+
+#: ../actions/userauthorization.php:140 actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr "ä¿å­˜è¿œç¨‹çš„个人信æ¯æ—¶å‡ºé”™"
+
+#: ../lib/openid.php:226 lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "ä¿å­˜ä¸ªäººä¿¡æ¯æ—¶å‡ºé”™ã€‚"
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr "ä¿å­˜ç”¨æˆ·æ—¶å‡ºé”™ã€‚"
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr "ä¿å­˜ç”¨æˆ·æ—¶å‡ºé”™ï¼›ä¸æ­£ç¡®ã€‚"
+
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr "ä¿å­˜ç”¨æˆ·è®¾ç½®æ—¶å‡ºé”™ã€‚"
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "更新个人信æ¯æ—¶å‡ºé”™"
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr "更新远程的个人信æ¯æ—¶å‡ºé”™"
+
+#: ../actions/recoverpassword.php:80 actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr "验è¯ç å‡ºé”™ã€‚"
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr "昵称已被使用"
+
+#: ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr "常è§é—®é¢˜FAQ"
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr "更新头åƒå¤±è´¥ã€‚"
+
+#: ../actions/all.php:61 ../actions/allrss.php:64 actions/all.php:61
+#: actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "%s 好å‹çš„èšåˆ"
+
+#: ../actions/replies.php:65 ../actions/repliesrss.php:80
+#: actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "%s 回å¤çš„èšåˆ"
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr "%s 标签的èšåˆ"
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr "æœç´¢é€šå‘Šå†…容"
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr "æœç´¢ç”¨æˆ·ä¿¡æ¯"
+
+#: ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr "由于安全原因,修改设置å‰éœ€è¦è¾“入用户å和密ç ã€‚"
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr "å…¨å"
+
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr "å…¨å过长(ä¸èƒ½è¶…过 255 个字符)。"
+
+#: ../lib/util.php:322 lib/util.php:338
+msgid "Help"
+msgstr "帮助"
+
+#: ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr "主页"
+
+#: ../actions/profilesettings.php:46 ../actions/register.php:167
+#: actions/profilesettings.php:79 actions/register.php:181
+msgid "Homepage"
+msgstr "主页"
+
+#: ../actions/profilesettings.php:95 ../actions/register.php:76
+#: actions/profilesettings.php:210 actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr "主页的URLä¸æ­£ç¡®ã€‚"
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr "我希望通过邮件å‘布信æ¯ã€‚"
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr "å³æ—¶é€šè®¯IM"
+
+#: ../actions/imsettings.php:60 actions/imsettings.php:61
+msgid "IM Address"
+msgstr "IM å¸å·"
+
+#: ../actions/imsettings.php:33 actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "IM 设置"
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr "如果您已ç»æ‹¥æœ‰å¸å·ï¼Œå¯ä»¥å°†å®ƒè¿žæŽ¥åˆ°æ‚¨çš„ OpenID。请输入用户å和密ç ã€‚"
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr "如果您希望添加新的 OpenID,输入它并点击“添加â€ã€‚"
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr "如果您忘记了密ç ï¼Œå¯ä»¥ä½¿ç”¨æ­¤é‚®ç®±æ”¶åˆ°æ–°çš„密ç ã€‚"
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr "å‘布用的电å­é‚®ä»¶"
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr "å‘布用的电å­é‚®ä»¶è¢«ç§»é™¤ã€‚"
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr "旧密ç ä¸æ­£ç¡®"
+
+#: ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr "用户å或密ç ä¸æ­£ç¡®ã€‚"
+
+#: ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr "æ¢å¤å¯†ç çš„指示已被å‘é€åˆ°æ‚¨çš„注册邮箱。"
+
+#: ../actions/updateprofile.php:114 actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "头åƒURL '%s'ä¸æ­£ç¡®"
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr "电å­é‚®ä»¶åœ°å€ %s ä¸æ­£ç¡®"
+
+#: ../actions/updateprofile.php:98 actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "主页'%s'ä¸æ­£ç¡®"
+
+#: ../actions/updateprofile.php:82 actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr "授æƒæ–¹å¼URL '%s'ä¸æ­£ç¡®"
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr "通告内容ä¸æ­£ç¡®"
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr "通告URIä¸æ­£ç¡®"
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr "通告URLä¸æ­£ç¡®"
+
+#: ../actions/updateprofile.php:87 actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "个人信æ¯URL '%s'ä¸æ­£ç¡®ã€‚"
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr "个人信æ¯URLä¸æ­£ç¡®(æ ¼å¼é”™è¯¯)"
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr "æœåŠ¡å™¨è¿”回的个人信æ¯URLä¸æ­£ç¡®ã€‚"
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "大å°ä¸æ­£ç¡®ã€‚"
+
+#: ../actions/finishopenidlogin.php:235 ../actions/register.php:93
+#: ../actions/register.php:111 actions/finishopenidlogin.php:241
+#: actions/register.php:103 actions/register.php:121
+msgid "Invalid username or password."
+msgstr "用户å或密ç ä¸æ­£ç¡®ã€‚"
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr "å·²å‘é€é‚€è¯·"
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr "å·²å‘é€é‚€è¯·ç»™è¿™äº›äººï¼š"
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr "邀请"
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr "邀请新用户"
+
+#: ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+"它è¿è¡Œ[Laconica](http://laconi.ca/)å¾®åšå®¢æœåŠ¡ï¼Œç‰ˆæœ¬ "
+"%s,采用[GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)授æƒã€‚"
+
+#: ../actions/imsettings.php:173 actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr "Jabber ID 属于å¦ä¸€ç”¨æˆ·ã€‚"
+
+#: ../actions/imsettings.php:62 actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+"Jabber 或 GTalk "
+"å¸å·ï¼Œç±»ä¼¼\"UserName@example.org\"。首先,必须在å³æ—¶èŠå¤©å·¥å…·æˆ–GTalk中将 %s 加为好å‹ã€‚"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr "语言"
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr "语言过长(ä¸èƒ½è¶…过50个字符)。"
+
+#: ../actions/profilesettings.php:52 ../actions/register.php:173
+#: actions/profilesettings.php:85 actions/register.php:187
+msgid "Location"
+msgstr "ä½ç½®"
+
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr "ä½ç½®è¿‡é•¿(ä¸èƒ½è¶…过255个字符)。"
+
+#: ../actions/login.php:97 ../actions/login.php:106
+#: ../actions/openidlogin.php:68 ../lib/util.php:310 actions/login.php:97
+#: actions/login.php:106 actions/openidlogin.php:77 lib/util.php:326
+msgid "Login"
+msgstr "登录"
+
+#: ../actions/openidlogin.php:44 actions/openidlogin.php:52
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "使用[OpenID](%%doc.openid%%)å¸å·ç™»å½•ã€‚"
+
+#: ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"ä"
+"½"
+"¿"
+"ç"
+"”"
+"¨"
+"ç"
+"”"
+"¨"
+"æ"
+"ˆ"
+"·"
+"å"
+"和密ç ç™»å½•ã€‚没有用户å?[注册](%%action.register%%)æ–°å¸å·ï¼Œæˆ–者使用[OpenID](%%action.openidlogin%%)。"
+
+#: ../lib/util.php:308 lib/util.php:324
+msgid "Logout"
+msgstr "登出"
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr "é•¿å字,最好是“实åâ€"
+
+#: ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr "忘记了密ç ï¼Ÿ"
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr "生æˆæ–°çš„电å­é‚®ä»¶åœ°å€ç”¨äºŽå‘布信æ¯ï¼›å–消旧的。"
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr "设置 %%site.name%% å‘é€çš„邮件。"
+
+#: ../actions/showstream.php:300 actions/showstream.php:315
+msgid "Member since"
+msgstr "注册于"
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr "%s çš„å¾®åšå®¢æœåŠ¡"
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr "电è¯çš„æœåŠ¡å•†ã€‚如果您的æœåŠ¡å•†æ”¯æŒé€šè¿‡ç”µå­é‚®ä»¶å‘é€SMS短信,而这里尚未列出,请è”ç³» %s 以告知。"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:188
+#: actions/finishopenidlogin.php:85 actions/register.php:202
+msgid "My text and files are available under "
+msgstr "我的文字和文件采用的授æƒæ–¹å¼ä¸º"
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr "新建"
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr "新的电å­é‚®ä»¶åœ°å€ï¼Œç”¨äºŽå‘布 %s ä¿¡æ¯"
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr "已添加新的å‘布用的电å­é‚®ä»¶åœ°å€ã€‚"
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr "新昵称"
+
+#: ../actions/newnotice.php:87 actions/newnotice.php:96
+msgid "New notice"
+msgstr "新通告"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:179
+#: actions/profilesettings.php:180 actions/recoverpassword.php:185
+msgid "New password"
+msgstr "新密ç "
+
+#: ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr "新密ç å·²ä¿å­˜ï¼Œæ‚¨çŽ°åœ¨å·²ç™»å½•ã€‚"
+
+#: ../actions/login.php:101 ../actions/profilesettings.php:41
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr "昵称"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr "昵称已被使用,æ¢ä¸€ä¸ªå§ã€‚"
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:88
+#: ../actions/register.php:67 ../actions/updateprofile.php:77
+#: actions/finishopenidlogin.php:171 actions/profilesettings.php:203
+#: actions/register.php:74 actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "昵称åªèƒ½ä½¿ç”¨å°å†™å­—æ¯å’Œæ•°å­—,ä¸åŒ…å«ç©ºæ ¼ã€‚"
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr "ä¸å…许的昵称。"
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr "希望订阅的用户的昵称"
+
+#: ../actions/recoverpassword.php:162 actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr "昵称或电å­é‚®ä»¶"
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr "å¦"
+
+#: ../actions/imsettings.php:156 actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr "没有 Jabber ID。"
+
+#: ../actions/userauthorization.php:129 actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr "未收到认è¯è¯·æ±‚ï¼"
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr "未选择è¿è¥å•†ã€‚"
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr "没有输入验è¯ç "
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "没有验è¯ç "
+
+#: ../actions/newnotice.php:44 actions/newmessage.php:53
+#: actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr "没有内容ï¼"
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr "没有电å­é‚®ä»¶åœ°å€ã€‚"
+
+#: ../actions/userbyid.php:32 actions/userbyid.php:32
+msgid "No id."
+msgstr "没有 id。"
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr "没有å‘布用的电å­é‚®ä»¶åœ°å€ã€‚"
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr "远程æœåŠ¡å™¨æ²¡æœ‰æ˜µç§°ã€‚"
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "没有昵称。"
+
+#: ../actions/emailsettings.php:222 ../actions/imsettings.php:206
+#: ../actions/smssettings.php:229 actions/emailsettings.php:240
+#: actions/imsettings.php:214 actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr "没有å¯ä»¥å–消的确认。"
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr "没有电è¯å·ç ã€‚"
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr "æœåŠ¡å™¨æ²¡æœ‰è¿”回个人信æ¯URL。"
+
+#: ../actions/recoverpassword.php:226 actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr "用户没有注册电å­é‚®ä»¶ã€‚"
+
+#: ../actions/userauthorization.php:49 actions/userauthorization.php:55
+msgid "No request found!"
+msgstr "没有找到请求ï¼"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr "没有结果"
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "没有大å°ã€‚"
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr "没有找到此IDçš„ä¿¡æ¯ã€‚"
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr "没有找到此IDçš„ä¿¡æ¯ã€‚"
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr "没有这个 OpenID。"
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr "没有这份文档。"
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:83
+#: ../lib/deleteaction.php:30 actions/shownotice.php:32
+#: actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr "没有这份通告。"
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "没有这个æ¢å¤ç ã€‚"
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr "没有这个订阅"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:40
+#: ../actions/remotesubscribe.php:84 ../actions/remotesubscribe.php:91
+#: ../actions/replies.php:57 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/userrss.php:35 ../actions/xrds.php:35 ../lib/gallery.php:57
+#: ../lib/subs.php:33 ../lib/subs.php:82 actions/all.php:34
+#: actions/allrss.php:35 actions/avatarbynickname.php:43
+#: actions/favoritesrss.php:35 actions/foaf.php:40 actions/ical.php:31
+#: actions/remotesubscribe.php:93 actions/remotesubscribe.php:100
+#: actions/replies.php:57 actions/repliesrss.php:35
+#: actions/showfavorites.php:34 actions/showstream.php:110
+#: actions/userbyid.php:36 actions/userrss.php:35 actions/xrds.php:35
+#: classes/Command.php:120 classes/Command.php:162 classes/Command.php:203
+#: classes/Command.php:237 lib/gallery.php:62 lib/mailbox.php:36
+#: lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr "没有这个用户。"
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr "没有拥有这个用户å或电å­é‚®ä»¶çš„用户。"
+
+#: ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr "ä¸æ˜¾ç¤ºä»»ä½•äººï¼"
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "ä¸æ˜¯æ¢å¤ç ã€‚"
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr "ä¸æ˜¯å·²æ³¨å†Œç”¨æˆ·ã€‚"
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr "ä¸æ”¯æŒçš„æ•°æ®æ ¼å¼ã€‚"
+
+#: ../actions/imsettings.php:167 actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„ Jabber ID"
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„ OpenID。"
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„电å­é‚®ä»¶"
+
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„电å­é‚®ä»¶ã€‚"
+
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„昵称。"
+
+#: ../actions/remotesubscribe.php:120 actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„个人信æ¯URL(ä¸æ­£ç¡®çš„æœåŠ¡)。"
+
+#: ../actions/remotesubscribe.php:113 actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„个人信æ¯URL(未定义XRDS)。"
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr "ä¸æ˜¯æœ‰æ•ˆçš„个人信æ¯URL(没有YADISæ•°æ®)。"
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr "ä¸æ˜¯å›¾ç‰‡æ–‡ä»¶æˆ–文件已æŸå。"
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr "未认è¯ã€‚"
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr "未预料的å“应ï¼"
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr "未找到"
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:33
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: ../lib/settingsaction.php:27 actions/disfavor.php:29 actions/favor.php:30
+#: actions/finishaddopenid.php:29 actions/logout.php:33
+#: actions/newmessage.php:28 actions/newnotice.php:29 actions/subscribe.php:28
+#: actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr "未登录。"
+
+#: ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr "未订阅ï¼"
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr "æœç´¢é€šå‘Š"
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr "%s 的通告èšåˆ"
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr "通告没有关è”个人信æ¯"
+
+#: ../actions/showstream.php:316 actions/showstream.php:331
+msgid "Notices"
+msgstr "通告"
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr "带 %s 标签的通告"
+
+#: ../actions/password.php:39 actions/profilesettings.php:178
+msgid "Old password"
+msgstr "旧密ç "
+
+#: ../lib/settingsaction.php:96 ../lib/util.php:314 lib/settingsaction.php:90
+#: lib/util.php:330
+msgid "OpenID"
+msgstr "OpenID"
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr "OpenID å¸å·è®¾ç½®"
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr "OpenID 自动æ交"
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr "OpenID 登录"
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr "OpenID 认è¯å·²å–消。"
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr "OpenID 认è¯å¤±è´¥ï¼š%s"
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr "OpenID 失败:%s"
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr "OpenID 已移除。"
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr "OpenID 设置"
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr "在邀请中加几å¥è¯(å¯é€‰)。"
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr "部分上传。"
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+#: actions/finishopenidlogin.php:96 actions/login.php:102
+#: actions/register.php:167
+msgid "Password"
+msgstr "密ç "
+
+#: ../actions/recoverpassword.php:288 actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr "密ç å’Œç¡®è®¤ä¸åŒ¹é…。"
+
+#: ../actions/recoverpassword.php:284 actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr "密ç å¿…须是 6 个字符或更多。"
+
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr "请求æ¢å¤å¯†ç "
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:313
+#: actions/profilesettings.php:408 actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr "密ç å·²ä¿å­˜ã€‚"
+
+#: ../actions/password.php:61 ../actions/register.php:88
+#: actions/profilesettings.php:380 actions/register.php:98
+msgid "Passwords don't match."
+msgstr "密ç ä¸åŒ¹é…。"
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr "用户"
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr "æœç´¢ç”¨æˆ·"
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr "æœç´¢ç”¨æˆ·"
+
+#: ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr "个人"
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr "个人消æ¯"
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr "电è¯å·ç ï¼Œä¸å¸¦æ ‡ç‚¹æˆ–空格,包å«åœ°åŒºä»£ç "
+
+#: ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr "请检查详细信æ¯ï¼Œç¡®è®¤å¸Œæœ›è®¢é˜…此用户的通告。如果您刚æ‰æ²¡æœ‰è¦æ±‚订阅任何人的通告,请点击\"å–消\"。"
+
+#: ../actions/imsettings.php:73 actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr "当我的Jabber/GTalk状æ€æ”¹å˜æ—¶è‡ªåŠ¨å‘布通告。"
+
+#: ../actions/emailsettings.php:85 ../actions/imsettings.php:67
+#: ../actions/smssettings.php:94 actions/emailsettings.php:86
+#: actions/imsettings.php:68 actions/smssettings.php:94
+#: actions/twittersettings.php:70
+msgid "Preferences"
+msgstr "首选项"
+
+#: ../actions/emailsettings.php:162 ../actions/imsettings.php:144
+#: ../actions/smssettings.php:163 actions/emailsettings.php:180
+#: actions/imsettings.php:152 actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr "首选项已ä¿å­˜ã€‚"
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr "首选语言"
+
+#: ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr "éšç§"
+
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr "ä¿å­˜é€šå‘Šæ—¶å‡ºé”™ã€‚"
+
+#: ../lib/settingsaction.php:84 ../lib/stream.php:60 lib/personal.php:60
+#: lib/settingsaction.php:84
+msgid "Profile"
+msgstr "个人信æ¯"
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr "个人信æ¯URL"
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr "个人设置"
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:52
+#: actions/postnotice.php:52 actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr "未知的å¸å·"
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr "公开的èšåˆ"
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr "公开的时间表"
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr "公开Jabber/GTalkå¸å·çš„ MicroID。"
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr "公开电å­é‚®ä»¶çš„ MicroID。"
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr "最近的标签"
+
+#: ../actions/recoverpassword.php:166 actions/recoverpassword.php:171
+msgid "Recover"
+msgstr "æ¢å¤"
+
+#: ../actions/recoverpassword.php:156 actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr "æ¢å¤å¯†ç "
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr "æ¢å¤ç æœªçŸ¥"
+
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr "注册"
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr "ä¸å…许注册。"
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr "注册æˆåŠŸã€‚"
+
+#: ../actions/userauthorization.php:120 actions/userauthorization.php:127
+msgid "Reject"
+msgstr "æ‹’ç»"
+
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr "è®°ä½ç™»å½•çŠ¶æ€"
+
+#: ../actions/updateprofile.php:70 actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr "没有匹é…的远程个人信æ¯"
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr "远程订阅"
+
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr "移除"
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr "移除OpenID"
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr "移除唯一的OpenIDå¯èƒ½ä½¿æ‚¨æ— æ³•ç™»é™†ï¼å¦‚果需è¦ç§»é™¤å®ƒï¼Œè¯·å…ˆæ·»åŠ ä¸€ä¸ªæ–°çš„。"
+
+#: ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr "回å¤"
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:56
+#: actions/replies.php:47 actions/repliesrss.php:62 lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr "%s 的回å¤"
+
+#: ../actions/recoverpassword.php:183 actions/recoverpassword.php:189
+msgid "Reset"
+msgstr "é‡ç½®"
+
+#: ../actions/recoverpassword.php:173 actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr "é‡ç½®å¯†ç "
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr "SMS短信"
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr "SMS短信电è¯å·ç "
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr "SMS短信设置"
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr "SMS短信确认"
+
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr "相åŒçš„密ç "
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr "相åŒçš„密ç ã€‚此项必填。"
+
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr "ä¿å­˜"
+
+#: ../lib/searchaction.php:84 ../lib/util.php:300 lib/searchaction.php:84
+#: lib/util.php:316
+msgid "Search"
+msgstr "æœç´¢"
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr "æœç´¢èšåˆ"
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr "在 %%site.name%% 的通告内容中æœç´¢ã€‚æœç´¢æ¡ä»¶è‡³å°‘åŒ…å« 3 个字符,多个æœç´¢æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”。"
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr "在 %%site.name%% 的用户信æ¯ä¸­æœç´¢ï¼Œå¯ä»¥æœç´¢å§“åã€æœªçŸ¥å’Œçˆ±å¥½ã€‚æœç´¢æ¡ä»¶è‡³å°‘åŒ…å« 3 个字符,多个æœç´¢æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”。"
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr "选择è¿è¥å•†"
+
+#: ../actions/invite.php:137 ../lib/util.php:1172 actions/invite.php:145
+#: lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr "å‘é€"
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr "å‘这个电å­é‚®ä»¶å‘信以å‘布新的通告。"
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr "如果有新订阅,通过电å­é‚®ä»¶å‘Šè¯‰æˆ‘。"
+
+#: ../actions/imsettings.php:70 actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr "通过Jabber/GTalkå‘é€é€šå‘Šã€‚"
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr "通过SMS短信将通告å‘给我;我了解这样也许会给我带æ¥ä¸è²çš„开支。"
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr "如果我尚未订阅的用户回我消æ¯ï¼Œä½¿ç”¨Jabber/GTalk通知我。"
+
+#: ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr "设置"
+
+#: ../actions/profilesettings.php:192 actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr "设置已ä¿å­˜ã€‚"
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr "显示上周以æ¥æœ€æµè¡Œçš„标签"
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr "其他人已ç»ä½¿ç”¨äº†è¿™ä¸ªOpenID。"
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr "å‘生了奇怪的错误……"
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr "对ä¸èµ·ï¼Œå‘布用的电å­é‚®ä»¶æ— æ³•ä½¿ç”¨ã€‚"
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr "对ä¸èµ·ï¼Œè¿™ä¸ªå‘布用的电å­é‚®ä»¶å±žäºŽå…¶ä»–用户。"
+
+#: ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr "æ¥æº"
+
+#: ../actions/showstream.php:296 actions/showstream.php:311
+msgid "Statistics"
+msgstr "统计"
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:246
+#: actions/finishopenidlogin.php:188 actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr "未找到已有的OpenID。"
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr "订阅"
+
+#: ../actions/showstream.php:313 ../actions/subscribers.php:27
+#: actions/showstream.php:328 actions/subscribers.php:27
+msgid "Subscribers"
+msgstr "订阅者"
+
+#: ../actions/userauthorization.php:310 actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr "订阅已确认"
+
+#: ../actions/userauthorization.php:320 actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr "订阅被拒ç»"
+
+#: ../actions/showstream.php:230 ../actions/showstream.php:307
+#: ../actions/subscriptions.php:27 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr "订阅"
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr "上传文件时出错。"
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr "标签"
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr "文本"
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr "æœç´¢æ–‡æœ¬"
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr "此OpenID属于他人。"
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr "此地å€å·²è¢«ç¡®è®¤ã€‚"
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr "此确认ç ä¸é€‚用ï¼"
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr "此电å­é‚®ä»¶å±žäºŽå…¶ä»–用户。"
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr "文件超出大å°é™åˆ¶ã€‚"
+
+#: ../actions/imsettings.php:170 actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr "您已登记此Jabberå¸å·ã€‚"
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr "您已登记此电å­é‚®ä»¶ã€‚"
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr "您已登记此电è¯å·ç ã€‚"
+
+#: ../actions/imsettings.php:233 actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr "è¿™ä¸æ˜¯æ‚¨çš„Jabberå¸å·ã€‚"
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr "这是他人的电å­é‚®ä»¶ã€‚"
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr "这是他人的电è¯å·ç ã€‚"
+
+#: ../actions/emailsettings.php:226 ../actions/imsettings.php:210
+#: actions/emailsettings.php:244 actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr "å³æ—¶é€šè®¯å¸å·é”™è¯¯ã€‚"
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr "确认ç é”™è¯¯ã€‚"
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr "这个电è¯å·ç å±žäºŽå¦ä¸€ä¸ªç”¨æˆ·ã€‚"
+
+#: ../actions/newnotice.php:49 ../actions/twitapistatuses.php:408
+#: actions/newnotice.php:49 actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr "超出长度é™åˆ¶ã€‚ä¸èƒ½è¶…过 140 个字符。"
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr "超出长度é™åˆ¶ã€‚ä¸èƒ½è¶…过 255 个字符。"
+
+#: ../actions/confirmaddress.php:92 actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr "åœ°å€ \"%s\" 已确认。"
+
+#: ../actions/emailsettings.php:264 ../actions/imsettings.php:250
+#: ../actions/smssettings.php:274 actions/emailsettings.php:282
+#: actions/imsettings.php:258 actions/smssettings.php:282
+msgid "The address was removed."
+msgstr "地å€è¢«ç§»é™¤ã€‚"
+
+#: ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr "订阅已确认,但是没有回传URL。请到此网站查看如何确认订阅。您的订阅标识是:"
+
+#: ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr "订阅已被拒ç»ï¼Œä½†æ˜¯æ²¡æœ‰å›žä¼ URL。请到此网站查看如何拒ç»è®¢é˜…。"
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr "这些用户订阅了 %s 的通告。"
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr "这些用户订阅了您的通告。"
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr "这是 %s 订阅的用户。"
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr "这是您订阅的用户。"
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr "这些好å‹å·²æ³¨å†Œï¼Œæ‚¨å·²è‡ªåŠ¨è®¢é˜…这些用户。"
+
+#: ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr "验è¯ç è¶…时,请é‡æ¥ã€‚"
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr "此表å•ä¼šè‡ªåŠ¨æ交。如果没有,点击“æ交â€ä»¥è½¬å‘ OpenID æ供者。"
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr "这是您第一次登陆 %s,我们需è¦å°† OpenID 连接到这里的å¸å·ã€‚您å¯ä»¥åˆ›å»ºæ–°å¸å·ï¼Œæˆ–者连接到已有的å¸å·ã€‚"
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr "此方法接å—POST或DELETE请求。"
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr "此方法接å—POST请求。"
+
+#: ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr "这个页é¢ä¸æ供您想è¦çš„媒体类型"
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr "时区"
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr "未选择时区。"
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+"è"
+"¯"
+"·"
+"["
+"ç"
+"™"
+"»"
+"å"
+"½"
+"•"
+"]"
+"("
+"%"
+"%"
+"a"
+"c"
+"t"
+"i"
+"o"
+"n"
+"."
+"l"
+"o"
+"g"
+"i"
+"n"
+"%"
+"%"
+")"
+"æ"
+"ˆ"
+"–"
+"["
+"æ"
+"³"
+"¨"
+"å"
+"†Œ](%%action.register%%)å¸å·ä»¥è®¢é˜…。如果您已ç»æœ‰äº†[兼容的微åšå®¢ç«™ç‚¹](%%doc.openmublog%%)çš„å¸å·ï¼Œè¯·è¾“入个人信æ¯URL。"
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr "å¿…é¡»æ供两个用户å¸å·æˆ–昵称。"
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:169
+#: actions/profilesettings.php:81 actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr "您的主页ã€åšå®¢æˆ–在其他站点的URL"
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr "您在其他兼容的微åšå®¢æœåŠ¡çš„个人信æ¯URL"
+
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/recoverpassword.php:39 ../actions/smssettings.php:135
+#: actions/emailsettings.php:144 actions/imsettings.php:118
+#: actions/recoverpassword.php:39 actions/smssettings.php:143
+#: actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr "未预料的表å•æ交。"
+
+#: ../actions/recoverpassword.php:276 actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr "未预料的密ç é‡ç½®ã€‚"
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr "未知动作"
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr "æ­¤OMBå议版本无效。"
+
+#: ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr "除éžå¦å¤–说明,此站点的内容由贡献者版æƒæ‰€æœ‰ï¼ŒæŽˆæƒæ–¹å¼ä¸º"
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr "ä¸å¯è¯†åˆ«çš„地å€ç±»åž‹ %s"
+
+#: ../actions/showstream.php:209 actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr "退订"
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:45
+#: actions/postnotice.php:45 actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr "ä¸æ”¯æŒæ­¤OMB版本"
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr "ä¸æ”¯æŒè¿™ç§å›¾åƒæ ¼å¼ã€‚"
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr "使用SMS短信更新"
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr "使用å³æ—¶é€šè®¯å·¥å…·(IM)æ›´æ–°"
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr "%2$s 上 %1$s 和好å‹çš„æ›´æ–°ï¼"
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr "%2$s 上 %1$s çš„æ›´æ–°ï¼"
+
+#: ../actions/avatar.php:68 actions/profilesettings.php:161
+msgid "Upload"
+msgstr "上传"
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr "在这里上传新的头åƒã€‚上传åŽæ— æ³•ä¿®æ”¹ï¼Œè¯·å°½é‡ä½¿ç”¨æ­£æ–¹å½¢å›¾ç‰‡ã€‚图片必须使用与站点相åŒçš„授æƒã€‚请使用您希望分享的属于您的图片。"
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr "上传个人信æ¯å¤´åƒ"
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr "使用这个表å•æ¥é‚€è¯·å¥½å‹å’ŒåŒäº‹åŠ å…¥ã€‚"
+
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr "åªç”¨äºŽæ›´æ–°ã€é€šå‘Šæˆ–密ç æ¢å¤"
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr "è¦æŸ¥çœ‹çš„用户ä¸å­˜åœ¨ã€‚"
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:47 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/twitapiaccount.php:82
+#: ../actions/twitapistatuses.php:319 ../actions/twitapistatuses.php:685
+#: ../actions/twitapiusers.php:82 actions/all.php:41
+#: actions/avatarbynickname.php:48 actions/foaf.php:47 actions/replies.php:41
+#: actions/showfavorites.php:41 actions/showstream.php:44
+#: actions/twitapiaccount.php:80 actions/twitapifavorites.php:68
+#: actions/twitapistatuses.php:235 actions/twitapistatuses.php:609
+#: actions/twitapiusers.php:87 lib/mailbox.php:50
+msgid "User has no profile."
+msgstr "用户没有个人信æ¯ã€‚"
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr "昵称"
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr "未找到用户。"
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr "您一般处于哪个时区?"
+
+#: ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr "怎么样,%s?"
+
+#: ../actions/profilesettings.php:54 ../actions/register.php:175
+#: actions/profilesettings.php:87 actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr "ä½ çš„ä½ç½®ï¼Œæ ¼å¼ç±»ä¼¼\"城市,çœä»½ï¼Œå›½å®¶\""
+
+#: ../actions/updateprofile.php:128 actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr "'%s' 图åƒæ ¼å¼é”™è¯¯"
+
+#: ../actions/updateprofile.php:123 actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr "图åƒå¤§å° '%s' 错误"
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr "是"
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr "已登记此OpenIDï¼"
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr "您选择了永久删除通告。这样åšæ˜¯æ— æ³•æ¢å¤çš„。"
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr "已登录ï¼"
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr "您已订阅这些用户:"
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr "您与此用户并éžå¥½å‹ã€‚"
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr "在这里修改密ç ã€‚选择一个好密ç ï¼"
+
+#: ../actions/register.php:135 actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr "请创建新å¸å·ï¼Œå¼€å§‹å‘布通告。"
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr "您å¯ä»¥é€šè¿‡ %%site.name%% 的电å­é‚®ä»¶æŽ¥æ”¶SMS短信。"
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr "您å¯ä»¥ç‚¹å‡»\"移除\"按钮,移除å¸å·çš„ OpenID。"
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr "您å¯ä»¥é€šè¿‡Jabber/GTalk [å³æ—¶é€šè®¯å·¥å…·](%%doc.im%%)å‘é€å’ŒæŽ¥å—通告。在这里é…置它们。"
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr "在这里更新个人信æ¯ï¼Œè®©å¤§å®¶å¯¹æ‚¨äº†è§£å¾—更多。"
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr "您å¯ä»¥åœ¨è¿™é‡Œè®¢é˜…ï¼"
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:61
+#: actions/finishopenidlogin.php:38 actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr "您必须åŒæ„此授æƒæ–¹å¯æ³¨å†Œã€‚"
+
+#: ../actions/updateprofile.php:63 actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr "您未告知此个人信æ¯"
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+"您的 %1$s å‘布用地å€å·²æ›´æ–°ã€‚\n"
+"\n"
+"å‘é€é‚®ä»¶åˆ° %2$s æ¥å‘布新消æ¯ã€‚\n\n更多电å­é‚®ä»¶çš„å¸®åŠ©è¯·è§ %3$s。\n\n为您效力的 %4$s"
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr "您ä¸èƒ½åˆ é™¤å…¶ä»–用户的状æ€ã€‚"
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr "您必须登录æ‰èƒ½é‚€è¯·å…¶ä»–人使用 %s"
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr "如果其他人接å—邀请并注册,您将得到通知。谢谢您推动了社区å‘展壮大ï¼"
+
+#: ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr "您已得到确认。请输入新密ç ã€‚"
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr "您的OpenID URL"
+
+#: ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr "您在此æœåŠ¡å™¨çš„昵称,或注册邮箱。"
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr "[OpenID](%%doc.openid%%)å…许您使用相åŒçš„å¸å·ç™»å½•è®¸å¤šä¸åŒçš„站点。在这里管ç†å·²å…³è”çš„ OpenID。"
+
+#: ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr "几秒å‰"
+
+#: ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr "%d 天å‰"
+
+#: ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr "%d å°æ—¶å‰"
+
+#: ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr "%d 分钟å‰"
+
+#: ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr "%d 个月å‰"
+
+#: ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr "一天å‰"
+
+#: ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr "一分钟å‰"
+
+#: ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr "一个月å‰"
+
+#: ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr "一年å‰"
+
+#: ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr "一å°æ—¶å‰"
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr "删除"
+
+#: ../actions/noticesearch.php:130 ../actions/showstream.php:408
+#: ../lib/stream.php:117 actions/noticesearch.php:136
+#: actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr "å…ˆå‰â€¦â€¦"
+
+#: ../actions/noticesearch.php:137 ../actions/showstream.php:415
+#: ../lib/stream.php:124 actions/noticesearch.php:143
+#: actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr "回å¤"
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr "相åŒçš„密ç "
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr "ä¸æ”¯æŒè¿™ç§ç±»åž‹çš„文件"
+
+#: ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr "« 之åŽ"
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr "会è¯æ ‡è¯†æœ‰é—®é¢˜ï¼Œè¯·é‡è¯•ã€‚"
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr "此通告未被收è—ï¼"
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr "无法删除收è—。"
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr "收è—"
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr "如果有人收è—我的通告,å‘邮件通知我。"
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr "如果收到ç§äººä¿¡æ¯ï¼Œå‘邮件通知我。"
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr "已收è—此通告ï¼"
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr "无法创建收è—。"
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr "å–消收è—"
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr "%s 收è—的通告"
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr "%s 的收è—çš„èšåˆ"
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr "%s 的收件箱 - 第 %d 页"
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr "%s 的收件箱"
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr "这是您的收件箱,包å«å‘给您的ç§äººæ¶ˆæ¯ã€‚"
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr "%1$s 邀请您加入 %2$s (%3$s)。\n\n"
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr "ä¿æŒç™»å½•çŠ¶æ€ï¼›"
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr "由于安全原因,请é‡æ–°è¾“å…¥"
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr "输入用户å和密ç ä»¥ç™»å½•ã€‚"
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr "超出长度é™åˆ¶ã€‚ä¸èƒ½è¶…过 140 个字符。"
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr "没有收件人。"
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr "无法å‘此用户å‘é€æ¶ˆæ¯ã€‚"
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr "ä¸è¦å‘自己å‘é€æ¶ˆæ¯ï¼›è·Ÿè‡ªå·±æ‚„悄说就得了。"
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr "未找到用户"
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr "新消æ¯"
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr "找ä¸åˆ°åŒ¹é…的通告"
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr "[OpenID](%%doc.openid%%)å…许您登录许多ä¸åŒçš„站点"
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr "如果您希望添加OpenID,"
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr "移除仅有的OpenIDå¯èƒ½ä½¿æ‚¨æ— æ³•ç™»é™†ï¼"
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr "您å¯ä»¥ç§»é™¤ä¸€ä¸ªOpenID"
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr "%s çš„å‘件箱 - 第 %d 页"
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr "%s çš„å‘件箱"
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr "这是您的å‘件箱,包å«æ‚¨å‘é€çš„ç§äººæ¶ˆæ¯ã€‚"
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr "在 %%site.name%% 的用户信æ¯ä¸­æœç´¢ï¼Œå¯ä»¥æœç´¢å§“åã€æœªçŸ¥å’Œçˆ±å¥½ã€‚"
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr "您å¯ä»¥åœ¨è¿™é‡Œæ›´æ–°ä¸ªäººä¿¡æ¯ã€‚"
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr "找ä¸åˆ°åŒ¹é…的用户。"
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr "验è¯ç è¶…时。"
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr "如果您忘记或丢失了您的"
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr "您已得到确认。输入"
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr "您在æœåŠ¡å™¨ä¸Šçš„昵称,"
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr "æ¢å¤å¯†ç çš„步骤"
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr "新密ç å·²ä¿å­˜ã€‚"
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr "密ç å¿…é¡»åŒ…å« 6 个或更多字符。"
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr "ç¥è´ºä½ ï¼Œ%sï¼æ¬¢è¿Žæ¥åˆ° %%%%site.name%%%%。在这里,您å¯ä»¥â€¦â€¦"
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr "(您将立å³æ”¶åˆ°ä¸€å°ç”µå­é‚®ä»¶ï¼Œå«æœ‰"
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr "è¦è®¢é˜…,请先[登录](%%action.login%%),"
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr "%s 的收è—çš„èšåˆ"
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr "无法获å–收è—的通告。"
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr "未找到此消æ¯ã€‚"
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr "åªæœ‰å‘é€å’ŒæŽ¥å—åŒæ–¹å¯ä»¥é˜…读此消æ¯ã€‚"
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr "å‘é€ç»™ %1$s çš„ %2$s 消æ¯"
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr "æ¥è‡ª %1$s çš„ %2$s 消æ¯"
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr "å‘é€æ¶ˆæ¯"
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr "手机的è¿è¥å•†ã€‚"
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr "å‘ç»™ %s 的直接消æ¯"
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr "å‘ç»™ %s 的直接消æ¯"
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr "您已å‘é€çš„直接消æ¯"
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr "%s å‘é€çš„直接消æ¯"
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr "消æ¯æ²¡æœ‰æ­£æ–‡ï¼"
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr "未找到收件人。"
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr "无法å‘并éžå¥½å‹çš„用户å‘é€ç›´æŽ¥æ¶ˆæ¯ã€‚"
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr "%s çš„æ”¶è— / %s"
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr "%s 收è—了 %s çš„ %s 通告。"
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr "%s 收è—了您的通告"
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr "%1$s 收è—了您的 %2$s 通告。\n\n"
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr "添加 Twitter å¸å·ï¼Œè‡ªåŠ¨å‘ Twitter å‘é€æ›´æ–°ã€‚"
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr "Twitter 设置"
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr "Twitter å¸å·"
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr "已验è¯çš„ Twitter å¸å·ã€‚"
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr "Twitter 用户å"
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr "请ä¸è¦åŒ…å«ç©ºæ ¼ã€‚"
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr "Twitter 密ç "
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr "自动将我的通告转å‘到 Twitter。"
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr "将这里的 \"@\" 回å¤å‘é€åˆ° Twitter。"
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr "在这里订阅 Twitter 好å‹ã€‚"
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr "用户ååªèƒ½åŒ…å«æ•°å­—,大写和å°å†™å­—æ¯å’Œä¸‹åˆ’线。ä¸èƒ½è¶…过 15 个字符。"
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr "ä¸èƒ½éªŒè¯ Twitter å¸å·ï¼"
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr "无法从 Twitter 获å–\"%s\"çš„å¸å·ä¿¡æ¯ã€‚"
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr "无法ä¿å­˜ Twitter 设置ï¼"
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr "Twitter 设置已ä¿å­˜ã€‚"
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr "æ­¤ Twitter å¸å·å±žäºŽä»–人。"
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr "无法移除 Twitter 用户。"
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr "Twitter å¸å·å·²ç§»é™¤ã€‚"
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr "无法ä¿å­˜ Twitter 首选项。"
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr "Twitter 首选项已ä¿å­˜ã€‚"
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr "请检查详细信æ¯"
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr "订阅已确认,但是"
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr "订阅已拒ç»ï¼Œä½†æ˜¯"
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr "执行结果"
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr "执行完毕"
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr "执行失败"
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr "对ä¸èµ·ï¼Œè¿™ä¸ªå‘½ä»¤è¿˜æ²¡æœ‰å®žçŽ°ã€‚"
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr "订阅:%1$s\n"
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr "用户没有通告。"
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr "通告被标记为收è—。"
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr "%1$s (%2$s)"
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr "å…¨å:%s"
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr "ä½ç½®ï¼š%s"
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr "主页:%s"
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr "关于:%s"
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr "您的消æ¯åŒ…å« %d 个字符,超出长度é™åˆ¶ - ä¸èƒ½è¶…过 140 个字符。"
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr "å·²å‘ %s å‘é€æ¶ˆæ¯"
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr "å‘é€æ¶ˆæ¯å‡ºé”™ã€‚"
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr "指定è¦è®¢é˜…的用户å"
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr "订阅 %s"
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr "指定è¦å–消订阅的用户å"
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr "å–消订阅 %s"
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr "命令尚未实现。"
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr "通告关闭。"
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr "无法关闭通告。"
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr "通告开å¯ã€‚"
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr "无法开å¯é€šå‘Šã€‚"
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr "命令:\n"
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr "无法添加信æ¯ã€‚"
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr "无法添加新URIçš„ä¿¡æ¯ã€‚"
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr "系统中没有相应用户的信æ¯ã€‚"
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr "您的新的å‘布用的地å€æ˜¯ %1$s。\n\n"
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr "%s å‘é€äº†æ–°çš„ç§äººä¿¡æ¯"
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr "%1$s (%2$s) å‘é€äº†æ–°çš„ç§äººä¿¡æ¯ï¼š\n\n"
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr "åªæœ‰ç”¨æˆ·è‡ªå·±å¯ä»¥è®¿é—®é‚®ç®±ã€‚"
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr "此表å•ä¼šè‡ªåŠ¨æ交。"
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr "收è—夹"
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr "%s 收è—的通告"
+
+#: lib/personal.php:66
+msgid "User"
+msgstr "用户"
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr "收件箱"
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr "您接收的消æ¯"
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr "å‘件箱"
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr "您å‘é€çš„消æ¯"
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr "Twitter"
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr "Twitter æ•´åˆé€‰é¡¹"
+
+#: lib/util.php:1718
+msgid "To"
+msgstr "到"
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr "无法解æžæ¶ˆæ¯ã€‚"
diff --git a/locale/zh_hant/LC_MESSAGES/laconica.po b/locale/zh_hant/LC_MESSAGES/laconica.po
new file mode 100644
index 000000000..5e96c56fb
--- /dev/null
+++ b/locale/zh_hant/LC_MESSAGES/laconica.po
@@ -0,0 +1,2985 @@
+# 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: 2008-10-13 21:07+0100\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=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../actions/noticesearchrss.php:64
+#, php-format
+msgid " Search Stream for \"%s\""
+msgstr "æœå°‹ \"%s\"相關資料"
+
+#: ../actions/finishopenidlogin.php:82 ../actions/register.php:193
+msgid ""
+" except this private data: password, email address, IM address, phone "
+"number."
+msgstr "ä¸åŒ…å«é€™äº›å€‹äººè³‡æ–™ï¼šå¯†ç¢¼ã€é›»å­ä¿¡ç®±ã€ç·šä¸Šå³æ™‚通信箱ã€é›»è©±è™Ÿç¢¼"
+
+#: ../actions/subscribe.php:84
+#, php-format
+msgid "%1$s is now listening to your notices on %2$s."
+msgstr "ç¾åœ¨%1$s在%2$sæˆç‚ºä½ çš„粉絲囉"
+
+#: ../actions/subscribe.php:86
+#, php-format
+msgid ""
+"%1$s is now listening to your notices on %2$s.\n"
+"\n"
+"\t%3$s\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s.\n"
+msgstr ""
+"ç¾åœ¨%1$s在%2$sæˆç‚ºä½ çš„粉絲囉。\n"
+"\n\t%3$s\n\n\n%4$s.\n敬上。"
+
+#: ../actions/shownotice.php:45
+#, php-format
+msgid "%1$s's status on %2$s"
+msgstr "%1$s的狀態是%2$s"
+
+#: ../actions/publicrss.php:60
+#, php-format
+msgid "%s Public Stream"
+msgstr "%s的公開內容"
+
+#: ../actions/all.php:47 ../actions/allrss.php:70 ../lib/stream.php:45
+#, php-format
+msgid "%s and friends"
+msgstr "%s與好å‹"
+
+#: ../lib/util.php:233
+#, php-format
+msgid ""
+"**%%site.name%%** is a microblogging service brought to you by "
+"[%%site.broughtby%%](%%site.broughtbyurl%%). "
+msgstr ""
+"*"
+"*"
+"%"
+"%site.name%%**是由[%%site.broughtby%%](%%site.broughtbyurl%%)所æ供的微型部è½æ ¼æœå‹™"
+
+#: ../lib/util.php:235
+#, php-format
+msgid "**%%site.name%%** is a microblogging service. "
+msgstr "**%%site.name%%**是個微型部è½æ ¼"
+
+#: ../lib/util.php:250 ../lib/util.php:274 lib/util.php:290
+msgid ". Contributors should be attributed by full name or nickname."
+msgstr "必須注明作者姓å或昵稱."
+
+#: ../actions/finishopenidlogin.php:73 ../actions/profilesettings.php:43
+#: ../actions/register.php:176
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces"
+msgstr "1-64個å°å¯«è‹±æ–‡å­—æ¯æˆ–數字,勿加標點符號或空格"
+
+#: ../actions/password.php:42 ../actions/register.php:178
+msgid "6 or more characters"
+msgstr "6個以上字元"
+
+#: ../actions/recoverpassword.php:165
+msgid "6 or more characters, and don't forget it!"
+msgstr "6個或6個以上字元,別忘了自己密碼喔"
+
+#: ../actions/imsettings.php:188
+#, php-format
+msgid ""
+"A confirmation code was sent to the IM address you added. You must approve "
+"%s for sending messages to you."
+msgstr "確èªä¿¡å·²å¯„到你的線上å³æ™‚通信箱。%sé€çµ¦ä½ å¾—訊æ¯è¦å…ˆç¶“éŽä½ çš„èªå¯ã€‚"
+
+#: ../lib/util.php:296
+msgid "About"
+msgstr "關於"
+
+#: ../actions/userauthorization.php:118
+msgid "Accept"
+msgstr "接å—"
+
+#: ../actions/imsettings.php:64 ../actions/openidsettings.php:57
+msgid "Add"
+msgstr "新增"
+
+#: ../actions/openidsettings.php:43
+msgid "Add OpenID"
+msgstr "新增OpenID"
+
+#: ../actions/imsettings.php:39
+msgid "Address"
+msgstr "ä¿¡ç®±"
+
+#: ../actions/showstream.php:254
+msgid "All subscriptions"
+msgstr "所有訂閱"
+
+#: ../actions/publicrss.php:62
+#, php-format
+msgid "All updates for %s"
+msgstr "%s的所有新增內容"
+
+#: ../actions/noticesearchrss.php:66
+#, php-format
+msgid "All updates matching search term \"%s\""
+msgstr "æ‰€æœ‰ç¬¦åˆ \"%s\"çš„æ›´æ–°"
+
+#: ../actions/finishopenidlogin.php:29 ../actions/login.php:27
+#: ../actions/openidlogin.php:29 ../actions/register.php:28
+msgid "Already logged in."
+msgstr "已登入"
+
+#: ../actions/subscribe.php:48
+msgid "Already subscribed!."
+msgstr "此帳號已註冊"
+
+#: ../actions/userauthorization.php:76
+msgid "Authorize subscription"
+msgstr "註冊確èª"
+
+#: ../actions/login.php:100 ../actions/register.php:184
+msgid "Automatically login in the future; not for shared computers!"
+msgstr "未來在åŒä¸€éƒ¨é›»è…¦è‡ªå‹•ç™»å…¥"
+
+#: ../actions/avatar.php:32
+msgid "Avatar"
+msgstr "個人圖åƒ"
+
+#: ../actions/avatar.php:113
+msgid "Avatar updated."
+msgstr "更新個人圖åƒ"
+
+#: ../actions/imsettings.php:55
+#, php-format
+msgid ""
+"Awaiting confirmation on this address. Check your Jabber/GTalk account for a "
+"message with further instructions. (Did you add %s to your buddy list?)"
+msgstr "等待確èªæ­¤ä¿¡ç®±ã€‚看看你的Jabber/GTalk是å¦æœ‰è¨Šæ¯æŒ‡ç¤ºä¸‹ä¸€æ­¥å‹•ä½œã€‚(你加入%s到你的好å‹æ¸…單了嗎?)"
+
+#: ../lib/util.php:1136
+msgid "Before »"
+msgstr "之å‰çš„內容»"
+
+#: ../actions/profilesettings.php:52
+msgid "Bio"
+msgstr "自我介紹"
+
+#: ../actions/profilesettings.php:93 ../actions/updateprofile.php:102
+msgid "Bio is too long (max 140 chars)."
+msgstr "自我介紹éŽé•·(å…±140個字元)"
+
+#: ../actions/updateprofile.php:118
+#, php-format
+msgid "Can't read avatar URL '%s'"
+msgstr "無法讀å–æ­¤%sURL的圖åƒ"
+
+#: ../actions/password.php:85 ../actions/recoverpassword.php:261
+msgid "Can't save new password."
+msgstr "無法存å–新密碼"
+
+#: ../actions/imsettings.php:59
+msgid "Cancel"
+msgstr "å–消"
+
+#: ../lib/openid.php:121 lib/openid.php:121
+msgid "Cannot instantiate OpenID consumer object."
+msgstr "無法åˆå§‹åŒ–OpenID用戶å°è±¡(consumer object)"
+
+#: ../actions/imsettings.php:154
+msgid "Cannot normalize that Jabber ID"
+msgstr "此JabberID錯誤"
+
+#: ../actions/password.php:45
+msgid "Change"
+msgstr "更改"
+
+#: ../actions/password.php:32
+msgid "Change password"
+msgstr "更改密碼"
+
+#: ../actions/password.php:43 ../actions/recoverpassword.php:166
+#: ../actions/register.php:179
+msgid "Confirm"
+msgstr "確èª"
+
+#: ../actions/confirmaddress.php:84
+msgid "Confirm Address"
+msgstr "確èªä¿¡ç®±"
+
+#: ../actions/imsettings.php:213
+msgid "Confirmation cancelled."
+msgstr "確èªå–消"
+
+#: ../actions/confirmaddress.php:38
+msgid "Confirmation code not found."
+msgstr "確èªç¢¼éºå¤±"
+
+#: ../actions/finishopenidlogin.php:91
+msgid "Connect"
+msgstr "連çµ"
+
+#: ../actions/finishopenidlogin.php:86
+msgid "Connect existing account"
+msgstr "與ç¾æœ‰å¸³è™Ÿé€£çµ"
+
+#: ../lib/util.php:304
+msgid "Contact"
+msgstr "好å‹åå–®"
+
+#: ../lib/openid.php:178 lib/openid.php:178
+#, php-format
+msgid "Could not create OpenID form: %s"
+msgstr "無法從 %s 建立OpenID"
+
+#: ../lib/openid.php:160
+#, php-format
+msgid "Could not redirect to server: %s"
+msgstr "無法連çµåˆ°ä¼ºæœå™¨:%s"
+
+#: ../actions/updateprofile.php:161
+msgid "Could not save avatar info"
+msgstr "無法存å–個人圖åƒè³‡æ–™"
+
+#: ../actions/updateprofile.php:154
+msgid "Could not save new profile info"
+msgstr "無法存å–新的個人資料"
+
+#: ../actions/profilesettings.php:146
+msgid "Couldn't confirm email."
+msgstr "無法確èªä¿¡ç®±"
+
+#: ../actions/finishremotesubscribe.php:99
+#: actions/finishremotesubscribe.php:101
+msgid "Couldn't convert request tokens to access tokens."
+msgstr "無法轉æ›è«‹æ±‚標記以致無法存å–標記"
+
+#: ../actions/subscribe.php:59
+msgid "Couldn't create subscription."
+msgstr "註冊失敗"
+
+#: ../actions/confirmaddress.php:78 ../actions/imsettings.php:209
+msgid "Couldn't delete email confirmation."
+msgstr "無法å–消信箱確èª"
+
+#: ../actions/unsubscribe.php:56
+msgid "Couldn't delete subscription."
+msgstr "無法刪除帳號"
+
+#: ../actions/remotesubscribe.php:125 ../actions/remotesubscribe.php:127
+#: actions/remotesubscribe.php:136
+msgid "Couldn't get a request token."
+msgstr "無法å–得轉æ›æ¨™è¨˜"
+
+#: ../actions/imsettings.php:178
+msgid "Couldn't insert confirmation code."
+msgstr "無法輸入確èªç¢¼"
+
+#: ../actions/finishremotesubscribe.php:180
+#: actions/finishremotesubscribe.php:182
+msgid "Couldn't insert new subscription."
+msgstr "無法新增訂閱"
+
+#: ../actions/profilesettings.php:175
+msgid "Couldn't save profile."
+msgstr "無法儲存個人資料"
+
+#: ../actions/confirmaddress.php:70 ../actions/imsettings.php:129
+#: ../actions/imsettings.php:234 ../actions/profilesettings.php:123
+msgid "Couldn't update user."
+msgstr "無法更新使用者"
+
+#: ../actions/finishopenidlogin.php:84
+msgid "Create"
+msgstr "新增"
+
+#: ../actions/finishopenidlogin.php:70
+msgid "Create a new user with this nickname."
+msgstr "以此暱稱新增使用者"
+
+#: ../actions/finishopenidlogin.php:68
+msgid "Create new account"
+msgstr "新增帳號"
+
+#: ../actions/finishopenidlogin.php:191 actions/finishopenidlogin.php:197
+msgid "Creating new account for OpenID that already has a user."
+msgstr "該OpenID已經注冊"
+
+#: ../actions/imsettings.php:45 actions/imsettings.php:46
+msgid "Current confirmed Jabber/GTalk address."
+msgstr "ç›®å‰å·²ç¢ºèªçš„Jabber/Gtalk地å€"
+
+#: ../actions/showstream.php:337
+msgid "Currently"
+msgstr "ç›®å‰"
+
+#: ../lib/util.php:893 ../lib/util.php:1061 lib/util.php:1110
+#, php-format
+msgid "DB error inserting reply: %s"
+msgstr "增加回覆時,資料庫發生錯誤: %s"
+
+#: ../actions/profilesettings.php:54 ../actions/profilesettings.php:51
+#: ../actions/register.php:172 actions/profilesettings.php:84
+#: actions/register.php:186
+msgid "Describe yourself and your interests in 140 chars"
+msgstr "請在140個字以內æ述你自己與你的興趣"
+
+#: ../actions/register.php:181 ../actions/register.php:158
+#: ../actions/register.php:161 ../lib/settingsaction.php:87
+#: actions/register.php:172 actions/register.php:175 lib/settingsaction.php:87
+msgid "Email"
+msgstr "é›»å­ä¿¡ç®±"
+
+#: ../actions/profilesettings.php:46
+msgid "Email address"
+msgstr "é›»å­ä¿¡ç®±"
+
+#: ../actions/profilesettings.php:102 ../actions/register.php:63
+#: ../actions/register.php:73 actions/register.php:80
+msgid "Email address already exists."
+msgstr "此電å­ä¿¡ç®±å·²è¨»å†ŠéŽäº†"
+
+#: ../lib/mail.php:82 ../lib/mail.php:90 lib/mail.php:90
+msgid "Email address confirmation"
+msgstr "確èªä¿¡ç®±"
+
+#: ../actions/recoverpassword.php:176 ../actions/recoverpassword.php:191
+#: actions/recoverpassword.php:197
+msgid "Enter a nickname or email address."
+msgstr "請輸入暱稱或電å­ä¿¡ç®±"
+
+#: ../actions/userauthorization.php:136 ../actions/userauthorization.php:137
+#: actions/userauthorization.php:144
+msgid "Error authorizing token"
+msgstr "授權錯誤(Error authorizing token)"
+
+#: ../actions/finishopenidlogin.php:282 ../actions/finishopenidlogin.php:253
+#: actions/finishopenidlogin.php:259
+msgid "Error connecting user to OpenID."
+msgstr "連接OpenID時發生錯誤"
+
+#: ../actions/finishaddopenid.php:78 actions/finishaddopenid.php:78
+msgid "Error connecting user."
+msgstr "連接用戶時發生錯誤(Error connecting user.)"
+
+#: ../actions/finishremotesubscribe.php:151
+#: actions/finishremotesubscribe.php:153
+msgid "Error inserting avatar"
+msgstr "個人圖åƒæ’入錯誤"
+
+#: ../actions/finishremotesubscribe.php:143
+#: actions/finishremotesubscribe.php:145
+msgid "Error inserting new profile"
+msgstr "新的更人資料輸入錯誤"
+
+#: ../actions/postnotice.php:88
+msgid "Error inserting notice"
+msgstr "新增訊æ¯æ™‚發生錯誤"
+
+#: ../actions/finishremotesubscribe.php:167
+#: actions/finishremotesubscribe.php:169
+msgid "Error inserting remote profile"
+msgstr "新增外部個人資料發生錯誤(Error inserting remote profile)"
+
+#: ../actions/recoverpassword.php:201 ../actions/recoverpassword.php:240
+#: actions/recoverpassword.php:246
+msgid "Error saving address confirmation."
+msgstr "儲存信箱確èªç™¼ç”ŸéŒ¯èª¤"
+
+#: ../actions/userauthorization.php:139 ../actions/userauthorization.php:140
+#: actions/userauthorization.php:147
+msgid "Error saving remote profile"
+msgstr "儲存é ç«¯å€‹äººè³‡æ–™ç™¼ç”ŸéŒ¯èª¤"
+
+#: ../actions/finishopenidlogin.php:222 ../lib/openid.php:226
+#: lib/openid.php:226
+msgid "Error saving the profile."
+msgstr "儲存個人資料發生錯誤"
+
+#: ../lib/openid.php:237 lib/openid.php:237
+msgid "Error saving the user."
+msgstr "儲存使用者發生錯誤"
+
+#: ../actions/password.php:80 actions/profilesettings.php:399
+msgid "Error saving user; invalid."
+msgstr "儲存使用者發生錯誤;使用者å稱無效"
+
+#: ../actions/login.php:43 ../actions/login.php:69
+#: ../actions/recoverpassword.php:268 ../actions/register.php:73
+#: ../actions/login.php:47 ../actions/login.php:73
+#: ../actions/recoverpassword.php:307 ../actions/register.php:98
+#: actions/login.php:47 actions/login.php:73 actions/recoverpassword.php:320
+#: actions/register.php:108
+msgid "Error setting user."
+msgstr "使用者設定發生錯誤"
+
+#: ../actions/finishaddopenid.php:83 actions/finishaddopenid.php:83
+msgid "Error updating profile"
+msgstr "更新個人資料發生錯誤"
+
+#: ../actions/finishremotesubscribe.php:161
+#: actions/finishremotesubscribe.php:163
+msgid "Error updating remote profile"
+msgstr "æ›´æ–°é ç«¯å€‹äººè³‡æ–™ç™¼ç”ŸéŒ¯èª¤"
+
+#: ../actions/recoverpassword.php:79 ../actions/recoverpassword.php:80
+#: actions/recoverpassword.php:80
+msgid "Error with confirmation code."
+msgstr "確èªç¢¼ç™¼ç”ŸéŒ¯èª¤"
+
+#: ../actions/finishopenidlogin.php:89 actions/finishopenidlogin.php:95
+msgid "Existing nickname"
+msgstr "這個暱稱已有人用了喔"
+
+#: ../lib/util.php:298 ../lib/util.php:326 lib/util.php:342
+msgid "FAQ"
+msgstr "常見å•é¡Œ"
+
+#: ../actions/avatar.php:115 actions/profilesettings.php:352
+msgid "Failed updating avatar."
+msgstr "無法上傳個人圖åƒ"
+
+#: ../actions/all.php:61 ../actions/allrss.php:74 ../actions/allrss.php:64
+#: actions/all.php:61 actions/allrss.php:64
+#, php-format
+msgid "Feed for friends of %s"
+msgstr "發é€çµ¦%s好å‹çš„訂閱"
+
+#: ../actions/replies.php:61 ../actions/repliesrss.php:80
+#: ../actions/replies.php:65 actions/replies.php:65 actions/repliesrss.php:66
+#, php-format
+msgid "Feed for replies to %s"
+msgstr "回應給%s的訂閱"
+
+#: ../actions/login.php:118 ../actions/login.php:122
+msgid ""
+"For security reasons, please re-enter your user name and password before "
+"changing your settings."
+msgstr "為安全起見,請先é‡æ–°è¼¸å…¥ä½ çš„使用者å稱與密碼å†æ›´æ”¹è¨­å®šã€‚"
+
+#: ../actions/profilesettings.php:44 ../actions/register.php:164
+#: actions/profilesettings.php:77 actions/register.php:178
+msgid "Full name"
+msgstr "å…¨å"
+
+#: ../actions/profilesettings.php:90 ../actions/updateprofile.php:92
+#: ../actions/profilesettings.php:98 ../actions/register.php:79
+#: ../actions/updateprofile.php:93 actions/profilesettings.php:213
+#: actions/register.php:86 actions/updateprofile.php:94
+msgid "Full name is too long (max 255 chars)."
+msgstr "å…¨åéŽé•·ï¼ˆæœ€å¤š255字元)"
+
+#: ../lib/util.php:279 ../lib/util.php:322 lib/util.php:338
+msgid "Help"
+msgstr "求救"
+
+#: ../lib/util.php:274 ../lib/util.php:298 lib/util.php:314
+msgid "Home"
+msgstr "主é "
+
+#: ../actions/profilesettings.php:49 ../actions/profilesettings.php:46
+#: ../actions/register.php:167 actions/profilesettings.php:79
+#: actions/register.php:181
+msgid "Homepage"
+msgstr "個人首é "
+
+#: ../actions/profilesettings.php:87 ../actions/profilesettings.php:95
+#: ../actions/register.php:76 actions/profilesettings.php:210
+#: actions/register.php:83
+msgid "Homepage is not a valid URL."
+msgstr "個人首é ä½å€éŒ¯èª¤"
+
+#: ../actions/imsettings.php:61 ../actions/imsettings.php:60
+#: actions/imsettings.php:61
+msgid "IM Address"
+msgstr "線上å³æ™‚通信箱"
+
+#: ../actions/imsettings.php:33 actions/imsettings.php:33
+msgid "IM Settings"
+msgstr "線上å³æ™‚通設定"
+
+#: ../actions/finishopenidlogin.php:88 actions/finishopenidlogin.php:94
+msgid ""
+"If you already have an account, login with your username and password to "
+"connect it to your OpenID."
+msgstr "若已經註冊éŽäº†ï¼Œè«‹è¼¸å…¥ä½¿ç”¨è€…å稱與密碼連çµåˆ°ä½ çš„OpenID。"
+
+#: ../actions/openidsettings.php:45
+msgid ""
+"If you want to add an OpenID to your account, enter it in the box below and "
+"click \"Add\"."
+msgstr "若想新增OpenID到你的帳號,請在下方空格輸入並勾é¸ã€Žæ–°å¢žã€"
+
+#: ../actions/recoverpassword.php:122
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent the "
+"email address you have stored in your account."
+msgstr "若忘記或éºå¤±å¯†ç¢¼ï¼Œæˆ‘們會寄新的密碼到你帳號中的信箱。"
+
+#: ../actions/password.php:69 actions/profilesettings.php:388
+msgid "Incorrect old password"
+msgstr "舊密碼錯誤"
+
+#: ../actions/login.php:63 ../actions/login.php:67 actions/login.php:67
+msgid "Incorrect username or password."
+msgstr "使用者å稱或密碼錯誤"
+
+#: ../actions/recoverpassword.php:226 ../actions/recoverpassword.php:265
+msgid ""
+"Instructions for recovering your password have been sent to the email "
+"address registered to your account."
+msgstr "我們已寄出一å°ä¿¡åˆ°ä½ å¸³è™Ÿä¸­çš„信箱,告訴你如何å–回你的密碼。"
+
+#: ../actions/updateprofile.php:113 ../actions/updateprofile.php:114
+#: actions/updateprofile.php:115
+#, php-format
+msgid "Invalid avatar URL '%s'"
+msgstr "個人圖åƒé€£çµ%s無效"
+
+#: ../actions/updateprofile.php:97 ../actions/updateprofile.php:98
+#: actions/updateprofile.php:99
+#, php-format
+msgid "Invalid homepage '%s'"
+msgstr "個人首é é€£çµ%s無效"
+
+#: ../actions/updateprofile.php:81 ../actions/updateprofile.php:82
+#: actions/updateprofile.php:83
+#, php-format
+msgid "Invalid license URL '%s'"
+msgstr ""
+
+#: ../actions/postnotice.php:61 actions/postnotice.php:62
+msgid "Invalid notice content"
+msgstr ""
+
+#: ../actions/postnotice.php:67 actions/postnotice.php:68
+msgid "Invalid notice uri"
+msgstr ""
+
+#: ../actions/postnotice.php:72 actions/postnotice.php:73
+msgid "Invalid notice url"
+msgstr ""
+
+#: ../actions/updateprofile.php:86 ../actions/updateprofile.php:87
+#: actions/updateprofile.php:88
+#, php-format
+msgid "Invalid profile URL '%s'."
+msgstr "個人資料連çµ%s無效"
+
+#: ../actions/remotesubscribe.php:96 actions/remotesubscribe.php:105
+msgid "Invalid profile URL (bad format)"
+msgstr "個人資料連çµç„¡æ•ˆ(æ ¼å¼éŒ¯èª¤)"
+
+#: ../actions/finishremotesubscribe.php:77
+#: actions/finishremotesubscribe.php:79
+msgid "Invalid profile URL returned by server."
+msgstr ""
+
+#: ../actions/avatarbynickname.php:37 actions/avatarbynickname.php:37
+msgid "Invalid size."
+msgstr "尺寸錯誤"
+
+#: ../actions/finishopenidlogin.php:264 ../actions/register.php:68
+#: ../actions/register.php:84 ../actions/finishopenidlogin.php:235
+#: ../actions/register.php:93 ../actions/register.php:111
+#: actions/finishopenidlogin.php:241 actions/register.php:103
+#: actions/register.php:121
+msgid "Invalid username or password."
+msgstr "使用者å稱或密碼無效"
+
+#: ../lib/util.php:237 ../lib/util.php:261 lib/util.php:277
+#, php-format
+msgid ""
+"It runs the [Laconica](http://laconi.ca/) microblogging software, version "
+"%s, available under the [GNU Affero General Public "
+"License](http://www.fsf.org/licensing/licenses/agpl-3.0.html)."
+msgstr ""
+
+#: ../actions/imsettings.php:164 ../actions/imsettings.php:173
+#: actions/imsettings.php:181
+msgid "Jabber ID already belongs to another user."
+msgstr "此Jabber ID已有人使用"
+
+#: ../actions/imsettings.php:63 ../actions/imsettings.php:62
+#: actions/imsettings.php:63
+#, php-format
+msgid ""
+"Jabber or GTalk address, like \"UserName@example.org\". First, make sure to "
+"add %s to your buddy list in your IM client or on GTalk."
+msgstr ""
+
+#: ../actions/profilesettings.php:55 ../actions/profilesettings.php:52
+#: ../actions/register.php:173 actions/profilesettings.php:85
+#: actions/register.php:187
+msgid "Location"
+msgstr "地點"
+
+#: ../actions/profilesettings.php:96 ../actions/updateprofile.php:107
+#: ../actions/profilesettings.php:104 ../actions/register.php:85
+#: ../actions/updateprofile.php:108 actions/profilesettings.php:219
+#: actions/register.php:92 actions/updateprofile.php:109
+msgid "Location is too long (max 255 chars)."
+msgstr "地點éŽé•·ï¼ˆå…±255個字)"
+
+#: ../actions/login.php:93 ../actions/login.php:102
+#: ../actions/openidlogin.php:68 ../lib/util.php:286 ../actions/login.php:97
+#: ../actions/login.php:106 ../lib/util.php:310 actions/login.php:97
+#: actions/login.php:106 actions/openidlogin.php:77 lib/util.php:326
+msgid "Login"
+msgstr "登入"
+
+#: ../actions/openidlogin.php:44 actions/openidlogin.php:52
+#, php-format
+msgid "Login with an [OpenID](%%doc.openid%%) account."
+msgstr "用OpenID(%%doc.openid%%)帳號登入"
+
+#: ../actions/login.php:122 ../actions/login.php:126
+#, php-format
+msgid ""
+"Login with your username and password. Don't have a username yet? "
+"[Register](%%action.register%%) a new account, or try "
+"[OpenID](%%action.openidlogin%%). "
+msgstr ""
+"è"
+"¼"
+"¸"
+"å"
+"…"
+"¥"
+"ä"
+"½"
+"¿"
+"ç"
+"”"
+"¨"
+"è"
+"€"
+"…"
+"å"
+""
+""
+"ç"
+"¨"
+"±èˆ‡å¯†ç¢¼ç™»å…¥ã€‚還沒有使用者å稱嗎?[註冊](%%action.register%%)一個新帳號,或使用[OpenID](%%action.openidlogin%%)。"
+
+#: ../lib/util.php:284 ../lib/util.php:308 lib/util.php:324
+msgid "Logout"
+msgstr "登出"
+
+#: ../actions/login.php:106 ../actions/login.php:110 actions/login.php:110
+msgid "Lost or forgotten password?"
+msgstr "éºå¤±æˆ–忘記密碼了嗎?"
+
+#: ../actions/showstream.php:281 ../actions/showstream.php:300
+#: actions/showstream.php:315
+msgid "Member since"
+msgstr "何時加入會員的呢?"
+
+#: ../actions/userrss.php:70 actions/userrss.php:67
+#, php-format
+msgid "Microblog by %s"
+msgstr "&s的微型部è½æ ¼"
+
+#: ../actions/finishopenidlogin.php:79 ../actions/register.php:190
+#: ../actions/register.php:188 actions/finishopenidlogin.php:85
+#: actions/register.php:202
+msgid "My text and files are available under "
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:71 actions/finishopenidlogin.php:77
+msgid "New nickname"
+msgstr "新暱稱"
+
+#: ../actions/newnotice.php:100 ../actions/newnotice.php:87
+#: actions/newnotice.php:96
+msgid "New notice"
+msgstr "新訊æ¯"
+
+#: ../actions/password.php:41 ../actions/recoverpassword.php:164
+#: ../actions/recoverpassword.php:179 actions/profilesettings.php:180
+#: actions/recoverpassword.php:185
+msgid "New password"
+msgstr "新密碼"
+
+#: ../actions/recoverpassword.php:275 ../actions/recoverpassword.php:314
+msgid "New password successfully saved. You are now logged in."
+msgstr "新密碼已儲存æˆåŠŸã€‚你已登入。"
+
+#: ../actions/login.php:97 ../actions/profilesettings.php:41
+#: ../actions/register.php:175 ../actions/login.php:101
+#: ../actions/register.php:151 actions/login.php:101
+#: actions/profilesettings.php:74 actions/register.php:165
+msgid "Nickname"
+msgstr "暱稱"
+
+#: ../actions/finishopenidlogin.php:175 ../actions/profilesettings.php:99
+#: ../actions/register.php:59 ../actions/profilesettings.php:110
+#: ../actions/register.php:69 actions/finishopenidlogin.php:181
+#: actions/profilesettings.php:225 actions/register.php:76
+msgid "Nickname already in use. Try another one."
+msgstr "此暱稱已有人使用。å†è©¦è©¦çœ‹åˆ¥çš„å§ã€‚"
+
+#: ../actions/finishopenidlogin.php:165 ../actions/profilesettings.php:80
+#: ../actions/register.php:57 ../actions/updateprofile.php:76
+#: ../actions/profilesettings.php:88 ../actions/register.php:67
+#: ../actions/updateprofile.php:77 actions/finishopenidlogin.php:171
+#: actions/profilesettings.php:203 actions/register.php:74
+#: actions/updateprofile.php:78
+msgid "Nickname must have only lowercase letters and numbers and no spaces."
+msgstr "暱稱請用å°å¯«å­—æ¯æˆ–數字,勿加空格。"
+
+#: ../actions/finishopenidlogin.php:170 actions/finishopenidlogin.php:176
+msgid "Nickname not allowed."
+msgstr "此暱稱無法使用"
+
+#: ../actions/remotesubscribe.php:72 actions/remotesubscribe.php:81
+msgid "Nickname of the user you want to follow"
+msgstr "你想æˆç‚ºèª°çš„粉絲呢?請輸入他/她的暱稱。"
+
+#: ../actions/recoverpassword.php:147 ../actions/recoverpassword.php:162
+#: actions/recoverpassword.php:167
+msgid "Nickname or email"
+msgstr "暱稱或信箱"
+
+#: ../actions/imsettings.php:147 ../actions/imsettings.php:156
+#: actions/imsettings.php:164
+msgid "No Jabber ID."
+msgstr "查無此Jabber ID"
+
+#: ../actions/userauthorization.php:128 ../actions/userauthorization.php:129
+#: actions/userauthorization.php:136
+msgid "No authorization request!"
+msgstr "無確èªè«‹æ±‚"
+
+#: ../actions/confirmaddress.php:33 actions/confirmaddress.php:33
+msgid "No confirmation code."
+msgstr "無確èªç¢¼"
+
+#: ../actions/newnotice.php:49 ../actions/newnotice.php:44
+#: actions/newmessage.php:53 actions/newnotice.php:44 classes/Command.php:197
+msgid "No content!"
+msgstr "無內容"
+
+#: ../actions/userbyid.php:27 ../actions/userbyid.php:32
+#: actions/userbyid.php:32
+msgid "No id."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:65
+#: actions/finishremotesubscribe.php:67
+msgid "No nickname provided by remote server."
+msgstr "ç„¡é ç«¯ä¼ºæœå™¨æ供的暱稱"
+
+#: ../actions/avatarbynickname.php:27 actions/avatarbynickname.php:27
+msgid "No nickname."
+msgstr "無暱稱"
+
+#: ../actions/imsettings.php:197 ../actions/emailsettings.php:222
+#: ../actions/imsettings.php:206 ../actions/smssettings.php:229
+#: actions/emailsettings.php:240 actions/imsettings.php:214
+#: actions/smssettings.php:237
+msgid "No pending confirmation to cancel."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:72
+#: actions/finishremotesubscribe.php:74
+msgid "No profile URL returned by server."
+msgstr ""
+
+#: ../actions/recoverpassword.php:189 ../actions/recoverpassword.php:226
+#: actions/recoverpassword.php:232
+msgid "No registered email address for that user."
+msgstr "查無此使用者所註冊的信箱"
+
+#: ../actions/userauthorization.php:48 ../actions/userauthorization.php:49
+#: actions/userauthorization.php:55
+msgid "No request found!"
+msgstr "ç›®å‰ç„¡è«‹æ±‚"
+
+#: ../actions/noticesearch.php:64 ../actions/peoplesearch.php:64
+#: actions/noticesearch.php:69 actions/peoplesearch.php:69
+msgid "No results"
+msgstr "ç„¡çµæžœ"
+
+#: ../actions/avatarbynickname.php:32 actions/avatarbynickname.php:32
+msgid "No size."
+msgstr "無尺寸"
+
+#: ../actions/openidsettings.php:135 actions/openidsettings.php:144
+msgid "No such OpenID."
+msgstr "ç„¡æ­¤OpenID"
+
+#: ../actions/doc.php:29 actions/doc.php:29
+msgid "No such document."
+msgstr "無此文件"
+
+#: ../actions/shownotice.php:32 ../actions/shownotice.php:65
+#: ../actions/shownotice.php:83 ../lib/deleteaction.php:30
+#: actions/shownotice.php:32 actions/shownotice.php:83 lib/deleteaction.php:30
+msgid "No such notice."
+msgstr "無此通知"
+
+#: ../actions/recoverpassword.php:56 actions/recoverpassword.php:56
+msgid "No such recovery code."
+msgstr "ç„¡æ­¤æ¢å¾©ç¢¼"
+
+#: ../actions/postnotice.php:56 actions/postnotice.php:57
+msgid "No such subscription"
+msgstr "無此訂閱"
+
+#: ../actions/all.php:34 ../actions/allrss.php:35
+#: ../actions/avatarbynickname.php:43 ../actions/foaf.php:36
+#: ../actions/recoverpassword.php:185 ../actions/remotesubscribe.php:84
+#: ../actions/remotesubscribe.php:91 ../actions/repliesrss.php:35
+#: ../actions/showstream.php:95 ../actions/subscribe.php:43
+#: ../actions/unsubscribe.php:38 ../actions/userbyid.php:31
+#: ../actions/userrss.php:35 ../actions/xrds.php:31 ../lib/gallery.php:53
+#: ../actions/foaf.php:40 ../actions/replies.php:57
+#: ../actions/showstream.php:110 ../actions/userbyid.php:36
+#: ../actions/xrds.php:35 ../lib/gallery.php:57 ../lib/subs.php:33
+#: ../lib/subs.php:82 actions/all.php:34 actions/allrss.php:35
+#: actions/avatarbynickname.php:43 actions/favoritesrss.php:35
+#: actions/foaf.php:40 actions/ical.php:31 actions/remotesubscribe.php:93
+#: actions/remotesubscribe.php:100 actions/replies.php:57
+#: actions/repliesrss.php:35 actions/showfavorites.php:34
+#: actions/showstream.php:110 actions/userbyid.php:36 actions/userrss.php:35
+#: actions/xrds.php:35 classes/Command.php:120 classes/Command.php:162
+#: classes/Command.php:203 classes/Command.php:237 lib/gallery.php:62
+#: lib/mailbox.php:36 lib/subs.php:33 lib/subs.php:95
+msgid "No such user."
+msgstr "無此使用者"
+
+#: ../lib/gallery.php:76 ../lib/gallery.php:80 lib/gallery.php:85
+msgid "Nobody to show!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:60 actions/recoverpassword.php:60
+msgid "Not a recovery code."
+msgstr "æ­¤æ¢å¾©ç¢¼éŒ¯èª¤"
+
+#: ../actions/imsettings.php:158 ../actions/imsettings.php:167
+#: actions/imsettings.php:175
+msgid "Not a valid Jabber ID"
+msgstr "此JabberID無效"
+
+#: ../lib/openid.php:131 lib/openid.php:131
+msgid "Not a valid OpenID."
+msgstr "此OpenID無效"
+
+#: ../actions/profilesettings.php:75 ../actions/register.php:53
+#: ../actions/register.php:63 actions/register.php:70
+msgid "Not a valid email address."
+msgstr "此信箱無效"
+
+#: ../actions/profilesettings.php:83 ../actions/register.php:61
+#: ../actions/profilesettings.php:91 ../actions/register.php:71
+#: actions/profilesettings.php:206 actions/register.php:78
+msgid "Not a valid nickname."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:118 ../actions/remotesubscribe.php:120
+#: actions/remotesubscribe.php:129
+msgid "Not a valid profile URL (incorrect services)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:111 ../actions/remotesubscribe.php:113
+#: actions/remotesubscribe.php:122
+msgid "Not a valid profile URL (no XRDS defined)."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:104 actions/remotesubscribe.php:113
+msgid "Not a valid profile URL (no YADIS document)."
+msgstr ""
+
+#: ../actions/avatar.php:95 actions/profilesettings.php:332
+msgid "Not an image or corrupt file."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:51
+#: actions/finishremotesubscribe.php:53
+msgid "Not authorized."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:38
+#: actions/finishremotesubscribe.php:38
+msgid "Not expecting this response!"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:29 ../actions/logout.php:28
+#: ../actions/newnotice.php:29 ../actions/subscribe.php:27
+#: ../actions/unsubscribe.php:24 ../lib/settingsaction.php:27
+#: ../actions/logout.php:33 ../actions/subscribe.php:28
+#: ../actions/unsubscribe.php:25 ../lib/deleteaction.php:38
+#: actions/disfavor.php:29 actions/favor.php:30 actions/finishaddopenid.php:29
+#: actions/logout.php:33 actions/newmessage.php:28 actions/newnotice.php:29
+#: actions/subscribe.php:28 actions/unsubscribe.php:25 lib/deleteaction.php:38
+#: lib/settingsaction.php:27
+msgid "Not logged in."
+msgstr ""
+
+#: ../actions/unsubscribe.php:43 ../lib/subs.php:91 lib/subs.php:104
+msgid "Not subscribed!."
+msgstr ""
+
+#: ../actions/showstream.php:82 actions/showstream.php:82
+#, php-format
+msgid "Notice feed for %s"
+msgstr ""
+
+#: ../actions/shownotice.php:39 actions/shownotice.php:39
+msgid "Notice has no profile"
+msgstr ""
+
+#: ../actions/showstream.php:297 ../actions/showstream.php:316
+#: actions/showstream.php:331
+msgid "Notices"
+msgstr ""
+
+#: ../actions/password.php:39 actions/profilesettings.php:178
+msgid "Old password"
+msgstr ""
+
+#: ../lib/util.php:288 ../lib/settingsaction.php:96 ../lib/util.php:314
+#: lib/settingsaction.php:90 lib/util.php:330
+msgid "OpenID"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:61 actions/finishopenidlogin.php:66
+msgid "OpenID Account Setup"
+msgstr ""
+
+#: ../lib/openid.php:180 lib/openid.php:180
+msgid "OpenID Auto-Submit"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:99 ../actions/finishopenidlogin.php:140
+#: ../actions/openidlogin.php:60 actions/finishaddopenid.php:99
+#: actions/finishopenidlogin.php:146 actions/openidlogin.php:68
+msgid "OpenID Login"
+msgstr ""
+
+#: ../actions/openidlogin.php:65 ../actions/openidsettings.php:49
+#: actions/openidlogin.php:74 actions/openidsettings.php:50
+msgid "OpenID URL"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:42 ../actions/finishopenidlogin.php:103
+#: actions/finishaddopenid.php:42 actions/finishopenidlogin.php:109
+msgid "OpenID authentication cancelled."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:46 ../actions/finishopenidlogin.php:107
+#: actions/finishaddopenid.php:46 actions/finishopenidlogin.php:113
+#, php-format
+msgid "OpenID authentication failed: %s"
+msgstr ""
+
+#: ../lib/openid.php:133 lib/openid.php:133
+#, php-format
+msgid "OpenID failure: %s"
+msgstr ""
+
+#: ../actions/openidsettings.php:144 actions/openidsettings.php:153
+msgid "OpenID removed."
+msgstr ""
+
+#: ../actions/openidsettings.php:37 actions/openidsettings.php:37
+msgid "OpenID settings"
+msgstr ""
+
+#: ../actions/avatar.php:84 actions/profilesettings.php:321
+msgid "Partial upload."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:90 ../actions/login.php:98
+#: ../actions/register.php:177 ../actions/login.php:102
+#: ../actions/register.php:153 ../lib/settingsaction.php:93
+#: actions/finishopenidlogin.php:96 actions/login.php:102
+#: actions/register.php:167
+msgid "Password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:249 ../actions/recoverpassword.php:288
+#: actions/recoverpassword.php:301
+msgid "Password and confirmation do not match."
+msgstr ""
+
+#: ../actions/recoverpassword.php:245 ../actions/recoverpassword.php:284
+#: actions/recoverpassword.php:297
+msgid "Password must be 6 chars or more."
+msgstr ""
+
+#: ../actions/recoverpassword.php:222 ../actions/recoverpassword.php:224
+#: ../actions/recoverpassword.php:261 ../actions/recoverpassword.php:263
+#: actions/recoverpassword.php:267 actions/recoverpassword.php:269
+msgid "Password recovery requested"
+msgstr ""
+
+#: ../actions/password.php:89 ../actions/recoverpassword.php:274
+#: ../actions/recoverpassword.php:313 actions/profilesettings.php:408
+#: actions/recoverpassword.php:326
+msgid "Password saved."
+msgstr ""
+
+#: ../actions/password.php:61 ../actions/register.php:65
+#: ../actions/register.php:88 actions/profilesettings.php:380
+#: actions/register.php:98
+msgid "Passwords don't match."
+msgstr ""
+
+#: ../actions/peoplesearch.php:33 actions/peoplesearch.php:33
+msgid "People search"
+msgstr ""
+
+#: ../lib/stream.php:44 ../lib/stream.php:50 lib/personal.php:50
+msgid "Personal"
+msgstr ""
+
+#: ../actions/userauthorization.php:77 ../actions/userauthorization.php:78
+msgid ""
+"Please check these details to make sure that you want to subscribe to this "
+"user's notices. If you didn't just ask to subscribe to someone's notices, "
+"click \"Cancel\"."
+msgstr ""
+
+#: ../actions/imsettings.php:74 ../actions/imsettings.php:73
+#: actions/imsettings.php:74
+msgid "Post a notice when my Jabber/GTalk status changes."
+msgstr ""
+
+#: ../actions/imsettings.php:68 ../actions/emailsettings.php:85
+#: ../actions/imsettings.php:67 ../actions/smssettings.php:94
+#: actions/emailsettings.php:86 actions/imsettings.php:68
+#: actions/smssettings.php:94 actions/twittersettings.php:70
+msgid "Preferences"
+msgstr ""
+
+#: ../actions/imsettings.php:135 ../actions/emailsettings.php:162
+#: ../actions/imsettings.php:144 ../actions/smssettings.php:163
+#: actions/emailsettings.php:180 actions/imsettings.php:152
+#: actions/smssettings.php:171
+msgid "Preferences saved."
+msgstr ""
+
+#: ../lib/util.php:300 ../lib/util.php:328 lib/util.php:344
+msgid "Privacy"
+msgstr ""
+
+#: ../actions/newnotice.php:61 ../actions/newnotice.php:69
+#: ../classes/Notice.php:95 ../classes/Notice.php:106 classes/Notice.php:109
+#: classes/Notice.php:119
+msgid "Problem saving notice."
+msgstr ""
+
+#: ../lib/stream.php:54 ../lib/settingsaction.php:84 ../lib/stream.php:60
+#: lib/personal.php:60 lib/settingsaction.php:84
+msgid "Profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:73 actions/remotesubscribe.php:82
+msgid "Profile URL"
+msgstr ""
+
+#: ../actions/profilesettings.php:34 actions/profilesettings.php:32
+msgid "Profile settings"
+msgstr ""
+
+#: ../actions/postnotice.php:51 ../actions/updateprofile.php:51
+#: ../actions/updateprofile.php:52 actions/postnotice.php:52
+#: actions/updateprofile.php:53
+msgid "Profile unknown"
+msgstr ""
+
+#: ../lib/util.php:276
+msgid "Public"
+msgstr ""
+
+#: ../actions/public.php:54 actions/public.php:54
+msgid "Public Stream Feed"
+msgstr ""
+
+#: ../actions/public.php:33 actions/public.php:33
+msgid "Public timeline"
+msgstr ""
+
+#: ../actions/recoverpassword.php:151 ../actions/recoverpassword.php:166
+#: actions/recoverpassword.php:171
+msgid "Recover"
+msgstr ""
+
+#: ../actions/recoverpassword.php:141 ../actions/recoverpassword.php:156
+#: actions/recoverpassword.php:161
+msgid "Recover password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:67 actions/recoverpassword.php:67
+msgid "Recovery code for unknown user."
+msgstr ""
+
+#: ../actions/register.php:171 ../actions/register.php:195 ../lib/util.php:287
+#: ../actions/register.php:142 ../actions/register.php:193 ../lib/util.php:312
+#: actions/register.php:152 actions/register.php:207 lib/util.php:328
+msgid "Register"
+msgstr ""
+
+#: ../actions/userauthorization.php:119 ../actions/userauthorization.php:120
+#: actions/userauthorization.php:127
+msgid "Reject"
+msgstr ""
+
+#: ../actions/login.php:99 ../actions/register.php:183
+#: ../actions/login.php:103 ../actions/register.php:176 actions/login.php:103
+#: actions/register.php:190
+msgid "Remember me"
+msgstr ""
+
+#: ../actions/updateprofile.php:69 ../actions/updateprofile.php:70
+#: actions/updateprofile.php:71
+msgid "Remote profile with no matching profile"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:65 actions/remotesubscribe.php:73
+msgid "Remote subscribe"
+msgstr ""
+
+#: ../actions/imsettings.php:48 ../actions/openidsettings.php:106
+#: ../actions/emailsettings.php:47 ../actions/emailsettings.php:75
+#: ../actions/smssettings.php:50 ../actions/smssettings.php:84
+#: actions/emailsettings.php:48 actions/emailsettings.php:76
+#: actions/imsettings.php:49 actions/openidsettings.php:108
+#: actions/smssettings.php:50 actions/smssettings.php:84
+#: actions/twittersettings.php:59
+msgid "Remove"
+msgstr ""
+
+#: ../actions/openidsettings.php:68 actions/openidsettings.php:69
+msgid "Remove OpenID"
+msgstr ""
+
+#: ../actions/openidsettings.php:73
+msgid ""
+"Removing your only OpenID would make it impossible to log in! If you need to "
+"remove it, add another OpenID first."
+msgstr ""
+
+#: ../lib/stream.php:49 ../lib/stream.php:55 lib/personal.php:55
+msgid "Replies"
+msgstr ""
+
+#: ../actions/replies.php:47 ../actions/repliesrss.php:76 ../lib/stream.php:50
+#: ../lib/stream.php:56 actions/replies.php:47 actions/repliesrss.php:62
+#: lib/personal.php:56
+#, php-format
+msgid "Replies to %s"
+msgstr ""
+
+#: ../actions/recoverpassword.php:168 ../actions/recoverpassword.php:183
+#: actions/recoverpassword.php:189
+msgid "Reset"
+msgstr ""
+
+#: ../actions/recoverpassword.php:158 ../actions/recoverpassword.php:173
+#: actions/recoverpassword.php:178
+msgid "Reset password"
+msgstr ""
+
+#: ../actions/recoverpassword.php:167 ../actions/register.php:180
+#: ../actions/recoverpassword.php:182 actions/recoverpassword.php:188
+msgid "Same as password above"
+msgstr ""
+
+#: ../actions/imsettings.php:76 ../actions/profilesettings.php:58
+#: ../actions/emailsettings.php:97 ../actions/imsettings.php:81
+#: ../actions/profilesettings.php:67 ../actions/smssettings.php:100
+#: actions/emailsettings.php:104 actions/imsettings.php:82
+#: actions/profilesettings.php:101 actions/smssettings.php:100
+#: actions/twittersettings.php:83
+msgid "Save"
+msgstr ""
+
+#: ../lib/searchaction.php:73 ../lib/util.php:277 ../lib/searchaction.php:84
+#: ../lib/util.php:300 lib/searchaction.php:84 lib/util.php:316
+msgid "Search"
+msgstr ""
+
+#: ../actions/noticesearch.php:80 actions/noticesearch.php:85
+msgid "Search Stream Feed"
+msgstr ""
+
+#: ../actions/noticesearch.php:30 actions/noticesearch.php:30
+#, php-format
+msgid ""
+"Search for notices on %%site.name%% by their contents. Separate search terms "
+"by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+"Separate the terms by spaces; they must be 3 characters or more."
+msgstr ""
+
+#: ../lib/util.php:982 ../actions/invite.php:137 ../lib/util.php:1172
+#: actions/invite.php:145 lib/util.php:1306 lib/util.php:1731
+msgid "Send"
+msgstr ""
+
+#: ../actions/imsettings.php:71 ../actions/imsettings.php:70
+#: actions/imsettings.php:71
+msgid "Send me notices through Jabber/GTalk."
+msgstr ""
+
+#: ../lib/util.php:282 ../lib/util.php:304 lib/util.php:320
+msgid "Settings"
+msgstr ""
+
+#: ../actions/profilesettings.php:183 ../actions/profilesettings.php:192
+#: actions/profilesettings.php:307
+msgid "Settings saved."
+msgstr ""
+
+#: ../actions/finishaddopenid.php:66 actions/finishaddopenid.php:66
+msgid "Someone else already has this OpenID."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:42 ../actions/openidsettings.php:126
+#: actions/finishopenidlogin.php:47 actions/openidsettings.php:135
+msgid "Something weird happened."
+msgstr ""
+
+#: ../lib/util.php:302 ../lib/util.php:330 lib/util.php:346
+msgid "Source"
+msgstr ""
+
+#: ../actions/showstream.php:277 ../actions/showstream.php:296
+#: actions/showstream.php:311
+msgid "Statistics"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:182 ../actions/finishopenidlogin.php:275
+#: ../actions/finishopenidlogin.php:246 actions/finishopenidlogin.php:188
+#: actions/finishopenidlogin.php:252
+msgid "Stored OpenID not found."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:75 ../actions/showstream.php:172
+#: ../actions/showstream.php:181 ../actions/showstream.php:188
+#: ../actions/showstream.php:197 actions/remotesubscribe.php:84
+#: actions/showstream.php:197 actions/showstream.php:206
+msgid "Subscribe"
+msgstr ""
+
+#: ../actions/showstream.php:294 ../actions/subscribers.php:27
+#: ../actions/showstream.php:313 actions/showstream.php:328
+#: actions/subscribers.php:27
+msgid "Subscribers"
+msgstr ""
+
+#: ../actions/userauthorization.php:309 ../actions/userauthorization.php:310
+#: actions/userauthorization.php:322
+msgid "Subscription authorized"
+msgstr ""
+
+#: ../actions/userauthorization.php:319 ../actions/userauthorization.php:320
+#: actions/userauthorization.php:332
+msgid "Subscription rejected"
+msgstr ""
+
+#: ../actions/showstream.php:212 ../actions/showstream.php:288
+#: ../actions/subscriptions.php:27 ../actions/showstream.php:230
+#: ../actions/showstream.php:307 actions/showstream.php:240
+#: actions/showstream.php:322 actions/subscriptions.php:27
+msgid "Subscriptions"
+msgstr ""
+
+#: ../actions/avatar.php:87 actions/profilesettings.php:324
+msgid "System error uploading file."
+msgstr ""
+
+#: ../actions/noticesearch.php:34 actions/noticesearch.php:34
+msgid "Text search"
+msgstr ""
+
+#: ../actions/openidsettings.php:140 actions/openidsettings.php:149
+msgid "That OpenID does not belong to you."
+msgstr ""
+
+#: ../actions/confirmaddress.php:52 actions/confirmaddress.php:52
+msgid "That address has already been confirmed."
+msgstr ""
+
+#: ../actions/confirmaddress.php:43 actions/confirmaddress.php:43
+msgid "That confirmation code is not for you!"
+msgstr ""
+
+#: ../actions/avatar.php:80 actions/profilesettings.php:317
+msgid "That file is too big."
+msgstr ""
+
+#: ../actions/imsettings.php:161 ../actions/imsettings.php:170
+#: actions/imsettings.php:178
+msgid "That is already your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:224 ../actions/imsettings.php:233
+#: actions/imsettings.php:241
+msgid "That is not your Jabber ID."
+msgstr ""
+
+#: ../actions/imsettings.php:201 ../actions/emailsettings.php:226
+#: ../actions/imsettings.php:210 actions/emailsettings.php:244
+#: actions/imsettings.php:218
+msgid "That is the wrong IM address."
+msgstr ""
+
+#: ../actions/newnotice.php:52 ../actions/newnotice.php:49
+#: ../actions/twitapistatuses.php:408 actions/newnotice.php:49
+#: actions/twitapistatuses.php:330
+msgid "That's too long. Max notice size is 140 chars."
+msgstr ""
+
+#: ../actions/confirmaddress.php:86 ../actions/confirmaddress.php:92
+#: actions/confirmaddress.php:92
+#, php-format
+msgid "The address \"%s\" has been confirmed for your account."
+msgstr ""
+
+#: ../actions/imsettings.php:241 ../actions/emailsettings.php:264
+#: ../actions/imsettings.php:250 ../actions/smssettings.php:274
+#: actions/emailsettings.php:282 actions/imsettings.php:258
+#: actions/smssettings.php:282
+msgid "The address was removed."
+msgstr ""
+
+#: ../actions/userauthorization.php:311 ../actions/userauthorization.php:312
+msgid ""
+"The subscription has been authorized, but no callback URL was passed. Check "
+"with the site's instructions for details on how to authorize the "
+"subscription. Your subscription token is:"
+msgstr ""
+
+#: ../actions/userauthorization.php:321 ../actions/userauthorization.php:322
+msgid ""
+"The subscription has been rejected, but no callback URL was passed. Check "
+"with the site's instructions for details on how to fully reject the "
+"subscription."
+msgstr ""
+
+#: ../actions/subscribers.php:35 actions/subscribers.php:35
+#, php-format
+msgid "These are the people who listen to %s's notices."
+msgstr ""
+
+#: ../actions/subscribers.php:33 actions/subscribers.php:33
+msgid "These are the people who listen to your notices."
+msgstr ""
+
+#: ../actions/subscriptions.php:35 actions/subscriptions.php:35
+#, php-format
+msgid "These are the people whose notices %s listens to."
+msgstr ""
+
+#: ../actions/subscriptions.php:33 actions/subscriptions.php:33
+msgid "These are the people whose notices you listen to."
+msgstr ""
+
+#: ../actions/recoverpassword.php:87 ../actions/recoverpassword.php:88
+msgid "This confirmation code is too old. Please start again."
+msgstr ""
+
+#: ../lib/openid.php:195
+msgid ""
+"This form should automatically submit itself. If not, click the submit "
+"button to go to your OpenID provider."
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:56 actions/finishopenidlogin.php:61
+#, php-format
+msgid ""
+"This is the first time you've logged into %s so we must connect your OpenID "
+"to a local account. You can either create a new account, or connect with "
+"your existing account, if you have one."
+msgstr ""
+
+#: ../lib/util.php:147 ../lib/util.php:164 lib/util.php:246
+msgid "This page is not available in a media type you accept"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:43
+#, php-format
+msgid ""
+"To subscribe, you can [login](%%action.login%%), or "
+"[register](%%action.register%%) a new account. If you already have an "
+"account on a [compatible microblogging site](%%doc.openmublog%%), enter "
+"your profile URL below."
+msgstr ""
+
+#: ../actions/profilesettings.php:51 ../actions/profilesettings.php:48
+#: ../actions/register.php:169 actions/profilesettings.php:81
+#: actions/register.php:183
+msgid "URL of your homepage, blog, or profile on another site"
+msgstr ""
+
+#: ../actions/remotesubscribe.php:74 actions/remotesubscribe.php:83
+msgid "URL of your profile on another compatible microblogging service"
+msgstr ""
+
+#: ../actions/imsettings.php:105 ../actions/recoverpassword.php:39
+#: ../actions/emailsettings.php:130 ../actions/imsettings.php:110
+#: ../actions/smssettings.php:135 actions/emailsettings.php:144
+#: actions/imsettings.php:118 actions/recoverpassword.php:39
+#: actions/smssettings.php:143 actions/twittersettings.php:108
+msgid "Unexpected form submission."
+msgstr ""
+
+#: ../actions/recoverpassword.php:237 ../actions/recoverpassword.php:276
+#: actions/recoverpassword.php:289
+msgid "Unexpected password reset."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:58
+#: actions/finishremotesubscribe.php:60
+msgid "Unknown version of OMB protocol."
+msgstr ""
+
+#: ../lib/util.php:245 ../lib/util.php:269 lib/util.php:285
+msgid ""
+"Unless otherwise specified, contents of this site are copyright by the "
+"contributors and available under the "
+msgstr ""
+
+#: ../actions/confirmaddress.php:48 actions/confirmaddress.php:48
+#, php-format
+msgid "Unrecognized address type %s"
+msgstr ""
+
+#: ../actions/showstream.php:193 ../actions/showstream.php:209
+#: actions/showstream.php:219
+msgid "Unsubscribe"
+msgstr ""
+
+#: ../actions/postnotice.php:44 ../actions/updateprofile.php:44
+#: ../actions/updateprofile.php:45 actions/postnotice.php:45
+#: actions/updateprofile.php:46
+msgid "Unsupported OMB version"
+msgstr ""
+
+#: ../actions/avatar.php:105 actions/profilesettings.php:342
+msgid "Unsupported image file format."
+msgstr ""
+
+#: ../actions/avatar.php:68 actions/profilesettings.php:161
+msgid "Upload"
+msgstr ""
+
+#: ../actions/avatar.php:27
+msgid ""
+"Upload a new \"avatar\" (user image) here. You can't edit the picture after "
+"you upload it, so make sure it's more or less square. It must be under the "
+"site license, also. Use a picture that belongs to you and that you want to "
+"share."
+msgstr ""
+
+#: ../actions/profilesettings.php:48 ../actions/register.php:182
+#: ../actions/register.php:159 ../actions/register.php:162
+#: actions/register.php:173 actions/register.php:176
+msgid "Used only for updates, announcements, and password recovery"
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:86
+#: actions/finishremotesubscribe.php:88
+msgid "User being listened to doesn't exist."
+msgstr ""
+
+#: ../actions/all.php:41 ../actions/avatarbynickname.php:48
+#: ../actions/foaf.php:43 ../actions/replies.php:41
+#: ../actions/showstream.php:44 ../actions/foaf.php:47
+#: ../actions/twitapiaccount.php:82 ../actions/twitapistatuses.php:319
+#: ../actions/twitapistatuses.php:685 ../actions/twitapiusers.php:82
+#: actions/all.php:41 actions/avatarbynickname.php:48 actions/foaf.php:47
+#: actions/replies.php:41 actions/showfavorites.php:41
+#: actions/showstream.php:44 actions/twitapiaccount.php:80
+#: actions/twitapifavorites.php:68 actions/twitapistatuses.php:235
+#: actions/twitapistatuses.php:609 actions/twitapiusers.php:87
+#: lib/mailbox.php:50
+msgid "User has no profile."
+msgstr ""
+
+#: ../actions/remotesubscribe.php:71 actions/remotesubscribe.php:80
+msgid "User nickname"
+msgstr ""
+
+#: ../lib/util.php:969 ../lib/util.php:1159 lib/util.php:1293
+#, php-format
+msgid "What's up, %s?"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 ../actions/profilesettings.php:54
+#: ../actions/register.php:175 actions/profilesettings.php:87
+#: actions/register.php:189
+msgid "Where you are, like \"City, State (or Region), Country\""
+msgstr ""
+
+#: ../actions/updateprofile.php:127 ../actions/updateprofile.php:128
+#: actions/updateprofile.php:129
+#, php-format
+msgid "Wrong image type for '%s'"
+msgstr ""
+
+#: ../actions/updateprofile.php:122 ../actions/updateprofile.php:123
+#: actions/updateprofile.php:124
+#, php-format
+msgid "Wrong size image at '%s'"
+msgstr ""
+
+#: ../actions/finishaddopenid.php:64 actions/finishaddopenid.php:64
+msgid "You already have this OpenID!"
+msgstr ""
+
+#: ../actions/recoverpassword.php:31 actions/recoverpassword.php:31
+msgid "You are already logged in!"
+msgstr ""
+
+#: ../actions/password.php:27
+msgid "You can change your password here. Choose a good one!"
+msgstr ""
+
+#: ../actions/register.php:164 ../actions/register.php:135
+#: actions/register.php:145
+msgid "You can create a new account to start posting notices."
+msgstr ""
+
+#: ../actions/openidsettings.php:86
+msgid ""
+"You can remove an OpenID from your account by clicking the button marked "
+"\"Remove\"."
+msgstr ""
+
+#: ../actions/imsettings.php:28 actions/imsettings.php:28
+#, php-format
+msgid ""
+"You can send and receive notices through Jabber/GTalk [instant "
+"messages](%%doc.im%%). Configure your address and settings below."
+msgstr ""
+
+#: ../actions/profilesettings.php:27
+msgid ""
+"You can update your personal profile info here so people know more about "
+"you."
+msgstr ""
+
+#: ../actions/finishremotesubscribe.php:31 ../actions/remotesubscribe.php:31
+#: actions/finishremotesubscribe.php:31 actions/remotesubscribe.php:31
+msgid "You can use the local subscription!"
+msgstr ""
+
+#: ../actions/finishopenidlogin.php:33 ../actions/register.php:51
+#: ../actions/register.php:61 actions/finishopenidlogin.php:38
+#: actions/register.php:68
+msgid "You can't register if you don't agree to the license."
+msgstr ""
+
+#: ../actions/updateprofile.php:62 ../actions/updateprofile.php:63
+#: actions/updateprofile.php:64
+msgid "You did not send us that profile"
+msgstr ""
+
+#: ../actions/recoverpassword.php:134 ../actions/recoverpassword.php:149
+msgid "You've been identified. Enter a new password below. "
+msgstr ""
+
+#: ../actions/openidlogin.php:67 actions/openidlogin.php:76
+msgid "Your OpenID URL"
+msgstr ""
+
+#: ../actions/recoverpassword.php:149 ../actions/recoverpassword.php:164
+msgid "Your nickname on this server, or your registered email address."
+msgstr ""
+
+#: ../actions/openidsettings.php:28
+#, php-format
+msgid ""
+"[OpenID](%%doc.openid%%) lets you log into many sites with the same user "
+"account. Manage your associated OpenIDs from here."
+msgstr ""
+
+#: ../lib/util.php:814 ../lib/util.php:943 lib/util.php:992
+msgid "a few seconds ago"
+msgstr ""
+
+#: ../lib/util.php:826 ../lib/util.php:955 lib/util.php:1004
+#, php-format
+msgid "about %d days ago"
+msgstr ""
+
+#: ../lib/util.php:822 ../lib/util.php:951 lib/util.php:1000
+#, php-format
+msgid "about %d hours ago"
+msgstr ""
+
+#: ../lib/util.php:818 ../lib/util.php:947 lib/util.php:996
+#, php-format
+msgid "about %d minutes ago"
+msgstr ""
+
+#: ../lib/util.php:830 ../lib/util.php:959 lib/util.php:1008
+#, php-format
+msgid "about %d months ago"
+msgstr ""
+
+#: ../lib/util.php:824 ../lib/util.php:953 lib/util.php:1002
+msgid "about a day ago"
+msgstr ""
+
+#: ../lib/util.php:816 ../lib/util.php:945 lib/util.php:994
+msgid "about a minute ago"
+msgstr ""
+
+#: ../lib/util.php:828 ../lib/util.php:957 lib/util.php:1006
+msgid "about a month ago"
+msgstr ""
+
+#: ../lib/util.php:832 ../lib/util.php:961 lib/util.php:1010
+msgid "about a year ago"
+msgstr ""
+
+#: ../lib/util.php:820 ../lib/util.php:949 lib/util.php:998
+msgid "about an hour ago"
+msgstr ""
+
+#: ../actions/noticesearch.php:126 ../actions/showstream.php:383
+#: ../lib/stream.php:101 ../actions/noticesearch.php:130
+#: ../actions/showstream.php:408 ../lib/stream.php:117
+#: actions/noticesearch.php:136 actions/showstream.php:426 lib/stream.php:84
+msgid "in reply to..."
+msgstr ""
+
+#: ../actions/noticesearch.php:133 ../actions/showstream.php:390
+#: ../lib/stream.php:108 ../actions/noticesearch.php:137
+#: ../actions/showstream.php:415 ../lib/stream.php:124
+#: actions/noticesearch.php:143 actions/showstream.php:433 lib/stream.php:91
+msgid "reply"
+msgstr ""
+
+#: ../actions/password.php:44 actions/profilesettings.php:183
+msgid "same as password above"
+msgstr ""
+
+#: ../lib/util.php:1127 ../lib/util.php:1309 lib/util.php:1443
+msgid "« After"
+msgstr ""
+
+#: ../actions/showstream.php:400 ../lib/stream.php:109
+#: actions/showstream.php:418 lib/mailbox.php:164 lib/stream.php:76
+msgid " from "
+msgstr ""
+
+#: ../actions/twitapistatuses.php:478 actions/twitapistatuses.php:412
+#, php-format
+msgid "%1$s / Updates replying to %2$s"
+msgstr ""
+
+#: ../actions/invite.php:168 actions/invite.php:176
+#, php-format
+msgid "%1$s has invited you to join them on %2$s"
+msgstr ""
+
+#: ../actions/invite.php:170
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+"%2$s is a micro-blogging service that lets you keep up-to-date with people "
+"you know and people who interest you.\n"
+"\n"
+"You can also share news about yourself, your thoughts, or your life online "
+"with people who know about you. It's also great for meeting new people who "
+"share your interests.\n"
+"\n"
+"%1$s said:\n"
+"\n"
+"%4$s\n"
+"\n"
+"You can see %1$s's profile page on %2$s here:\n"
+"\n"
+"%5$s\n"
+"\n"
+"If you'd like to try the service, click on the link below to accept the "
+"invitation.\n"
+"\n"
+"%6$s\n"
+"\n"
+"If not, you can ignore this message. Thanks for your patience and your "
+"time.\n"
+"\n"
+"Sincerely, %2$s\n"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:482 actions/twitapistatuses.php:415
+#, php-format
+msgid "%1$s updates that reply to updates from %2$s / %3$s."
+msgstr ""
+
+#: ../actions/invite.php:84 ../actions/invite.php:92 actions/invite.php:91
+#: actions/invite.php:99
+#, php-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:49 actions/twitapistatuses.php:49
+#, php-format
+msgid "%s public timeline"
+msgstr ""
+
+#: ../lib/mail.php:206 lib/mail.php:212
+#, php-format
+msgid "%s status"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:338 actions/twitapistatuses.php:265
+#, php-format
+msgid "%s timeline"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:52 actions/twitapistatuses.php:52
+#, php-format
+msgid "%s updates from everyone!"
+msgstr ""
+
+#: ../actions/register.php:213
+msgid ""
+"(You should receive a message by email momentarily, with instructions on how "
+"to confirm your email address.)"
+msgstr ""
+
+#: ../actions/register.php:152 actions/register.php:166
+msgid "1-64 lowercase letters or numbers, no punctuation or spaces. Required."
+msgstr ""
+
+#: ../actions/register.php:154 actions/register.php:168
+msgid "6 or more characters. Required."
+msgstr ""
+
+#: ../actions/emailsettings.php:213 actions/emailsettings.php:231
+msgid ""
+"A confirmation code was sent to the email address you added. Check your "
+"inbox (and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/smssettings.php:216 actions/smssettings.php:224
+msgid ""
+"A confirmation code was sent to the phone number you added. Check your inbox "
+"(and spam box!) for the code and instructions on how to use it."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:49 ../actions/twitapihelp.php:45
+#: ../actions/twitapistatuses.php:88 ../actions/twitapistatuses.php:259
+#: ../actions/twitapistatuses.php:370 ../actions/twitapistatuses.php:532
+#: ../actions/twitapiusers.php:122 actions/twitapiaccount.php:49
+#: actions/twitapidirect_messages.php:104 actions/twitapifavorites.php:111
+#: actions/twitapifavorites.php:120 actions/twitapifriendships.php:156
+#: actions/twitapihelp.php:46 actions/twitapistatuses.php:93
+#: actions/twitapistatuses.php:176 actions/twitapistatuses.php:288
+#: actions/twitapistatuses.php:298 actions/twitapistatuses.php:454
+#: actions/twitapistatuses.php:463 actions/twitapistatuses.php:504
+#: actions/twitapiusers.php:55
+msgid "API method not found!"
+msgstr ""
+
+#: ../actions/twitapiaccount.php:57 ../actions/twitapiaccount.php:113
+#: ../actions/twitapiaccount.php:119 ../actions/twitapiblocks.php:28
+#: ../actions/twitapiblocks.php:34 ../actions/twitapidirect_messages.php:43
+#: ../actions/twitapidirect_messages.php:49
+#: ../actions/twitapidirect_messages.php:56
+#: ../actions/twitapidirect_messages.php:62 ../actions/twitapifavorites.php:41
+#: ../actions/twitapifavorites.php:47 ../actions/twitapifavorites.php:53
+#: ../actions/twitapihelp.php:52 ../actions/twitapinotifications.php:29
+#: ../actions/twitapinotifications.php:35 ../actions/twitapistatuses.php:768
+#: actions/twitapiaccount.php:56 actions/twitapiaccount.php:109
+#: actions/twitapiaccount.php:114 actions/twitapiblocks.php:28
+#: actions/twitapiblocks.php:33 actions/twitapidirect_messages.php:170
+#: actions/twitapifavorites.php:168 actions/twitapihelp.php:53
+#: actions/twitapinotifications.php:29 actions/twitapinotifications.php:34
+#: actions/twitapistatuses.php:690
+msgid "API method under construction."
+msgstr ""
+
+#: ../lib/settingsaction.php:97 lib/settingsaction.php:91
+msgid "Add or remove OpenIDs"
+msgstr ""
+
+#: ../actions/invite.php:131 actions/invite.php:139
+msgid "Addresses of friends to invite (one per line)"
+msgstr ""
+
+#: ../actions/deletenotice.php:54 actions/deletenotice.php:55
+msgid "Are you sure you want to delete this notice?"
+msgstr ""
+
+#: ../actions/profilesettings.php:65 actions/profilesettings.php:98
+msgid ""
+"Automatically subscribe to whoever subscribes to me (best for "
+"non-humans)"
+msgstr ""
+
+#: ../actions/emailsettings.php:54 actions/emailsettings.php:55
+msgid ""
+"Awaiting confirmation on this address. Check your inbox (and spam box!) for "
+"a message with further instructions."
+msgstr ""
+
+#: ../actions/smssettings.php:58 actions/smssettings.php:58
+msgid "Awaiting confirmation on this phone number."
+msgstr ""
+
+#: ../lib/deleteaction.php:41 lib/deleteaction.php:41
+msgid "Can't delete this notice."
+msgstr ""
+
+#: ../actions/emailsettings.php:181 actions/emailsettings.php:199
+msgid "Cannot normalize that email address"
+msgstr ""
+
+#: ../lib/settingsaction.php:88 lib/settingsaction.php:88
+msgid "Change email handling"
+msgstr ""
+
+#: ../lib/settingsaction.php:94
+msgid "Change your password"
+msgstr ""
+
+#: ../lib/settingsaction.php:85 lib/settingsaction.php:85
+msgid "Change your profile settings"
+msgstr ""
+
+#: ../actions/smssettings.php:63 actions/smssettings.php:63
+msgid "Confirmation code"
+msgstr ""
+
+#: ../actions/register.php:202
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to...\n"
+"\n"
+"* Go to [your profile](%s) and post your first message.\n"
+"* Add a [Jabber/GTalk address](%%%%action.imsettings%%%%) so you can send "
+"notices through instant messages.\n"
+"* [Search for people](%%%%action.peoplesearch%%%%) that you may know or that "
+"share your interests. \n"
+"* Update your [profile settings](%%%%action.profilesettings%%%%) to tell "
+"others more about you. \n"
+"* Read over the [online docs](%%%%doc.help%%%%) for features you may have "
+"missed. \n"
+"\n"
+"Thanks for signing up and we hope you enjoy using this service."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:60 ../actions/twitapifriendships.php:76
+#: actions/twitapifriendships.php:60 actions/twitapifriendships.php:76
+#, php-format
+msgid "Could not follow user: %s is already on your list."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:53 actions/twitapifriendships.php:53
+msgid "Could not follow user: User not found."
+msgstr ""
+
+#: ../lib/subs.php:54 lib/subs.php:61
+msgid "Could not subscribe other to you."
+msgstr ""
+
+#: ../lib/subs.php:46 lib/subs.php:46
+msgid "Could not subscribe."
+msgstr ""
+
+#: ../actions/recoverpassword.php:102 actions/recoverpassword.php:105
+msgid "Could not update user with confirmed email address."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:93 actions/twitapistatuses.php:98
+msgid "Couldn't find any statuses."
+msgstr ""
+
+#: ../actions/profilesettings.php:161 actions/profilesettings.php:276
+msgid "Couldn't update user for autosubscribe."
+msgstr ""
+
+#: ../actions/emailsettings.php:280 ../actions/emailsettings.php:294
+#: actions/emailsettings.php:298 actions/emailsettings.php:312
+msgid "Couldn't update user record."
+msgstr ""
+
+#: ../actions/smssettings.php:46 actions/smssettings.php:46
+msgid "Current confirmed SMS-enabled phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:44 actions/emailsettings.php:45
+msgid "Current confirmed email address."
+msgstr ""
+
+#: ../classes/Notice.php:72 classes/Notice.php:86
+#, php-format
+msgid "DB error inserting hashtag: %s"
+msgstr ""
+
+#: ../actions/deletenotice.php:41 actions/deletenotice.php:41
+msgid "Delete notice"
+msgstr ""
+
+#: ../actions/emailsettings.php:59 actions/emailsettings.php:60
+msgid "Email Address"
+msgstr ""
+
+#: ../actions/emailsettings.php:32 actions/emailsettings.php:32
+msgid "Email Settings"
+msgstr ""
+
+#: ../actions/emailsettings.php:61 actions/emailsettings.php:62
+msgid "Email address, like \"UserName@example.org\""
+msgstr ""
+
+#: ../actions/invite.php:129 actions/invite.php:137
+msgid "Email addresses"
+msgstr ""
+
+#: ../actions/smssettings.php:64 actions/smssettings.php:64
+msgid "Enter the code you received on your phone."
+msgstr ""
+
+#: ../actions/tag.php:55 actions/tag.php:55
+#, php-format
+msgid "Feed for tag %s"
+msgstr ""
+
+#: ../lib/searchaction.php:105 lib/searchaction.php:105
+msgid "Find content of notices"
+msgstr ""
+
+#: ../lib/searchaction.php:101 lib/searchaction.php:101
+msgid "Find people on this site"
+msgstr ""
+
+#: ../actions/emailsettings.php:91 actions/emailsettings.php:98
+msgid "I want to post notices by email."
+msgstr ""
+
+#: ../lib/settingsaction.php:102 lib/settingsaction.php:96
+msgid "IM"
+msgstr ""
+
+#: ../actions/recoverpassword.php:137
+msgid ""
+"If you've forgotten or lost your password, you can get a new one sent to the "
+"email address you have stored in your account."
+msgstr ""
+
+#: ../actions/emailsettings.php:67 ../actions/smssettings.php:76
+#: actions/emailsettings.php:68 actions/smssettings.php:76
+msgid "Incoming email"
+msgstr ""
+
+#: ../actions/emailsettings.php:283 actions/emailsettings.php:301
+msgid "Incoming email address removed."
+msgstr ""
+
+#: ../actions/invite.php:55 actions/invite.php:62
+#, php-format
+msgid "Invalid email address: %s"
+msgstr ""
+
+#: ../actions/invite.php:79 actions/invite.php:86
+msgid "Invitation(s) sent"
+msgstr ""
+
+#: ../actions/invite.php:97 actions/invite.php:104
+msgid "Invitation(s) sent to the following people:"
+msgstr ""
+
+#: ../lib/util.php:306 lib/util.php:322
+msgid "Invite"
+msgstr ""
+
+#: ../actions/invite.php:123 actions/invite.php:130
+msgid "Invite new users"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Language"
+msgstr ""
+
+#: ../actions/profilesettings.php:113 actions/profilesettings.php:228
+msgid "Language is too long (max 50 chars)."
+msgstr ""
+
+#: ../actions/register.php:166 actions/register.php:180
+msgid "Longer name, preferably your \"real\" name"
+msgstr ""
+
+#: ../actions/emailsettings.php:80 ../actions/smssettings.php:89
+#: actions/emailsettings.php:81 actions/smssettings.php:89
+msgid "Make a new email address for posting to; cancels the old one."
+msgstr ""
+
+#: ../actions/emailsettings.php:27 actions/emailsettings.php:27
+#, php-format
+msgid "Manage how you get email from %%site.name%%."
+msgstr ""
+
+#: ../actions/smssettings.php:304
+#, php-format
+msgid ""
+"Mobile carrier for your phone. If you know a carrier that accepts SMS over "
+"email but isn't listed here, send email to let us know at %s."
+msgstr ""
+
+#: ../actions/emailsettings.php:82 ../actions/smssettings.php:91
+#: actions/emailsettings.php:83 actions/smssettings.php:91
+msgid "New"
+msgstr ""
+
+#: ../lib/mail.php:144 lib/mail.php:144
+#, php-format
+msgid "New email address for posting to %s"
+msgstr ""
+
+#: ../actions/emailsettings.php:297 actions/emailsettings.php:315
+msgid "New incoming email address added."
+msgstr ""
+
+#: ../actions/deletenotice.php:59 actions/deletenotice.php:60
+msgid "No"
+msgstr ""
+
+#: ../actions/smssettings.php:181 actions/smssettings.php:189
+msgid "No carrier selected."
+msgstr ""
+
+#: ../actions/smssettings.php:316 actions/smssettings.php:324
+msgid "No code entered"
+msgstr ""
+
+#: ../actions/emailsettings.php:174 actions/emailsettings.php:192
+msgid "No email address."
+msgstr ""
+
+#: ../actions/emailsettings.php:271 actions/emailsettings.php:289
+msgid "No incoming email address."
+msgstr ""
+
+#: ../actions/smssettings.php:176 actions/smssettings.php:184
+msgid "No phone number."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:595 actions/twitapifavorites.php:136
+#: actions/twitapistatuses.php:520
+msgid "No status found with that ID."
+msgstr ""
+
+#: ../actions/twitapistatuses.php:555 actions/twitapistatuses.php:478
+msgid "No status with that ID found."
+msgstr ""
+
+#: ../actions/recoverpassword.php:211 actions/recoverpassword.php:217
+msgid "No user with that email address or username."
+msgstr ""
+
+#: ../scripts/maildaemon.php:50 scripts/maildaemon.php:50
+msgid "Not a registered user."
+msgstr ""
+
+#: ../lib/twitterapi.php:226 ../lib/twitterapi.php:247
+#: ../lib/twitterapi.php:332 lib/twitterapi.php:391 lib/twitterapi.php:418
+#: lib/twitterapi.php:502
+msgid "Not a supported data format."
+msgstr ""
+
+#: ../actions/emailsettings.php:185 actions/emailsettings.php:203
+msgid "Not a valid email address"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:422 actions/twitapistatuses.php:361
+msgid "Not found"
+msgstr ""
+
+#: ../actions/opensearch.php:35 actions/opensearch.php:35
+msgid "Notice Search"
+msgstr ""
+
+#: ../actions/tag.php:35 ../actions/tag.php:81 actions/tag.php:35
+#: actions/tag.php:81
+#, php-format
+msgid "Notices tagged with %s"
+msgstr ""
+
+#: ../actions/invite.php:135 actions/invite.php:143
+msgid "Optionally add a personal message to the invitation."
+msgstr ""
+
+#: ../lib/searchaction.php:100 lib/searchaction.php:100
+msgid "People"
+msgstr ""
+
+#: ../actions/opensearch.php:33 actions/opensearch.php:33
+msgid "People Search"
+msgstr ""
+
+#: ../actions/invite.php:133 actions/invite.php:141
+msgid "Personal message"
+msgstr ""
+
+#: ../actions/smssettings.php:69 actions/smssettings.php:69
+msgid "Phone number, no punctuation or spaces, with area code"
+msgstr ""
+
+#: ../actions/profilesettings.php:57 actions/profilesettings.php:90
+msgid "Preferred language"
+msgstr ""
+
+#: ../actions/imsettings.php:79 actions/imsettings.php:80
+msgid "Publish a MicroID for my Jabber/GTalk address."
+msgstr ""
+
+#: ../actions/emailsettings.php:94 actions/emailsettings.php:101
+msgid "Publish a MicroID for my email address."
+msgstr ""
+
+#: ../actions/tag.php:75 ../actions/tag.php:76 actions/tag.php:75
+#: actions/tag.php:76
+msgid "Recent Tags"
+msgstr ""
+
+#: ../actions/register.php:28 actions/register.php:28
+msgid "Registration not allowed."
+msgstr ""
+
+#: ../actions/register.php:200 actions/register.php:214
+msgid "Registration successful"
+msgstr ""
+
+#: ../lib/settingsaction.php:99 lib/settingsaction.php:93
+msgid "SMS"
+msgstr ""
+
+#: ../actions/smssettings.php:67 actions/smssettings.php:67
+msgid "SMS Phone number"
+msgstr ""
+
+#: ../actions/smssettings.php:33 actions/smssettings.php:33
+msgid "SMS Settings"
+msgstr ""
+
+#: ../lib/mail.php:219 lib/mail.php:225
+msgid "SMS confirmation"
+msgstr ""
+
+#: ../actions/register.php:156 actions/register.php:170
+msgid "Same as password above. Required."
+msgstr ""
+
+#: ../actions/smssettings.php:296 actions/smssettings.php:304
+msgid "Select a carrier"
+msgstr ""
+
+#: ../actions/emailsettings.php:73 ../actions/smssettings.php:82
+#: actions/emailsettings.php:74 actions/smssettings.php:82
+msgid "Send email to this address to post new notices."
+msgstr ""
+
+#: ../actions/emailsettings.php:88 actions/emailsettings.php:89
+msgid "Send me notices of new subscriptions through email."
+msgstr ""
+
+#: ../actions/smssettings.php:97 actions/smssettings.php:97
+msgid ""
+"Send me notices through SMS; I understand I may incur exorbitant charges "
+"from my carrier."
+msgstr ""
+
+#: ../actions/imsettings.php:76 actions/imsettings.php:77
+msgid "Send me replies through Jabber/GTalk from people I'm not subscribed to."
+msgstr ""
+
+#: ../actions/tag.php:60 actions/tag.php:60
+msgid "Showing most popular tags from the last week"
+msgstr ""
+
+#: ../scripts/maildaemon.php:58 scripts/maildaemon.php:58
+msgid "Sorry, no incoming email allowed."
+msgstr ""
+
+#: ../scripts/maildaemon.php:54 scripts/maildaemon.php:54
+msgid "Sorry, that is not your incoming email address."
+msgstr ""
+
+#: ../actions/tag.php:41 ../lib/util.php:301 actions/tag.php:41
+#: lib/util.php:317
+msgid "Tags"
+msgstr ""
+
+#: ../lib/searchaction.php:104 lib/searchaction.php:104
+msgid "Text"
+msgstr ""
+
+#: ../actions/emailsettings.php:191 actions/emailsettings.php:209
+msgid "That email address already belongs to another user."
+msgstr ""
+
+#: ../actions/emailsettings.php:188 actions/emailsettings.php:206
+msgid "That is already your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:188 actions/smssettings.php:196
+msgid "That is already your phone number."
+msgstr ""
+
+#: ../actions/emailsettings.php:249 actions/emailsettings.php:267
+msgid "That is not your email address."
+msgstr ""
+
+#: ../actions/smssettings.php:257 actions/smssettings.php:265
+msgid "That is not your phone number."
+msgstr ""
+
+#: ../actions/smssettings.php:233 actions/smssettings.php:241
+msgid "That is the wrong confirmation number."
+msgstr ""
+
+#: ../actions/smssettings.php:191 actions/smssettings.php:199
+msgid "That phone number already belongs to another user."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:74 actions/twitapiaccount.php:72
+msgid "That's too long. Max notice size is 255 chars."
+msgstr ""
+
+#: ../actions/invite.php:89 actions/invite.php:96
+msgid ""
+"These people are already users and you were automatically subscribed to "
+"them:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:108 ../actions/twitapistatuses.php:586
+#: actions/twitapifavorites.php:127 actions/twitapifriendships.php:108
+#: actions/twitapistatuses.php:511
+msgid "This method requires a POST or DELETE."
+msgstr ""
+
+#: ../actions/twitapiaccount.php:65 ../actions/twitapifriendships.php:44
+#: ../actions/twitapistatuses.php:381 actions/twitapiaccount.php:63
+#: actions/twitapidirect_messages.php:114 actions/twitapifriendships.php:44
+#: actions/twitapistatuses.php:303
+msgid "This method requires a POST."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "Timezone"
+msgstr ""
+
+#: ../actions/profilesettings.php:107 actions/profilesettings.php:222
+msgid "Timezone not selected."
+msgstr ""
+
+#: ../actions/twitapifriendships.php:163 actions/twitapifriendships.php:167
+msgid "Two user ids or screen_names must be supplied."
+msgstr ""
+
+#: ../index.php:57 index.php:57
+msgid "Unknown action"
+msgstr ""
+
+#: ../lib/settingsaction.php:100 lib/settingsaction.php:94
+msgid "Updates by SMS"
+msgstr ""
+
+#: ../lib/settingsaction.php:103 lib/settingsaction.php:97
+msgid "Updates by instant messenger (IM)"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:241 actions/twitapistatuses.php:158
+#, php-format
+msgid "Updates from %1$s and friends on %2$s!"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:341 actions/twitapistatuses.php:268
+#, php-format
+msgid "Updates from %1$s on %2$s!"
+msgstr ""
+
+#: ../lib/settingsaction.php:91
+msgid "Upload a new profile image"
+msgstr ""
+
+#: ../actions/invite.php:114 actions/invite.php:121
+msgid ""
+"Use this form to invite your friends and colleagues to use this "
+"service."
+msgstr ""
+
+#: ../actions/twitapiusers.php:75 actions/twitapiusers.php:80
+msgid "User not found."
+msgstr ""
+
+#: ../actions/profilesettings.php:63 actions/profilesettings.php:96
+msgid "What timezone are you normally in?"
+msgstr ""
+
+#: ../actions/deletenotice.php:63 ../actions/deletenotice.php:72
+#: actions/deletenotice.php:64 actions/deletenotice.php:79
+msgid "Yes"
+msgstr ""
+
+#: ../actions/deletenotice.php:37 actions/deletenotice.php:37
+msgid ""
+"You are about to permanently delete a notice. Once this is done, it cannot "
+"be undone."
+msgstr ""
+
+#: ../actions/invite.php:81 actions/invite.php:88
+msgid "You are already subscribed to these users:"
+msgstr ""
+
+#: ../actions/twitapifriendships.php:128 actions/twitapifriendships.php:128
+msgid "You are not friends with the specified user."
+msgstr ""
+
+#: ../actions/smssettings.php:28 actions/smssettings.php:28
+#, php-format
+msgid "You can receive SMS messages through email from %%site.name%%."
+msgstr ""
+
+#: ../lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+"Send email to %2$s to post new messages.\n"
+"\n"
+"More email instructions at %3$s.\n"
+"\n"
+"Faithfully yours,\n"
+"%4$s"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:612 actions/twitapistatuses.php:537
+msgid "You may not delete another user's status."
+msgstr ""
+
+#: ../actions/invite.php:31 actions/invite.php:31
+#, php-format
+msgid "You must be logged in to invite other users to use %s"
+msgstr ""
+
+#: ../actions/invite.php:103 actions/invite.php:110
+msgid ""
+"You will be notified when your invitees accept the invitation and register "
+"on the site. Thanks for growing the community!"
+msgstr ""
+
+#: ../actions/showstream.php:423 ../lib/stream.php:132
+#: actions/showstream.php:441 lib/stream.php:99
+msgid "delete"
+msgstr ""
+
+#: ../actions/twitapistatuses.php:755 actions/twitapistatuses.php:678
+msgid "unsupported file type"
+msgstr ""
+
+#: actions/deletenotice.php:74 actions/disfavor.php:43
+#: actions/emailsettings.php:127 actions/favor.php:45
+#: actions/finishopenidlogin.php:33 actions/imsettings.php:105
+#: actions/invite.php:46 actions/newmessage.php:45 actions/openidlogin.php:36
+#: actions/openidsettings.php:123 actions/profilesettings.php:47
+#: actions/recoverpassword.php:282 actions/register.php:42
+#: actions/remotesubscribe.php:40 actions/smssettings.php:124
+#: actions/subscribe.php:44 actions/twittersettings.php:97
+#: actions/unsubscribe.php:41 actions/userauthorization.php:35
+msgid "There was a problem with your session token. Try again, please."
+msgstr ""
+
+#: actions/disfavor.php:55
+msgid "This notice is not a favorite!"
+msgstr ""
+
+#: actions/disfavor.php:63
+msgid "Could not delete favorite."
+msgstr ""
+
+#: actions/disfavor.php:72
+msgid "Favor"
+msgstr ""
+
+#: actions/emailsettings.php:92
+msgid "Send me email when someone adds my notice as a favorite."
+msgstr ""
+
+#: actions/emailsettings.php:95
+msgid "Send me email when someone sends me a private message."
+msgstr ""
+
+#: actions/favor.php:53 actions/twitapifavorites.php:142
+msgid "This notice is already a favorite!"
+msgstr ""
+
+#: actions/favor.php:60 actions/twitapifavorites.php:151
+#: classes/Command.php:132
+msgid "Could not create favorite."
+msgstr ""
+
+#: actions/favor.php:70
+msgid "Disfavor"
+msgstr ""
+
+#: actions/favoritesrss.php:60 actions/showfavorites.php:47
+#, php-format
+msgid "%s favorite notices"
+msgstr ""
+
+#: actions/favoritesrss.php:64
+#, php-format
+msgid "Feed of favorite notices of %s"
+msgstr ""
+
+#: actions/inbox.php:28
+#, php-format
+msgid "Inbox for %s - page %d"
+msgstr ""
+
+#: actions/inbox.php:30
+#, php-format
+msgid "Inbox for %s"
+msgstr ""
+
+#: actions/inbox.php:53
+msgid "This is your inbox, which lists your incoming private messages."
+msgstr ""
+
+#: actions/invite.php:178
+#, php-format
+msgid ""
+"%1$s has invited you to join them on %2$s (%3$s).\n"
+"\n"
+msgstr ""
+
+#: actions/login.php:104
+msgid "Automatically login in the future; "
+msgstr ""
+
+#: actions/login.php:122
+msgid "For security reasons, please re-enter your "
+msgstr ""
+
+#: actions/login.php:126
+msgid "Login with your username and password. "
+msgstr ""
+
+#: actions/newmessage.php:58 actions/twitapidirect_messages.php:130
+msgid "That's too long. Max message size is 140 chars."
+msgstr ""
+
+#: actions/newmessage.php:65
+msgid "No recipient specified."
+msgstr ""
+
+#: actions/newmessage.php:68 actions/newmessage.php:113
+#: classes/Command.php:206
+msgid "You can't send a message to this user."
+msgstr ""
+
+#: actions/newmessage.php:71 actions/twitapidirect_messages.php:146
+#: classes/Command.php:209
+msgid ""
+"Don't send a message to yourself; just say it to yourself quietly "
+"instead."
+msgstr ""
+
+#: actions/newmessage.php:108
+msgid "No such user"
+msgstr ""
+
+#: actions/newmessage.php:117
+msgid "New message"
+msgstr ""
+
+#: actions/noticesearch.php:95
+msgid "Notice without matching profile"
+msgstr ""
+
+#: actions/openidsettings.php:28
+#, php-format
+msgid "[OpenID](%%doc.openid%%) lets you log into many sites "
+msgstr ""
+
+#: actions/openidsettings.php:46
+msgid "If you want to add an OpenID to your account, "
+msgstr ""
+
+#: actions/openidsettings.php:74
+msgid "Removing your only OpenID would make it impossible to log in! "
+msgstr ""
+
+#: actions/openidsettings.php:87
+msgid "You can remove an OpenID from your account "
+msgstr ""
+
+#: actions/outbox.php:28
+#, php-format
+msgid "Outbox for %s - page %d"
+msgstr ""
+
+#: actions/outbox.php:30
+#, php-format
+msgid "Outbox for %s"
+msgstr ""
+
+#: actions/outbox.php:53
+msgid "This is your outbox, which lists private messages you have sent."
+msgstr ""
+
+#: actions/peoplesearch.php:28
+#, php-format
+msgid ""
+"Search for people on %%site.name%% by their name, location, or interests. "
+msgstr ""
+
+#: actions/profilesettings.php:27
+msgid "You can update your personal profile info here "
+msgstr ""
+
+#: actions/profilesettings.php:115 actions/remotesubscribe.php:320
+#: actions/userauthorization.php:159 actions/userrss.php:76
+msgid "User without matching profile"
+msgstr ""
+
+#: actions/recoverpassword.php:91
+msgid "This confirmation code is too old. "
+msgstr ""
+
+#: actions/recoverpassword.php:141
+msgid "If you've forgotten or lost your"
+msgstr ""
+
+#: actions/recoverpassword.php:154
+msgid "You've been identified. Enter a "
+msgstr ""
+
+#: actions/recoverpassword.php:169
+msgid "Your nickname on this server, "
+msgstr ""
+
+#: actions/recoverpassword.php:271
+msgid "Instructions for recovering your password "
+msgstr ""
+
+#: actions/recoverpassword.php:327
+msgid "New password successfully saved. "
+msgstr ""
+
+#: actions/register.php:95
+msgid "Password must be 6 or more characters."
+msgstr ""
+
+#: actions/register.php:216
+#, php-format
+msgid ""
+"Congratulations, %s! And welcome to %%%%site.name%%%%. From here, you may "
+"want to..."
+msgstr ""
+
+#: actions/register.php:227
+msgid "(You should receive a message by email momentarily, with "
+msgstr ""
+
+#: actions/remotesubscribe.php:51
+#, php-format
+msgid "To subscribe, you can [login](%%action.login%%),"
+msgstr ""
+
+#: actions/showfavorites.php:61
+#, php-format
+msgid "Feed for favorites of %s"
+msgstr ""
+
+#: actions/showfavorites.php:84 actions/twitapifavorites.php:85
+msgid "Could not retrieve favorite notices."
+msgstr ""
+
+#: actions/showmessage.php:33
+msgid "No such message."
+msgstr ""
+
+#: actions/showmessage.php:42
+msgid "Only the sender and recipient may read this message."
+msgstr ""
+
+#: actions/showmessage.php:61
+#, php-format
+msgid "Message to %1$s on %2$s"
+msgstr ""
+
+#: actions/showmessage.php:66
+#, php-format
+msgid "Message from %1$s on %2$s"
+msgstr ""
+
+#: actions/showstream.php:154
+msgid "Send a message"
+msgstr ""
+
+#: actions/smssettings.php:312
+#, php-format
+msgid "Mobile carrier for your phone. "
+msgstr ""
+
+#: actions/twitapidirect_messages.php:76
+#, php-format
+msgid "Direct messages to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:77
+#, php-format
+msgid "All the direct messages sent to %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:81
+msgid "Direct Messages You've Sent"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:82
+#, php-format
+msgid "All the direct messages sent from %s"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:128
+msgid "No message text!"
+msgstr ""
+
+#: actions/twitapidirect_messages.php:138
+msgid "Recipient user not found."
+msgstr ""
+
+#: actions/twitapidirect_messages.php:141
+msgid "Can't send direct messages to users who aren't your friend."
+msgstr ""
+
+#: actions/twitapifavorites.php:92
+#, php-format
+msgid "%s / Favorites from %s"
+msgstr ""
+
+#: actions/twitapifavorites.php:95
+#, php-format
+msgid "%s updates favorited by %s / %s."
+msgstr ""
+
+#: actions/twitapifavorites.php:187 lib/mail.php:275
+#, php-format
+msgid "%s added your notice as a favorite"
+msgstr ""
+
+#: actions/twitapifavorites.php:188 lib/mail.php:276
+#, php-format
+msgid ""
+"%1$s just added your notice from %2$s as one of their favorites.\n"
+"\n"
+msgstr ""
+
+#: actions/twittersettings.php:27
+msgid ""
+"Add your Twitter account to automatically send your notices to Twitter, "
+msgstr ""
+
+#: actions/twittersettings.php:41
+msgid "Twitter settings"
+msgstr ""
+
+#: actions/twittersettings.php:48
+msgid "Twitter Account"
+msgstr ""
+
+#: actions/twittersettings.php:56
+msgid "Current verified Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:63
+msgid "Twitter Username"
+msgstr ""
+
+#: actions/twittersettings.php:65
+msgid "No spaces, please."
+msgstr ""
+
+#: actions/twittersettings.php:67
+msgid "Twitter Password"
+msgstr ""
+
+#: actions/twittersettings.php:72
+msgid "Automatically send my notices to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:75
+msgid "Send local \"@\" replies to Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:78
+msgid "Subscribe to my Twitter friends here."
+msgstr ""
+
+#: actions/twittersettings.php:122
+msgid ""
+"Username must have only numbers, upper- and lowercase letters, and "
+"underscore (_). 15 chars max."
+msgstr ""
+
+#: actions/twittersettings.php:128
+msgid "Could not verify your Twitter credentials!"
+msgstr ""
+
+#: actions/twittersettings.php:137
+#, php-format
+msgid "Unable to retrieve account information for \"%s\" from Twitter."
+msgstr ""
+
+#: actions/twittersettings.php:151 actions/twittersettings.php:170
+msgid "Unable to save your Twitter settings!"
+msgstr ""
+
+#: actions/twittersettings.php:174
+msgid "Twitter settings saved."
+msgstr ""
+
+#: actions/twittersettings.php:192
+msgid "That is not your Twitter account."
+msgstr ""
+
+#: actions/twittersettings.php:200 actions/twittersettings.php:208
+msgid "Couldn't remove Twitter user."
+msgstr ""
+
+#: actions/twittersettings.php:212
+msgid "Twitter account removed."
+msgstr ""
+
+#: actions/twittersettings.php:225 actions/twittersettings.php:239
+msgid "Couldn't save Twitter preferences."
+msgstr ""
+
+#: actions/twittersettings.php:245
+msgid "Twitter preferences saved."
+msgstr ""
+
+#: actions/userauthorization.php:84
+msgid "Please check these details to make sure "
+msgstr ""
+
+#: actions/userauthorization.php:324
+msgid "The subscription has been authorized, but no "
+msgstr ""
+
+#: actions/userauthorization.php:334
+msgid "The subscription has been rejected, but no "
+msgstr ""
+
+#: classes/Channel.php:113
+msgid "Command results"
+msgstr ""
+
+#: classes/Channel.php:148
+msgid "Command complete"
+msgstr ""
+
+#: classes/Channel.php:158
+msgid "Command failed"
+msgstr ""
+
+#: classes/Command.php:39
+msgid "Sorry, this command is not yet implemented."
+msgstr ""
+
+#: classes/Command.php:96
+#, php-format
+msgid "Subscriptions: %1$s\n"
+msgstr ""
+
+#: classes/Command.php:125 classes/Command.php:242
+msgid "User has no last notice"
+msgstr ""
+
+#: classes/Command.php:146
+msgid "Notice marked as fave."
+msgstr ""
+
+#: classes/Command.php:166
+#, php-format
+msgid "%1$s (%2$s)"
+msgstr ""
+
+#: classes/Command.php:169
+#, php-format
+msgid "Fullname: %s"
+msgstr ""
+
+#: classes/Command.php:172
+#, php-format
+msgid "Location: %s"
+msgstr ""
+
+#: classes/Command.php:175
+#, php-format
+msgid "Homepage: %s"
+msgstr ""
+
+#: classes/Command.php:178
+#, php-format
+msgid "About: %s"
+msgstr ""
+
+#: classes/Command.php:200
+#, php-format
+msgid "Message too long - maximum is 140 characters, you sent %d"
+msgstr ""
+
+#: classes/Command.php:214
+#, php-format
+msgid "Direct message to %s sent"
+msgstr ""
+
+#: classes/Command.php:216
+msgid "Error sending direct message."
+msgstr ""
+
+#: classes/Command.php:263
+msgid "Specify the name of the user to subscribe to"
+msgstr ""
+
+#: classes/Command.php:270
+#, php-format
+msgid "Subscribed to %s"
+msgstr ""
+
+#: classes/Command.php:288
+msgid "Specify the name of the user to unsubscribe from"
+msgstr ""
+
+#: classes/Command.php:295
+#, php-format
+msgid "Unsubscribed from %s"
+msgstr ""
+
+#: classes/Command.php:310 classes/Command.php:330
+msgid "Command not yet implemented."
+msgstr ""
+
+#: classes/Command.php:313
+msgid "Notification off."
+msgstr ""
+
+#: classes/Command.php:315
+msgid "Can't turn off notification."
+msgstr ""
+
+#: classes/Command.php:333
+msgid "Notification on."
+msgstr ""
+
+#: classes/Command.php:335
+msgid "Can't turn on notification."
+msgstr ""
+
+#: classes/Command.php:344
+msgid "Commands:\n"
+msgstr ""
+
+#: classes/Message.php:53
+msgid "Could not insert message."
+msgstr ""
+
+#: classes/Message.php:63
+msgid "Could not update message with new URI."
+msgstr ""
+
+#: lib/gallery.php:46
+msgid "User without matching profile in system."
+msgstr ""
+
+#: lib/mail.php:147
+#, php-format
+msgid ""
+"You have a new posting address on %1$s.\n"
+"\n"
+msgstr ""
+
+#: lib/mail.php:249
+#, php-format
+msgid "New private message from %s"
+msgstr ""
+
+#: lib/mail.php:253
+#, php-format
+msgid ""
+"%1$s (%2$s) sent you a private message:\n"
+"\n"
+msgstr ""
+
+#: lib/mailbox.php:43
+msgid "Only the user can read their own mailboxes."
+msgstr ""
+
+#: lib/openid.php:195
+msgid "This form should automatically submit itself. "
+msgstr ""
+
+#: lib/personal.php:65
+msgid "Favorites"
+msgstr ""
+
+#: lib/personal.php:66
+#, php-format
+msgid "%s's favorite notices"
+msgstr ""
+
+#: lib/personal.php:66
+msgid "User"
+msgstr ""
+
+#: lib/personal.php:75
+msgid "Inbox"
+msgstr ""
+
+#: lib/personal.php:76
+msgid "Your incoming messages"
+msgstr ""
+
+#: lib/personal.php:80
+msgid "Outbox"
+msgstr ""
+
+#: lib/personal.php:81
+msgid "Your sent messages"
+msgstr ""
+
+#: lib/settingsaction.php:99
+msgid "Twitter"
+msgstr ""
+
+#: lib/settingsaction.php:100
+msgid "Twitter integration options"
+msgstr ""
+
+#: lib/util.php:1718
+msgid "To"
+msgstr ""
+
+#: scripts/maildaemon.php:45
+msgid "Could not parse message."
+msgstr ""
diff --git a/scripts/cleardb.sh b/scripts/cleardb.sh
new file mode 100755
index 000000000..06690ed70
--- /dev/null
+++ b/scripts/cleardb.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+export user=$1
+export password=$2
+export DB=$3
+export SCR=$4
+
+mysqladmin -u $user --password=$password -f drop $DB
+mysqladmin -u $user --password=$password create $DB
+mysql -u $user --password=$password $DB < $SCR
+
diff --git a/scripts/enjitqueuehandler.php b/scripts/enjitqueuehandler.php
new file mode 100755
index 000000000..8538ae09a
--- /dev/null
+++ b/scripts/enjitqueuehandler.php
@@ -0,0 +1,128 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/mail.php');
+require_once(INSTALLDIR . '/lib/queuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class EnjitQueueHandler extends QueueHandler {
+
+ function transport() {
+ return 'enjit';
+ }
+
+ function start() {
+ $this->log(LOG_INFO, "Starting EnjitQueueHandler");
+ $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl'));
+ return true;
+ }
+
+ function handle_notice($notice) {
+
+ $profile = Profile::staticGet($notice->profile_id);
+
+ $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname);
+
+ if ( ! $notice->is_local ) {
+ $this->log(LOG_INFO, "Skipping remote notice");
+ return "skipped";
+ }
+
+
+ #
+ # Build an Atom message from the notice
+ #
+ $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
+ $msg = $profile->nickname . ': ' . $notice->content;
+
+ $atom = "<entry xmlns='http://www.w3.org/2005/Atom'>\n";
+ $atom .= "<apisource>".common_config('enjit','source')."</apisource>\n";
+ $atom .= "<source>\n";
+ $atom .= "<title>" . $profile->nickname . " - " . common_config('site', 'name') . "</title>\n";
+ $atom .= "<link href='" . $profile->profileurl . "'/>\n";
+ $atom .= "<link rel='self' type='application/rss+xml' href='" . common_local_url('userrss', array('nickname' => $profile->nickname)) . "'/>\n";
+ $atom .= "<author><name>" . $profile->nickname . "</name></author>\n";
+ $atom .= "<icon>" . common_profile_avatar_url($profile, AVATAR_PROFILE_SIZE) . "</icon>\n";
+ $atom .= "</source>\n";
+ $atom .= "<title>" . htmlspecialchars($msg) . "</title>\n";
+ $atom .= "<summary>" . htmlspecialchars($msg) . "</summary>\n";
+ $atom .= "<link rel='alternate' href='" . $noticeurl . "' />\n";
+ $atom .= "<id>". $notice->uri . "</id>\n";
+ $atom .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
+ $atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
+ $atom .= "</entry>\n";
+
+ $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
+ $data = "msg=$atom";
+
+ #
+ # POST the message to $config['enjit']['apiurl']
+ #
+ $ch = curl_init();
+
+ curl_setopt($ch, CURLOPT_URL, $url);
+
+ curl_setopt($ch, CURLOPT_HEADER, 1);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ curl_setopt($ch, CURLOPT_POST, 1) ;
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+
+ # SSL and Debugging options
+ #
+ # curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+ # curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
+ # curl_setopt($ch, CURLOPT_VERBOSE, 1);
+
+ $result = curl_exec($ch);
+
+ $code = curl_getinfo($ch, CURLINFO_HTTP_CODE );
+
+ $this->log(LOG_INFO, "Response Code: $code");
+
+ curl_close($ch);
+
+ return $code;
+ }
+
+
+}
+
+mb_internal_encoding('UTF-8');
+
+$id = ($argc > 1) ? $argv[1] : NULL;
+
+$handler = new EnjitQueueHandler($id);
+
+if ($handler->start()) {
+ $handler->handle_queue();
+}
+
+$handler->finish();
diff --git a/scripts/fixup_hashtags.php b/scripts/fixup_hashtags.php
new file mode 100755
index 000000000..88f385798
--- /dev/null
+++ b/scripts/fixup_hashtags.php
@@ -0,0 +1,46 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+common_log(LOG_INFO, 'Starting to do old notices.');
+
+$notice = new Notice();
+$cnt = $notice->find();
+
+while ($notice->fetch()) {
+ common_log(LOG_INFO, 'Getting tags for notice #' . $notice->id);
+ $notice->saveTags();
+ $original = clone($notice);
+ $notice->rendered = common_render_content($notice->content, $notice);
+ $result = $notice->update($original);
+ if (!$result) {
+ common_log_db_error($notice, 'UPDATE', __FILE__);
+ }
+}
diff --git a/scripts/fixup_inboxes.php b/scripts/fixup_inboxes.php
new file mode 100755
index 000000000..1715b0bc1
--- /dev/null
+++ b/scripts/fixup_inboxes.php
@@ -0,0 +1,80 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+$start_at = ($argc > 1) ? $argv[1] : NULL;
+
+common_log(LOG_INFO, 'Updating user inboxes.');
+
+$user = new User();
+
+if ($start_at) {
+ $user->whereAdd('id >= ' . $start_at);
+}
+
+$cnt = $user->find();
+$cache = common_memcache();
+
+while ($user->fetch()) {
+ common_log(LOG_INFO, 'Updating inbox for user ' . $user->id);
+ $user->query('BEGIN');
+ $inbox = new Notice_inbox();
+ $result = $inbox->query('INSERT LOW_PRIORITY INTO notice_inbox (user_id, notice_id, created) ' .
+ 'SELECT ' . $user->id . ', notice.id, notice.created ' .
+ 'FROM subscription JOIN notice ON subscription.subscribed = notice.profile_id ' .
+ 'WHERE subscription.subscriber = ' . $user->id . ' ' .
+ 'AND notice.created >= subscription.created ' .
+ 'AND NOT EXISTS (SELECT user_id, notice_id ' .
+ 'FROM notice_inbox ' .
+ 'WHERE user_id = ' . $user->id . ' ' .
+ 'AND notice_id = notice.id)');
+ if (is_null($result) || $result === false) {
+ common_log_db_error($inbox, 'INSERT', __FILE__);
+ continue;
+ }
+ $orig = clone($user);
+ $user->inboxed = 1;
+ $result = $user->update($orig);
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ continue;
+ }
+ $user->query('COMMIT');
+ $inbox->free();
+ unset($inbox);
+ if ($cache) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
+ }
+}
diff --git a/scripts/fixup_notices_rendered.php b/scripts/fixup_notices_rendered.php
new file mode 100755
index 000000000..c6c925729
--- /dev/null
+++ b/scripts/fixup_notices_rendered.php
@@ -0,0 +1,50 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+common_log(LOG_INFO, 'Starting to render old notices.');
+
+$start_at = ($argc > 1) ? $argv[1] : NULL;
+
+$notice = new Notice();
+if ($start_at) {
+ $notice->whereAdd('id >= ' . $start_at);
+}
+$cnt = $notice->find();
+
+while ($notice->fetch()) {
+ common_log(LOG_INFO, 'Pre-rendering notice #' . $notice->id);
+ $original = clone($notice);
+ $notice->rendered = common_render_content($notice->content, $notice);
+ $result = $notice->update($original);
+ if (!$result) {
+ common_log_db_error($notice, 'UPDATE', __FILE__);
+ }
+}
diff --git a/scripts/fixup_replies.php b/scripts/fixup_replies.php
new file mode 100755
index 000000000..6010e21d1
--- /dev/null
+++ b/scripts/fixup_replies.php
@@ -0,0 +1,40 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+common_log(LOG_INFO, 'Starting to do old notices.');
+
+$notice = new Notice();
+$cnt = $notice->find();
+
+while ($notice->fetch()) {
+ common_log(LOG_INFO, 'Getting replies for notice #' . $notice->id);
+ common_save_replies($notice);
+}
diff --git a/scripts/getpiddir.php b/scripts/getpiddir.php
new file mode 100755
index 000000000..b4dda2254
--- /dev/null
+++ b/scripts/getpiddir.php
@@ -0,0 +1,32 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+echo common_config('daemon','piddir');
diff --git a/scripts/inbox_users.php b/scripts/inbox_users.php
new file mode 100755
index 000000000..0543abb2a
--- /dev/null
+++ b/scripts/inbox_users.php
@@ -0,0 +1,109 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+$id_file = ($argc > 1) ? $argv[1] : 'ids.txt';
+
+common_log(LOG_INFO, 'Updating user inboxes.');
+
+$ids = file($id_file);
+
+foreach ($ids as $id) {
+
+ $user = User::staticGet('id', $id);
+
+ if (!$user) {
+ common_log(LOG_WARNING, 'No such user: ' . $id);
+ continue;
+ }
+
+ if ($user->inboxed) {
+ common_log(LOG_WARNING, 'Already inboxed: ' . $id);
+ continue;
+ }
+
+ common_log(LOG_INFO, 'Updating inbox for user ' . $user->id);
+
+ $user->query('BEGIN');
+
+ $old_inbox = new Notice_inbox();
+ $old_inbox->user_id = $user->id;
+
+ $result = $old_inbox->delete();
+
+ if (is_null($result) || $result === false) {
+ common_log_db_error($old_inbox, 'DELETE', __FILE__);
+ continue;
+ }
+
+ $old_inbox->free();
+
+ $inbox = new Notice_inbox();
+
+ $result = $inbox->query('INSERT INTO notice_inbox (user_id, notice_id, created) ' .
+ 'SELECT ' . $user->id . ', notice.id, notice.created ' .
+ 'FROM subscription JOIN notice ON subscription.subscribed = notice.profile_id ' .
+ 'WHERE subscription.subscriber = ' . $user->id . ' ' .
+ 'AND notice.created >= subscription.created ' .
+ 'AND now() - notice.created < ' . (7 * 24 * 3600) . ' ' .
+ 'AND NOT EXISTS (SELECT user_id, notice_id ' .
+ 'FROM notice_inbox ' .
+ 'WHERE user_id = ' . $user->id . ' ' .
+ 'AND notice_id = notice.id)');
+
+ if (is_null($result) || $result === false) {
+ common_log_db_error($inbox, 'INSERT', __FILE__);
+ continue;
+ }
+
+ $orig = clone($user);
+ $user->inboxed = 1;
+ $result = $user->update($orig);
+
+ if (!$result) {
+ common_log_db_error($user, 'UPDATE', __FILE__);
+ continue;
+ }
+
+ $user->query('COMMIT');
+
+ $inbox->free();
+ unset($inbox);
+
+ if ($cache) {
+ $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
+ }
+}
diff --git a/scripts/jabberqueuehandler.php b/scripts/jabberqueuehandler.php
new file mode 100755
index 000000000..59cdb94ad
--- /dev/null
+++ b/scripts/jabberqueuehandler.php
@@ -0,0 +1,63 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/jabber.php');
+require_once(INSTALLDIR . '/lib/xmppqueuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class JabberQueueHandler extends XmppQueueHandler {
+
+ var $conn = NULL;
+
+ function transport() {
+ return 'jabber';
+ }
+
+ function handle_notice($notice) {
+ try {
+ return jabber_broadcast_notice($notice);
+ } catch (XMPPHP_Exception $e) {
+ $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
+ exit(1);
+ }
+ }
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+$resource = ($argc > 1) ? $argv[1] : (common_config('xmpp','resource') . '-queuehandler');
+
+$handler = new JabberQueueHandler($resource);
+
+$handler->runOnce(); \ No newline at end of file
diff --git a/scripts/maildaemon.php b/scripts/maildaemon.php
new file mode 100755
index 000000000..8b809f646
--- /dev/null
+++ b/scripts/maildaemon.php
@@ -0,0 +1,215 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/mail.php');
+require_once('Mail/mimeDecode.php');
+
+# FIXME: we use both Mail_mimeDecode and mailparse
+# Need to move everything to mailparse
+
+class MailerDaemon {
+
+ function __construct() {
+ }
+
+ function handle_message($fname='php://stdin') {
+ list($from, $to, $msg) = $this->parse_message($fname);
+ if (!$from || !$to || !$msg) {
+ $this->error(NULL, _('Could not parse message.'));
+ }
+ common_log(LOG_INFO, "Mail from $from to $to: " .substr($msg, 0, 20));
+ $user = $this->user_from($from);
+ if (!$user) {
+ $this->error($from, _('Not a registered user.'));
+ return false;
+ }
+ if (!$this->user_match_to($user, $to)) {
+ $this->error($from, _('Sorry, that is not your incoming email address.'));
+ return false;
+ }
+ if (!$user->emailpost) {
+ $this->error($from, _('Sorry, no incoming email allowed.'));
+ return false;
+ }
+ $response = $this->handle_command($user, $from, $msg);
+ if ($response) {
+ return true;
+ }
+ $msg = $this->cleanup_msg($msg);
+ $this->add_notice($user, $msg);
+ }
+
+ function error($from, $msg) {
+ file_put_contents("php://stderr", $msg . "\n");
+ exit(1);
+ }
+
+ function user_from($from_hdr) {
+ $froms = mailparse_rfc822_parse_addresses($from_hdr);
+ if (!$froms) {
+ return NULL;
+ }
+ $from = $froms[0];
+ $addr = common_canonical_email($from['address']);
+ $user = User::staticGet('email', $addr);
+ if (!$user) {
+ $user = User::staticGet('smsemail', $addr);
+ }
+ return $user;
+ }
+
+ function user_match_to($user, $to_hdr) {
+ $incoming = $user->incomingemail;
+ $tos = mailparse_rfc822_parse_addresses($to_hdr);
+ foreach ($tos as $to) {
+ if (strcasecmp($incoming, $to['address']) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function handle_command($user, $from, $msg) {
+ $inter = new CommandInterpreter();
+ $cmd = $inter->handle_command($user, $msg);
+ if ($cmd) {
+ $cmd->execute(new MailChannel($from));
+ return true;
+ }
+ return false;
+ }
+
+ function respond($from, $to, $response) {
+
+ $headers['From'] = $to;
+ $headers['To'] = $from;
+ $headers['Subject'] = "Command complete";
+
+ return mail_send(array($from), $headers, $response);
+ }
+
+ function log($level, $msg) {
+ common_log($level, 'MailDaemon: '.$msg);
+ }
+
+ function add_notice($user, $msg) {
+ // should test
+ // $msg_shortened = common_shorten_links($msg);
+ // if (mb_strlen($msg_shortened) > 140) ERROR and STOP
+ $notice = Notice::saveNew($user->id, $msg, 'mail');
+ if (is_string($notice)) {
+ $this->log(LOG_ERR, $notice);
+ return;
+ }
+ common_broadcast_notice($notice);
+ $this->log(LOG_INFO,
+ 'Added notice ' . $notice->id . ' from user ' . $user->nickname);
+ }
+
+ function parse_message($fname) {
+ $contents = file_get_contents($fname);
+ $parsed = Mail_mimeDecode::decode(array('input' => $contents,
+ 'include_bodies' => true,
+ 'decode_headers' => true,
+ 'decode_bodies' => true));
+ if (!$parsed) {
+ return NULL;
+ }
+
+ $from = $parsed->headers['from'];
+
+ $to = $parsed->headers['to'];
+
+ $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
+
+ if ($parsed->ctype_primary == 'multipart') {
+ foreach ($parsed->parts as $part) {
+ if ($part->ctype_primary == 'text' &&
+ $part->ctype_secondary == 'plain') {
+ $msg = $part->body;
+ break;
+ }
+ }
+ } else if ($type == 'text/plain') {
+ $msg = $parsed->body;
+ } else {
+ $this->unsupported_type($type);
+ }
+
+ return array($from, $to, $msg);
+ }
+
+ function unsupported_type($type) {
+ $this->error(NULL, "Unsupported message type: " . $type);
+ }
+
+ function cleanup_msg($msg) {
+ $lines = explode("\n", $msg);
+
+ $output = '';
+
+ foreach ($lines as $line) {
+ // skip quotes
+ if (preg_match('/^\s*>.*$/', $line)) {
+ continue;
+ }
+ // skip start of quote
+ if (preg_match('/^\s*On.*wrote:\s*$/', $line)) {
+ continue;
+ }
+ // probably interesting to someone, not us
+ if (preg_match('/^\s*Sent via/', $line)) {
+ continue;
+ }
+ // skip everything after a sig
+ if (preg_match('/^\s*--+\s*$/', $line) ||
+ preg_match('/^\s*__+\s*$/', $line))
+ {
+ break;
+ }
+ // skip everything after Outlook quote
+ if (preg_match('/^\s*-+\s*Original Message\s*-+\s*$/', $line)) {
+ break;
+ }
+ // skip everything after weird forward
+ if (preg_match('/^\s*Begin\s+forward/', $line)) {
+ break;
+ }
+
+ $output .= ' ' . $line;
+ }
+
+ preg_replace('/\s+/', ' ', $output);
+ return trim($output);
+ }
+}
+
+$md = new MailerDaemon();
+$md->handle_message('php://stdin');
diff --git a/scripts/ombqueuehandler.php b/scripts/ombqueuehandler.php
new file mode 100755
index 000000000..1df816d14
--- /dev/null
+++ b/scripts/ombqueuehandler.php
@@ -0,0 +1,74 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/omb.php');
+require_once(INSTALLDIR . '/lib/queuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class OmbQueueHandler extends QueueHandler {
+
+ function transport() {
+ return 'omb';
+ }
+
+ function start() {
+ $this->log(LOG_INFO, "INITIALIZE");
+ return true;
+ }
+
+ function handle_notice($notice) {
+ if ($this->is_remote($notice)) {
+ $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id);
+ return true;
+ } else {
+ return omb_broadcast_remote_subscribers($notice);
+ }
+ }
+
+ function finish() {
+ }
+
+ function is_remote($notice) {
+ $user = User::staticGet($notice->profile_id);
+ return is_null($user);
+ }
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+$id = ($argc > 1) ? $argv[1] : NULL;
+
+$handler = new OmbQueueHandler($id);
+
+$handler->runOnce();
diff --git a/scripts/publicqueuehandler.php b/scripts/publicqueuehandler.php
new file mode 100755
index 000000000..b1ae1d581
--- /dev/null
+++ b/scripts/publicqueuehandler.php
@@ -0,0 +1,61 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/jabber.php');
+require_once(INSTALLDIR . '/lib/xmppqueuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class PublicQueueHandler extends XmppQueueHandler {
+
+ function transport() {
+ return 'public';
+ }
+
+ function handle_notice($notice) {
+ try {
+ return jabber_public_notice($notice);
+ } catch (XMPPHP_Exception $e) {
+ $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
+ die($e->getMessage());
+ }
+ }
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+$resource = ($argc > 1) ? $argv[1] : (common_config('xmpp','resource') . '-public');
+
+$handler = new PublicQueueHandler($resource);
+
+$handler->runOnce();
diff --git a/scripts/rebuilddb.sh b/scripts/rebuilddb.sh
new file mode 100755
index 000000000..89bc6e252
--- /dev/null
+++ b/scripts/rebuilddb.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+export user=$1
+export password=$2
+export DB=$3
+export SCR=$4
+
+mysqldump -u $user --password=$password -c -t --hex-blob $DB > /tmp/$DB.sql
+mysqladmin -u $user --password=$password -f drop $DB
+mysqladmin -u $user --password=$password create $DB
+mysql -u $user --password=$password $DB < $SCR
+mysql -u $user --password=$password $DB < /tmp/$DB.sql
+
+
diff --git a/scripts/setpassword.php b/scripts/setpassword.php
new file mode 100755
index 000000000..d694eed09
--- /dev/null
+++ b/scripts/setpassword.php
@@ -0,0 +1,68 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit(1);
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+if ($argc != 3) {
+ print "USAGE: setpassword.php <username> <password>\n";
+ print "Sets the password of user with name <username> to <password>\n";
+ exit(1);
+}
+
+$nickname = $argv[1];
+$password = $argv[2];
+
+if (mb_strlen($password) < 6) {
+ print "Password must be 6 characters or more.\n";
+ exit(1);
+}
+
+$user = User::staticGet('nickname', $nickname);
+
+if (!$user) {
+ print "No such user '$nickname'.\n";
+ exit(1);
+}
+
+$original = clone($user);
+
+$user->password = common_munge_password($password, $user->id);
+
+if (!$user->update($original)) {
+ print "Error updating user '$nickname'.\n";
+ exit(1);
+} else {
+ print "Password for user '$nickname' updated.\n";
+ exit(0);
+}
diff --git a/scripts/sitemap.php b/scripts/sitemap.php
new file mode 100755
index 000000000..6b845beae
--- /dev/null
+++ b/scripts/sitemap.php
@@ -0,0 +1,376 @@
+<?php
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/util.php');
+
+$output_paths = parse_args();
+
+standard_map();
+notices_map();
+user_map();
+index_map();
+
+# ------------------------------------------------------------------------------
+# Main functions: get data out and turn them into sitemaps
+# ------------------------------------------------------------------------------
+
+# Generate index sitemap of all other sitemaps.
+function index_map() {
+ global $output_paths;
+ $output_dir = $output_paths['output_dir'];
+ $output_url = $output_paths['output_url'];
+
+ foreach (glob("$output_dir*.xml") as $file_name) {
+
+ # Just the file name please.
+ $file_name = preg_replace("|$output_dir|", '', $file_name);
+
+ $index_urls .= sitemap(
+ array(
+ 'url' => $output_url . $file_name,
+ 'changefreq' => 'daily'
+ )
+ );
+ }
+
+ write_file($output_paths['index_file'], sitemapindex($index_urls));
+}
+
+# Generate sitemap of standard site elements.
+function standard_map() {
+ global $output_paths;
+
+ $standard_map_urls .= url(
+ array(
+ 'url' => common_local_url('public'),
+ 'changefreq' => 'daily',
+ 'priority' => '1',
+ )
+ );
+
+ $standard_map_urls .= url(
+ array(
+ 'url' => common_local_url('publicrss'),
+ 'changefreq' => 'daily',
+ 'priority' => '0.3',
+ )
+ );
+
+ $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog', 'privacy', 'source');
+
+ foreach($docs as $title) {
+ $standard_map_urls .= url(
+ array(
+ 'url' => common_local_url('doc', array('title' => $title)),
+ 'changefreq' => 'monthly',
+ 'priority' => '0.2',
+ )
+ );
+ }
+
+ $urlset_path = $output_paths['output_dir'] . 'standard.xml';
+
+ write_file($urlset_path, urlset($standard_map_urls));
+}
+
+# Generate sitemaps of all notices.
+function notices_map() {
+ global $output_paths;
+
+ $notices = DB_DataObject::factory('notice');
+
+ $notices->query('SELECT id, uri, url, modified FROM notice where is_local = 1');
+
+ $notice_count = 0;
+ $map_count = 1;
+
+ while ($notices->fetch()) {
+
+ # Maximum 50,000 URLs per sitemap file.
+ if ($notice_count == 50000) {
+ $notice_count = 0;
+ $map_count++;
+ }
+
+ # remote notices have an URL
+
+ if (!$notices->url && $notices->uri) {
+ $notice = array(
+ 'url' => ($notices->uri) ? $notices->uri : common_local_url('shownotice', array('notice' => $notices->id)),
+ 'lastmod' => common_date_w3dtf($notices->modified),
+ 'changefreq' => 'never',
+ 'priority' => '1',
+ );
+
+ $notice_list[$map_count] .= url($notice);
+ $notice_count++;
+ }
+ }
+
+ # Make full sitemaps from the lists and save them.
+ array_to_map($notice_list, 'notice');
+}
+
+# Generate sitemaps of all users.
+function user_map() {
+ global $output_paths;
+
+ $users = DB_DataObject::factory('user');
+
+ $users->query('SELECT id, nickname FROM user');
+
+ $user_count = 0;
+ $map_count = 1;
+
+ while ($users->fetch()) {
+
+ # Maximum 50,000 URLs per sitemap file.
+ if ($user_count == 50000) {
+ $user_count = 0;
+ $map_count++;
+ }
+
+ $user_args = array('nickname' => $users->nickname);
+
+ # Define parameters for generating <url></url> elements.
+ $user = array(
+ 'url' => common_local_url('showstream', $user_args),
+ 'changefreq' => 'daily',
+ 'priority' => '1',
+ );
+
+ $user_rss = array(
+ 'url' => common_local_url('userrss', $user_args),
+ 'changefreq' => 'daily',
+ 'priority' => '0.3',
+ );
+
+ $all = array(
+ 'url' => common_local_url('all', $user_args),
+ 'changefreq' => 'daily',
+ 'priority' => '1',
+ );
+
+ $all_rss = array(
+ 'url' => common_local_url('allrss', $user_args),
+ 'changefreq' => 'daily',
+ 'priority' => '0.3',
+ );
+
+ $replies = array(
+ 'url' => common_local_url('replies', $user_args),
+ 'changefreq' => 'daily',
+ 'priority' => '1',
+ );
+
+ $replies_rss = array(
+ 'url' => common_local_url('repliesrss', $user_args),
+ 'changefreq' => 'daily',
+ 'priority' => '0.3',
+ );
+
+ $foaf = array(
+ 'url' => common_local_url('foaf', $user_args),
+ 'changefreq' => 'weekly',
+ 'priority' => '0.5',
+ );
+
+ # Construct a <url></url> element for each user facet and add it
+ # to our existing list of those.
+ $user_list[$map_count] .= url($user);
+ $user_rss_list[$map_count] .= url($user_rss);
+ $all_list[$map_count] .= url($all);
+ $all_rss_list[$map_count] .= url($all_rss);
+ $replies_list[$map_count] .= url($replies);
+ $replies_rss_list[$map_count] .= url($replies_rss);
+ $foaf_list[$map_count] .= url($foaf);
+
+ $user_count++;
+ }
+
+ # Make full sitemaps from the lists and save them.
+ # Possible factoring: put all the lists into a master array, thus allowing
+ # calling with single argument (i.e., array_to_map('user')).
+ array_to_map($user_list, 'user');
+ array_to_map($user_rss_list, 'user_rss');
+ array_to_map($all_list, 'all');
+ array_to_map($all_rss_list, 'all_rss');
+ array_to_map($replies_list, 'replies');
+ array_to_map($replies_rss_list, 'replies_rss');
+ array_to_map($foaf_list, 'foaf');
+}
+
+# ------------------------------------------------------------------------------
+# XML generation functions
+# ------------------------------------------------------------------------------
+
+# Generate a <url></url> element.
+function url($url_args) {
+ $url = preg_replace('/&/', '&amp;', $url_args['url']); # escape ampersands for XML
+ $lastmod = $url_args['lastmod'];
+ $changefreq = $url_args['changefreq'];
+ $priority = $url_args['priority'];
+
+ if (is_null($url)) {
+ error("url() arguments require 'url' value.");
+ }
+
+ $url_out = "\t<url>\n";
+ $url_out .= "\t\t<loc>$url</loc>\n";
+
+ if ($changefreq) {
+ $url_out .= "\t\t<changefreq>$changefreq</changefreq>\n";
+ }
+
+ if ($lastmod) {
+ $url_out .= "\t\t<lastmod>$lastmod</lastmod>\n";
+ }
+
+ if ($priority) {
+ $url_out .= "\t\t<priority>$priority</priority>\n";
+ }
+
+ $url_out .= "\t</url>\n";
+
+ return $url_out;
+}
+
+function sitemap($sitemap_args) {
+ $url = preg_replace('/&/', '&amp;', $sitemap_args['url']); # escape ampersands for XML
+ $lastmod = $sitemap_args['lastmod'];
+
+ if (is_null($url)) {
+ error("url() arguments require 'url' value.");
+ }
+
+ $sitemap_out = "\t<sitemap>\n";
+ $sitemap_out .= "\t\t<loc>$url</loc>\n";
+
+ if ($lastmod) {
+ $sitemap_out .= "\t\t<lastmod>$lastmod</lastmod>\n";
+ }
+
+ $sitemap_out .= "\t</sitemap>\n";
+
+ return $sitemap_out;
+}
+
+# Generate a <urlset></urlset> element.
+function urlset($urlset_text) {
+ $urlset = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n" .
+ $urlset_text .
+ '</urlset>';
+
+ return $urlset;
+}
+
+# Generate a <urlset></urlset> element.
+function sitemapindex($sitemapindex_text) {
+ $sitemapindex = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
+ '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n" .
+ $sitemapindex_text .
+ '</sitemapindex>';
+
+ return $sitemapindex;
+}
+
+# Generate a sitemap from an array containing <url></url> elements and write it to a file.
+function array_to_map($url_list, $filename_prefix) {
+ global $output_paths;
+
+ if ($url_list) {
+ # $map_urls is a long string containing concatenated <url></url> elements.
+ while (list($map_idx, $map_urls) = each($url_list)) {
+ $urlset_path = $output_paths['output_dir'] . "$filename_prefix-$map_idx.xml";
+
+ write_file($urlset_path, urlset($map_urls));
+ }
+ }
+}
+
+# ------------------------------------------------------------------------------
+# Internal functions
+# ------------------------------------------------------------------------------
+
+# Parse command line arguments.
+function parse_args() {
+ $args = getopt('f:d:u:');
+
+ if (is_null($args[f]) && is_null($args[d]) && is_null($args[u])) {
+ error('Mandatory arguments: -f <index file path> -d <output directory path> -u <URL of sitemaps directory>');
+ }
+
+ if (is_null($args[f])) {
+ error('You must specify an index file name with the -f option.');
+ }
+
+ if (is_null($args[d])) {
+ error('You must specify a directory for the output file with the -d option.');
+ }
+
+ if (is_null($args[u])) {
+ error('You must specify a URL for the directory where the sitemaps will be kept with the -u option.');
+ }
+
+ $index_file = $args[f];
+ $output_dir = $args[d];
+ $output_url = $args[u];
+
+ if (file_exists($output_dir)) {
+ if (is_writable($output_dir) === FALSE) {
+ error("$output_dir is not writable.");
+ }
+ } else {
+ error("output directory $output_dir does not exist.");
+ }
+
+ $paths = array(
+ 'index_file' => $index_file,
+ 'output_dir' => trailing_slash($output_dir),
+ 'output_url' => trailing_slash($output_url),
+ );
+
+ return $paths;
+}
+
+# Ensure paths end with a "/".
+function trailing_slash($path) {
+ if (preg_match('/\/$/', $path) == 0) {
+ $path .= '/';
+ }
+
+ return $path;
+}
+
+# Write data to disk.
+function write_file($path, $data) {
+ if (is_null($path)) {
+ error('No path specified for writing to.');
+ } elseif (is_null($data)) {
+ error('No data specified for writing.');
+ }
+
+ if (($fh_out = fopen($path,'w')) === FALSE) {
+ error("couldn't open $path for writing.");
+ }
+
+ if (fwrite($fh_out, $data) === FALSE) {
+ error("couldn't write to $path.");
+ }
+}
+
+# Display an error message and exit.
+function error ($error_msg) {
+ if (is_null($error_msg)) {
+ $error_msg = 'error() was called without any explanation!';
+ }
+
+ echo "Error: $error_msg\n";
+ exit(1);
+}
+
+?> \ No newline at end of file
diff --git a/scripts/smsqueuehandler.php b/scripts/smsqueuehandler.php
new file mode 100755
index 000000000..8f0d02d9b
--- /dev/null
+++ b/scripts/smsqueuehandler.php
@@ -0,0 +1,64 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/mail.php');
+require_once(INSTALLDIR . '/lib/queuehandler.php');
+
+set_error_handler('common_error_handler');
+
+class SmsQueueHandler extends QueueHandler {
+
+ function transport() {
+ return 'sms';
+ }
+
+ function start() {
+ $this->log(LOG_INFO, "INITIALIZE");
+ return true;
+ }
+
+ function handle_notice($notice) {
+ return mail_broadcast_notice_sms($notice);
+ }
+
+ function finish() {
+ }
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+$id = ($argc > 1) ? $argv[1] : NULL;
+
+$handler = new SmsQueueHandler($id);
+
+$handler->runOnce();
diff --git a/scripts/sphinx-cron.sh b/scripts/sphinx-cron.sh
new file mode 100755
index 000000000..9759adbd0
--- /dev/null
+++ b/scripts/sphinx-cron.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Laconica - a distributed open-source microblogging tool
+
+# Copyright (C) 2008, Controlez-Vous, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This program tries to start the daemons for Laconica.
+# Note that the 'maildaemon' needs to run as a mail filter.
+
+/usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all --rotate
+
diff --git a/scripts/sphinx-indexer.sh b/scripts/sphinx-indexer.sh
new file mode 100755
index 000000000..3311b2ed1
--- /dev/null
+++ b/scripts/sphinx-indexer.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+# Laconica - a distributed open-source microblogging tool
+
+# Copyright (C) 2008, Controlez-Vous, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This program tries to start the daemons for Laconica.
+# Note that the 'maildaemon' needs to run as a mail filter.
+
+/usr/local/bin/indexer --config /usr/local/etc/sphinx.conf --all
+
diff --git a/scripts/sphinx.sh b/scripts/sphinx.sh
new file mode 100755
index 000000000..b8edeb302
--- /dev/null
+++ b/scripts/sphinx.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+if [[ $1 = "start" ]]
+then
+ echo "Stopping any running daemons..."
+ /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf --stop 2> /dev/null
+ echo "Starting sphinx search daemon..."
+ /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf 2> /dev/null
+fi
+
+if [[ $1 = "stop" ]]
+then
+ echo "Stopping sphinx search daemon..."
+ /usr/local/bin/searchd --config /usr/local/etc/sphinx.conf --stop 2> /dev/null
+fi
diff --git a/scripts/startdaemons.sh b/scripts/startdaemons.sh
new file mode 100755
index 000000000..685bd938f
--- /dev/null
+++ b/scripts/startdaemons.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# Laconica - a distributed open-source microblogging tool
+
+# Copyright (C) 2008, Controlez-Vous, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This program tries to start the daemons for Laconica.
+# Note that the 'maildaemon' needs to run as a mail filter.
+
+DIR=`dirname $0`
+
+for f in xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php \
+ xmppconfirmhandler.php smsqueuehandler.php ombqueuehandler.php; do
+
+ echo -n "Starting $f...";
+ php $DIR/$f
+ echo "DONE."
+done
diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh
new file mode 100755
index 000000000..08e1d4714
--- /dev/null
+++ b/scripts/stopdaemons.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Laconica - a distributed open-source microblogging tool
+
+# Copyright (C) 2008, Controlez-Vous, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This program tries to stop the daemons for Laconica that were
+# previously started by startdaemons.sh
+
+SDIR=`dirname $0`
+DIR=`php $SDIR/getpiddir.php`
+
+for f in jabberhandler ombhandler publichandler smshandler \
+ xmppconfirmhandler xmppdaemon; do
+
+ FILES="$DIR/$f.*.pid"
+ for ff in "$FILES" ; do
+
+ echo -n "Stopping $f..."
+ PID=`cat $ff`
+ kill -3 $PID
+ if kill -9 $PID ; then
+ echo "DONE."
+ else
+ echo "FAILED."
+ fi
+ rm -f $ff
+ done
+done
+
diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php
new file mode 100755
index 000000000..070eb9bbb
--- /dev/null
+++ b/scripts/synctwitterfriends.php
@@ -0,0 +1,58 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+
+$flink = new Foreign_link();
+$flink->service = 1; // Twitter
+$flink->find();
+
+while ($flink->fetch()) {
+
+ if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
+
+ $user = User::staticGet($flink->user_id);
+
+ print "Updating Twitter friends for user $user->nickname ($user->id)\n";
+
+ $fuser = $flink->getForeignUser();
+
+ $result = save_twitter_friends($user, $fuser->id, $fuser->nickname, $flink->credentials);
+
+ if ($result == false) {
+ print "Problems updating Twitter friends! Check the log.\n";
+ exit(1);
+ }
+ }
+
+}
+
+exit(0);
+
+
diff --git a/scripts/update_pot.sh b/scripts/update_pot.sh
new file mode 100755
index 000000000..f3526d514
--- /dev/null
+++ b/scripts/update_pot.sh
@@ -0,0 +1,3 @@
+cd `dirname $0`
+cd ..
+xgettext --from-code=UTF-8 --default-domain=laconica --output=locale/laconica.pot --language=PHP --join-existing actions/*.php classes/*.php lib/*.php scripts/*.php
diff --git a/scripts/update_translations.php b/scripts/update_translations.php
new file mode 100755
index 000000000..4d7adafea
--- /dev/null
+++ b/scripts/update_translations.php
@@ -0,0 +1,66 @@
+<?php
+
+set_time_limit(60);
+chdir(dirname(__FILE__) . '/..');
+
+/* Languages to pull */
+$languages = array(
+ 'da_DK' => 'http://laconi.ca/translate/download.php?file_id=93',
+ 'nl_NL' => 'http://laconi.ca/translate/download.php?file_id=97',
+ 'en_NZ' => 'http://laconi.ca/translate/download.php?file_id=87',
+ 'eo' => 'http://laconi.ca/translate/download.php?file_id=88',
+ 'fr_FR' => 'http://laconi.ca/translate/download.php?file_id=99',
+ 'de_DE' => 'http://laconi.ca/translate/download.php?file_id=100',
+ 'it_IT' => 'http://laconi.ca/translate/download.php?file_id=101',
+ 'ko' => 'http://laconi.ca/translate/download.php?file_id=102',
+ 'no_NB' => 'http://laconi.ca/translate/download.php?file_id=104',
+ 'pt' => 'http://laconi.ca/translate/download.php?file_id=106',
+ 'pt_BR' => 'http://laconi.ca/translate/download.php?file_id=107',
+ 'ru_RU' => 'http://laconi.ca/translate/download.php?file_id=109',
+ 'es' => 'http://laconi.ca/translate/download.php?file_id=110',
+ 'tr_TR' => 'http://laconi.ca/translate/download.php?file_id=114',
+ 'uk_UA' => 'http://laconi.ca/translate/download.php?file_id=115',
+ 'he_IL' => 'http://laconi.ca/translate/download.php?file_id=116',
+ 'mk_MK' => 'http://laconi.ca/translate/download.php?file_id=103',
+ 'ja_JP' => 'http://laconi.ca/translate/download.php?file_id=117',
+ 'cs_CZ' => 'http://laconi.ca/translate/download.php?file_id=96',
+ 'ca_ES' => 'http://laconi.ca/translate/download.php?file_id=95',
+ 'pl_PL' => 'http://laconi.ca/translate/download.php?file_id=105',
+ 'sv_SE' => 'http://laconi.ca/translate/download.php?file_id=128'
+);
+
+/* Update the languages */
+foreach ($languages as $code => $file) {
+
+ $lcdir='locale/'.$code;
+ $msgdir=$lcdir.'/LC_MESSAGES';
+ $pofile=$msgdir.'/laconica.po';
+ $mofile=$msgdir.'/laconica.mo';
+
+ /* Check for an existing */
+ if (!is_dir($msgdir)) {
+ mkdir($lcdir);
+ mkdir($msgdir);
+ $existingSHA1 = '';
+ } else {
+ $existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : '';
+ }
+
+ /* Get the remote one */
+ $newFile = file_get_contents($file);
+
+ // Update if the local .po file is different to the one downloaded, or
+ // if the .mo file is not present.
+ if(sha1($newFile)!=$existingSHA1 || !file_exists($mofile)) {
+ echo "Updating ".$code."\n";
+ file_put_contents($pofile, $newFile);
+ $prevdir = getcwd();
+ chdir($msgdir);
+ system('msgmerge -U laconica.po ../../laconica.pot');
+ system('msgfmt -f -o laconica.mo laconica.po');
+ chdir($prevdir);
+ } else {
+ echo "Unchanged - ".$code."\n";
+ }
+}
+echo "Finished\n";
diff --git a/scripts/xmppconfirmhandler.php b/scripts/xmppconfirmhandler.php
new file mode 100755
index 000000000..8961b0b6e
--- /dev/null
+++ b/scripts/xmppconfirmhandler.php
@@ -0,0 +1,148 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/jabber.php');
+require_once(INSTALLDIR . '/lib/xmppqueuehandler.php');
+
+set_error_handler('common_error_handler');
+
+define('CLAIM_TIMEOUT', 1200);
+
+class XmppConfirmHandler extends XmppQueueHandler {
+
+ var $_id = 'confirm';
+
+ function class_name() {
+ return 'XmppConfirmHandler';
+ }
+
+ function run() {
+ if (!$this->start()) {
+ return false;
+ }
+ $this->log(LOG_INFO, 'checking for queued confirmations');
+ do {
+ $confirm = $this->next_confirm();
+ if ($confirm) {
+ $this->log(LOG_INFO, 'Sending confirmation for ' . $confirm->address);
+ $user = User::staticGet($confirm->user_id);
+ if (!$user) {
+ $this->log(LOG_WARNING, 'Confirmation for unknown user ' . $confirm->user_id);
+ continue;
+ }
+ $success = jabber_confirm_address($confirm->code,
+ $user->nickname,
+ $confirm->address);
+ if (!$success) {
+ $this->log(LOG_ERR, 'Confirmation failed for ' . $confirm->address);
+ # Just let the claim age out; hopefully things work then
+ continue;
+ } else {
+ $this->log(LOG_INFO, 'Confirmation sent for ' . $confirm->address);
+ # Mark confirmation sent; need a dupe so we don't have the WHERE clause
+ $dupe = Confirm_address::staticGet('code', $confirm->code);
+ if (!$dupe) {
+ common_log(LOG_WARNING, 'Could not refetch confirm', __FILE__);
+ continue;
+ }
+ $orig = clone($dupe);
+ $dupe->sent = $dupe->claimed;
+ $result = $dupe->update($orig);
+ if (!$result) {
+ common_log_db_error($dupe, 'UPDATE', __FILE__);
+ # Just let the claim age out; hopefully things work then
+ continue;
+ }
+ $dupe->free();
+ unset($dupe);
+ }
+ $user->free();
+ unset($user);
+ $confirm->free();
+ unset($confirm);
+ $this->idle(0);
+ } else {
+# $this->clear_old_confirm_claims();
+ $this->idle(10);
+ }
+ } while (true);
+ if (!$this->finish()) {
+ return false;
+ }
+ return true;
+ }
+
+ function next_confirm() {
+ $confirm = new Confirm_address();
+ $confirm->whereAdd('claimed IS NULL');
+ $confirm->whereAdd('sent IS NULL');
+ # XXX: eventually we could do other confirmations in the queue, too
+ $confirm->address_type = 'jabber';
+ $confirm->orderBy('modified DESC');
+ $confirm->limit(1);
+ if ($confirm->find(TRUE)) {
+ $this->log(LOG_INFO, 'Claiming confirmation for ' . $confirm->address);
+ # working around some weird DB_DataObject behaviour
+ $confirm->whereAdd(''); # clears where stuff
+ $original = clone($confirm);
+ $confirm->claimed = common_sql_now();
+ $result = $confirm->update($original);
+ if ($result) {
+ $this->log(LOG_INFO, 'Succeeded in claim! '. $result);
+ return $confirm;
+ } else {
+ $this->log(LOG_INFO, 'Failed in claim!');
+ return false;
+ }
+ }
+ return NULL;
+ }
+
+ function clear_old_confirm_claims() {
+ $confirm = new Confirm();
+ $confirm->claimed = NULL;
+ $confirm->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
+ $confirm->update(DB_DATAOBJECT_WHEREADD_ONLY);
+ $confirm->free();
+ unset($confirm);
+ }
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+$resource = ($argc > 1) ? $argv[1] : (common_config('xmpp', 'resource').'-confirm');
+
+$handler = new XmppConfirmHandler($resource);
+
+$handler->runOnce();
+
diff --git a/scripts/xmppdaemon.php b/scripts/xmppdaemon.php
new file mode 100755
index 000000000..9a60970a6
--- /dev/null
+++ b/scripts/xmppdaemon.php
@@ -0,0 +1,312 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once(INSTALLDIR . '/lib/jabber.php');
+require_once(INSTALLDIR . '/lib/daemon.php');
+
+set_error_handler('common_error_handler');
+
+# This is kind of clunky; we create a class to call the global functions
+# in jabber.php, which create a new XMPP class. A more elegant (?) solution
+# might be to use make this a subclass of XMPP.
+
+class XMPPDaemon extends Daemon {
+
+ function XMPPDaemon($resource=NULL) {
+ static $attrs = array('server', 'port', 'user', 'password', 'host');
+
+ foreach ($attrs as $attr)
+ {
+ $this->$attr = common_config('xmpp', $attr);
+ }
+
+ if ($resource) {
+ $this->resource = $resource;
+ } else {
+ $this->resource = common_config('xmpp', 'resource') . 'daemon';
+ }
+
+ $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->user}@{$this->server}/{$this->resource}");
+ }
+
+ function connect() {
+
+ $connect_to = ($this->host) ? $this->host : $this->server;
+
+ $this->log(LOG_INFO, "Connecting to $connect_to on port $this->port");
+
+ $this->conn = jabber_connect($this->resource);
+
+ if (!$this->conn) {
+ return false;
+ }
+
+ $this->conn->setReconnectTimeout(600);
+
+ jabber_send_presence("Send me a message to post a notice", 'available',
+ NULL, 'available', 100);
+ return !$this->conn->isDisconnected();
+ }
+
+ function name() {
+ return strtolower('xmppdaemon.'.$this->resource);
+ }
+
+ function run() {
+ if ($this->connect()) {
+
+ $this->conn->addEventHandler('message', 'handle_message', $this);
+ $this->conn->addEventHandler('presence', 'handle_presence', $this);
+ $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
+
+ $this->conn->process();
+ }
+ }
+
+ function handle_reconnect(&$pl) {
+ $this->conn->processUntil('session_start');
+ $this->conn->presence('Send me a message to post a notice', 'available', NULL, 'available', 100);
+ }
+
+ function get_user($from) {
+ $user = User::staticGet('jabber', jabber_normalize_jid($from));
+ return $user;
+ }
+
+ function handle_message(&$pl) {
+ if ($pl['type'] != 'chat') {
+ return;
+ }
+ if (mb_strlen($pl['body']) == 0) {
+ return;
+ }
+
+ $from = jabber_normalize_jid($pl['from']);
+
+ # Forwarded from another daemon (probably a broadcaster) for
+ # us to handle
+
+ if ($this->is_self($from)) {
+ $from = $this->get_ofrom($pl);
+ if (is_null($from) || $this->is_self($from)) {
+ return;
+ }
+ }
+
+ $user = $this->get_user($from);
+
+ if (!$user) {
+ $this->from_site($from, 'Unknown user; go to ' .
+ common_local_url('imsettings') .
+ ' to add your address to your account');
+ $this->log(LOG_WARNING, 'Message from unknown user ' . $from);
+ return;
+ }
+ if ($this->handle_command($user, $pl['body'])) {
+ return;
+ } else if ($this->is_autoreply($pl['body'])) {
+ $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from);
+ return;
+ } else if ($this->is_otr($pl['body'])) {
+ $this->log(LOG_INFO, 'Ignoring OTR from ' . $from);
+ return;
+ } else if ($this->is_direct($pl['body'])) {
+ preg_match_all('/d[\ ]*([a-z0-9]{1,64})/', $pl['body'], $to);
+
+ $to = preg_replace('/^d([\ ])*/', '', $to[0][0]);
+ $body = preg_replace('/d[\ ]*('. $to .')[\ ]*/', '', $pl['body']);
+ $this->add_direct($user, $body, $to, $from);
+ } else {
+ $len = mb_strlen($pl['body']);
+ if($len > 140) {
+ $this->from_site($from, 'Message too long - maximum is 140 characters, you sent ' . $len);
+ return;
+ }
+ $this->add_notice($user, $pl);
+ }
+
+ $user->free();
+ unset($user);
+ }
+
+ function is_self($from) {
+ return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from));
+ }
+
+ function get_ofrom($pl) {
+ $xml = $pl['xml'];
+ $addresses = $xml->sub('addresses');
+ if (!$addresses) {
+ $this->log(LOG_WARNING, 'Forwarded message without addresses');
+ return NULL;
+ }
+ $address = $addresses->sub('address');
+ if (!$address) {
+ $this->log(LOG_WARNING, 'Forwarded message without address');
+ return NULL;
+ }
+ if (!array_key_exists('type', $address->attrs)) {
+ $this->log(LOG_WARNING, 'No type for forwarded message');
+ return NULL;
+ }
+ $type = $address->attrs['type'];
+ if ($type != 'ofrom') {
+ $this->log(LOG_WARNING, 'Type of forwarded message is not ofrom');
+ return NULL;
+ }
+ if (!array_key_exists('jid', $address->attrs)) {
+ $this->log(LOG_WARNING, 'No jid for forwarded message');
+ return NULL;
+ }
+ $jid = $address->attrs['jid'];
+ if (!$jid) {
+ $this->log(LOG_WARNING, 'Could not get jid from address');
+ return NULL;
+ }
+ $this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid);
+ return $jid;
+ }
+
+ function is_autoreply($txt) {
+ if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function is_otr($txt) {
+ if (preg_match('/^\?OTR/', $txt)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function is_direct($txt) {
+ if (strtolower(substr($txt, 0, 2))=='d ') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function from_site($address, $msg) {
+ $text = '['.common_config('site', 'name') . '] ' . $msg;
+ jabber_send_message($address, $text);
+ }
+
+ function handle_command($user, $body) {
+ $inter = new CommandInterpreter();
+ $cmd = $inter->handle_command($user, $body);
+ if ($cmd) {
+ $chan = new XMPPChannel($this->conn);
+ $cmd->execute($chan);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ function add_notice(&$user, &$pl) {
+ $body = trim($pl['body']);
+ $content_shortened = common_shorten_link($body);
+ if (mb_strlen($content_shortened) > 140) {
+ $content = trim(mb_substr($body, 0, 140));
+ $content_shortened = common_shorten_link($content);
+ }
+ else {
+ $content = $body;
+ }
+ $notice = Notice::saveNew($user->id, $content, 'xmpp');
+ if (is_string($notice)) {
+ $this->log(LOG_ERR, $notice);
+ return;
+ }
+ common_broadcast_notice($notice);
+ $this->log(LOG_INFO,
+ 'Added notice ' . $notice->id . ' from user ' . $user->nickname);
+ $notice->free();
+ unset($notice);
+ }
+
+ function handle_presence(&$pl) {
+ $from = jabber_normalize_jid($pl['from']);
+ switch ($pl['type']) {
+ case 'subscribe':
+ # We let anyone subscribe
+ $this->subscribed($from);
+ $this->log(LOG_INFO,
+ 'Accepted subscription from ' . $from);
+ break;
+ case 'subscribed':
+ case 'unsubscribed':
+ case 'unsubscribe':
+ $this->log(LOG_INFO,
+ 'Ignoring "' . $pl['type'] . '" from ' . $from);
+ break;
+ default:
+ if (!$pl['type']) {
+ $user = User::staticGet('jabber', $from);
+ if (!$user) {
+ $this->log(LOG_WARNING, 'Presence from unknown user ' . $from);
+ return;
+ }
+ if ($user->updatefrompresence) {
+ $this->log(LOG_INFO, 'Updating ' . $user->nickname .
+ ' status from presence.');
+ $this->add_notice($user, $pl);
+ }
+ $user->free();
+ unset($user);
+ }
+ break;
+ }
+ }
+
+ function log($level, $msg) {
+ common_log($level, 'XMPPDaemon('.$this->resource.'): '.$msg);
+ }
+
+ function subscribed($to) {
+ jabber_special_presence('subscribed', $to);
+ }
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+$resource = ($argc > 1) ? $argv[1] : (common_config('xmpp','resource') . '-listen');
+
+$daemon = new XMPPDaemon($resource);
+
+$daemon->runOnce();
diff --git a/sphinx.conf.sample b/sphinx.conf.sample
new file mode 100644
index 000000000..b79adf15c
--- /dev/null
+++ b/sphinx.conf.sample
@@ -0,0 +1,71 @@
+#
+# Minimal Sphinx configuration sample for laconica
+#
+
+source src1
+{
+ type = mysql
+ sql_host = localhost
+ sql_user = USERNAME
+ sql_pass = PASSWORD
+ sql_db = identi_ca
+ sql_port = 3306
+ sql_query = SELECT id, UNIX_TIMESTAMP(created) as created_ts, nickname, fullname, location, bio, homepage FROM profile
+ sql_query_info = SELECT * FROM profile where id = $id
+ sql_attr_timestamp = created_ts
+}
+
+
+source src2
+{
+ type = mysql
+ sql_host = localhost
+ sql_user = USERNAME
+ sql_pass = PASSWORD
+ sql_db = identi_ca
+ sql_port = 3306
+ sql_query = SELECT id, UNIX_TIMESTAMP(created) as created_ts, content FROM notice
+ sql_query_info = SELECT * FROM notice where id = $id
+ sql_attr_timestamp = created_ts
+}
+
+index identica_notices
+{
+ source = src2
+ path = DIRECTORY/data/identica_notices
+ docinfo = extern
+ charset_type = utf-8
+ min_word_len = 3
+ stopwords = DIRECTORY/data/stopwords-en.txt
+}
+
+
+index identica_people
+{
+ source = src1
+ path = DIRECTORY/data/identica_people
+ docinfo = extern
+ charset_type = utf-8
+ min_word_len = 3
+ stopwords = DIRECTORY/data/stopwords-en.txt
+}
+
+indexer
+{
+ mem_limit = 32M
+}
+
+searchd
+{
+ port = 3312
+ log = DIRECTORY/log/searchd.log
+ query_log = DIRECTORY/log/query.log
+ read_timeout = 5
+ max_children = 30
+ pid_file = DIRECTORY/log/searchd.pid
+ max_matches = 1000
+ seamless_rotate = 1
+ preopen_indexes = 0
+ unlink_old = 1
+}
+
diff --git a/theme/default/bg-body.gif b/theme/default/bg-body.gif
new file mode 100644
index 000000000..5e1afa048
--- /dev/null
+++ b/theme/default/bg-body.gif
Binary files differ
diff --git a/theme/default/bg-header.gif b/theme/default/bg-header.gif
new file mode 100644
index 000000000..a34b9b34f
--- /dev/null
+++ b/theme/default/bg-header.gif
Binary files differ
diff --git a/theme/default/default-avatar-mini.png b/theme/default/default-avatar-mini.png
new file mode 100644
index 000000000..38b8692b4
--- /dev/null
+++ b/theme/default/default-avatar-mini.png
Binary files differ
diff --git a/theme/default/default-avatar-profile.png b/theme/default/default-avatar-profile.png
new file mode 100644
index 000000000..f8357d4fc
--- /dev/null
+++ b/theme/default/default-avatar-profile.png
Binary files differ
diff --git a/theme/default/default-avatar-stream.png b/theme/default/default-avatar-stream.png
new file mode 100644
index 000000000..6b63baa70
--- /dev/null
+++ b/theme/default/default-avatar-stream.png
Binary files differ
diff --git a/theme/default/display.css b/theme/default/display.css
new file mode 100644
index 000000000..0b894550c
--- /dev/null
+++ b/theme/default/display.css
@@ -0,0 +1,1081 @@
+/* CSS Document */
+/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
+
+html {
+ background-color: #f6e5b0;
+ }
+body {
+ position: absolute;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ line-height: 12px;
+ min-height: 100%;
+ height: 100%;
+ color: #701238;
+ }
+a {
+ color: #d1451a;
+ text-decoration: none;
+ }
+a:hover {
+ text-decoration: underline;
+ }
+img, img a {
+ border: 0;
+ }
+h1 {
+ font-size: 14px;
+ }
+
+#wrap {
+ margin: 0 auto;
+ padding: 0 20px;
+ width: 760px;
+ background: url(bg-header.gif) repeat-x #fbf2d7;
+ }
+#header {
+ position: relative;
+ margin: 0 auto;
+ width: 600px;
+ height: 216px;
+ }
+#logo {
+ margin-top: 9px;
+ }
+p#branding {
+ margin: 0;
+ padding: 6px 0 3px 0;
+ color: #fbf2d7;
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 27px;
+ }
+p#branding a {
+ color: #dab134;
+ }
+
+#header h1.pagetitle {
+ margin: 0;
+ padding: 0;
+ font-size: 15px;
+ line-height: 24px;
+ color: #fff6d5;
+}
+
+#header h2.sitename {
+ display: none;
+ margin: 0;
+ padding: 0;
+ color: #fff6d5;
+}
+
+abbr.published { border-bottom:0; }
+
+/* ===== Begin Navigation Styling ===== */
+
+/* ----- Navigation ------ */
+#nav {
+ float: right;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ font-size: 12px;
+ }
+#nav li {
+ display: block;
+ float: left;
+ }
+#nav li a {
+ display: block;
+ padding: 9px 9px 12px 9px;
+ color: #F60;
+ }
+#nav li a:hover {
+ text-decoration: underline;
+ }
+
+/* ----- Tabs ----- */
+#nav_views {
+ margin: 0 auto;
+ padding: 0;
+ position: absolute;
+ bottom: 0;
+ list-style-type: none;
+ font-size: 14px;
+ font-weight: bold;
+ width: 600px;
+ /*height: 30px;*/
+ }
+#nav_views li {
+ display: block;
+ float: left;
+ line-height: 21px;
+ }
+#nav_views li a {
+ display: block;
+ margin: 0;
+ padding: 4px 12px 3px 12px;
+ color: #fff6d5;
+ background-color: #d1451a;
+ border-right: 1px solid #dcaa3f;
+ }
+#nav_views li a:hover {
+ text-decoration: none;
+ }
+#nav_views li.current a, #nav_views li.current a:hover {
+ color: #701238;
+ background-color: #fff6d5;
+ border-right: 1px solid #dcaa3f;
+ }
+#nav_views li.current a:hover {
+ color: #d1451a;
+ }
+#nav_views li a:hover {
+ color: #fff6d5;
+ background-color: #701238;
+ border-right: 1px solid #dcaa3f;
+ }
+
+.feeds {
+clear:both;
+float:right;
+margin-top:1.25em;
+position:absolute;
+right:0;
+bottom:-30px;
+}
+.feeds * {
+line-height:1.4;
+padding:0;
+margin:0;
+font-size:12px;
+}
+
+.feeds p {
+font-weight:bold;
+display:inline;
+display:none;
+}
+.feeds ul {
+display:inline;
+}
+.feeds li {
+list-style-type:none;
+display:inline;
+margin-left:0.5em;
+}
+.feeds li a.rss,
+.feeds li a.atom {
+background:url(icon_feed.jpg) no-repeat;
+padding-top:2px;
+padding-left:20px;
+}
+.feeds li a.foaf {
+background:url(icon_foaf.gif) no-repeat;
+padding-top:2px;
+padding-left:30px;
+}
+
+form#disfavor, form.disfavor,
+form#favor, form.favor {
+ float: right;
+}
+
+/*favorites*/
+input#favor, input.favor,
+input#disfavor, input.disfavor {
+ background-color:#fcfff5;
+ background-color:transparent;
+ background-image:url(icon_heart-02.gif);
+ background-repeat:no-repeat;
+ cursor: pointer;
+ border: 0;
+ width: 16px;
+ height:16px;
+ text-indent:-9999px;
+}
+
+input#disfavor, input.disfavor {
+ background-image:url(icon_heart-01.gif);
+}
+
+.notice_single:hover input.favor,
+.notice_single:hover input.disfavor {
+ background-color:#f3f8ea;
+}
+
+/*profile_actions*/
+#profile_actions {
+padding-left:0;
+list-style-type:none;
+margin:0;
+}
+#profile_actions li {
+margin-bottom:0.5em;
+clear:both;
+}
+
+#profile_actions #profile_nudge input.submit,
+#profile_actions #profile_block input.submit,
+.profile_single form.block input.submit {
+margin:0 0 0 -3px;
+padding:0;
+background-color:transparent;
+color:#C15D42;
+font-family:Georgia,"Times New Roman",Times,serif;
+font-weight:normal;
+font-size:14px;
+text-align:left;
+float:left;
+line-height:18px;
+}
+#profile_actions #profile_nudge input.submit:hover,
+#profile_actions #profile_block input.submit:hover {
+background-color:transparent;
+color:#C15D42;
+}
+
+#wrap p#nudge_response {
+background-color:transparent;
+line-height:18px;
+font-size:14px;
+}
+#wrap #profile_nudge input.disabled {
+color:#999;
+cursor:default;
+}
+
+
+#wrap form input.disabled,
+#wrap form input.disabled:hover {
+background-color:#999;
+cursor:default;
+}
+
+/* ----- Nav Footer ----- */
+#nav_sub {
+ clear: both;
+ margin: 18px auto 0 auto;
+ padding: 0;
+ list-style-type: none;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 21px;
+ border-top: 1px solid #dec5b5;
+ width: 600px;
+ }
+#nav_sub li {
+ display: block;
+ float: left;
+ }
+#nav_sub li a {
+ padding: 6px 24px 6px 0;
+ }
+#nav_sub li a:hover {
+ text-decoration: underline;
+ }
+/* ===== End Navigation Styling ===== */
+
+#content {
+ clear: left;
+ margin: 40px 0 45px 0;
+ padding: 0 80px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 14px;
+ line-height: 18px;
+ }
+#content h2 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 15px;
+ }
+#content label {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ }
+.instructions p, .success, .error {
+ font-weight: normal;
+ margin: 36px 0 0 0;
+ padding: 10px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 15px;
+ border: 1px solid #dec5b5;
+ color: #fff6d5;
+ }
+.instructions a, .success a, .error a {
+ color: #d8e2d7;
+ text-decoration: underline;
+ }
+.instructions a:hover, .success a:hover, .error a:hover {
+ color: #fff6d5;
+ }
+.success {
+ background-color: #48705b;
+ }
+.error {
+ background-color: #ce3728;
+ }
+
+/* ----- Stream -----*/
+
+#notices {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 600px;
+ border-top: 1px solid #dec5b5;
+ }
+#notices a:hover {
+ text-decoration: underline;
+ }
+.notice_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #dec5b5;
+ background-color:#FCFFF5;
+ opacity:1;
+ }
+.notice_single:hover {
+ background-color: #f7ebcc;
+ }
+.notice_single p {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ }
+#notice_delete_form #confirmation_text {
+ display: block;
+ font-size: 14px;
+ font-weight: bold;
+ }
+
+input#submit_yes, input#submit_no {
+ margin: 18px 10px 0px 0px;
+ padding: 4px;
+ font-weight: bold;
+ color: #fff6d5;
+ background-color: #F60;
+ cursor: pointer;
+ border: 0;
+ width: 40px;
+ }
+input#submit_yes:hover, input#submit_no:hover {
+ background-color: #701238;
+ }
+.avatar.stream {
+ float: left;
+ margin: 0 10px 0.5em 0;
+ }
+p.time {
+ display: block;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ line-height: 15px;
+ }
+p.time a {
+ color: #dab134;
+ }
+
+/* ----- Profile -----*/
+#profile {
+ clear: left;
+ margin: 0 -80px;
+ padding: 10px 0 0 0;
+ min-height: 170px;
+ border-top: 1px solid #dec5b5;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ margin-bottom:1em;
+ float:left;
+ width:750px;
+ }
+#profile h1 {
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ }
+#profile h2 {
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ text-transform: uppercase;
+ color: #dab134;
+ }
+#profile p {
+ margin: 0 10px 0 0;
+ font-size: 12px;
+ line-height: 14px;
+ }
+#profile p.location {
+ margin: 0 10px 12px 0;
+ font-style: italic;
+ }
+#profile p.notice_current {
+ font-size: 18px;
+ line-height: 21px;
+ }
+#profile_avatar {
+ float: left;
+ margin-right: 4px;
+ }
+#profile_avatar img {
+ margin-bottom: 5px;
+ }
+.avatar.profile {
+ clear: left;
+ margin: 0 10px 5px 0;
+ }
+.avatar.original {
+ float: left;
+ margin: 0 10px 18px 0;
+ }
+a.nickname {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 12px;
+ padding-right: 3px;
+ }
+.hentry a.nickname {
+ font-weight:normal;
+}
+#profile_information {
+ float: left;
+ position: relative;
+ width: 270px;
+ }
+.statistics {
+ margin-top: 18px;
+ }
+.statistics h2 {
+ margin: 12px 0 3px 0;
+ }
+dl.statistics {
+ margin: 0;
+ font-size: 12px;
+ line-height: 14px;
+ }
+.statistics dt {
+ float: left;
+ width: 96px;
+}
+.statistics dd {
+ margin-left: 100px;
+}
+.statistics dt:after {
+ content: ":";
+ }
+#subscriptions {
+ float: left;
+ margin: 18px 0 30px 0;
+ }
+#subscriptions_avatars {
+ float: left;
+ margin: 6px 0 0 0;
+ padding: 0;
+ list-style-type: none;
+ width: 270px;
+ }
+#subscriptions_avatars li .avatar.mini {
+ float: left;
+ margin: 0 3px 3px 0;
+ padding: 0;
+ line-height: 0;
+ /* border: 1px solid #f00; */
+ }
+#subscriptions_viewall {
+ clear: left;
+ }
+/* ----- End Profile -----*/
+
+/* ----- Begin Subscriptions & Subscribers -----*/
+
+ul.subscriptions, ul.subscribers {
+ float: none;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ overflow: auto;
+ clear:both;
+ }
+ul.subscriptions li, ul.subscribers li {
+ display: block;
+ float: left;
+ padding: 0;
+ }
+/* ----- End Subscriptions & Subscribers -----*/
+
+#pagination {
+ margin: 18px auto;
+ width: 600px;
+ }
+#nav_pagination {
+ margin: 0 0 36px 0;
+ padding: 0;
+ float: right;
+ list-style-type: none;
+ font-size: 12px;
+ font-weight: bold;
+ }
+#nav_pagination li {
+ display: block;
+ float: left;
+ background-color: #701238;
+ }
+#nav_pagination li.before {
+ margin-right: 1px;
+ }
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 21px;
+ background-color: #701238;
+ color: #fff6d5;
+ }
+#nav_pagination li a:hover {
+ background-color: #3F606F;
+ color: #fff6d5;
+ text-decoration: none;
+ }
+
+#footer {
+ clear: both;
+ margin: 0 auto;
+ padding: 0 0 36px 0;
+ width: 600px;
+ border-top: 1px solid #dec5b5;
+ }
+#footer p {
+ margin-top: 9px;
+ line-height: 12px;
+ }
+#cc {
+ float: left;
+ margin: 3px 10px 0 0;
+ }
+
+/* ===== Begin Forms Styling ===== */
+
+/* ----- Forms General Style ----- */
+form {
+ margin: 0 auto;
+ padding: 0;
+ }
+form {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ }
+form label {
+ display: block;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 18px;
+ }
+form input {
+ border: 1px solid #dec5b5;
+ width: 264px;
+ }
+input#submit, input.submit {
+ display: block;
+ margin: 18px 0;
+ padding: 4px;
+ font-weight: bold;
+ color: #fff6d5;
+ background-color: #F60;
+ cursor: pointer;
+ border: 0;
+ width: auto;
+ }
+input#submit:hover, input.submit:hover {
+ background-color: #701238;
+ }
+input.checkbox {
+ /*width: 14px;
+ height: 14px;*/
+ width: auto;
+ border: 0;
+ }
+
+label.checkbox_label {
+ display: inline;
+ font-weight: normal;
+}
+
+textarea, input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #701238;
+ padding: 3px;
+ }
+textarea:focus, input:focus {
+ background-color: #f8ebc0;
+ }
+textarea {
+ width: 270px;
+ border: 1px solid #D8E2D7;
+ }
+.input_instructions {
+ margin-top: 3px;
+ display: block;
+ font-size: 11px;
+ line-height: 15px;
+ color: #924959;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ }
+
+/* ----- Status Form ----- */
+#status_form {
+ height: 96px;
+ /*background-color: #F00;*/
+ }
+#status_form p {
+ margin: 36px 0 0 0;
+ padding: 0;
+ }
+#status_label {
+ display: block;
+ clear: both;
+ margin: 0;
+ padding: 0 0 3px 0;
+ font-size: 18px;
+ font-weight: bold;
+ line-height: 24px;
+ color: #dab134;
+ }
+#status_textarea {
+ display: block;
+ float: left;
+ width: 463px;
+ height: 35px;
+ padding: 5px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #701238;
+ border: 0;
+ }
+#status_submit {
+ display: block;
+ float: left;
+ margin: 1px 0 0 4px;
+ width: 63px;
+ height: 45px;
+ background-color: #F60;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 14px;
+ color: #fff6d5;
+ cursor: pointer;
+ border: 0;
+ }
+#status_submit:hover {
+ background-color: #d1451a;
+ }
+#counter {
+ position: absolute;
+ top: 140px;
+ left: -64px;
+ width: 50px;
+ font-weight: bold;
+ text-align: right;
+}
+.response_error textarea,
+.response_error .on_max {
+background-color:#fee;
+}
+
+/* ----- Subscribe Form ----- */
+#content .subscribe .submit, #content .unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
+ clear: left;
+ margin: 0;
+ width: 96px;
+ height: 27px;
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 10px;
+ text-transform: uppercase;
+ background-color: #F60;
+ color: #fff6d5;
+ border: 0;
+ }
+#remotesubscribe {
+ width: 96px;
+ height: 22px;
+ padding: 5px 0 0 0;
+ text-align: center;
+ }
+#content .subscribe .button:hover, #content .unsubscribe .button:hover {
+ background-color: #904632;
+ cursor: pointer;
+ }
+
+a#remotesubscribe {
+ display: block;
+}
+
+/* ----- Login Form -----*/
+input#license {
+ width: auto;
+ border: 0;
+ }
+/* ----- Avatar Form -----*/
+form {
+ clear: left;
+}
+
+/* ----- OpenID Form -----*/
+
+input#openid_url {
+ background: url(login-bg.gif) no-repeat;
+ background-color: #fff;
+ background-position: 4px 50%;
+ color: #000;
+ padding-left: 24px;
+}
+
+/* People lists (search results, maybe subscribers) */
+
+#profiles {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 600px;
+ border-top: 1px solid #dec5b5;
+ /*border: 1px solid #F00;*/
+ }
+#profiles a:hover {
+ text-decoration: underline;
+ }
+
+.profile_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #dec5b5;
+ width:100%;
+ float:left;
+ }
+.profile_single:hover {
+ background-color: #f7ebcc;
+ }
+
+.profile_single form.block,
+.profile_single form.subscribe,
+.profile_single form.unsubscribe {
+ float: right;
+}
+
+form.subedit {
+ margin-left:4.5em;
+}
+form.subedit p {
+ display:inline;
+
+}
+form.subedit input.submit {
+ margin:0 0 0 0.5em;
+ display:inline;
+ background-color:transparent;
+ padding:0;
+ color:#C15D42;
+ border:1px solid #C15D42;
+}
+
+
+
+/* ----- IM Settings Form -----*/
+
+#imsettings p {
+ margin: 0;
+ padding: 0;
+ line-height: 15px;
+}
+
+/* ----- direct message ----- */
+
+#message_form {
+ height: 96px;
+ /*background-color: #F00;*/
+ }
+
+#message_form p {
+ margin: 21px 0 0 0;
+ padding: 0;
+ }
+#message_form label {
+ display: inline;
+ }
+
+#message_content {
+ display: block;
+ float: left;
+ width: 463px;
+ height: 35px;
+ padding: 5px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #193441;
+ border: 0;
+ }
+
+#message_send {
+ display: block;
+ float: left;
+ margin: 1px 0 0 4px;
+ width: 63px;
+ height: 45px;
+ background-color: #C15D42;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 14px;
+ color: #FCFFF5;
+ cursor: pointer;
+ border: 0;
+ }
+
+#message_send:hover {
+ background-color: #904632;
+ }
+
+/* ===== End Forms Styling ===== */
+
+/* ===== Tag Cloud Styling ===== */
+
+p.tagcloud {
+text-align: center;
+}
+
+p.tagcloud a {
+line-height:100%;
+vertical-align:middle;
+}
+
+p.tagcloud a.largest {
+font-size: 400%;
+}
+p.tagcloud a.verylarge {
+font-size: 300%;
+}
+
+p.tagcloud a.large {
+font-size: 200%;
+}
+
+p.tagcloud a.medium {
+font-size: 150%;
+}
+
+p.tagcloud a.small {
+font-size: 100%;
+}
+
+p.tagcloud a.verysmall {
+font-size: 80%;
+}
+
+p.tagcloud a.smallest {
+font-size: 60%;
+}
+
+#subscriptions_nav,
+#filter_tags {
+margin:0 0 2em 0;
+}
+
+#subscriptions_nav {
+padding-bottom:0.5em;
+/*border-bottom:1px solid #D8E2D7;*/
+float:right;
+}
+#filter_tags {
+float:left;
+}
+
+#subscriptions_nav dt,
+#filter_tags dt {
+display:none;
+}
+#subscriptions_nav dd,
+#filter_tags dd {
+margin-left:0;
+}
+#subscriptions_nav ul,
+#filter_tags ul {
+padding-left:0;
+list-style-type:none;
+margin-left:0;
+}
+#subscriptions_nav ul li {
+display:inline;
+padding-left:0.5em;
+margin-left:0.5em;
+border-left:1px solid #D8E2D7;;
+}
+#subscriptions_nav ul li.child_1 {
+border-left:0;
+padding-left:0;
+margin-left:0;
+}
+
+#filter_tags ul li {
+float:left;
+margin-left:0.5em;
+padding-left:0.5em;
+border-left:1px solid #D8E2D7;;
+}
+#filter_tags ul li.child_1 {
+margin-left:0;
+border-left:0;
+padding-left:0;
+}
+#filter_tags ul li li {
+margin-left:0;
+}
+#filter_tags ul li#filter_tags_item {
+width:30em;
+}
+#filter_tags ul li#filter_tags_item form {
+clear:none;
+}
+#filter_tags ul li#filter_tags_item label {
+margin-right:0.5em;
+font-size:14px;
+font-weight:normal;
+font-family:Georgia,"Times New Roman",Times,serif;
+}
+#filter_tags ul li#filter_tags_item label,
+#filter_tags ul li#filter_tags_item select {
+margin-top:-1px;
+margin-bottom:0.5em;
+display:inline;
+}
+#filter_tags ul li#filter_tags_item p {
+margin:0 1em 0 0;
+padding:0;
+float:left;
+}
+#filter_tags ul li .input_instructions {
+display:inline;
+display:block;
+margin:0;
+}
+#filter_tags ul li#filter_tags_item .submit {
+margin:0;
+}
+
+.tags_self,
+.tags_user {
+margin-left:4.5em;
+}
+.tags_self dl,
+.tags_user dl {
+margin-left:0;
+}
+
+.tags_self dt,
+.tags_user dt {
+display:inline;
+margin-right:0.5em;
+}
+
+.tags_self dd,
+.tags_user dd {
+margin-left:0;
+display:inline;
+}
+
+ul.tags {
+padding-left:0;
+margin-left:0;
+list-style-type:none;
+display:inline;
+}
+ul.tags li {
+display:inline;
+margin-right:0.75em;
+}
+ul.tags li a {
+padding-left:17px;
+background:url(icon_tag-01.gif) no-repeat;
+line-height:1.5;
+}
+
+.tags_user {
+margin-bottom:0.5em;
+}
+
+form#tag_user {
+margin-left:8.75em;
+clear:both;
+}
+form#tag_user p {
+margin:0;
+}
+
+form#tag_user label {
+display:inline;
+margin-right:1em;
+}
+form#tag_user .submit {
+margin-left:4em;
+}
+
+form#tag_user .input_instructions {
+margin-left:4.5em;
+}
+
+.profile_list p {
+margin:0 0 0.5em 0;
+}
+
+.profile_list .bio {
+margin-left:4.5em;
+}
+
+/* ----- Mailbox ----- */
+#messages {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 600px;
+ border-top: 1px solid #dec5b5;
+ }
+
+#messages a:hover {
+ text-decoration: underline;
+ }
+
+.message_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #dec5b5;
+ }
+.message_single:hover {
+ background-color: #f7ebcc;
+ }
+.message_single p {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ }
diff --git a/theme/default/icon_feed.jpg b/theme/default/icon_feed.jpg
new file mode 100644
index 000000000..53c4d4959
--- /dev/null
+++ b/theme/default/icon_feed.jpg
Binary files differ
diff --git a/theme/default/icon_foaf.gif b/theme/default/icon_foaf.gif
new file mode 100644
index 000000000..f8f784423
--- /dev/null
+++ b/theme/default/icon_foaf.gif
Binary files differ
diff --git a/theme/default/icon_heart-01.gif b/theme/default/icon_heart-01.gif
new file mode 100644
index 000000000..bdfd7483f
--- /dev/null
+++ b/theme/default/icon_heart-01.gif
Binary files differ
diff --git a/theme/default/icon_heart-02.gif b/theme/default/icon_heart-02.gif
new file mode 100644
index 000000000..cbb35e0dc
--- /dev/null
+++ b/theme/default/icon_heart-02.gif
Binary files differ
diff --git a/theme/default/icon_tag-01.gif b/theme/default/icon_tag-01.gif
new file mode 100644
index 000000000..92e5742ce
--- /dev/null
+++ b/theme/default/icon_tag-01.gif
Binary files differ
diff --git a/theme/default/ie6.css b/theme/default/ie6.css
new file mode 100644
index 000000000..f9fc8150e
--- /dev/null
+++ b/theme/default/ie6.css
@@ -0,0 +1,73 @@
+@charset "UTF-8";
+/* CSS Document */
+body {
+ text-align: center;
+}
+input {
+ height: 24px;
+}
+#wrap {
+ margin: 0 auto;
+ padding: 0 20px;
+ width: 800px;
+ text-align: left;
+ background: url(bg-header.gif) repeat-x #fbf2d7;
+ }
+#header {
+ position: relative;
+ margin-left: 108px;
+ }
+#nav_views {
+ margin: 0;
+ }
+#nav_views li {
+ line-height: 19px;
+ }
+.statistics dd {
+ margin-top: -15px;
+ clear: both;
+ }
+#notices {
+ margin: 0;
+ }
+.notice_single {
+ height: 48px;
+ }
+#profile {
+ margin-left:0px;
+}
+#profile p.notice_current {
+ height: 96px;
+ }
+#filter_tags {
+ margin-left:20px;
+}
+#content .subscriptions {
+ margin-left:30px;
+ width:100%;
+}
+
+#subscriptions_avatars li {
+ float: left;
+ margin: 0;
+ padding: 0;
+ }
+img.avatar.original, img.avatar.profile {
+ clear: none;
+ float: left;
+}
+#status_textarea {
+ height: 46px;
+ }
+
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 27px;
+ }
+#nav_sub {
+ position: relative;
+ margin-left: 108px;
+ }
+#footer {
+ margin-left: 108px;
+}
diff --git a/theme/default/ie7.css b/theme/default/ie7.css
new file mode 100644
index 000000000..23a3241b7
--- /dev/null
+++ b/theme/default/ie7.css
@@ -0,0 +1,39 @@
+@charset "UTF-8";
+/* CSS Document */
+input.disfavor,
+input.favor {
+ text-indent:0;
+ text-align:right;
+ padding-left:25px;
+}
+
+#profile_actions li {
+float:left;
+clear:both;
+}
+#profile_actions #profile_nudge input.submit {
+margin-left:0;
+width:100px;
+}
+
+#statistics dd {
+ clear: both;
+ }
+
+#subscriptions_avatars li {
+ float: left;
+ }
+img.avatar.original, img.avatar.profile {
+ clear: none;
+ float: left;
+}
+
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 27px;
+ }
+
+#filter_tags ul li#filter_tags_item label {
+position:relative;
+top:-8px;
+} \ No newline at end of file
diff --git a/theme/default/login-bg.gif b/theme/default/login-bg.gif
new file mode 100644
index 000000000..e2d8377db
--- /dev/null
+++ b/theme/default/login-bg.gif
Binary files differ
diff --git a/theme/identica/bg-body.gif b/theme/identica/bg-body.gif
new file mode 100644
index 000000000..d87e2e8d7
--- /dev/null
+++ b/theme/identica/bg-body.gif
Binary files differ
diff --git a/theme/identica/bg-header.gif b/theme/identica/bg-header.gif
new file mode 100644
index 000000000..5154b2e5e
--- /dev/null
+++ b/theme/identica/bg-header.gif
Binary files differ
diff --git a/theme/identica/default-avatar-mini.png b/theme/identica/default-avatar-mini.png
new file mode 100644
index 000000000..38b8692b4
--- /dev/null
+++ b/theme/identica/default-avatar-mini.png
Binary files differ
diff --git a/theme/identica/default-avatar-profile.png b/theme/identica/default-avatar-profile.png
new file mode 100644
index 000000000..f8357d4fc
--- /dev/null
+++ b/theme/identica/default-avatar-profile.png
Binary files differ
diff --git a/theme/identica/default-avatar-stream.png b/theme/identica/default-avatar-stream.png
new file mode 100644
index 000000000..6b63baa70
--- /dev/null
+++ b/theme/identica/default-avatar-stream.png
Binary files differ
diff --git a/theme/identica/display.css b/theme/identica/display.css
new file mode 100644
index 000000000..62d34f00e
--- /dev/null
+++ b/theme/identica/display.css
@@ -0,0 +1,187 @@
+/* CSS Document */
+/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
+
+@import url(../default/display.css);
+
+html {
+ background: url(bg-body.gif) repeat-y top center #d8e2d7;
+}
+
+body {
+ color: #193441;
+}
+
+a {
+ color: #C15D42;
+}
+
+#wrap {
+ background: url(bg-header.gif) repeat-x #FCFFF5;
+}
+
+#header h1.pagetitle {
+ color: #d8e2d7;
+}
+
+#header h2.sitename {
+ color: #FCFFF5;
+}
+
+#nav li a {
+ color: #91AA9D;
+}
+
+#nav_views li a {
+ color: #FCFFF5;
+ background-color: #91AA9D;
+ border-right: 1px solid #6A8787;
+}
+
+#nav_views li.current a, #nav_views li.current a:hover {
+ color: #3F606F;
+ background-color: #FCFFF5;
+ border-right: 1px solid #6A8787;
+}
+
+#nav_views li.current a:hover {
+ color: #193441;
+}
+
+#nav_views li a:hover {
+ color: #FCFFF5;
+ background-color: #3F606F;
+ border-right: 1px solid #6A8787;
+ }
+
+#nav_sub {
+ border-top: 1px solid #D8E2D7;
+ }
+
+.instructions p, .success, .error {
+ border: 1px solid #91AA9D;
+ color: #FCFFF5;
+ }
+.instructions a:hover, .success a:hover, .error a:hover {
+ color: #FCFFF5;
+ }
+
+#notices {
+ border-top: 1px solid #D8E2D7;
+ }
+
+.notice_single {
+ border-bottom: 1px solid #D8E2D7;
+}
+
+.notice_single:hover {
+ background-color: #F3F8EA;
+ }
+
+input#submit_yes, input#submit_no {
+ color: #FCFFF5;
+ background-color: #C15D42;
+ }
+
+input#submit_yes:hover, input#submit_no:hover {
+ background-color: #904632;
+ }
+
+p.time a {
+ color: #91AA9D;
+ }
+
+#profile {
+ border-top: 1px solid #D8E2D7;
+ }
+
+#profile h2 {
+ color: #91AA9D;
+ }
+
+#nav_pagination li {
+ background-color: #91AA9D;
+ }
+
+#nav_pagination li a {
+ background-color: #91AA9D;
+ color: #FCFFF5;
+ }
+
+#nav_pagination li a:hover {
+ color: #FCFFF5;
+ }
+
+#footer {
+ border-top: 1px solid #D8E2D7;
+ }
+
+form input {
+ border: 1px solid #D8E2D7;
+ }
+
+input#submit, input.submit {
+ color: #FCFFF5;
+ background-color: #C15D42;
+ }
+
+input#submit:hover, input.submit:hover {
+ background-color: #904632;
+ }
+
+textarea, input {
+ color: #193441;
+ }
+
+textarea:focus, input:focus {
+ background-color: #f0f6eb;
+ }
+
+.input_instructions {
+ color: #91aa9d;
+ }
+
+#status_label {
+ color: #91AA9D;
+ }
+
+#status_textarea {
+ color: #193441;
+}
+
+#status_submit {
+ background-color: #C15D42;
+ color: #FCFFF5;
+ }
+
+#status_submit:hover {
+ background-color: #904632;
+ }
+
+#content .subscribe .submit, #content .unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
+ background-color: #c15d42;
+ color: #fcfff5;
+ }
+
+#profiles {
+ border-top: 1px solid #D8E2D7;
+ }
+
+.profile_single {
+ border-bottom: 1px solid #D8E2D7;
+ }
+
+.profile_single:hover {
+ background-color: #F3F8EA;
+ }
+
+#messages {
+ border-top: 1px solid #D8E2D7;
+ }
+
+.message_single {
+ border-bottom: 1px solid #D8E2D7;
+ }
+
+.message_single:hover {
+ background-color: #F3F8EA;
+ }
diff --git a/theme/identica/facebookapp.css b/theme/identica/facebookapp.css
new file mode 100644
index 000000000..b2bbbac51
--- /dev/null
+++ b/theme/identica/facebookapp.css
@@ -0,0 +1,161 @@
+
+/* XXX: Most of this just copied out of display.css -- need to factor out what we really neeed -- Zach */
+
+body {
+ color: #193441;
+}
+
+a {
+color: #d1451a;
+text-decoration: none;
+}
+a:hover {
+text-decoration: underline;
+}
+img, img a {
+border: 0;
+}
+h1 {
+font-size: 14px;
+}
+
+#wrap {
+margin: 0 auto;
+padding: 0 20px;
+width: 760px;
+background: url(bg-header.gif) repeat-x #fbf2d7;
+}
+
+
+#notices {
+clear: both;
+margin: 0 auto;
+padding: 0;
+list-style-type: none;
+width: 600px;
+border-top: 1px solid #dec5b5;
+}
+#notices a:hover {
+text-decoration: underline;
+}
+.notice_single {
+clear: both;
+display: block;
+margin: 0;
+padding: 5px 5px 5px 0;
+min-height: 48px;
+font-family: Georgia, "Times New Roman", Times, serif;
+font-size: 13px;
+line-height: 16px;
+border-bottom: 1px solid #dec5b5;
+background-color:#FCFFF5;
+opacity:1;
+}
+.notice_single:hover {
+background-color: #f7ebcc;
+}
+.notice_single p {
+display: inline;
+margin: 0;
+padding: 0;
+}
+#notice_delete_form #confirmation_text {
+ display: block;
+font-size: 14px;
+font-weight: bold;
+}
+
+input#submit_yes, input#submit_no {
+margin: 18px 10px 0px 0px;
+padding: 4px;
+font-weight: bold;
+color: #fff6d5;
+background-color: #F60;
+cursor: pointer;
+border: 0;
+width: 40px;
+}
+input#submit_yes:hover, input#submit_no:hover {
+background-color: #701238;
+}
+.avatar.stream {
+float: left;
+margin: 0 10px 0.5em 0;
+}
+p.time {
+display: block;
+font-family: Verdana, Arial, Helvetica, sans-serif;
+font-size: 10px;
+line-height: 15px;
+}
+p.time a {
+color: #dab134;
+}
+
+
+/* ----- Forms General Style ----- */
+form {
+ margin: 0 auto;
+ padding: 0;
+ }
+form {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ }
+form label {
+ display: block;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 18px;
+ }
+form input {
+ border: 1px solid #dec5b5;
+ width: 264px;
+ }
+input#submit, input.submit {
+ display: block;
+ margin: 18px 0;
+ padding: 4px;
+ font-weight: bold;
+ color: #fff6d5;
+ background-color: #F60;
+ cursor: pointer;
+ border: 0;
+ width: auto;
+ }
+input#submit:hover, input.submit:hover {
+ background-color: #701238;
+ }
+input.checkbox {
+ /*width: 14px;
+ height: 14px;*/
+ width: auto;
+ border: 0;
+ }
+
+label.checkbox_label {
+ display: inline;
+ font-weight: normal;
+}
+
+textarea, input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #701238;
+ padding: 3px;
+ }
+textarea:focus, input:focus {
+ background-color: #f8ebc0;
+ }
+textarea {
+ width: 270px;
+ border: 1px solid #D8E2D7;
+ }
+.input_instructions {
+ margin-top: 3px;
+ display: block;
+ font-size: 11px;
+ line-height: 15px;
+ color: #924959;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ }
diff --git a/theme/identica/ie6.css b/theme/identica/ie6.css
new file mode 100644
index 000000000..a301f1dc5
--- /dev/null
+++ b/theme/identica/ie6.css
@@ -0,0 +1,14 @@
+@charset "UTF-8";
+/* CSS Document */
+@import url(../default/ie6.css);
+#wrap {
+ background: url(bg-header.gif) repeat-x #FCFFF5;
+ }
+
+
+input.disfavor,
+input.favor {
+ text-indent:0px;
+ text-align:right;
+ padding-left:25px;
+} \ No newline at end of file
diff --git a/theme/identica/ie7.css b/theme/identica/ie7.css
new file mode 100644
index 000000000..cab6f4059
--- /dev/null
+++ b/theme/identica/ie7.css
@@ -0,0 +1,3 @@
+@charset "UTF-8";
+/* CSS Document */
+@import url(../default/ie7.css);
diff --git a/theme/identica/login-bg.gif b/theme/identica/login-bg.gif
new file mode 100644
index 000000000..e2d8377db
--- /dev/null
+++ b/theme/identica/login-bg.gif
Binary files differ
diff --git a/theme/identica/logo.png b/theme/identica/logo.png
new file mode 100644
index 000000000..3b271814d
--- /dev/null
+++ b/theme/identica/logo.png
Binary files differ
diff --git a/theme/iphone/bg-body.gif b/theme/iphone/bg-body.gif
new file mode 100644
index 000000000..d87e2e8d7
--- /dev/null
+++ b/theme/iphone/bg-body.gif
Binary files differ
diff --git a/theme/iphone/bg-header.gif b/theme/iphone/bg-header.gif
new file mode 100644
index 000000000..5154b2e5e
--- /dev/null
+++ b/theme/iphone/bg-header.gif
Binary files differ
diff --git a/theme/iphone/default-avatar-mini.png b/theme/iphone/default-avatar-mini.png
new file mode 100644
index 000000000..38b8692b4
--- /dev/null
+++ b/theme/iphone/default-avatar-mini.png
Binary files differ
diff --git a/theme/iphone/default-avatar-profile.png b/theme/iphone/default-avatar-profile.png
new file mode 100644
index 000000000..f8357d4fc
--- /dev/null
+++ b/theme/iphone/default-avatar-profile.png
Binary files differ
diff --git a/theme/iphone/default-avatar-stream.png b/theme/iphone/default-avatar-stream.png
new file mode 100644
index 000000000..6b63baa70
--- /dev/null
+++ b/theme/iphone/default-avatar-stream.png
Binary files differ
diff --git a/theme/iphone/display.css b/theme/iphone/display.css
new file mode 100644
index 000000000..6ac471c1e
--- /dev/null
+++ b/theme/iphone/display.css
@@ -0,0 +1,700 @@
+/* CSS Document */
+/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
+/* Simplified for mobile by Ken Sheppardson http://identi.ca/kshep */
+
+html {}
+body {
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 24px;
+ min-height: 100%;
+ height: 100%;
+ color: #193441;
+}
+
+a {
+ color: #C15D42;
+ text-decoration: none;
+}
+a:hover {
+ text-decoration: underline;
+}
+img, img a {
+ border: 0;
+}
+h1 {
+ font-size: 1.2em;
+}
+
+
+#wrap {
+ margin: 0;
+}
+
+#header {
+ width: 100%;
+ float: left;
+ background-color: #193441;
+ margin: 0 0 20px 0;
+ padding: 0;
+}
+#logo {
+ float: left;
+ margin: 10px 0px 0px 10px;
+}
+p#branding {
+ margin: 0;
+ padding: 6px 0 3px 0;
+ color: #fbf2d7;
+ font-size: 2em;
+ font-weight: bold;
+ line-height: 2.5em;
+}
+p#branding a {
+ color: #dab134;
+}
+
+#header h1.pagetitle {
+ display: none;
+ margin: 0;
+ padding: 0;
+ font-size: 1.2em;
+ line-height: 2em;
+ color: #d8e2d7;
+}
+
+#header h2.sitename {
+ display: none;
+ margin: 0;
+ padding: 0;
+ color: #FCFFF5;
+}
+
+/* ===== Begin Navigation Styling ===== */
+
+/* ----- Navigation ------ */
+#nav {
+ float: right;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ font-size: 1.2em;
+}
+#nav li {
+ display: block;
+ float: left;
+}
+#nav li a {
+ display: block;
+ padding: 9px 15px 12px 0px;
+ color: #91AA9D;
+}
+#nav li a:hover {
+ text-decoration: underline;
+}
+
+/* ----- Tabs ----- */
+#nav_views {
+ clear: both;
+ float: left;
+ margin: 10px 0px 0px 5px;
+ padding: 0;
+ bottom: 0;
+ list-style-type: none;
+ font-size: 1.1em;
+ font-weight: bold;
+}
+#nav_views li {
+ display: block;
+ float: left;
+ line-height: 1.3em;
+}
+#nav_views li a {
+ display: block;
+ margin: 0;
+ padding: 4px 12px 3px 12px;
+ color: #FCFFF5;
+ background-color: #91AA9D;
+ border-right: 1px solid #6A8787;
+}
+#nav_views li a:hover {
+ text-decoration: none;
+}
+#nav_views li.current a, #nav_views li.current a:hover {
+ color: #3F606F;
+ background-color: #FCFFF5;
+ border-right: 1px solid #6A8787;
+}
+#nav_views li.current a:hover {
+ color: #193441;
+}
+#nav_views li a:hover {
+ color: #FCFFF5;
+ background-color: #3F606F;
+ border-right: 1px solid #6A8787;
+}
+
+/* ----- Nav Footer ----- */
+#nav_sub {
+ clear: both;
+ margin: 18px 10px 0 10px;
+ padding: 0;
+ list-style-type: none;
+ font-size: 1.1em;
+ font-weight: bold;
+ line-height: 2em;
+ border-top: 1px solid #D8E2D7;
+}
+#nav_sub li {
+ display: block;
+ float: left;
+}
+#nav_sub li a {
+ padding: 6px 24px 6px 0;
+}
+#nav_sub li a:hover {
+ text-decoration: underline;
+}
+/* ===== End Navigation Styling ===== */
+
+#content {
+ clear: left;
+ margin: 10px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 1em;
+ line-height: 1.1em;
+}
+#content h2 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1.1em;
+}
+#content label {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1.1em;
+}
+
+.instructions {
+ clear: both;
+ float: left;
+ margin: 5px 5px 10px 5px;
+}
+.instructions p, .success, .error {
+ font-weight: normal;
+ margin: 0;
+ padding: 10px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 1.1em;
+ line-height: 1.2em;
+ border: 1px solid #91AA9D;
+ color: #FCFFF5;
+}
+.instructions a, .success a, .error a {
+ color: #d8e2d7;
+ text-decoration: underline;
+}
+.instructions a:hover, .success a:hover, .error a:hover {
+ color: #FCFFF5;
+}
+.success {
+ clear: both;
+ float: left;
+ margin: 5px 5px 10px 5px;
+ background-color: #48705b;
+}
+.error {
+ clear: both;
+ float: left;
+ margin: 5px 5px 10px 5px;
+ background-color: #ce3728;
+}
+
+
+/* ----- Stream -----*/
+
+#notices {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ border-top: 1px solid #D8E2D7;
+}
+#notices a:hover {
+ text-decoration: underline;
+}
+.notice_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 1em;
+ line-height: 1.4em;
+ border-bottom: 1px solid #D8E2D7;
+}
+.notice_single:hover {
+ background-color: #F3F8EA;
+}
+.notice_single p {
+ display: inline;
+ margin: 0;
+ padding: 0;
+}
+#notice_delete_form #confirmation_text {
+ display: block;
+ font-size: 1.1em;
+ font-weight: bold;
+}
+input#submit_yes, input#submit_no {
+ margin: 18px 10px 0px 0px;
+ padding: 4px;
+ font-weight: bold;
+ color: #FCFFF5;
+ background-color: #C15D42;
+ cursor: pointer;
+ border: 0;
+ width: 40px;
+}
+input#submit_yes:hover, input#submit_no:hover {
+ background-color: #904632;
+}
+.avatar.stream {
+ float: left;
+ margin: 0 10px 0 0;
+}
+p.time {
+ display: block;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 0.9em;
+ line-height: 2em;
+}
+p.time a {
+ color: #91AA9D;
+}
+
+/* ----- Profile -----*/
+#profile {
+ clear: both;
+ float: left;
+ padding: 10px 0 0 0;
+ border-top: 1px solid #D8E2D7;
+ font-family: Georgia, "Times New Roman", Times, serif;
+}
+#profile h1 {
+ clear: both;
+ float: left;
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1.2em;
+}
+#profile h2 {
+ clear: both;
+ float: left;
+ margin: 0;
+ padding: 1em 0 0.2em 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1.1em;
+ text-transform: uppercase;
+ color: #91AA9D;
+}
+#profile p {
+ clear: both;
+ float: left;
+ margin: 0 10px 0 0;
+ font-size: 1em;
+ line-height: 1.4em;
+}
+#profile p.location {
+ margin: 0 10px 12px 0;
+ font-style: italic;
+}
+#profile p.notice_current {
+ font-size: 1.2em;
+ line-height: 1.3em;
+}
+#profile_avatar {
+ float: left;
+ margin-right: 4px;
+}
+#profile_avatar img {
+ margin-bottom: 5px;
+}
+.avatar.profile {
+ clear: left;
+ margin: 0 10px 5px 0;
+}
+.avatar.original {
+ float: left;
+ margin: 0 10px 18px 0;
+}
+a.nickname {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 1.1em;
+ padding-right: 3px;
+}
+#profile_information {
+ float: left;
+}
+
+.statistics {
+ clear: both;
+ float: left;
+}
+.statistics h2 {
+ clear: both;
+ float: left;
+ margin: 12px 0 3px 0;
+}
+dl.statistics {
+ margin: 0;
+}
+.statistics dt {
+ clear: left;
+ float: left;
+ width: 200px;
+}
+.statistics dd {
+ float: left;
+}
+.statistics dt:after {
+ content: ":";
+}
+#subscriptions {
+ clear: both;
+ float: left;
+ margin: 18px 0 30px 0;
+}
+#subscriptions_avatars {
+ clear: both;
+ float: left;
+ margin: 6px 0 0 0;
+ padding: 0;
+ list-style-type: none;
+}
+#subscriptions_avatars li .avatar.mini {
+ float: left;
+ margin: 0 3px 3px 0;
+ padding: 0;
+ line-height: 0;
+}
+#subscriptions_viewall {
+ clear: left;
+}
+/* ----- End Profile -----*/
+
+/* ----- Begin Subscriptions & Subscribers -----*/
+
+ul.subscriptions, ul.subscribers {
+ float: none;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ overflow: auto;
+}
+ul.subscriptions li, ul.subscribers li {
+ display: block;
+ float: left;
+ padding: 0;
+}
+/* ----- End Subscriptions & Subscribers -----*/
+
+
+
+#pagination {
+ margin: 18px auto;
+}
+#nav_pagination {
+ margin: 0 0 36px 0;
+ padding: 0;
+ float: right;
+ list-style-type: none;
+ font-size: 12px;
+ font-weight: bold;
+}
+#nav_pagination li {
+ display: block;
+ float: left;
+ background-color: #91AA9D;
+}
+#nav_pagination li.before {
+ margin-right: 1px;
+}
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 2em;
+ background-color: #91AA9D;
+ color: #FCFFF5;
+}
+#nav_pagination li a:hover {
+ background-color: #3F606F;
+ color: #FCFFF5;
+ text-decoration: none;
+}
+
+#footer {
+ clear: both;
+ margin: 10px;
+ border-top: 1px solid #D8E2D7;
+}
+#footer p {
+ font-size: 0.8em;
+ margin-top: 1em;
+ line-height: 1.2em;
+}
+#cc {
+ float: left;
+ margin: 3px 10px 0 0;
+}
+
+/* ===== Begin Forms Styling ===== */
+
+/* ----- Forms General Style ----- */
+form {
+ margin: 0 auto;
+ padding: 0;
+}
+form {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1em;
+}
+form label {
+ display: block;
+ font-size: 1em;
+ font-weight: bold;
+ line-height: 1.5em;
+}
+form input {
+ border: 1px solid #D8E2D7;
+ width: 264px;
+}
+input#submit, input.submit {
+ display: block;
+ margin: 18px 0;
+ padding: 4px;
+ font-weight: bold;
+ color: #FCFFF5;
+ background-color: #C15D42;
+ cursor: pointer;
+ border: 0;
+ width: auto;
+}
+input#submit:hover, input.submit:hover {
+ background-color: #904632;
+}
+input.checkbox {
+ width: auto;
+ border: 0;
+}
+textarea, input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1em;
+ color: #193441;
+ padding: 3px;
+}
+textarea:focus, input:focus {
+ background-color: #f0f6eb;
+}
+textarea {
+ width: 270px;
+ border: 1px solid #D8E2D7;
+}
+.input_instructions {
+ margin-top: 3px;
+ display: block;
+ font-size: 1em;
+ line-height: 1.2em;
+ color: #91aa9d;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+}
+
+/* ----- Status Form ----- */
+#status_form {
+ width: 100%;
+ margin: 0px 0px 10px 5px;
+}
+#status_form p {
+ margin: 0;
+ padding: 0;
+}
+#status_label {
+ display: none;
+ clear: both;
+ margin: 0;
+ padding: 0 0 3px 0;
+ font-size: 1.5em;
+ font-weight: bold;
+ line-height: 2em;
+ color: #91AA9D;
+}
+#status_textarea {
+ display: block;
+ float: left;
+ width: 70%;
+ height: 3em;
+ margin: 0 0 10px 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 1.1em;
+ color: #193441;
+ border: 0;
+}
+#status_submit {
+ display: block;
+ float: left;
+ margin: 0 0 0 4px;
+ padding: 1em 10px 1em 10px;
+ line-height: 1em;
+ width: 10%;
+ background-color: #C15D42;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 1em;
+ color: #FCFFF5;
+ cursor: pointer;
+ border: 0;
+}
+#status_submit:hover {
+ background-color: #904632;
+}
+#counter {
+ padding: 1em .5em 1em 5px;
+ color: #fff;
+ clear: both;
+ float: left;
+ font-weight: bold;
+ text-align: right;
+}
+/* ----- Subscribe Form ----- */
+#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
+ clear: left;
+ margin: 0;
+ width: 96px;
+ height: 27px;
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 12px;
+ text-transform: uppercase;
+ background-color: #c15d42;
+ color: #fcfff5;
+ border: 0;
+}
+#remotesubscribe {
+ width: 96px;
+ height: 22px;
+ padding: 5px 0 0 0;
+ text-align: center;
+}
+#subscribe .button:hover, #unsubscribe .button:hover {
+ background-color: #904632;
+ cursor: pointer;
+}
+
+a#remotesubscribe {
+ display: block;
+}
+
+/* ----- Login Form -----*/
+input#license {
+ width: auto;
+ border: 0;
+}
+/* ----- Avatar Form -----*/
+form {
+ clear: left;
+}
+
+/* ----- OpenID Form -----*/
+
+input#openid_url {
+ background: url(login-bg.gif) no-repeat;
+ background-color: #fff;
+ background-position: 4px 50%;
+ color: #000;
+ padding-left: 24px;
+}
+
+/* People lists (search results, maybe subscribers) */
+
+#profiles {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ border-top: 1px solid #D8E2D7;
+}
+#profiles a:hover {
+ text-decoration: underline;
+}
+
+.profile_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 1.2em;
+ line-height: 1.4em;
+ border-bottom: 1px solid #D8E2D7;
+}
+.profile_single:hover {
+ background-color: #F3F8EA;
+}
+
+/* ----- IM Settings Form -----*/
+
+#imsettings p {
+ margin: 0;
+ padding: 0;
+ line-height: 1.3em;
+}
+
+/* ===== End Forms Styling ===== */
+
+/* ===== Tag Cloud Styling ===== */
+
+p.tagcloud {
+text-align: center;
+}
+
+p.tagcloud a {
+line-height:1em;
+vertical-align:middle;
+}
+
+p.tagcloud a.largest {
+font-size: 4em;
+}
+p.tagcloud a.verylarge {
+font-size: 3em;
+}
+
+p.tagcloud a.large {
+font-size: 2em;
+}
+
+p.tagcloud a.medium {
+font-size: 1.5em;
+}
+
+p.tagcloud a.small {
+font-size: 1em;
+}
+
+p.tagcloud a.verysmall {
+font-size: 80%;
+}
+
+p.tagcloud a.smallest {
+font-size: 60%;
+}
+
+a.replybutton {
+ border: 1px solid #D8E2D7;
+ padding: 0px 10px 0px 10px;
+ line-height: 0.8em;
+}
diff --git a/theme/iphone/display.css.dist b/theme/iphone/display.css.dist
new file mode 100644
index 000000000..395da2e2f
--- /dev/null
+++ b/theme/iphone/display.css.dist
@@ -0,0 +1,686 @@
+/* CSS Document */
+/* Design & CSS by Marie-Claude Doyon http://www.marieclaudedoyon.com */
+
+html {
+ background: url(bg-body.gif) repeat-y top center #d8e2d7;
+ }
+body {
+ position: absolute;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ line-height: 12px;
+ min-height: 100%;
+ height: 100%;
+ color: #193441;
+ }
+a {
+ color: #C15D42;
+ text-decoration: none;
+ }
+a:hover {
+ text-decoration: underline;
+ }
+img, img a {
+ border: 0;
+ }
+h1 {
+ font-size: 14px;
+ }
+
+
+#wrap {
+ margin: 0 auto;
+ padding: 0 20px;
+ width: 760px;
+ background: url(bg-header.gif) repeat-x #FCFFF5;
+ }
+#header {
+ position: relative;
+ margin: 0 auto;
+ width: 540px;
+ height: 216px;
+ }
+#logo {
+ margin-top: 9px;
+ }
+p#branding {
+ margin: 0;
+ padding: 6px 0 3px 0;
+ color: #fbf2d7;
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 27px;
+ }
+p#branding a {
+ color: #dab134;
+ }
+
+#header h1.pagetitle {
+ margin: 0;
+ padding: 0;
+ font-size: 15px;
+ line-height: 24px;
+ color: #d8e2d7;
+}
+
+#header h2.sitename {
+ display: none;
+ margin: 0;
+ padding: 0;
+ color: #FCFFF5;
+}
+
+/* ===== Begin Navigation Styling ===== */
+
+/* ----- Navigation ------ */
+#nav {
+ float: right;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ font-size: 12px;
+ }
+#nav li {
+ display: block;
+ float: left;
+ }
+#nav li a {
+ display: block;
+ padding: 9px 9px 12px 9px;
+ color: #91AA9D;
+ }
+#nav li a:hover {
+ text-decoration: underline;
+ }
+
+/* ----- Tabs ----- */
+#nav_views {
+ margin: 0 auto;
+ padding: 0;
+ position: absolute;
+ bottom: 0;
+ list-style-type: none;
+ font-size: 14px;
+ font-weight: bold;
+ width: 540px;
+ /*height: 30px;*/
+ }
+#nav_views li {
+ display: block;
+ float: left;
+ line-height: 21px;
+ }
+#nav_views li a {
+ display: block;
+ margin: 0;
+ padding: 4px 12px 3px 12px;
+ color: #FCFFF5;
+ background-color: #91AA9D;
+ border-right: 1px solid #6A8787;
+ }
+#nav_views li a:hover {
+ text-decoration: none;
+ }
+#nav_views li.current a, #nav_views li.current a:hover {
+ color: #3F606F;
+ background-color: #FCFFF5;
+ border-right: 1px solid #6A8787;
+ }
+#nav_views li.current a:hover {
+ color: #193441;
+ }
+#nav_views li a:hover {
+ color: #FCFFF5;
+ background-color: #3F606F;
+ border-right: 1px solid #6A8787;
+ }
+
+/* ----- Nav Footer ----- */
+#nav_sub {
+ clear: both;
+ margin: 18px auto 0 auto;
+ padding: 0;
+ list-style-type: none;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 21px;
+ border-top: 1px solid #D8E2D7;
+ width: 540px;
+ }
+#nav_sub li {
+ display: block;
+ float: left;
+ }
+#nav_sub li a {
+ padding: 6px 24px 6px 0;
+ }
+#nav_sub li a:hover {
+ text-decoration: underline;
+ }
+/* ===== End Navigation Styling ===== */
+
+#content {
+ clear: left;
+ margin: 40px 0 45px 0;
+ padding: 0 110px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 14px;
+ line-height: 18px;
+ }
+#content h2 {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 15px;
+ }
+#content label {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ }
+.instructions p, .success, .error {
+ font-weight: normal;
+ margin: 36px 0 0 0;
+ padding: 10px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 15px;
+ border: 1px solid #91AA9D;
+ color: #FCFFF5;
+ }
+.instructions a, .success a, .error a {
+ color: #d8e2d7;
+ text-decoration: underline;
+ }
+.instructions a:hover, .success a:hover, .error a:hover {
+ color: #FCFFF5;
+ }
+.success {
+ background-color: #48705b;
+ }
+.error {
+ background-color: #ce3728;
+ }
+
+
+/* ----- Stream -----*/
+
+#notices {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 540px;
+ border-top: 1px solid #D8E2D7;
+ }
+#notices a:hover {
+ text-decoration: underline;
+ }
+.notice_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #D8E2D7;
+ }
+.notice_single:hover {
+ background-color: #F3F8EA;
+ }
+.notice_single p {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ }
+#notice_delete_form #confirmation_text {
+ display: block;
+ font-size: 14px;
+ font-weight: bold;
+ }
+input#submit_yes, input#submit_no {
+ margin: 18px 10px 0px 0px;
+ padding: 4px;
+ font-weight: bold;
+ color: #FCFFF5;
+ background-color: #C15D42;
+ cursor: pointer;
+ border: 0;
+ width: 40px;
+ }
+input#submit_yes:hover, input#submit_no:hover {
+ background-color: #904632;
+ }
+.avatar.stream {
+ float: left;
+ margin: 0 10px 0 0;
+ }
+p.time {
+ display: block;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 10px;
+ line-height: 15px;
+ }
+p.time a {
+ color: #91AA9D;
+ }
+
+/* ----- Profile -----*/
+#profile {
+ clear: left;
+ margin: 0 -110px;
+ padding: 10px 0 0 0;
+ min-height: 170px;
+ border-top: 1px solid #D8E2D7;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ }
+#profile h1 {
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ }
+#profile h2 {
+ margin: 0;
+ padding: 0;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 11px;
+ text-transform: uppercase;
+ color: #91AA9D;
+ }
+#profile p {
+ margin: 0 10px 0 0;
+ font-size: 12px;
+ line-height: 14px;
+ }
+#profile p.location {
+ margin: 0 10px 12px 0;
+ font-style: italic;
+ }
+#profile p.notice_current {
+ font-size: 18px;
+ line-height: 21px;
+ }
+#profile_avatar {
+ float: left;
+ margin-right: 4px;
+ }
+#profile_avatar img {
+ margin-bottom: 5px;
+ }
+.avatar.profile {
+ clear: left;
+ margin: 0 10px 5px 0;
+ }
+.avatar.original {
+ float: left;
+ margin: 0 10px 18px 0;
+ }
+a.nickname {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 12px;
+ padding-right: 3px;
+ }
+#profile_information {
+ float: left;
+ position: relative;
+ width: 270px;
+ height: 200px;
+ }
+.statistics {
+ margin-top: 18px;
+ }
+.statistics h2 {
+ margin: 12px 0 3px 0;
+ }
+dl.statistics {
+ margin: 0;
+ font-size: 12px;
+ line-height: 14px;
+ }
+.statistics dt {
+ float: left;
+ width: 96px;
+}
+.statistics dd {
+ margin-left: 100px;
+}
+.statistics dt:after {
+ content: ":";
+ }
+#subscriptions {
+ float: left;
+ margin: 18px 0 30px 0;
+ }
+#subscriptions_avatars {
+ float: left;
+ margin: 6px 0 0 0;
+ padding: 0;
+ list-style-type: none;
+ width: 270px;
+ }
+#subscriptions_avatars li .avatar.mini {
+ float: left;
+ margin: 0 3px 3px 0;
+ padding: 0;
+ line-height: 0;
+ /* border: 1px solid #f00; */
+ }
+#subscriptions_viewall {
+ clear: left;
+ }
+/* ----- End Profile -----*/
+
+/* ----- Begin Subscriptions & Subscribers -----*/
+
+ul.subscriptions, ul.subscribers {
+ float: none;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ overflow: auto;
+ }
+ul.subscriptions li, ul.subscribers li {
+ display: block;
+ float: left;
+ padding: 0;
+ }
+/* ----- End Subscriptions & Subscribers -----*/
+
+
+
+#pagination {
+ margin: 18px auto;
+ width: 540px;
+ }
+#nav_pagination {
+ margin: 0 0 36px 0;
+ padding: 0;
+ float: right;
+ list-style-type: none;
+ font-size: 12px;
+ font-weight: bold;
+ }
+#nav_pagination li {
+ display: block;
+ float: left;
+ background-color: #91AA9D;
+ }
+#nav_pagination li.before {
+ margin-right: 1px;
+ }
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 21px;
+ background-color: #91AA9D;
+ color: #FCFFF5;
+ }
+#nav_pagination li a:hover {
+ background-color: #3F606F;
+ color: #FCFFF5;
+ text-decoration: none;
+ }
+
+#footer {
+ clear: both;
+ margin: 0 auto;
+ padding: 0 0 36px 0;
+ width: 540px;
+ border-top: 1px solid #D8E2D7;
+ }
+#footer p {
+ margin-top: 9px;
+ line-height: 12px;
+ }
+#cc {
+ float: left;
+ margin: 3px 10px 0 0;
+ }
+
+/* ===== Begin Forms Styling ===== */
+
+/* ----- Forms General Style ----- */
+form {
+ margin: 0 auto;
+ padding: 0;
+ }
+form {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ }
+form label {
+ display: block;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 18px;
+ }
+form input {
+ border: 1px solid #D8E2D7;
+ width: 264px;
+ }
+input#submit, input.submit {
+ display: block;
+ margin: 18px 0;
+ padding: 4px;
+ font-weight: bold;
+ color: #FCFFF5;
+ background-color: #C15D42;
+ cursor: pointer;
+ border: 0;
+ width: auto;
+ }
+input#submit:hover, input.submit:hover {
+ background-color: #904632;
+ }
+input.checkbox {
+ /*width: 14px;
+ height: 14px;*/
+ width: auto;
+ border: 0;
+ }
+textarea, input {
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #193441;
+ padding: 3px;
+ }
+textarea:focus, input:focus {
+ background-color: #f0f6eb;
+ }
+textarea {
+ width: 270px;
+ border: 1px solid #D8E2D7;
+ }
+.input_instructions {
+ margin-top: 3px;
+ display: block;
+ font-size: 11px;
+ line-height: 15px;
+ color: #91aa9d;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ }
+
+/* ----- Status Form ----- */
+#status_form {
+ height: 96px;
+ /*background-color: #F00;*/
+ }
+#status_form p {
+ margin: 36px 0 0 0;
+ padding: 0;
+ }
+#status_label {
+ display: block;
+ clear: both;
+ margin: 0;
+ padding: 0 0 3px 0;
+ font-size: 18px;
+ font-weight: bold;
+ line-height: 24px;
+ color: #91AA9D;
+ }
+#status_textarea {
+ display: block;
+ float: left;
+ width: 463px;
+ height: 35px;
+ padding: 5px;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ color: #193441;
+ border: 0;
+ }
+#status_submit {
+ display: block;
+ float: left;
+ margin: 1px 0 0 4px;
+ width: 63px;
+ height: 45px;
+ background-color: #C15D42;
+ font-family: Verdana, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 14px;
+ color: #FCFFF5;
+ cursor: pointer;
+ border: 0;
+ }
+#status_submit:hover {
+ background-color: #904632;
+ }
+#counter {
+ position: absolute;
+ top: 140px;
+ left: -64px;
+ width: 50px;
+ font-weight: bold;
+ text-align: right;
+}
+/* ----- Subscribe Form ----- */
+#subscribe .submit, #unsubscribe .submit, #remotesubscribe .button, #remotesubscribe {
+ clear: left;
+ margin: 0;
+ width: 96px;
+ height: 27px;
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-weight: bold;
+ font-size: 10px;
+ text-transform: uppercase;
+ background-color: #c15d42;
+ color: #fcfff5;
+ border: 0;
+ }
+#remotesubscribe {
+ width: 96px;
+ height: 22px;
+ padding: 5px 0 0 0;
+ text-align: center;
+ }
+#subscribe .button:hover, #unsubscribe .button:hover {
+ background-color: #904632;
+ cursor: pointer;
+ }
+
+a#remotesubscribe {
+ display: block;
+}
+
+/* ----- Login Form -----*/
+input#license {
+ width: auto;
+ border: 0;
+ }
+/* ----- Avatar Form -----*/
+form {
+ clear: left;
+}
+
+/* ----- OpenID Form -----*/
+
+input#openid_url {
+ background: url(login-bg.gif) no-repeat;
+ background-color: #fff;
+ background-position: 4px 50%;
+ color: #000;
+ padding-left: 24px;
+}
+
+/* People lists (search results, maybe subscribers) */
+
+#profiles {
+ clear: both;
+ margin: 0 auto;
+ padding: 0;
+ list-style-type: none;
+ width: 540px;
+ border-top: 1px solid #D8E2D7;
+ /*border: 1px solid #F00;*/
+ }
+#profiles a:hover {
+ text-decoration: underline;
+ }
+
+.profile_single {
+ clear: both;
+ display: block;
+ margin: 0;
+ padding: 5px 5px 5px 0;
+ min-height: 48px;
+ font-family: Georgia, "Times New Roman", Times, serif;
+ font-size: 13px;
+ line-height: 16px;
+ border-bottom: 1px solid #D8E2D7;
+ }
+.profile_single:hover {
+ background-color: #F3F8EA;
+ }
+
+/* ----- IM Settings Form -----*/
+
+#imsettings p {
+ margin: 0;
+ padding: 0;
+ line-height: 15px;
+}
+
+/* ===== End Forms Styling ===== */
+
+/* ===== Tag Cloud Styling ===== */
+
+p.tagcloud {
+text-align: center;
+}
+
+p.tagcloud a {
+line-height:100%;
+vertical-align:middle;
+}
+
+p.tagcloud a.largest {
+font-size: 400%;
+}
+p.tagcloud a.verylarge {
+font-size: 300%;
+}
+
+p.tagcloud a.large {
+font-size: 200%;
+}
+
+p.tagcloud a.medium {
+font-size: 150%;
+}
+
+p.tagcloud a.small {
+font-size: 100%;
+}
+
+p.tagcloud a.verysmall {
+font-size: 80%;
+}
+
+p.tagcloud a.smallest {
+font-size: 60%;
+}
diff --git a/theme/iphone/ie6.css.dist b/theme/iphone/ie6.css.dist
new file mode 100644
index 000000000..97d9fee3f
--- /dev/null
+++ b/theme/iphone/ie6.css.dist
@@ -0,0 +1,63 @@
+@charset "UTF-8";
+/* CSS Document */
+body {
+ text-align: center;
+}
+input {
+ height: 24px;
+}
+#wrap {
+ margin: 0 auto;
+ padding: 0 20px;
+ width: 800px;
+ text-align: left;
+ background: url(bg-header.gif) repeat-x #FCFFF5;
+ }
+#header {
+ position: relative;
+ margin-left: 108px;
+ }
+#nav_views {
+ margin: 0;
+ }
+#nav_views li {
+ line-height: 19px;
+ }
+.statistics dd {
+ margin-top: -15px;
+ clear: both;
+ }
+#notices {
+ margin: 0;
+ }
+.notice_single {
+ height: 48px;
+ }
+#profile p.notice_current {
+ height: 96px;
+ }
+
+#subscriptions_avatars li {
+ float: left;
+ margin: 0;
+ padding: 0;
+ }
+img.avatar.original, img.avatar.profile {
+ clear: none;
+ float: left;
+}
+#status_textarea {
+ height: 46px;
+ }
+
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 27px;
+ }
+#nav_sub {
+ position: relative;
+ margin-left: 108px;
+ }
+#footer {
+ margin-left: 108px;
+}
diff --git a/theme/iphone/ie7.css.dist b/theme/iphone/ie7.css.dist
new file mode 100644
index 000000000..bbf52d5cf
--- /dev/null
+++ b/theme/iphone/ie7.css.dist
@@ -0,0 +1,20 @@
+@charset "UTF-8";
+/* CSS Document */
+
+#statistics dd {
+ clear: both;
+ }
+
+
+#subscriptions_avatars li {
+ float: left;
+ }
+img.avatar.original, img.avatar.profile {
+ clear: none;
+ float: left;
+}
+
+#nav_pagination li a {
+ padding: 6px 15px;
+ line-height: 27px;
+ } \ No newline at end of file
diff --git a/theme/iphone/login-bg.gif b/theme/iphone/login-bg.gif
new file mode 100644
index 000000000..e2d8377db
--- /dev/null
+++ b/theme/iphone/login-bg.gif
Binary files differ
diff --git a/theme/iphone/logo.png b/theme/iphone/logo.png
new file mode 100644
index 000000000..3b271814d
--- /dev/null
+++ b/theme/iphone/logo.png
Binary files differ