]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.7.x' into 0.8.x
authorEvan Prodromou <evan@controlyourself.ca>
Fri, 10 Jul 2009 17:03:57 +0000 (10:03 -0700)
committerEvan Prodromou <evan@controlyourself.ca>
Fri, 10 Jul 2009 17:03:57 +0000 (10:03 -0700)
Conflicts:
README
actions/twitapiusers.php

560 files changed:
.gitignore
EVENTS.txt
README
actions/accesstoken.php
actions/all.php
actions/allrss.php
actions/api.php
actions/attachment.php [new file with mode: 0644]
actions/attachment_ajax.php [new file with mode: 0644]
actions/attachment_thumbnail.php [new file with mode: 0644]
actions/avatarbynickname.php
actions/block.php
actions/blockedfromgroup.php [new file with mode: 0644]
actions/conversation.php [new file with mode: 0644]
actions/deletenotice.php
actions/disfavor.php
actions/doc.php
actions/editgroup.php
actions/facebookhome.php
actions/facebookinvite.php
actions/facebooklogin.php
actions/facebookremove.php
actions/facebooksettings.php
actions/favor.php
actions/favorited.php
actions/favoritesrss.php
actions/featured.php
actions/file.php [new file with mode: 0644]
actions/finishopenidlogin.php
actions/finishremotesubscribe.php
actions/foaf.php
actions/groupblock.php [new file with mode: 0644]
actions/groupdesignsettings.php [new file with mode: 0644]
actions/grouplogo.php
actions/groupmembers.php
actions/grouprss.php
actions/groups.php
actions/groupsearch.php
actions/groupunblock.php [new file with mode: 0644]
actions/invite.php
actions/joingroup.php
actions/logout.php
actions/makeadmin.php [new file with mode: 0644]
actions/microsummary.php
actions/newgroup.php
actions/newnotice.php
actions/noticesearch.php
actions/noticesearchrss.php
actions/nudge.php
actions/openidlogin.php
actions/opensearch.php
actions/othersettings.php
actions/peoplesearch.php
actions/peopletag.php
actions/postnotice.php
actions/public.php
actions/publicrss.php
actions/publicxrds.php
actions/recoverpassword.php
actions/register.php
actions/remotesubscribe.php
actions/replies.php
actions/repliesrss.php
actions/requesttoken.php
actions/showfavorites.php
actions/showgroup.php
actions/shownotice.php
actions/showstream.php
actions/subedit.php
actions/subscribe.php
actions/subscribers.php
actions/subscriptions.php
actions/sup.php
actions/tag.php
actions/tagother.php
actions/tagrss.php
actions/twitapiaccount.php
actions/twitapiblocks.php
actions/twitapidirect_messages.php
actions/twitapifavorites.php
actions/twitapifriendships.php
actions/twitapihelp.php
actions/twitapinotifications.php
actions/twitapisearchatom.php
actions/twitapisearchjson.php
actions/twitapistatuses.php
actions/twitapiusers.php
actions/twittersettings.php
actions/unblock.php
actions/unsubscribe.php
actions/updateprofile.php
actions/userauthorization.php
actions/userbyid.php
actions/userdesignsettings.php [new file with mode: 0644]
actions/usergroups.php
actions/userrss.php
actions/xrds.php
background/.gitignore [new file with mode: 0644]
classes/Avatar.php
classes/Design.php [new file with mode: 0644]
classes/Fave.php
classes/File.php [new file with mode: 0644]
classes/File_oembed.php [new file with mode: 0644]
classes/File_redirection.php [new file with mode: 0644]
classes/File_thumbnail.php [new file with mode: 0644]
classes/File_to_post.php [new file with mode: 0644]
classes/Foreign_link.php
classes/Foreign_user.php
classes/Group_alias.php [new file with mode: 0644]
classes/Group_block.php [new file with mode: 0644]
classes/Group_inbox.php [changed mode: 0755->0644]
classes/Group_member.php [changed mode: 0755->0644]
classes/Memcached_DataObject.php
classes/Notice.php
classes/Notice_inbox.php
classes/Notice_tag.php
classes/Profile.php
classes/Profile_block.php
classes/Queue_item.php
classes/Related_group.php [changed mode: 0755->0644]
classes/Remote_profile.php
classes/Session.php [new file with mode: 0644]
classes/Status_network.php [new file with mode: 0644]
classes/Subscription.php
classes/User.php
classes/User_group.php [changed mode: 0755->0644]
classes/laconica.ini [changed mode: 0755->0644]
classes/laconica.links.ini
classes/statusnet.ini [new file with mode: 0644]
config.php.sample
db/074to080.sql [new file with mode: 0644]
db/foreign_services.sql
db/innodb.sql [new file with mode: 0644]
db/laconica.sql
db/laconica_pg.sql
db/notice_source.sql
db/site.sql [new file with mode: 0644]
doc-src/tos [new file with mode: 0644]
extlib/Console/Getopt.php [new file with mode: 0644]
extlib/DB/DataObject.php
extlib/HTTP/Request.php [new file with mode: 0644]
extlib/HTTP/Request/Listener.php [new file with mode: 0644]
extlib/MIME/Type.php [new file with mode: 0644]
extlib/MIME/Type/Extension.php [new file with mode: 0644]
extlib/MIME/Type/Parameter.php [new file with mode: 0644]
extlib/Mail/mimeDecode.php [new file with mode: 0644]
extlib/Net/URL2.php [new file with mode: 0644]
extlib/Services/oEmbed.php [new file with mode: 0644]
extlib/Services/oEmbed/Exception.php [new file with mode: 0644]
extlib/Services/oEmbed/Exception/NoSupport.php [new file with mode: 0644]
extlib/Services/oEmbed/Object.php [new file with mode: 0644]
extlib/Services/oEmbed/Object/Common.php [new file with mode: 0644]
extlib/Services/oEmbed/Object/Exception.php [new file with mode: 0644]
extlib/Services/oEmbed/Object/Link.php [new file with mode: 0644]
extlib/Services/oEmbed/Object/Photo.php [new file with mode: 0644]
extlib/Services/oEmbed/Object/Rich.php [new file with mode: 0644]
extlib/Services/oEmbed/Object/Video.php [new file with mode: 0644]
extlib/Stomp.php [new file with mode: 0644]
extlib/Stomp/Exception.php [new file with mode: 0644]
extlib/Stomp/Frame.php [new file with mode: 0644]
extlib/Stomp/Message.php [new file with mode: 0644]
extlib/Stomp/Message/Bytes.php [new file with mode: 0644]
extlib/Stomp/Message/Map.php [new file with mode: 0644]
extlib/System/Command.php [new file with mode: 0644]
extlib/Validate.php
extlib/XMPPHP/BOSH.php
extlib/XMPPHP/XMLStream.php
extlib/XMPPHP/XMPP.php
extlib/facebook/facebook.php
extlib/facebook/facebook_desktop.php
extlib/facebook/facebookapi_php5_restlib.php [changed mode: 0644->0755]
index.php
install.php
js/farbtastic/LICENSE.txt [new file with mode: 0644]
js/farbtastic/farbtastic.js [new file with mode: 0644]
js/farbtastic/marker.png [new file with mode: 0755]
js/farbtastic/mask.png [new file with mode: 0644]
js/farbtastic/wheel.png [new file with mode: 0644]
js/install.js [new file with mode: 0644]
js/jcrop/jquery.Jcrop.go.js
js/jquery.form.js
js/jquery.joverlay.js [new file with mode: 0644]
js/jquery.joverlay.min.js [new file with mode: 0644]
js/userdesign.go.js [new file with mode: 0644]
js/util.js
lib/Shorturl_api.php
lib/accountsettingsaction.php
lib/action.php
lib/arraywrapper.php
lib/attachmentlist.php [new file with mode: 0644]
lib/attachmentnoticesection.php [new file with mode: 0644]
lib/attachmenttagcloudsection.php [new file with mode: 0644]
lib/channel.php
lib/clienterroraction.php
lib/command.php
lib/commandinterpreter.php
lib/common.php
lib/currentuserdesignaction.php [new file with mode: 0644]
lib/daemon.php
lib/dberroraction.php
lib/dbqueuemanager.php [new file with mode: 0644]
lib/designsettings.php [new file with mode: 0644]
lib/error.php
lib/event.php
lib/facebookaction.php
lib/facebookutil.php
lib/form.php
lib/galleryaction.php
lib/groupdesignaction.php [new file with mode: 0644]
lib/groupeditform.php
lib/grouplist.php
lib/groupnav.php
lib/grouptagcloudsection.php
lib/imagefile.php
lib/jabber.php
lib/jsonsearchresultslist.php
lib/language.php
lib/mail.php
lib/mailbox.php
lib/noticeform.php
lib/noticelist.php
lib/noticesection.php
lib/oauthstore.php
lib/omb.php
lib/openid.php
lib/ownerdesignaction.php [new file with mode: 0644]
lib/peoplesearchresults.php [deleted file]
lib/personal.php [deleted file]
lib/ping.php
lib/popularnoticesection.php
lib/profileaction.php
lib/profilelist.php
lib/profileminilist.php
lib/profilesection.php
lib/queuehandler.php
lib/queuemanager.php [new file with mode: 0644]
lib/router.php
lib/rssaction.php
lib/search_engines.php
lib/searchaction.php
lib/servererroraction.php
lib/settingsaction.php
lib/snapshot.php [new file with mode: 0644]
lib/stompqueuemanager.php [new file with mode: 0644]
lib/stream.php [deleted file]
lib/subgroupnav.php
lib/subs.php
lib/subscriptionlist.php [new file with mode: 0644]
lib/tagcloudsection.php
lib/theme.php
lib/twitter.php
lib/twitterapi.php
lib/unqueuemanager.php [new file with mode: 0644]
lib/util.php
lib/webcolor.php [new file with mode: 0644]
lib/xmppqueuehandler.php
plugins/FBConnect/FBCLoginGroupNav.php [new file with mode: 0644]
plugins/FBConnect/FBCSettingsNav.php [new file with mode: 0644]
plugins/FBConnect/FBC_XDReceiver.php [new file with mode: 0644]
plugins/FBConnect/FBConnectAuth.php [new file with mode: 0644]
plugins/FBConnect/FBConnectLogin.php [new file with mode: 0644]
plugins/FBConnect/FBConnectPlugin.css [new file with mode: 0644]
plugins/FBConnect/FBConnectPlugin.php [new file with mode: 0644]
plugins/FBConnect/FBConnectSettings.php [new file with mode: 0644]
plugins/FBConnect/fbfavicon.ico [new file with mode: 0644]
scripts/allsites.php [new file with mode: 0755]
scripts/commandline.inc [new file with mode: 0644]
scripts/decache.php
scripts/delete_status_network.sh [new file with mode: 0755]
scripts/enjitqueuehandler.php
scripts/facebookqueuehandler.php
scripts/fixup_conversations.php [new file with mode: 0755]
scripts/fixup_hashtags.php
scripts/fixup_inboxes.php
scripts/fixup_notices_rendered.php
scripts/fixup_replies.php
scripts/fixup_utf8.php
scripts/getpiddir.php
scripts/getvaliddaemons.php
scripts/inbox_users.php
scripts/jabberqueuehandler.php
scripts/maildaemon.php
scripts/ombqueuehandler.php
scripts/pingqueuehandler.php
scripts/publicqueuehandler.php
scripts/reportsnapshot.php [new file with mode: 0644]
scripts/setpassword.php
scripts/setup.cfg.sample [new file with mode: 0644]
scripts/setup_status_network.sh [new file with mode: 0755]
scripts/showcache.php [new file with mode: 0644]
scripts/sitemap.php
scripts/smsqueuehandler.php
scripts/sphinx-cron.sh
scripts/sphinx-indexer.sh
scripts/startdaemons.sh
scripts/stopdaemons.sh
scripts/synctwitterfriends.php
scripts/triminboxes.php
scripts/twitterqueuehandler.php
scripts/twitterstatusfetcher.php [new file with mode: 0755]
scripts/uncache_users.php
scripts/update_translations.php
scripts/xmppconfirmhandler.php
scripts/xmppdaemon.php
sphinx.conf.sample
theme/base/css/display.css
theme/base/css/facebookapp.css
theme/base/css/farbtastic.css [new file with mode: 0644]
theme/base/css/ie.css
theme/base/css/ie6.css
theme/base/default-avatar-mini.png [new file with mode: 0644]
theme/base/default-avatar-profile.png [new file with mode: 0644]
theme/base/default-avatar-stream.png [new file with mode: 0644]
theme/base/images/icons/twotone/green/admin.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/arrow-left.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/arrow-right.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/clip-01.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/clip-02.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/disfavourite.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/edit.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/favourite.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/mail.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/news.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/quote.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/reply.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/shield.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/trash.gif [new file with mode: 0644]
theme/base/logo.png [new file with mode: 0644]
theme/biz/css/base.css [new file with mode: 0644]
theme/biz/css/display.css [new file with mode: 0644]
theme/biz/css/ie.css [new file with mode: 0644]
theme/biz/default-avatar-mini.png [new file with mode: 0644]
theme/biz/default-avatar-profile.png [new file with mode: 0644]
theme/biz/default-avatar-stream.png [new file with mode: 0644]
theme/biz/images/illustrations/illu_pattern-01.png [new file with mode: 0644]
theme/biz/images/illustrations/illu_pattern-02.png [new file with mode: 0644]
theme/biz/logo.png [new file with mode: 0644]
theme/cloudy/css/display.css [new file with mode: 0644]
theme/cloudy/css/ie.css [new file with mode: 0644]
theme/cloudy/default-avatar-mini.png [new file with mode: 0644]
theme/cloudy/default-avatar-profile.png [new file with mode: 0644]
theme/cloudy/default-avatar-stream.png [new file with mode: 0644]
theme/cloudy/images/icons/icon_atom.png [new file with mode: 0644]
theme/cloudy/images/icons/icon_disfavourite.gif [new file with mode: 0644]
theme/cloudy/images/icons/icon_favourite.gif [new file with mode: 0644]
theme/cloudy/images/icons/icon_foaf.gif [new file with mode: 0644]
theme/cloudy/images/icons/icon_processing.gif [new file with mode: 0644]
theme/cloudy/images/icons/icon_reply.gif [new file with mode: 0644]
theme/cloudy/images/icons/icon_rss.png [new file with mode: 0644]
theme/cloudy/images/icons/icon_trash.gif [new file with mode: 0644]
theme/cloudy/images/icons/icon_vcard.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/arrow-left.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/arrow-right.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/edit.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/mail.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/news.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/quote.gif [new file with mode: 0644]
theme/cloudy/images/icons/twotone/green/shield.gif [new file with mode: 0644]
theme/cloudy/images/illustrations/illu_arrow-up-01.gif [new file with mode: 0644]
theme/cloudy/images/illustrations/illu_clouds-01.gif [new file with mode: 0644]
theme/cloudy/images/illustrations/illu_jcrop.gif [new file with mode: 0644]
theme/cloudy/images/illustrations/illu_progress_loading-01.gif [new file with mode: 0644]
theme/cloudy/images/illustrations/illu_unicorn-01.png [new file with mode: 0644]
theme/cloudy/logo.png [new file with mode: 0644]
theme/default/css/display.css
theme/default/css/ie.css
theme/default/images/icons/icon_atom.jpg [deleted file]
theme/default/images/icons/icon_foaf.gif [deleted file]
theme/default/images/icons/icon_rss.jpg [deleted file]
theme/default/images/icons/icon_vcard.gif [deleted file]
theme/default/images/icons/twotone/green/against.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-down.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-downleft.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-downright.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-left.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-right.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-up.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-upleft.gif [deleted file]
theme/default/images/icons/twotone/green/arrow-upright.gif [deleted file]
theme/default/images/icons/twotone/green/back-forth.gif [deleted file]
theme/default/images/icons/twotone/green/bookmark.gif [deleted file]
theme/default/images/icons/twotone/green/bulb.gif [deleted file]
theme/default/images/icons/twotone/green/calendar.gif [deleted file]
theme/default/images/icons/twotone/green/calendar2.gif [deleted file]
theme/default/images/icons/twotone/green/camera.gif [deleted file]
theme/default/images/icons/twotone/green/cart.gif [deleted file]
theme/default/images/icons/twotone/green/caution.gif [deleted file]
theme/default/images/icons/twotone/green/chart.gif [deleted file]
theme/default/images/icons/twotone/green/checkmark.gif [deleted file]
theme/default/images/icons/twotone/green/clipboard.gif [deleted file]
theme/default/images/icons/twotone/green/clock.gif [deleted file]
theme/default/images/icons/twotone/green/closed-folder.gif [deleted file]
theme/default/images/icons/twotone/green/database.gif [deleted file]
theme/default/images/icons/twotone/green/disfavourite.gif [deleted file]
theme/default/images/icons/twotone/green/diskette.gif [deleted file]
theme/default/images/icons/twotone/green/document.gif [deleted file]
theme/default/images/icons/twotone/green/double-arrow.gif [deleted file]
theme/default/images/icons/twotone/green/edit.gif [deleted file]
theme/default/images/icons/twotone/green/eject.gif [deleted file]
theme/default/images/icons/twotone/green/exclaim.gif [deleted file]
theme/default/images/icons/twotone/green/fastforward.gif [deleted file]
theme/default/images/icons/twotone/green/favourite.gif [deleted file]
theme/default/images/icons/twotone/green/flag.gif [deleted file]
theme/default/images/icons/twotone/green/graph.gif [deleted file]
theme/default/images/icons/twotone/green/grow.gif [deleted file]
theme/default/images/icons/twotone/green/headphones.gif [deleted file]
theme/default/images/icons/twotone/green/home.gif [deleted file]
theme/default/images/icons/twotone/green/hourglass.gif [deleted file]
theme/default/images/icons/twotone/green/info.gif [deleted file]
theme/default/images/icons/twotone/green/key.gif [deleted file]
theme/default/images/icons/twotone/green/lock.gif [deleted file]
theme/default/images/icons/twotone/green/mail.gif [deleted file]
theme/default/images/icons/twotone/green/move.gif [deleted file]
theme/default/images/icons/twotone/green/music.gif [deleted file]
theme/default/images/icons/twotone/green/news.gif [deleted file]
theme/default/images/icons/twotone/green/note.gif [deleted file]
theme/default/images/icons/twotone/green/open-folder.gif [deleted file]
theme/default/images/icons/twotone/green/paper-clip.gif [deleted file]
theme/default/images/icons/twotone/green/paper-clip2.gif [deleted file]
theme/default/images/icons/twotone/green/pause.gif [deleted file]
theme/default/images/icons/twotone/green/phone.gif [deleted file]
theme/default/images/icons/twotone/green/play.gif [deleted file]
theme/default/images/icons/twotone/green/plus.gif [deleted file]
theme/default/images/icons/twotone/green/print.gif [deleted file]
theme/default/images/icons/twotone/green/question-mark.gif [deleted file]
theme/default/images/icons/twotone/green/quote.gif [deleted file]
theme/default/images/icons/twotone/green/refresh.gif [deleted file]
theme/default/images/icons/twotone/green/reply.gif [deleted file]
theme/default/images/icons/twotone/green/rewind.gif [deleted file]
theme/default/images/icons/twotone/green/search.gif [deleted file]
theme/default/images/icons/twotone/green/shield.gif [deleted file]
theme/default/images/icons/twotone/green/skip-back.gif [deleted file]
theme/default/images/icons/twotone/green/skip.gif [deleted file]
theme/default/images/icons/twotone/green/skull.gif [deleted file]
theme/default/images/icons/twotone/green/statusbar.gif [deleted file]
theme/default/images/icons/twotone/green/stop.gif [deleted file]
theme/default/images/icons/twotone/green/template.gif [deleted file]
theme/default/images/icons/twotone/green/text-bigger.gif [deleted file]
theme/default/images/icons/twotone/green/text-smaller.gif [deleted file]
theme/default/images/icons/twotone/green/trash.gif [deleted file]
theme/default/images/icons/twotone/green/two-docs.gif [deleted file]
theme/default/images/icons/twotone/green/twotone.gif [deleted file]
theme/default/images/icons/twotone/green/undo.gif [deleted file]
theme/default/images/icons/twotone/green/user.gif [deleted file]
theme/default/images/icons/twotone/green/vegetable.gif [deleted file]
theme/default/images/icons/twotone/green/x.gif [deleted file]
theme/default/images/icons/twotone/green/zoom-in.gif [deleted file]
theme/default/images/icons/twotone/green/zoom-out.gif [deleted file]
theme/default/logo.png [new file with mode: 0644]
theme/h4ck3r/css/base.css [new file with mode: 0644]
theme/h4ck3r/css/display.css [new file with mode: 0644]
theme/h4ck3r/css/ie.css [new file with mode: 0644]
theme/h4ck3r/default-avatar-mini.png [new file with mode: 0644]
theme/h4ck3r/default-avatar-profile.png [new file with mode: 0644]
theme/h4ck3r/default-avatar-stream.png [new file with mode: 0644]
theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif [new file with mode: 0644]
theme/h4ck3r/logo.png [new file with mode: 0644]
theme/identica/css/display.css
theme/identica/css/ie.css
theme/identica/images/icons/icon_atom.jpg [deleted file]
theme/identica/images/icons/icon_foaf.gif [deleted file]
theme/identica/images/icons/icon_rss.jpg [deleted file]
theme/identica/images/icons/icon_vcard.gif [deleted file]
theme/identica/images/icons/twotone/green/against.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-down.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-downleft.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-downright.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-left.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-right.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-up.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-upleft.gif [deleted file]
theme/identica/images/icons/twotone/green/arrow-upright.gif [deleted file]
theme/identica/images/icons/twotone/green/back-forth.gif [deleted file]
theme/identica/images/icons/twotone/green/bookmark.gif [deleted file]
theme/identica/images/icons/twotone/green/bulb.gif [deleted file]
theme/identica/images/icons/twotone/green/calendar.gif [deleted file]
theme/identica/images/icons/twotone/green/calendar2.gif [deleted file]
theme/identica/images/icons/twotone/green/camera.gif [deleted file]
theme/identica/images/icons/twotone/green/cart.gif [deleted file]
theme/identica/images/icons/twotone/green/caution.gif [deleted file]
theme/identica/images/icons/twotone/green/chart.gif [deleted file]
theme/identica/images/icons/twotone/green/checkmark.gif [deleted file]
theme/identica/images/icons/twotone/green/clipboard.gif [deleted file]
theme/identica/images/icons/twotone/green/clock.gif [deleted file]
theme/identica/images/icons/twotone/green/closed-folder.gif [deleted file]
theme/identica/images/icons/twotone/green/database.gif [deleted file]
theme/identica/images/icons/twotone/green/disfavourite.gif [deleted file]
theme/identica/images/icons/twotone/green/diskette.gif [deleted file]
theme/identica/images/icons/twotone/green/document.gif [deleted file]
theme/identica/images/icons/twotone/green/double-arrow.gif [deleted file]
theme/identica/images/icons/twotone/green/edit.gif [deleted file]
theme/identica/images/icons/twotone/green/eject.gif [deleted file]
theme/identica/images/icons/twotone/green/exclaim.gif [deleted file]
theme/identica/images/icons/twotone/green/fastforward.gif [deleted file]
theme/identica/images/icons/twotone/green/favourite.gif [deleted file]
theme/identica/images/icons/twotone/green/flag.gif [deleted file]
theme/identica/images/icons/twotone/green/graph.gif [deleted file]
theme/identica/images/icons/twotone/green/grow.gif [deleted file]
theme/identica/images/icons/twotone/green/headphones.gif [deleted file]
theme/identica/images/icons/twotone/green/home.gif [deleted file]
theme/identica/images/icons/twotone/green/hourglass.gif [deleted file]
theme/identica/images/icons/twotone/green/info.gif [deleted file]
theme/identica/images/icons/twotone/green/key.gif [deleted file]
theme/identica/images/icons/twotone/green/lock.gif [deleted file]
theme/identica/images/icons/twotone/green/mail.gif [deleted file]
theme/identica/images/icons/twotone/green/move.gif [deleted file]
theme/identica/images/icons/twotone/green/music.gif [deleted file]
theme/identica/images/icons/twotone/green/news.gif [deleted file]
theme/identica/images/icons/twotone/green/note.gif [deleted file]
theme/identica/images/icons/twotone/green/open-folder.gif [deleted file]
theme/identica/images/icons/twotone/green/paper-clip.gif [deleted file]
theme/identica/images/icons/twotone/green/paper-clip2.gif [deleted file]
theme/identica/images/icons/twotone/green/pause.gif [deleted file]
theme/identica/images/icons/twotone/green/phone.gif [deleted file]
theme/identica/images/icons/twotone/green/play.gif [deleted file]
theme/identica/images/icons/twotone/green/plus.gif [deleted file]
theme/identica/images/icons/twotone/green/print.gif [deleted file]
theme/identica/images/icons/twotone/green/question-mark.gif [deleted file]
theme/identica/images/icons/twotone/green/quote.gif [deleted file]
theme/identica/images/icons/twotone/green/refresh.gif [deleted file]
theme/identica/images/icons/twotone/green/reply.gif [deleted file]
theme/identica/images/icons/twotone/green/rewind.gif [deleted file]
theme/identica/images/icons/twotone/green/search.gif [deleted file]
theme/identica/images/icons/twotone/green/shield.gif [deleted file]
theme/identica/images/icons/twotone/green/skip-back.gif [deleted file]
theme/identica/images/icons/twotone/green/skip.gif [deleted file]
theme/identica/images/icons/twotone/green/skull.gif [deleted file]
theme/identica/images/icons/twotone/green/statusbar.gif [deleted file]
theme/identica/images/icons/twotone/green/stop.gif [deleted file]
theme/identica/images/icons/twotone/green/template.gif [deleted file]
theme/identica/images/icons/twotone/green/text-bigger.gif [deleted file]
theme/identica/images/icons/twotone/green/text-smaller.gif [deleted file]
theme/identica/images/icons/twotone/green/trash.gif [deleted file]
theme/identica/images/icons/twotone/green/two-docs.gif [deleted file]
theme/identica/images/icons/twotone/green/twotone.gif [deleted file]
theme/identica/images/icons/twotone/green/undo.gif [deleted file]
theme/identica/images/icons/twotone/green/user.gif [deleted file]
theme/identica/images/icons/twotone/green/vegetable.gif [deleted file]
theme/identica/images/icons/twotone/green/x.gif [deleted file]
theme/identica/images/icons/twotone/green/zoom-in.gif [deleted file]
theme/identica/images/icons/twotone/green/zoom-out.gif [deleted file]
theme/otalk/css/base.css [new file with mode: 0644]
theme/otalk/css/display.css [new file with mode: 0644]
theme/otalk/css/ie.css [new file with mode: 0644]
theme/otalk/default-avatar-mini.png [new file with mode: 0644]
theme/otalk/default-avatar-profile.png [new file with mode: 0644]
theme/otalk/default-avatar-stream.png [new file with mode: 0644]
theme/otalk/images/illustrations/illu_arrow-left-01.gif [new file with mode: 0644]
theme/otalk/images/illustrations/illu_pattern-01.png [new file with mode: 0644]
theme/otalk/logo.png [new file with mode: 0644]
theme/pigeonthoughts/css/base.css [new file with mode: 0644]
theme/pigeonthoughts/css/display.css [new file with mode: 0644]
theme/pigeonthoughts/css/ie.css [new file with mode: 0644]
theme/pigeonthoughts/default-avatar-mini.png [new file with mode: 0644]
theme/pigeonthoughts/default-avatar-profile.png [new file with mode: 0644]
theme/pigeonthoughts/default-avatar-stream.png [new file with mode: 0644]
theme/pigeonthoughts/images/illustrations/illu_pigeons-01.png [new file with mode: 0644]
theme/pigeonthoughts/images/illustrations/illu_pigeons-02.png [new file with mode: 0644]
theme/pigeonthoughts/logo.png [new file with mode: 0644]
theme/readme.txt

index da6947bfdc3f27978ab534a07be6a96a4797a6ef..f4c2bba5f7af504503ab4a03fa2d3faf50d99b2e 100644 (file)
@@ -1,5 +1,7 @@
 avatar/*
+background/*
 files/*
+file/*
 _darcs/*
 logs/*
 config.php
@@ -16,3 +18,9 @@ dataobject.ini
 .buildpath
 .project
 .settings
+TODO.rym
+config-*.php
+good-config.php
+lac08.log
+php.log
+config.php.*
index e0ce1166679a90f4e5c6289d9bc7f8dafaa94a13..2c43469d4642c3e8a9edc5f1bd5f6da2fd33831c 100644 (file)
@@ -1,5 +1,4 @@
-InitializePlugin: a chance to initialize a plugin in a complete
-                 environment
+InitializePlugin: a chance to initialize a plugin in a complete environment
 
 CleanupPlugin: a chance to cleanup a plugin at the end of a program
 
@@ -109,5 +108,17 @@ EndSubGroupNav: At the end of the subscriptions group nav menu
 RouterInitialized: After the router instance has been initialized
 - $m: the Net_URL_Mapper that has just been set up
 
+StartLogout: Before logging out
+- $action: the logout action
+
+EndLogout: After logging out
+- $action: the logout action
+
 ArgsInitialized: After the argument array has been initialized
 - $args: associative array of arguments, can be modified
+
+StartAddressData: Allows the site owner to provide additional information about themselves for contact (e.g., tagline, email, location)
+- $action: the current action
+
+EndAddressData: At the end of <address>
+- $action: the current action
diff --git a/README b/README
index 679a096b3bad6df536ced4c887432b3469cb90d3..40e5263550ef3c563c35566ae934e3c975b477b6 100644 (file)
--- a/README
+++ b/README
@@ -2,8 +2,8 @@
 README
 ------
 
-Laconica 0.7.4 ("Can't Get There From Here")
-29 May 2009
+Laconica 0.8.0 ("Shiny Happy People")
+8 July 2009
 
 This is the README file for Laconica, the Open Source microblogging
 platform. It includes installation instructions, descriptions of
@@ -71,29 +71,52 @@ for additional terms.
 New this version
 ================
 
-This is a minor bug-fix and feature release since version 0.7.3,
-released Apr 4 2009. Notable changes this version:
-
-- Improved handling of UTF-8 characters. The new code is *not* backwards
-  compatible by default; see "Upgrading" below for instructions on
-  converting existing databases to the correct character set.
-- Unroll joins for large queries. This greatly enhanced database
-  performance -- up to 50x for some queries -- at the expense of making
-  an extra DB hit for some queries.
-- Added an optional plugin to use WikiHashtags
-  (http://hashtags.wikia.com/) for the sidebar on hashtag pages.
-- Optimized Twitter friend synchronization.
-- Better error handling for Ajax posting of notices, including
-  HTTP errors and timeouts.
-- Experimental Comet plugin -- supports the cometd and the Bayeux
-  protocol. Using this plugin, you can show "real time" updates on the
-  public and tag pages. However, server configuration is complex.
-- If queues are enabled, update inboxes and memcached off-line. Speeds
-  up posting considerably.
-- Correctly shorten links posted through XMPP.
-- <link> elements for pagination, supported by some browsers like Opera.
-- Corrected date format in search API.
-- Made the public XRDS file work correctly.
+This is a major feature release since version 0.7.4, released May 31
+2009. Notable changes this version:
+
+- Support for a hosted service (status network). Multiple sites can
+  share the same codebase but use different databases.
+- OEmbed. Links to pages that support OEmbed (http://www.oembed.com/)
+  become popup links, and the media are shown in a special lightbox.
+- File attachments. Users can attach files of the size and type approved
+  by an administrator, and a shortened link will be included in the
+  notice.
+- Related notices are organized into conversations, with each reply a
+  branch in a tree. Conversations have pages and are linked to from each
+  notice in the conversation.
+- User designs. Users can specify colours and backgrounds
+  for their profile pages and other "personal" pages.
+- Group designs. Group administrators can specify similar designs for
+  group profiles and related pages.
+- Site designs. Site authors can specify a design (background and
+  colors) for the site.
+- New themes. Five new themes are added to the base release; these show
+  off the flexibility of Laconica's theming system.
+- Statistics. Public sites will periodically send usage statistics,
+  configuration options, and dependency information to Laconica dev site.
+  This will help us understand how the software is used and plan future
+  versions of the software.
+- Additional hooks. The hooks and plugins system introduced in 0.7.x was
+  expanded with additional points of access.
+- Facebook Connect. A new plugin allows logging in with Facebook Connect
+  (http://developers.facebook.com/connect.php).
+- A session handler. A new optional session handler class to manage PHP
+  sessions reliably and quickly for large sites.
+- STOMP queuing. Queue management for offline daemons has been
+  abstracted with three concrete instances. A new interface that should
+  work with STOMP servers like ActiveMQ and RabbitMQ is available, which
+  should make things scale better.
+- Group block. Group admins can block users from joining or posting to
+  a group.
+- Group aliases. Groups can be referred to with aliases, additional
+  names. For example, "!yul" and "!montreal" can be the same group.
+- Bidirectional Twitter bridge. Users can read the tweets their Twitter
+  friends post on Twitter.
+- Adaptation of WordPress.com Terms of Service (http://en.wordpress.com/tos/)
+  as default TOS for Laconica sites.
+- Better command-line handling for scripts, including standard options
+  and ability to set hostname and path from the command line.
+- Many, many bug fixes.
 
 Prerequisites
 =============
@@ -176,6 +199,11 @@ and the URLs are listed here for your convenience.
   version may render your Laconica site unable to send or receive XMPP
   messages.
 - Facebook library. Used for the Facebook application.
+- PEAR Services_oEmbed. Used for some multimedia integration.
+- PEAR HTTP_Request is an oEmbed dependency.
+- PEAR Validate is an oEmbed dependency.
+- PEAR Net_URL2 is an oEmbed dependency.
+- Console_GetOpt for parsing command-line options.
 
 A design goal of Laconica is that the basic Web functionality should
 work on even the most restrictive commercial hosting services.
@@ -193,9 +221,9 @@ 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.7.4.tar.gz
+          tar zxf laconica-0.8.0.tar.gz
 
-   ...which will make a laconica-0.7.4 subdirectory in your current
+   ...which will make a laconica-0.8.0 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.)
@@ -203,7 +231,7 @@ especially if you've previously installed PHP/MySQL packages.
 2. Move the tarball to a directory of your choosing in your Web root
    directory. Usually something like this will work:
 
-          mv laconica-0.7.4 /var/www/mublog
+          mv laconica-0.8.0 /var/www/mublog
 
    This will make your Laconica instance available in the mublog path of
    your server, like "http://example.net/mublog". "microblog" or
@@ -507,6 +535,11 @@ All the daemons write their process IDs (pids) to /var/run/ by
 default. This can be useful for starting, stopping, and monitoring the
 daemons.
 
+With version 0.8.0, it's now possible to use a STOMP server instead of
+our kind of hacky home-grown DB-based queue solution. See the "queues"
+config section below for how to configure to use STOMP. As of this
+writing, the software has been tested with ActiveMQ (
+
 Twitter Friends Syncing
 -----------------------
 
@@ -697,11 +730,11 @@ However, older installations will have the incorrect storage, and will
 consequently show up "wrong" in browsers. See below for how to deal
 with this situation.
 
-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.7.4. Try these step-by-step instructions; read
-to the end first before trying them.
+If you've been using Laconica 0.7, 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.8.0. 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.
@@ -721,20 +754,31 @@ to the end first before trying them.
 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
+7. Unpack your Laconica 0.8.0 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. For MySQL, go to your Laconica directory and
-    run the rebuilddb.sh script like this:
+10. Rebuild the database. NOTE: this step is destructive and cannot be
+    reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't
+    do it without a known-good backup!
+
+    If your database is at version 0.7.4, you can run a special upgrade
+    script:
+
+    mysql -u<rootuser> -p<rootpassword> <database> db/074to080.sql
+
+    Otherwise, go to your Laconica directory and AFTER YOU MAKE A
+    BACKUP 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.
+    that's _not_ the user Laconica runs as. Note that rebuilddb.sh drops
+    your database and rebuilds it; if there is an error you have no
+    database. Make sure you have a backup.
     For PostgreSQL databases there is an equivalent, rebuilddb_psql.sh,
     which operates slightly differently. Read the documentation in that
     script before running it.
@@ -786,6 +830,9 @@ problem.
 3. When fixup_inboxes is finished, you can set the enabled flag to
    'true'.
 
+NOTE: we will drop support for non-inboxed sites in the 0.9.x version
+of Laconica. It's time to switch now!
+
 UTF-8 Database
 --------------
 
@@ -812,7 +859,7 @@ what to do.
 Configuration options
 =====================
 
-The sole configuration file for Laconica (excepting configurations for
+The main 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
@@ -850,6 +897,8 @@ fancy: whether or not your site uses fancy URLs (see Fancy URLs
 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.
+logdebug: whether to log additional debug info like backtraces on
+          hard errors. Default false.
 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.
@@ -904,6 +953,15 @@ sslserver: use an alternate server name for SSL URLs, like
 shorturllength: Length of URL at which URLs in a message exceeding 140
                 characters will be sent to the user's chosen
                 shortening service.
+<<<<<<< HEAD:README
+design: a default design (colors and background) for the site.
+        Sub-items are: backgroundcolor, contentcolor, sidebarcolor,
+        textcolor, linkcolor, backgroundimage, disposition.
+dupelimit: minimum time allowed for one person to say the same thing
+           twice. Default 60s. Anything lower is considered a user
+           or UI error.
+=======
+>>>>>>> 0.7.x:README
 
 db
 --
@@ -954,6 +1012,10 @@ 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.
+priority: level to log at. Currently ignored.
+facility: what syslog facility to used. Defaults to LOG_USER, only
+          reset if you know what syslog is and have a good reason
+          to change it.
 
 queue
 -----
@@ -963,7 +1025,19 @@ 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.
-
+subsystem: Which kind of queueserver to use. Values include "db" for
+           our hacked-together database queuing (no other server
+           required) and "stomp" for a stomp server.
+stomp_server: "broker URI" for stomp server. Something like
+              "tcp://hostname:61613". More complicated ones are
+              possible; see your stomp server's documentation for
+              details.
+queue_basename: a root name to use for queues (stomp only). Typically
+                something like '/queue/sitename/' makes sense.
+stomp_username: username for connecting to the stomp server; defaults
+                to null.
+stomp_password: password for connecting to the stomp server; defaults
+                to null.
 license
 -------
 
@@ -1007,6 +1081,12 @@ avatar
 
 For configuring avatar access.
 
+dir:    Directory to look for avatar files and to put them into.
+       Defaults to avatar subdirectory of install directory; if
+       you change it, make sure to change path, too.
+path:  Path to avatars. Defaults to path for avatar subdirectory,
+       but you can change it if you wish. Note that this will
+       be included with the avatar server, too.
 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
@@ -1027,14 +1107,23 @@ localonly: If set to true, only messages posted by users of this
 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.
+autosource: Sources of notices that are from automatic posters, and thus
+            should be kept off the public timeline. Default empty.
 
 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.
+       theme file lookup to another server (virtual or real).
+       Defaults to NULL, meaning to use the site server.
+dir:    Directory where theme files are stored. Used to determine
+       whether to show parts of a theme file. Defaults to the theme
+       subdirectory of the install directory.
+path:  Path part of theme URLs, before the theme name. Relative to the
+       theme server. It may make sense to change this path when upgrading,
+       (using version numbers as the path) to make sure that all files are
+       reloaded by caching clients or proxies. Defaults to null,
+       which means to use the site path + '/theme'.
 
 xmpp
 ----
@@ -1066,6 +1155,13 @@ debug: if turned on, this will make the XMPP library blurt out all of
 public: an array of JIDs to send _all_ notices to. This is useful for
        participating in third-party search and archiving services.
 
+invite
+------
+
+For configuring invites.
+
+enabled: Whether to allow users to send invites. Default true.
+
 tag
 ---
 
@@ -1075,6 +1171,15 @@ 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.
 
+popular
+-------
+
+Settings for the "popular" section of the site.
+
+dropoff: Decay factor for popularity 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
 ------
 
@@ -1099,6 +1204,13 @@ 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.
+base: memcached uses key-value pairs to store data. We build long,
+      funny-looking keys to make sure we don't have any conflicts. The
+      base of the key is usually a simplified version of the site name
+      (like "Identi.ca" => "identica"), but you can overwrite this if
+      you need to. You can safely ignore it if you only have one
+      Laconica site using your memcached server.
+port: Port to connect to; defaults to 11211.
 
 sphinx
 ------
@@ -1121,6 +1233,7 @@ source: The name to use for the source of posts to Twitter. Defaults
        Twitter <http://twitter.com/help/request_source>, you can use
        that here instead. Status updates on Twitter will then have
        links to your site.
+taguri: base for tag:// URIs. Defaults to site-server + ',2009'.
 
 inboxes
 -------
@@ -1169,6 +1282,148 @@ welcome: nickname of a user account that sends welcome messages to new
 If either of these special user accounts are specified, the users should
 be created before the configuration is updated.
 
+snapshot
+--------
+
+The software will, by default, send statistical snapshots about the
+local installation to a stats server on the laconi.ca Web site. This
+data is used by the developers to prioritize development decisions. No
+identifying data about users or organizations is collected. The data
+is available to the public for review. Participating in this survey
+helps Laconica developers take your needs into account when updating
+the software.
+
+run: string indicating when to run the statistics. Values can be 'web'
+     (run occasionally at Web time), 'cron' (run from a cron script),
+     or 'never' (don't ever run). If you set it to 'cron', remember to
+     schedule the script to run on a regular basis.
+frequency: if run value is 'web', how often to report statistics.
+           Measured in Web hits; depends on how active your site is.
+           Default is 10000 -- that is, one report every 10000 Web hits,
+           on average.
+reporturl: URL to post statistics to. Defaults to Laconica developers'
+           report system, but if they go evil or disappear you may
+           need to update this to another value. Note: if you
+           don't want to report stats, it's much better to
+           set 'run' to 'never' than to set this value to something
+           nonsensical.
+
+attachments
+-----------
+
+The software lets users upload files with their notices. You can configure
+the types of accepted files by mime types and a trio of quota options:
+per file, per user (total), per user per month.
+
+We suggest the use of the pecl file_info extension to handle mime type
+detection.
+
+supported: an array of mime types you accept to store and distribute,
+           like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
+           setup your server to properly recognize the types you want to
+           support.
+uploads:   false to disable uploading files with notices (true by default).
+filecommand: The required MIME_Type library may need to use the 'file'
+            command. It tries the one in the Web server's path, but if
+            you're having problems with uploads, try setting this to the
+            correct value. Note: 'file' must accept '-b' and '-i' options.
+
+For quotas, be sure you've set the upload_max_filesize and post_max_size
+in php.ini to be large enough to handle your upload. In httpd.conf
+(if you're using apache), check that the LimitRequestBody directive isn't
+set too low (it's optional, so it may not be there at all).
+
+file_quota: maximum size for a single file upload in bytes. A user can send
+            any amount of notices with attachments as long as each attachment
+            is smaller than file_quota.
+user_quota: total size in bytes a user can store on this server. Each user
+            can store any number of files as long as their total size does
+            not exceed the user_quota.
+monthly_quota: total size permitted in the current month. This is the total
+            size in bytes that a user can upload each month.
+dir: directory accessible to the Web process where uploads should go.
+     Defaults to the 'file' subdirectory of the install directory, which
+     should be writeable by the Web user.
+server: server name to use when creating URLs for uploaded files.
+        Defaults to null, meaning to use the default Web server. Using
+        a virtual server here can speed up Web performance.
+path: URL path, relative to the server, to find files. Defaults to
+      main path + '/file/'.
+filecommand: command to use for determining the type of a file. May be
+             skipped if fileinfo extension is installed. Defaults to
+             '/usr/bin/file'.
+
+group
+-----
+
+Options for group functionality.
+
+maxaliases: maximum number of aliases a group can have. Default 3. Set
+            to 0 or less to prevent aliases in a group.
+
+oohembed
+--------
+
+oEmbed endpoint for multimedia attachments (links in posts).
+
+endpoint: oohembed endpoint using http://oohembed.com/ software.
+
+search
+------
+
+Some stuff for search.
+
+type: type of search. Ignored if PostgreSQL or Sphinx are enabled. Can either
+      be 'fulltext' (default) or 'like'. The former is faster and more efficient
+      but requires the lame old MyISAM engine for MySQL. The latter
+      will work with InnoDB but could be miserably slow on large
+      systems. We'll probably add another type sometime in the future,
+      with our own indexing system (maybe like MediaWiki's).
+
+sessions
+--------
+
+Session handling.
+
+handle: boolean. Whether we should register our own PHP session-handling
+       code (using the database and memcache if enabled). Defaults to false.
+       Setting this to true makes some sense on large or multi-server
+       sites, but it probably won't hurt for smaller ones, either.
+debug: whether to output debugging info for session storage. Can help
+       with weird session bugs, sometimes. Default false.
+
+background
+----------
+
+Users can upload backgrounds for their pages; this section defines
+their use.
+
+server: the server to use for background. Using a separate (even
+        virtual) server for this can speed up load times. Default is
+        null; same as site server.
+dir: directory to write backgrounds too. Default is '/background/'
+     subdir of install dir.
+path: path to backgrounds. Default is sub-path of install path; note
+      that you may need to change this if you change site-path too.
+
+twitterbridge
+-------------
+
+A bi-direction bridge to Twitter (http://twitter.com/).
+
+enabled: default false. If true, will show user's Twitter friends'
+         notices in their inbox and faves pages, only to the user. You
+         must also run the twitterstatusfetcher.php script.
+
+ping
+----
+
+Using the "XML-RPC Ping" method initiated by weblogs.com, the site can
+notify third-party servers of updates.
+
+notify: an array of URLs for ping endpoints. Default is the empty
+        array (no notification).
+
 Troubleshooting
 ===============
 
@@ -1265,7 +1520,7 @@ if anyone's been overlooked in error.
 * Ori Avtalion
 * Meitar Moscovitz
 * Ken Sheppardson (Trac server, man-about-town)
-* Tiago 'gouki' Faria (i18n managerx)
+* Tiago 'gouki' Faria (i18n manager)
 * Sean Murphy
 * Leslie Michael Orchard
 * Eric Helgeson
@@ -1274,6 +1529,10 @@ if anyone's been overlooked in error.
 * Tobias Diekershoff
 * Dan Moore
 * Fil
+* Jeff Mitchell
+* Brenda Wallace
+* Jeffery To
+* Federico Marani
 
 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,
index 46b43c70216e106a5722434c38f365d56c0d3cc5..2a8cd17134c84833ae1446fa8957c35ac5e06031 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 03179a2468cfa0396f28e43e7f15c3ea4bf19764..f06ead2a8c4776f70bd89b5f8d20a9683ede9450 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -98,7 +98,13 @@ class AllAction extends ProfileAction
 
     function showContent()
     {
-        $notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        $cur = common_current_user();
+
+        if (!empty($cur) && $cur->id == $this->user->id) {
+            $notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        } else {
+            $notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        }
 
         $nl = new NoticeList($notice, $this);
 
index 45f3946a61cfda010765ae3b54ca94360dc4d0a7..885a67f6188af13b07a0ea7e6bf9a8302078e476 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -81,6 +81,14 @@ class AllrssAction extends Rss10Action
      */
     function getNotices($limit=0)
     {
+        $cur = common_current_user();
+
+        if (!empty($cur) && $cur->id == $user->id) {
+            $notice = $this->user->noticeInbox(0, $limit);
+        } else {
+            $notice = $this->user->noticesWithFriends(0, $limit);
+        }
+
         $user    = $this->user;
         $notice  = $user->noticesWithFriends(0, $limit);
         $notices = array();
index b8da852b536d469682f6fcee277894247696695e..18c3b68d4b336e2e6da9b66708f998dc11e9efa2 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -67,20 +67,22 @@ class ApiAction extends Action
                     $this->process_command();
                 } else {
                     # basic authentication failed
-                    common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname.");
+                    list($proxy, $ip) = common_client_ip();
+
+                    common_log(LOG_WARNING, "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip.");
                     $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
-                       }
+            // 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();
         }
@@ -115,7 +117,7 @@ class ApiAction extends Action
         }
     }
 
-    # Whitelist of API methods that don't need authentication
+    // Whitelist of API methods that don't need authentication
     function requires_auth()
     {
         static $noauth = array( 'statuses/public_timeline',
@@ -133,28 +135,61 @@ class ApiAction extends Action
                                  'statuses/replies',
                                  'statuses/mentions',
                                  'statuses/followers',
-                                 'favorites/favorites');
+                                 'favorites/favorites',
+                                 'friendships/show');
 
         $fullname = "$this->api_action/$this->api_method";
 
         // If the site is "private", all API methods except laconica/config
         // need authentication
+
         if (common_config('site', 'private')) {
             return $fullname != 'laconica/config' || false;
         }
 
+        // bareauth: only needs auth if without an argument or query param specifying user
+
         if (in_array($fullname, $bareauth)) {
-            # bareauth: only needs auth if without an argument or query param specifying user
-            if ($this->api_arg || $this->arg('id') || is_numeric($this->arg('user_id')) || $this->arg('screen_name')) {
+
+            // Special case: friendships/show only needs auth if source_id or
+            // source_screen_name is not specified as a param
+
+            if ($fullname == 'friendships/show') {
+
+                $source_id          = $this->arg('source_id');
+                $source_screen_name = $this->arg('source_screen_name');
+
+                if (empty($source_id) && empty($source_screen_name)) {
+                    return true;
+                }
+
                 return false;
-            } else {
+            }
+
+            // if all of these are empty, auth is required
+
+            $id          = $this->arg('id');
+            $user_id     = $this->arg('user_id');
+            $screen_name = $this->arg('screen_name');
+
+            if (empty($this->api_arg) &&
+                empty($id)            &&
+                empty($user_id)       &&
+                empty($screen_name)) {
                 return true;
+            } else {
+                return false;
             }
+
         } else if (in_array($fullname, $noauth)) {
-            # noauth: never needs auth
+
+            // noauth: never needs auth
+
             return false;
         } else {
-            # everybody else needs auth
+
+            // everybody else needs auth
+
             return true;
         }
     }
diff --git a/actions/attachment.php b/actions/attachment.php
new file mode 100644 (file)
index 0000000..ee4cd96
--- /dev/null
@@ -0,0 +1,172 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Show notice attachments
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Personal
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/attachmentlist.php';
+
+/**
+ * Show notice attachments
+ *
+ * @category Personal
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class AttachmentAction extends Action
+{
+    /**
+     * Attachment object to show
+     */
+
+    var $attachment = null;
+
+    /**
+     * Load attributes based on database arguments
+     *
+     * Loads all the DB stuff
+     *
+     * @param array $args $_REQUEST array
+     *
+     * @return success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if ($id = $this->trimmed('attachment')) {
+            $this->attachment = File::staticGet($id);
+        }
+
+        if (empty($this->attachment)) {
+            $this->clientError(_('No such attachment.'), 404);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Is this action read-only?
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string title of the page
+     */
+    function title()
+    {
+        $a = new Attachment($this->attachment);
+        return $a->title();
+    }
+
+    /**
+     * Handle input
+     *
+     * Only handles get, so just show the page.
+     *
+     * @param array $args $_REQUEST data (unused)
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (empty($this->attachment->filename)) {
+
+            // if it's not a local file, gtfo
+
+            common_redirect($this->attachment->url, 303);
+
+        } else {
+            $this->showPage();
+        }
+    }
+
+    /**
+     * Don't show local navigation
+     *
+     * @return void
+     */
+
+    function showLocalNavBlock()
+    {
+    }
+
+    /**
+     * Fill the content area of the page
+     *
+     * Shows a single notice list item.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $ali = new Attachment($this->attachment, $this);
+        $cnt = $ali->show();
+    }
+
+    /**
+     * Don't show page notice
+     *
+     * @return void
+     */
+
+    function showPageNoticeBlock()
+    {
+    }
+
+    /**
+     * Show aside: this attachments appears in what notices
+     *
+     * @return void
+     */
+    function showSections() {
+        $ns = new AttachmentNoticeSection($this);
+        $ns->show();
+        $atcs = new AttachmentTagCloudSection($this);
+        $atcs->show();
+    }
+}
+
diff --git a/actions/attachment_ajax.php b/actions/attachment_ajax.php
new file mode 100644 (file)
index 0000000..4caa159
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Show notice attachments
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Personal
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/actions/attachment.php';
+
+/**
+ * Show notice attachments
+ *
+ * @category Personal
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class Attachment_ajaxAction extends AttachmentAction
+{
+    /**
+     * Show page, a template method.
+     *
+     * @return nothing
+     */
+    function showPage()
+    {
+        if (Event::handle('StartShowBody', array($this))) {
+            $this->showCore();
+            Event::handle('EndShowBody', array($this));
+        }
+    }
+
+    function handle($args)
+    {
+        $this->showPage();
+    }
+
+    /**
+     * Show core.
+     *
+     * Shows local navigation, content block and aside.
+     *
+     * @return nothing
+     */
+    function showCore()
+    {
+        $this->elementStart('div', array('id' => 'core'));
+        if (Event::handle('StartShowContentBlock', array($this))) {
+            $this->showContentBlock();
+            Event::handle('EndShowContentBlock', array($this));
+        }
+        $this->elementEnd('div');
+    }
+}
+
diff --git a/actions/attachment_thumbnail.php b/actions/attachment_thumbnail.php
new file mode 100644 (file)
index 0000000..248d16e
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Show notice attachments
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Personal
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/actions/attachment.php';
+
+/**
+ * Show notice attachments
+ *
+ * @category Personal
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class Attachment_thumbnailAction extends AttachmentAction
+{
+
+    function handle($args)
+    {
+        $this->showPage();
+    }
+
+    /**
+     * Show page, a template method.
+     *
+     * @return nothing
+     */
+    function showPage()
+    {
+        if (Event::handle('StartShowBody', array($this))) {
+            $this->showCore();
+            Event::handle('EndShowBody', array($this));
+        }
+    }
+
+    /**
+     * Show core.
+     *
+     * Shows local navigation, content block and aside.
+     *
+     * @return nothing
+     */
+    function showCore()
+    {
+        $file_thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
+        if (empty($file_thumbnail->url)) {
+            return;
+        }
+        $this->element('img', array('src' => $file_thumbnail->url, 'alt' => 'Thumbnail'));
+    }
+
+}
+
index e92a993722366ec1c9551857e11a369aec210f6f..3e615261fefbcd5272ca34433bbf936f607eea9d 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 0efee5932c101901d6945eb11d08681881d2f08f..06f92254e031b0e9cb52cfa34a8f71ffe3d4c3ca 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -125,16 +125,18 @@ class BlockAction extends Action
     function areYouSureForm()
     {
         $id = $this->profile->id;
+        $this->elementStart('form', array('id' => 'block-' . $id,
+                                           'method' => 'post',
+                                           'class' => 'form_settings form_entity_block',
+                                           'action' => common_local_url('block')));
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+        $this->element('legend', _('Block user'));
         $this->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.'));
-        $this->elementStart('form', array('id' => 'block-' . $id,
-                                           'method' => 'post',
-                                           'class' => 'block',
-                                           'action' => common_local_url('block')));
-        $this->hidden('token', common_session_token());
         $this->element('input', array('id' => 'blockto-' . $id,
                                       'name' => 'blockto',
                                       'type' => 'hidden',
@@ -144,8 +146,9 @@ class BlockAction extends Action
                 $this->hidden($k, $v);
             }
         }
-        $this->submit('no', _('No'));
-        $this->submit('yes', _('Yes'));
+        $this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not block this user from this group"));
+        $this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Block this user from this group'));
+        $this->elementEnd('fieldset');
         $this->elementEnd('form');
     }
 
diff --git a/actions/blockedfromgroup.php b/actions/blockedfromgroup.php
new file mode 100644 (file)
index 0000000..5c1eab3
--- /dev/null
@@ -0,0 +1,315 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * List of group members
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Group
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * List of profiles blocked from this group
+ *
+ * @category Group
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class BlockedfromgroupAction extends GroupDesignAction
+{
+    var $page = null;
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
+
+        $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->page != 1) {
+                $args['page'] = $this->page;
+            }
+            common_redirect(common_local_url('blockedfromgroup', $args), 301);
+            return false;
+        }
+
+        if (!$nickname) {
+            $this->clientError(_('No nickname'), 404);
+            return false;
+        }
+
+        $this->group = User_group::staticGet('nickname', $nickname);
+
+        if (!$this->group) {
+            $this->clientError(_('No such group'), 404);
+            return false;
+        }
+
+        return true;
+    }
+
+    function title()
+    {
+        if ($this->page == 1) {
+            return sprintf(_('%s blocked profiles'),
+                           $this->group->nickname);
+        } else {
+            return sprintf(_('%s blocked profiles, page %d'),
+                           $this->group->nickname,
+                           $this->page);
+        }
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function showPageNotice()
+    {
+        $this->element('p', 'instructions',
+                       _('A list of the users blocked from joining this group.'));
+    }
+
+    function showLocalNav()
+    {
+        $nav = new GroupNav($this, $this->group);
+        $nav->show();
+    }
+
+    function showContent()
+    {
+        $offset = ($this->page-1) * PROFILES_PER_PAGE;
+        $limit =  PROFILES_PER_PAGE + 1;
+
+        $cnt = 0;
+
+        $blocked = $this->group->getBlocked($offset, $limit);
+
+        if ($blocked) {
+            $blocked_list = new GroupBlockList($blocked, $this->group, $this);
+            $cnt = $blocked_list->show();
+        }
+
+        $blocked->free();
+
+        $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
+                          $this->page, 'blockedfromgroup',
+                          array('nickname' => $this->group->nickname));
+    }
+}
+
+class GroupBlockList extends ProfileList
+{
+    var $group = null;
+
+    function __construct($profile, $group, $action)
+    {
+        parent::__construct($profile, $action);
+
+        $this->group = $group;
+    }
+
+    function newListItem($profile)
+    {
+        return new GroupBlockListItem($profile, $this->group, $this->action);
+    }
+}
+
+class GroupBlockListItem extends ProfileListItem
+{
+    var $group = null;
+
+    function __construct($profile, $group, $action)
+    {
+        parent::__construct($profile, $action);
+
+        $this->group = $group;
+    }
+
+    function showActions()
+    {
+        $this->startActions();
+        $this->showGroupUnblockForm();
+        $this->endActions();
+    }
+
+    function showGroupUnblockForm()
+    {
+        $user = common_current_user();
+
+        if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
+            $this->out->elementStart('li', 'entity_block');
+            $bf = new GroupUnblockForm($this->out, $this->profile, $this->group,
+                                       array('action' => 'blockedfromgroup',
+                                             'nickname' => $this->group->nickname));
+            $bf->show();
+            $this->out->elementEnd('li');
+        }
+    }
+}
+
+/**
+ * Form for unblocking a user from a group
+ *
+ * @category Form
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ * @see      UnblockForm
+ */
+
+class GroupUnblockForm extends Form
+{
+    /**
+     * Profile of user to block
+     */
+
+    var $profile = null;
+
+    /**
+     * Group to block the user from
+     */
+
+    var $group = null;
+
+    /**
+     * Return-to args
+     */
+
+    var $args = null;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out     output channel
+     * @param Profile       $profile profile of user to block
+     * @param User_group    $group   group to block user from
+     * @param array         $args    return-to args
+     */
+
+    function __construct($out=null, $profile=null, $group=null, $args=null)
+    {
+        parent::__construct($out);
+
+        $this->profile = $profile;
+        $this->group   = $group;
+        $this->args    = $args;
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        // This should be unique for the page.
+        return 'unblock-' . $this->profile->id;
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_group_unblock';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        return common_local_url('groupunblock');
+    }
+
+    /**
+     * Legend of the Form
+     *
+     * @return void
+     */
+    function formLegend()
+    {
+        $this->out->element('legend', null, _('Unblock user from group'));
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        $this->out->hidden('unblockto-' . $this->profile->id,
+                           $this->profile->id,
+                           'unblockto');
+        $this->out->hidden('unblockgroup-' . $this->group->id,
+                           $this->group->id,
+                           'unblockgroup');
+        if ($this->args) {
+            foreach ($this->args as $k => $v) {
+                $this->out->hidden('returnto-' . $k, $v);
+            }
+        }
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Unblock'), 'submit', null, _('Unblock this user'));
+    }
+}
diff --git a/actions/conversation.php b/actions/conversation.php
new file mode 100644 (file)
index 0000000..0eb0d86
--- /dev/null
@@ -0,0 +1,294 @@
+<?php
+/**
+ * Display a conversation in the browser
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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);
+}
+
+// XXX: not sure how to do paging yet,
+// so set a 60-notice limit
+
+require_once INSTALLDIR.'/lib/noticelist.php';
+
+/**
+ * Conversation tree in the browser
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class ConversationAction extends Action
+{
+    var $id   = null;
+    var $page = null;
+
+    /**
+     * Initialization.
+     *
+     * @param array $args Web and URL arguments
+     *
+     * @return boolean false if id not passed in
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->id = $this->trimmed('id');
+        if (empty($this->id)) {
+            return false;
+        }
+        $this->id = $this->id+0;
+        $this->page = $this->trimmed('page');
+        if (empty($this->page)) {
+            $this->page = 1;
+        }
+        return true;
+    }
+
+    /**
+     * Handle the action
+     *
+     * @param array $args Web and URL arguments
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    /**
+     * Returns the page title
+     *
+     * @return string page title
+     */
+
+    function title()
+    {
+        return _("Conversation");
+    }
+
+    /**
+     * Show content.
+     *
+     * Display a hierarchical unordered list in the content area.
+     * Uses ConversationTree to do most of the heavy lifting.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $notices = Notice::conversationStream($this->id, null, null);
+
+        $ct = new ConversationTree($notices, $this);
+
+        $cnt = $ct->show();
+    }
+}
+
+/**
+ * Conversation tree
+ *
+ * The widget class for displaying a hierarchical list of notices.
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class ConversationTree extends NoticeList
+{
+    var $tree  = null;
+    var $table = null;
+
+    /**
+     * Show the tree of notices
+     *
+     * @return void
+     */
+
+    function show()
+    {
+        $cnt = $this->_buildTree();
+
+        $this->out->elementStart('div', array('id' =>'notices_primary'));
+        $this->out->element('h2', null, _('Notices'));
+        $this->out->elementStart('ol', array('class' => 'notices xoxo'));
+
+        if (array_key_exists('root', $this->tree)) {
+            $rootid = $this->tree['root'][0];
+            $this->showNoticePlus($rootid);
+        }
+
+        $this->out->elementEnd('ol');
+        $this->out->elementEnd('div');
+
+        return $cnt;
+    }
+
+    function _buildTree()
+    {
+        $this->tree  = array();
+        $this->table = array();
+
+        while ($this->notice->fetch()) {
+
+            $cnt++;
+
+            $id     = $this->notice->id;
+            $notice = clone($this->notice);
+
+            $this->table[$id] = $notice;
+
+            if (is_null($notice->reply_to)) {
+                $this->tree['root'] = array($notice->id);
+            } else if (array_key_exists($notice->reply_to, $this->tree)) {
+                $this->tree[$notice->reply_to][] = $notice->id;
+            } else {
+                $this->tree[$notice->reply_to] = array($notice->id);
+            }
+        }
+
+        return $cnt;
+    }
+
+    /**
+     * Shows a notice plus its list of children.
+     *
+     * @param integer $id ID of the notice to show
+     *
+     * @return void
+     */
+
+    function showNoticePlus($id)
+    {
+        $notice = $this->table[$id];
+
+        // We take responsibility for doing the li
+
+        $this->out->elementStart('li', array('class' => 'hentry notice',
+                                             'id' => 'notice-' . $id));
+
+        $item = $this->newListItem($notice);
+        $item->show();
+
+        if (array_key_exists($id, $this->tree)) {
+            $children = $this->tree[$id];
+
+            $this->out->elementStart('ol', array('class' => 'notices'));
+
+            sort($children);
+
+            foreach ($children as $child) {
+                $this->showNoticePlus($child);
+            }
+
+            $this->out->elementEnd('ol');
+        }
+
+        $this->out->elementEnd('li');
+    }
+
+    /**
+     * Override parent class to return our preferred item.
+     *
+     * @param Notice $notice Notice to display
+     *
+     * @return NoticeListItem a list item to show
+     */
+
+    function newListItem($notice)
+    {
+        return new ConversationTreeItem($notice, $this->out);
+    }
+}
+
+/**
+ * Conversation tree list item
+ *
+ * Special class of NoticeListItem for use inside conversation trees.
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class ConversationTreeItem extends NoticeListItem
+{
+    /**
+     * start a single notice.
+     *
+     * The default creates the <li>; we skip, since the ConversationTree
+     * takes care of that.
+     *
+     * @return void
+     */
+
+    function showStart()
+    {
+        return;
+    }
+
+    /**
+     * finish the notice
+     *
+     * The default closes the <li>; we skip, since the ConversationTree
+     * takes care of that.
+     *
+     * @return void
+     */
+
+    function showEnd()
+    {
+        return;
+    }
+
+    /**
+     * show link to notice conversation page
+     *
+     * Since we're only used on the conversation page, we skip this
+     *
+     * @return void
+     */
+
+    function showContext()
+    {
+        return;
+    }
+}
index 6c350b33ab36e3c45d278f5009f20d1259ef435c..e733f9650ad12eb2e75960f3bdec0df9054a5019 100644 (file)
@@ -112,8 +112,8 @@ class DeletenoticeAction extends DeleteAction
         $this->hidden('token', common_session_token());
         $this->hidden('notice', $this->trimmed('notice'));
         $this->element('p', null, _('Are you sure you want to delete this notice?'));
-        $this->submit('form_action-yes', _('Yes'), 'submit form_action-primary', 'yes');
-        $this->submit('form_action-no', _('No'), 'submit form_action-secondary', 'no');
+        $this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not delete this notice"));
+        $this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Delete this notice'));
         $this->elementEnd('fieldset');
         $this->elementEnd('form');
     }
index bc13b09da5ea931224a08cc07834034771167fd4..02e01d6e006bd3747beca0bfdfb694fd530309d0 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -75,7 +75,7 @@ class DisfavorAction extends Action
             return;
         }
         $fave            = new Fave();
-        $fave->user_id   = $this->id;
+        $fave->user_id   = $user->id;
         $fave->notice_id = $notice->id;
         if (!$fave->find(true)) {
             $this->clientError(_('This notice is not a favorite!'));
index e6508030b658770e3009df0d47959886d4b89d52..54ae1380362b618f2ea22e0bfe78c1dd0aaca77e 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 39dad0465eb070ced615f205661e1115cb7f3b39..6aa6f8b11f20d82d59b46a57bf8129d81e9b327e 100644 (file)
@@ -23,6 +23,7 @@
  * @package   Laconica
  * @author    Evan Prodromou <evan@controlyourself.ca>
  * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @author   Zach Copley <zach@controlyourself.ca>
  * @copyright 2008-2009 Control Yourself, Inc.
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link      http://laconi.ca/
@@ -40,14 +41,15 @@ if (!defined('LACONICA')) {
  * @category Group
  * @package  Laconica
  * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Zach Copley <zach@controlyourself.ca>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://laconi.ca/
  */
 
-class EditgroupAction extends Action
+class EditgroupAction extends GroupDesignAction
 {
+
     var $msg;
-    var $group = null;
 
     function title()
     {
@@ -171,6 +173,7 @@ class EditgroupAction extends Action
         $homepage    = $this->trimmed('homepage');
         $description = $this->trimmed('description');
         $location    = $this->trimmed('location');
+        $aliasstring = $this->trimmed('aliases');
 
         if (!Validate::string($nickname, array('min_length' => 1,
                                                'max_length' => 64,
@@ -201,6 +204,39 @@ class EditgroupAction extends Action
             return;
         }
 
+        if (!empty($aliasstring)) {
+            $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
+        } else {
+            $aliases = array();
+        }
+
+        if (count($aliases) > common_config('group', 'maxaliases')) {
+            $this->showForm(sprintf(_('Too many aliases! Maximum %d.'),
+                                    common_config('group', 'maxaliases')));
+            return;
+        }
+
+        foreach ($aliases as $alias) {
+            if (!Validate::string($alias, array('min_length' => 1,
+                                                'max_length' => 64,
+                                                'format' => NICKNAME_FMT))) {
+                $this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
+                return;
+            }
+            if ($this->nicknameExists($alias)) {
+                $this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
+                                        $alias));
+                return;
+            }
+            // XXX assumes alphanum nicknames
+            if (strcmp($alias, $nickname) == 0) {
+                $this->showForm(_('Alias can\'t be the same as nickname.'));
+                return;
+            }
+        }
+
+        $this->group->query('BEGIN');
+
         $orig = clone($this->group);
 
         $this->group->nickname    = $nickname;
@@ -217,6 +253,14 @@ class EditgroupAction extends Action
             $this->serverError(_('Could not update group.'));
         }
 
+        $result = $this->group->setAliases($aliases);
+
+        if (!$result) {
+            $this->serverError(_('Could not create aliases.'));
+        }
+
+        $this->group->query('COMMIT');
+
         if ($this->group->nickname != $orig->nickname) {
             common_redirect(common_local_url('editgroup',
                                              array('nickname' => $nickname)),
@@ -229,9 +273,20 @@ class EditgroupAction extends Action
     function nicknameExists($nickname)
     {
         $group = User_group::staticGet('nickname', $nickname);
-        return (!is_null($group) &&
-                $group != false &&
-                $group->id != $this->group->id);
+
+        if (!empty($group) &&
+            $group->id != $this->group->id) {
+            return true;
+        }
+
+        $alias = Group_alias::staticGet('alias', $nickname);
+
+        if (!empty($alias) &&
+            $alias->group_id != $this->group->id) {
+            return true;
+        }
+
+        return false;
     }
 }
 
index 4c2b26355ca65a2c469155d270438327dd25fbf3..34989c9786aa9814f9396d49bcba0db3934e9af3 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -21,29 +21,28 @@ if (!defined('LACONICA')) { exit(1); }
 
 require_once INSTALLDIR.'/lib/facebookaction.php';
 
-
 class FacebookhomeAction extends FacebookAction
 {
 
     var $page = null;
-    
+
     function prepare($argarray)
-    {        
+    {
         parent::prepare($argarray);
-        
+
         $this->page = $this->trimmed('page');
-       
+
         if (!$this->page) {
             $this->page = 1;
         }
-        
+
         return true;
     }
 
     function handle($args)
     {
-        parent::handle($args);        
-                
+        parent::handle($args);
+
         // If the user has opted not to initially allow the app to have
         // Facebook status update permission, store that preference. Only
         // promt the user the first time she uses the app
@@ -73,7 +72,7 @@ class FacebookhomeAction extends FacebookAction
                  $this->updateProfileBox($notice);
              }
 
-             if ($this->arg('status_submit') == 'Send') {            
+             if ($this->arg('status_submit') == 'Send') {
                 $this->saveNewNotice();
              }
 
@@ -81,7 +80,7 @@ class FacebookhomeAction extends FacebookAction
             // Facebook status update permission? Then show the main page
             // of the app
             $this->showPage();
-            
+
         } else {
 
             // User hasn't authenticated yet, prompt for creds
@@ -89,12 +88,12 @@ class FacebookhomeAction extends FacebookAction
         }
 
     }
-    
+
     function login()
     {
-        
+
         $this->showStylesheets();
-        
+
         $nickname = common_canonical_nickname($this->trimmed('nickname'));
         $password = $this->arg('password');
 
@@ -115,7 +114,7 @@ class FacebookhomeAction extends FacebookAction
                 $flink->foreign_id = $this->fbuid;
                 $flink->service = FACEBOOK_SERVICE;
                 $flink->created = common_sql_now();
-                $flink->set_flags(true, false, false);
+                $flink->set_flags(true, false, false, false);
 
                 $flink_id = $flink->insert();
 
@@ -141,13 +140,12 @@ class FacebookhomeAction extends FacebookAction
         $this->facebook->api_client->data_setUserPreference(
             FACEBOOK_PROMPTED_UPDATE_PREF, 'false');
     }
-    
 
     function showNoticeForm()
     {
         $post_action = "$this->app_uri/index.php";
-        
-        $notice_form = new FacebookNoticeForm($this, $post_action, null, 
+
+        $notice_form = new FacebookNoticeForm($this, $post_action, null,
             $post_action, $this->user);
         $notice_form->show();
     }
@@ -163,9 +161,8 @@ class FacebookhomeAction extends FacebookAction
 
     function showContent()
     {
-        $notice = $this->user->noticesWithFriends(($this->page-1) *
-            NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
-        
+        $notice = $this->user->noticeInbox(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+
         $nl = new NoticeList($notice, $this);
 
         $cnt = $nl->show();
@@ -176,7 +173,7 @@ class FacebookhomeAction extends FacebookAction
 
     function showNoticeList($notice)
     {
-                
+
         $nl = new NoticeList($notice, $this);
         return $nl->show();
     }
@@ -201,16 +198,16 @@ class FacebookhomeAction extends FacebookAction
 
         $this->elementStart('ul', array('id' => 'fb-permissions-list'));
         $this->elementStart('li', array('id' => 'fb-permissions-item'));
-        
+
         $next = urlencode("$this->app_uri/index.php");
         $api_key = common_config('facebook', 'apikey');
-        
+
         $auth_url = 'http://www.facebook.com/authorize.php?api_key=' .
-            $api_key . '&v=1.0&ext_perm=status_update&next=' . $next . 
+            $api_key . '&v=1.0&ext_perm=status_update&next=' . $next .
             '&next_cancel=' . $next . '&submit=skip';
-        
+
         $this->elementStart('span', array('class' => 'facebook-button'));
-        $this->element('a', array('href' => $auth_url), 
+        $this->element('a', array('href' => $auth_url),
             sprintf(_('Okay, do it!'), $this->app_name));
         $this->elementEnd('span');
 
@@ -225,7 +222,7 @@ class FacebookhomeAction extends FacebookAction
         $this->elementEnd('div');
 
     }
-    
+
     /**
      * Generate pagination links
      *
@@ -239,11 +236,11 @@ class FacebookhomeAction extends FacebookAction
      */
     function pagination($have_before, $have_after, $page, $action, $args=null)
     {
-                
+
         // Does a little before-after block for next/prev page
-     
+
         // XXX: Fix so this uses common_local_url() if possible.
-     
+
         if ($have_before || $have_after) {
             $this->elementStart('div', array('class' => 'pagination'));
             $this->elementStart('dl', null);
@@ -254,7 +251,7 @@ class FacebookhomeAction extends FacebookAction
         if ($have_before) {
             $pargs   = array('page' => $page-1);
             $newargs = $args ? array_merge($args, $pargs) : $pargs;
-            $this->elementStart('li', array('class' => 'nav_prev'));            
+            $this->elementStart('li', array('class' => 'nav_prev'));
             $this->element('a', array('href' => "$action?page=$newargs[page]", 'rel' => 'prev'),
                            _('After'));
             $this->elementEnd('li');
@@ -274,6 +271,5 @@ class FacebookhomeAction extends FacebookAction
             $this->elementEnd('div');
         }
     }
-    
 
 }
index 2207580f778781e07de381b697fd08473c7a22a3..f43d04e27f422a957a88901969983e071c6077d8 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 94d494a82cb42a5f718aecd33ff021bb8f9fb52b..22007da4fa6940315795058ce016b177a5747416 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 376e12a2e9ddfb2d64ae8f282945197c86d567b3..9ca7a77a84413c0aee266656fccb25fdd113811e 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 236460c1c9a42f1f15463aa69959fddb802fbf97..ee2c279ab5a95b2289e02148d587d043f7666fe9 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction
         $prefix = $this->trimmed('prefix');
 
         $original = clone($this->flink);
-        $this->flink->set_flags($noticesync, $replysync, false);
+        $this->flink->set_flags($noticesync, $replysync, false, false);
         $result = $this->flink->update($original);
 
         $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX,
index a7aff87f2c78c8f831df09f76abf61749d1c684d..fe51e34a272c8d73ddfd9244e2fda14d28a9fe10 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index c902d80f5387e2e5c4f9f50a770864380c9cbd94..156c7a70094a68513f22ec298d568146a0b0324e 100644 (file)
@@ -194,7 +194,7 @@ class FavoritedAction extends Action
         $qry = 'SELECT notice.*, '.
           $weightexpr . ' as weight ' .
           'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
-          'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source ' .
+          'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source,notice.conversation ' .
           'ORDER BY weight DESC';
 
         $offset = ($this->page - 1) * NOTICES_PER_PAGE;
index 6b46b8dec7ecf94b94667aa81173a907b66b9257..c439a9a62b5cc34d7b37ec7533f230b31e7b00e7 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 79eba2aa67b9985372a2e23a1e977306a62439ca..04365687d763c3e3b3dac39c60e5e3ff5266791f 100644 (file)
@@ -32,7 +32,7 @@ if (!defined('LACONICA')) {
     exit(1);
 }
 
-require_once(INSTALLDIR.'/lib/profilelist.php');
+require_once INSTALLDIR.'/lib/profilelist.php';
 require_once INSTALLDIR.'/lib/publicgroupnav.php';
 
 /**
@@ -107,7 +107,6 @@ class FeaturedAction extends Action
 
         $featured_nicks = common_config('nickname', 'featured');
 
-
         if (count($featured_nicks) > 0) {
 
             $quoted = array();
@@ -136,7 +135,7 @@ class FeaturedAction extends Action
             $cnt = $profile->find();
 
             if ($cnt > 0) {
-                $featured = new ProfileList($profile, null, $this);
+                $featured = new ProfileList($profile, $this);
                 $featured->show();
             }
 
diff --git a/actions/file.php b/actions/file.php
new file mode 100644 (file)
index 0000000..8310e48
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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/shownotice.php');
+
+class FileAction extends Action
+{
+    var $id = null;
+    var $filerec = null;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->id = $this->trimmed('notice');
+        if (empty($this->id)) {
+            $this->clientError(_('No notice id'));
+        }
+        $notice = Notice::staticGet('id', $this->id);
+        if (empty($notice)) {
+            $this->clientError(_('No notice'));
+        }
+        $atts = $notice->attachments();
+        if (empty($atts)) {
+            $this->clientError(_('No attachments'));
+        }
+        foreach ($atts as $att) {
+            if (!empty($att->filename)) {
+                $this->filerec = $att;
+                break;
+            }
+        }
+        if (empty($this->filerec)) {
+            $this->clientError(_('No uploaded attachments'));
+        }
+        return true;
+    }
+
+    function handle() {
+        common_redirect($this->filerec->url);
+    }
+
+    /**
+     * Is this action read-only?
+     *
+     * @return boolean true
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+}
+
index b08b96df6c2afd017cecf9852fc663ec41b65e67..e9f7c746bb9195888f4bab95e558dcd3db06fc23 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 3e3a817154ddef6c5a81bb413a15817cf7956be8..5c764aeb0d320605fcd95b5865af89b1965c0ece 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 2d5b78d123066a58d75f7ffdb5ec21e05015ccf9..b481b24377139ebbff5e5929f6eb9c95503947bb 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -82,14 +82,18 @@ class FoafAction extends Action
                                               'http://www.w3.org/2000/01/rdf-schema#',
                                               'xmlns:geo' =>
                                               'http://www.w3.org/2003/01/geo/wgs84_pos#',
+                                              'xmlns:bio' =>
+                                              'http://purl.org/vocab/bio/0.1/',
+                                              'xmlns:sioc' =>
+                                              'http://rdfs.org/sioc/ns#',
                                               'xmlns' => 'http://xmlns.com/foaf/0.1/'));
 
         // This is the document about the user
 
         $this->showPpd('', $this->user->uri);
 
-        // XXX: might not be a person
-        $this->elementStart('Person', array('rdf:about' =>
+        // Would be nice to tell if they were a Person or not (e.g. a #person usertag?)
+        $this->elementStart('Agent', array('rdf:about' =>
                                              $this->user->uri));
         $this->element('mbox_sha1sum', null, sha1('mailto:' . $this->user->email));
         if ($this->profile->fullname) {
@@ -98,8 +102,11 @@ class FoafAction extends Action
         if ($this->profile->homepage) {
             $this->element('homepage', array('rdf:resource' => $this->profile->homepage));
         }
+        if ($this->profile->profileurl) {
+            $this->element('weblog', array('rdf:resource' => $this->profile->profileurl));
+        }
         if ($this->profile->bio) {
-            $this->element('rdfs:comment', null, $this->profile->bio);
+            $this->element('bio:olb', null, $this->profile->bio);
         }
         // XXX: more structured location data
         if ($this->profile->location) {
@@ -110,10 +117,7 @@ class FoafAction extends Action
             $this->elementEnd('based_near');
         }
 
-        $this->showMicrobloggingAccount($this->profile, common_root_url());
-
         $avatar = $this->profile->getOriginalAvatar();
-
         if ($avatar) {
             $this->elementStart('img');
             $this->elementStart('Image', array('rdf:about' => $avatar->url));
@@ -129,39 +133,8 @@ class FoafAction extends Action
             $this->elementEnd('img');
         }
 
-        // Get people user is subscribed to
-
-        $person = array();
-
-        $sub = new Subscription();
-        $sub->subscriber = $this->profile->id;
-        $sub->whereAdd('subscriber != subscribed');
-
-        if ($sub->find()) {
-            while ($sub->fetch()) {
-                if (!empty($sub->token)) {
-                    $other = Remote_profile::staticGet('id', $sub->subscribed);
-                } else {
-                    $other = User::staticGet('id', $sub->subscribed);
-                }
-                if (empty($other)) {
-                    common_debug('Got a bad subscription: '.print_r($sub,true));
-                    continue;
-                }
-                $this->element('knows', array('rdf:resource' => $other->uri));
-                $person[$other->uri] = array(LISTENEE,
-                                             $other->id,
-                                             $other->nickname,
-                                             (empty($sub->token)) ? 'User' : 'Remote_profile');
-                $other->free();
-                $other = null;
-                unset($other);
-            }
-        }
-
-        $sub->free();
-        $sub = null;
-        unset($sub);
+        $person = $this->showMicrobloggingAccount($this->profile,
+                                     common_root_url(), $this->user->uri, false);
 
         // Get people who subscribe to user
 
@@ -198,7 +171,15 @@ class FoafAction extends Action
         $sub = null;
         unset($sub);
 
-        $this->elementEnd('Person');
+        foreach ($person as $uri => $p) {
+            list($type, $id, $nickname, $cls) = $p;
+            if ($type == BOTH) {
+                $this->element('knows', array('rdf:resource' => $uri));
+            }
+        }
+        
+        $this->elementEnd('Agent');
+
 
         foreach ($person as $uri => $p) {
             $foaf_url = null;
@@ -207,16 +188,18 @@ class FoafAction extends Action
                 $foaf_url = common_local_url('foaf', array('nickname' => $nickname));
             }
             $profile = Profile::staticGet($id);
-            $this->elementStart('Person', array('rdf:about' => $uri));
-            if ($type == LISTENER || $type == BOTH) {
+            $this->elementStart('Agent', array('rdf:about' => $uri));
+            if ($type == BOTH) {
                 $this->element('knows', array('rdf:resource' => $this->user->uri));
             }
-            $this->showMicrobloggingAccount($profile, ($cls == 'User') ?
-                                            common_root_url() : null);
+            $this->showMicrobloggingAccount($profile,
+                                   ($cls == 'User') ? common_root_url() : null,
+                                   $uri,
+                                   true);
             if ($foaf_url) {
                 $this->element('rdfs:seeAlso', array('rdf:resource' => $foaf_url));
             }
-            $this->elementEnd('Person');
+            $this->elementEnd('Agent');
             if ($foaf_url) {
                 $this->showPpd($foaf_url, $uri);
             }
@@ -237,18 +220,66 @@ class FoafAction extends Action
         $this->elementEnd('PersonalProfileDocument');
     }
 
-    function showMicrobloggingAccount($profile, $service=null)
+    function showMicrobloggingAccount($profile, $service=null, $useruri=null, $isSubscriber=false)
     {
+        $attr = array();
+        if ($useruri) {
+            $attr['rdf:about'] = $useruri . '#acct';
+        }
+
         // Their account
         $this->elementStart('holdsAccount');
-        $this->elementStart('OnlineAccount');
+        $this->elementStart('OnlineAccount', $attr);
         if ($service) {
             $this->element('accountServiceHomepage', array('rdf:resource' =>
                                                            $service));
         }
         $this->element('accountName', null, $profile->nickname);
-        $this->element('homepage', array('rdf:resource' => $profile->profileurl));
+        $this->element('accountProfilePage', array('rdf:resource' => $profile->profileurl));
+        if ($useruri) {
+            $this->element('sioc:account_of', array('rdf:resource'=>$useruri));
+        }
+
+        $person = array();
+
+        if ($isSubscriber) {
+             $this->element('sioc:follows', array('rdf:resource'=>$this->user->uri . '#acct'));
+        } else {
+            // Get people user is subscribed to
+            $sub = new Subscription();
+            $sub->subscriber = $profile->id;
+            $sub->whereAdd('subscriber != subscribed');
+
+            if ($sub->find()) {
+                while ($sub->fetch()) {
+                    if (!empty($sub->token)) {
+                        $other = Remote_profile::staticGet('id', $sub->subscribed);
+                    } else {
+                        $other = User::staticGet('id', $sub->subscribed);
+                    }
+                    if (empty($other)) {
+                        common_debug('Got a bad subscription: '.print_r($sub,true));
+                        continue;
+                    }
+                    $this->element('sioc:follows', array('rdf:resource' => $other->uri.'#acct'));
+                    $person[$other->uri] = array(LISTENEE,
+                                                 $other->id,
+                                                 $other->nickname,
+                                                 (empty($sub->token)) ? 'User' : 'Remote_profile');
+                    $other->free();
+                    $other = null;
+                    unset($other);
+                }
+            }
+
+            $sub->free();
+            $sub = null;
+            unset($sub);
+        }
+
         $this->elementEnd('OnlineAccount');
         $this->elementEnd('holdsAccount');
+
+        return $person;
     }
 }
diff --git a/actions/groupblock.php b/actions/groupblock.php
new file mode 100644 (file)
index 0000000..ce2c6c1
--- /dev/null
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Block a user from a group action class.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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);
+}
+
+/**
+ * Block a user from a group
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class GroupblockAction extends Action
+{
+    var $profile = null;
+    var $group = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'));
+            return false;
+        }
+        $token = $this->trimmed('token');
+        if (empty($token) || $token != common_session_token()) {
+            $this->clientError(_('There was a problem with your session token. Try again, please.'));
+            return;
+        }
+        $id = $this->trimmed('blockto');
+        if (empty($id)) {
+            $this->clientError(_('No profile specified.'));
+            return false;
+        }
+        $this->profile = Profile::staticGet('id', $id);
+        if (empty($this->profile)) {
+            $this->clientError(_('No profile with that ID.'));
+            return false;
+        }
+        $group_id = $this->trimmed('blockgroup');
+        if (empty($group_id)) {
+            $this->clientError(_('No group specified.'));
+            return false;
+        }
+        $this->group = User_group::staticGet('id', $group_id);
+        if (empty($this->group)) {
+            $this->clientError(_('No such group.'));
+            return false;
+        }
+        $user = common_current_user();
+        if (!$user->isAdmin($this->group)) {
+            $this->clientError(_('Only an admin can block group members.'), 401);
+            return false;
+        }
+        if (Group_block::isBlocked($this->group, $this->profile)) {
+            $this->clientError(_('User is already blocked from group.'));
+            return false;
+        }
+        // XXX: could have proactive blocks, but we don't have UI for it.
+        if (!$this->profile->isMember($this->group)) {
+            $this->clientError(_('User is not a member of group.'));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Handle request
+     *
+     * Shows a page with list of favorite notices
+     *
+     * @param array $args $_REQUEST args; handled in prepare()
+     *
+     * @return void
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            if ($this->arg('no')) {
+                common_redirect(common_local_url('groupmembers',
+                                                 array('nickname' => $this->group->nickname)),
+                                303);
+            } elseif ($this->arg('yes')) {
+                $this->blockProfile();
+            } elseif ($this->arg('blockto')) {
+                $this->showPage();
+            }
+        }
+    }
+
+    function showContent() {
+        $this->areYouSureForm();
+    }
+
+    function title() {
+        return _('Block user from group');
+    }
+
+    function showNoticeForm() {
+        // nop
+    }
+
+    /**
+     * Confirm with user.
+     *
+     * Shows a confirmation form.
+     *
+     * @return void
+     */
+
+    function areYouSureForm()
+    {
+        $id = $this->profile->id;
+        $this->element('p', null,
+                       sprintf(_('Are you sure you want to block user "%s" from the group "%s"? '.
+                                 'They will be removed from the group, unable to post, and '.
+                                 'unable to subscribe to the group in the future.'),
+                               $this->profile->getBestName(),
+                               $this->group->getBestName()));
+        $this->elementStart('form', array('id' => 'block-' . $id,
+                                           'method' => 'post',
+                                           'class' => 'block',
+                                           'action' => common_local_url('groupblock')));
+        $this->hidden('token', common_session_token());
+        $this->hidden('blockto-' . $this->profile->id,
+                      $this->profile->id,
+                      'blockto');
+        $this->hidden('blockgroup-' . $this->group->id,
+                      $this->group->id,
+                      'blockgroup');
+        foreach ($this->args as $k => $v) {
+            if (substr($k, 0, 9) == 'returnto-') {
+                $this->hidden($k, $v);
+            }
+        }
+        $this->submit('no', _('No'));
+        $this->submit('yes', _('Yes'));
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Actually block a user.
+     *
+     * @return void
+     */
+
+    function blockProfile()
+    {
+        $block = Group_block::blockProfile($this->group, $this->profile,
+                                           common_current_user());
+
+        if (empty($block)) {
+            $this->serverError(_("Database error blocking user from group."));
+            return false;
+        }
+
+        // Now, gotta figure where we go back to
+        foreach ($this->args as $k => $v) {
+            if ($k == 'returnto-action') {
+                $action = $v;
+            } elseif (substr($k, 0, 9) == 'returnto-') {
+                $args[substr($k, 9)] = $v;
+            }
+        }
+
+        if ($action) {
+            common_redirect(common_local_url($action, $args), 303);
+        } else {
+            common_redirect(common_local_url('groupmembers',
+                                             array('nickname' => $this->group->nickname)),
+                            303);
+        }
+    }
+}
+
diff --git a/actions/groupdesignsettings.php b/actions/groupdesignsettings.php
new file mode 100644 (file)
index 0000000..bb01243
--- /dev/null
@@ -0,0 +1,315 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Change user password
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Settings
+ * @package   Laconica
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/designsettings.php';
+
+/**
+ * Set a group's design
+ *
+ * Saves a design for a given group
+ *
+ * @category Settings
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class GroupDesignSettingsAction extends DesignSettingsAction
+{
+    var $group = null;
+
+    /**
+     * Sets the right action for the form, and passes request args into
+     * the base action
+     *
+     * @param array $args misc. arguments
+     *
+     * @return boolean true
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if (!common_config('inboxes', 'enabled')) {
+            $this->serverError(_('Inboxes must be enabled for groups to work'));
+            return false;
+        }
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to edit a group.'));
+            return false;
+        }
+
+        $nickname_arg = $this->trimmed('nickname');
+        $nickname     = common_canonical_nickname($nickname_arg);
+
+        // Permanent redirect on non-canonical nickname
+
+        if ($nickname_arg != $nickname) {
+            $args = array('nickname' => $nickname);
+            common_redirect(common_local_url('groupdesignsettings', $args), 301);
+            return false;
+        }
+
+        if (!$nickname) {
+            $this->clientError(_('No nickname'), 404);
+            return false;
+        }
+
+        $groupid = $this->trimmed('groupid');
+
+        if ($groupid) {
+            $this->group = User_group::staticGet('id', $groupid);
+        } else {
+            $this->group = User_group::staticGet('nickname', $nickname);
+        }
+
+        if (!$this->group) {
+            $this->clientError(_('No such group'), 404);
+            return false;
+        }
+
+        $cur = common_current_user();
+
+        if (!$cur->isAdmin($this->group)) {
+            $this->clientError(_('You must be an admin to edit the group'), 403);
+            return false;
+        }
+
+        $this->submitaction = common_local_url('groupdesignsettings',
+            array('nickname' => $this->group->nickname));
+
+        return true;
+    }
+
+    /**
+     * A design for this action
+     *
+     * if the group attribute has been set, returns that group's
+     * design.
+     *
+     * @return Design a design object to use
+     */
+
+    function getDesign()
+    {
+
+        if (empty($this->group)) {
+            return null;
+        }
+
+        return $this->group->getDesign();
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Group design');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Customize the way your group looks ' .
+        'with a background image and a colour palette of your choice.');
+    }
+
+    /**
+     * Override to show group nav stuff
+     *
+     * @return nothing
+     */
+
+    function showLocalNav()
+    {
+        $nav = new GroupNav($this, $this->group);
+        $nav->show();
+    }
+
+    /**
+     * Get the design we want to edit
+     *
+     * @return Design
+     */
+
+    function getWorkingDesign()
+    {
+
+        $design = null;
+
+        if (isset($this->group)) {
+            $design = $this->group->getDesign();
+        }
+
+        if (empty($design)) {
+            $design = $this->defaultDesign();
+        }
+
+        return $design;
+    }
+
+    /**
+     * Content area of the page
+     *
+     * Shows a form for changing the design
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $this->showDesignForm($this->getWorkingDesign());
+    }
+
+    /**
+     * Save or update the group's design settings
+     *
+     * @return void
+     */
+
+    function saveDesign()
+    {
+        try {
+
+            $bgcolor = new WebColor($this->trimmed('design_background'));
+            $ccolor  = new WebColor($this->trimmed('design_content'));
+            $sbcolor = new WebColor($this->trimmed('design_sidebar'));
+            $tcolor  = new WebColor($this->trimmed('design_text'));
+            $lcolor  = new WebColor($this->trimmed('design_links'));
+
+        } catch (WebColorException $e) {
+            $this->showForm($e->getMessage());
+            return;
+        }
+
+        $onoff = $this->arg('design_background-image_onoff');
+
+        $on   = false;
+        $off  = false;
+        $tile = false;
+
+        if ($onoff == 'on') {
+            $on = true;
+        } else {
+            $off = true;
+        }
+
+        $repeat = $this->boolean('design_background-image_repeat');
+
+        if ($repeat) {
+            $tile = true;
+        }
+
+        $design = $this->group->getDesign();
+
+        if (!empty($design)) {
+
+            // update design
+
+            $original = clone($design);
+
+            $design->backgroundcolor = $bgcolor->intValue();
+            $design->contentcolor    = $ccolor->intValue();
+            $design->sidebarcolor    = $sbcolor->intValue();
+            $design->textcolor       = $tcolor->intValue();
+            $design->linkcolor       = $lcolor->intValue();
+
+            $design->setDisposition($on, $off, $tile);
+
+            $result = $design->update($original);
+
+            if ($result === false) {
+                common_log_db_error($design, 'UPDATE', __FILE__);
+                $this->showForm(_('Couldn\'t update your design.'));
+                return;
+            }
+
+        } else {
+
+            $this->group->query('BEGIN');
+
+            // save new design
+
+            $design = new Design();
+
+            $design->backgroundcolor = $bgcolor->intValue();
+            $design->contentcolor    = $ccolor->intValue();
+            $design->sidebarcolor    = $sbcolor->intValue();
+            $design->textcolor       = $tcolor->intValue();
+            $design->linkcolor       = $lcolor->intValue();
+
+            $design->setDisposition($on, $off, $tile);
+
+            $id = $design->insert();
+
+            if (empty($id)) {
+                common_log_db_error($id, 'INSERT', __FILE__);
+                $this->showForm(_('Unable to save your design settings!'));
+                return;
+            }
+
+            $original               = clone($this->group);
+            $this->group->design_id = $id;
+            $result                 = $this->group->update($original);
+
+            if (empty($result)) {
+                common_log_db_error($original, 'UPDATE', __FILE__);
+                $this->showForm(_('Unable to save your design settings!'));
+                $this->group->query('ROLLBACK');
+                return;
+            }
+
+            $this->group->query('COMMIT');
+
+        }
+
+        $this->saveBackgroundImage($design);
+
+        $this->showForm(_('Design preferences saved.'), true);
+    }
+
+}
index fe6127da296243a0c13f15625a8fc57f162f5c83..8f6158dacaafbbd9626b525284745d3451c3f548 100644 (file)
@@ -50,7 +50,7 @@ define('MAX_ORIGINAL', 480);
  * @link     http://laconi.ca/
  */
 
-class GrouplogoAction extends Action
+class GrouplogoAction extends GroupDesignAction
 {
     var $mode = null;
     var $imagefile = null;
index 21e5ebbaa19c443b0bc81f8f9163b827eb488655..14256526a059453bf887ab819fbedf00cf511b48 100644 (file)
@@ -44,7 +44,7 @@ require_once INSTALLDIR.'/lib/publicgroupnav.php';
  * @link     http://laconi.ca/
  */
 
-class GroupmembersAction extends Action
+class GroupmembersAction extends GroupDesignAction
 {
     var $page = null;
 
@@ -127,7 +127,7 @@ class GroupmembersAction extends Action
         $members = $this->group->getMembers($offset, $limit);
 
         if ($members) {
-            $member_list = new ProfileList($members, null, $this);
+            $member_list = new GroupMemberList($members, $this->group, $this);
             $cnt = $member_list->show();
         }
 
@@ -138,3 +138,335 @@ class GroupmembersAction extends Action
                           array('nickname' => $this->group->nickname));
     }
 }
+
+class GroupMemberList extends ProfileList
+{
+    var $group = null;
+
+    function __construct($profile, $group, $action)
+    {
+        parent::__construct($profile, $action);
+
+        $this->group = $group;
+    }
+
+    function newListItem($profile)
+    {
+        return new GroupMemberListItem($profile, $this->group, $this->action);
+    }
+}
+
+class GroupMemberListItem extends ProfileListItem
+{
+    var $group = null;
+
+    function __construct($profile, $group, $action)
+    {
+        parent::__construct($profile, $action);
+
+        $this->group = $group;
+    }
+
+    function showFullName()
+    {
+        parent::showFullName();
+        if ($this->profile->isAdmin($this->group)) {
+            $this->out->text(' ');
+            $this->out->element('span', 'role', _('Admin'));
+        }
+    }
+
+    function showActions()
+    {
+        $this->startActions();
+        $this->showSubscribeButton();
+        $this->showMakeAdminForm();
+        $this->showGroupBlockForm();
+        $this->endActions();
+    }
+
+    function showMakeAdminForm()
+    {
+        $user = common_current_user();
+
+        if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group) &&
+            !$this->profile->isAdmin($this->group)) {
+            $this->out->elementStart('li', 'entity_make_admin');
+            $maf = new MakeAdminForm($this->out, $this->profile, $this->group,
+                                     array('action' => 'groupmembers',
+                                           'nickname' => $this->group->nickname));
+            $maf->show();
+            $this->out->elementEnd('li');
+        }
+
+    }
+    function showGroupBlockForm()
+    {
+        $user = common_current_user();
+
+        if (!empty($user) && $user->id != $this->profile->id && $user->isAdmin($this->group)) {
+            $this->out->elementStart('li', 'entity_block');
+            $bf = new GroupBlockForm($this->out, $this->profile, $this->group,
+                                array('action' => 'groupmembers',
+                                      'nickname' => $this->group->nickname));
+            $bf->show();
+            $this->out->elementEnd('li');
+        }
+
+    }
+}
+
+/**
+ * Form for blocking a user from a group
+ *
+ * @category Form
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ * @see      BlockForm
+ */
+
+class GroupBlockForm extends Form
+{
+    /**
+     * Profile of user to block
+     */
+
+    var $profile = null;
+
+    /**
+     * Group to block the user from
+     */
+
+    var $group = null;
+
+    /**
+     * Return-to args
+     */
+
+    var $args = null;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out     output channel
+     * @param Profile       $profile profile of user to block
+     * @param User_group    $group   group to block user from
+     * @param array         $args    return-to args
+     */
+
+    function __construct($out=null, $profile=null, $group=null, $args=null)
+    {
+        parent::__construct($out);
+
+        $this->profile = $profile;
+        $this->group   = $group;
+        $this->args    = $args;
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        // This should be unique for the page.
+        return 'block-' . $this->profile->id;
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_group_block';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        return common_local_url('groupblock');
+    }
+
+    /**
+     * Legend of the Form
+     *
+     * @return void
+     */
+    function formLegend()
+    {
+        $this->out->element('legend', null, _('Block user from group'));
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        $this->out->hidden('blockto-' . $this->profile->id,
+                           $this->profile->id,
+                           'blockto');
+        $this->out->hidden('blockgroup-' . $this->group->id,
+                           $this->group->id,
+                           'blockgroup');
+        if ($this->args) {
+            foreach ($this->args as $k => $v) {
+                $this->out->hidden('returnto-' . $k, $v);
+            }
+        }
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Block'), 'submit', null, _('Block this user'));
+    }
+}
+
+/**
+ * Form for making a user an admin for a group
+ *
+ * @category Form
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class MakeAdminForm extends Form
+{
+    /**
+     * Profile of user to block
+     */
+
+    var $profile = null;
+
+    /**
+     * Group to block the user from
+     */
+
+    var $group = null;
+
+    /**
+     * Return-to args
+     */
+
+    var $args = null;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out     output channel
+     * @param Profile       $profile profile of user to block
+     * @param User_group    $group   group to block user from
+     * @param array         $args    return-to args
+     */
+
+    function __construct($out=null, $profile=null, $group=null, $args=null)
+    {
+        parent::__construct($out);
+
+        $this->profile = $profile;
+        $this->group   = $group;
+        $this->args    = $args;
+    }
+
+    /**
+     * ID of the form
+     *
+     * @return int ID of the form
+     */
+
+    function id()
+    {
+        // This should be unique for the page.
+        return 'makeadmin-' . $this->profile->id;
+    }
+
+    /**
+     * class of the form
+     *
+     * @return string class of the form
+     */
+
+    function formClass()
+    {
+        return 'form_make_admin';
+    }
+
+    /**
+     * Action of the form
+     *
+     * @return string URL of the action
+     */
+
+    function action()
+    {
+        return common_local_url('makeadmin', array('nickname' => $this->group->nickname));
+    }
+
+    /**
+     * Legend of the Form
+     *
+     * @return void
+     */
+
+    function formLegend()
+    {
+        $this->out->element('legend', null, _('Make user an admin of the group'));
+    }
+
+    /**
+     * Data elements of the form
+     *
+     * @return void
+     */
+
+    function formData()
+    {
+        $this->out->hidden('profileid-' . $this->profile->id,
+                           $this->profile->id,
+                           'profileid');
+        $this->out->hidden('groupid-' . $this->group->id,
+                           $this->group->id,
+                           'groupid');
+        if ($this->args) {
+            foreach ($this->args as $k => $v) {
+                $this->out->hidden('returnto-' . $k, $v);
+            }
+        }
+    }
+
+    /**
+     * Action elements
+     *
+     * @return void
+     */
+
+    function formActions()
+    {
+        $this->out->submit('submit', _('Make Admin'), 'submit', null, _('Make this user an admin'));
+    }
+}
index 0b7280a11c33648584096b95176eafb63fea7835..2bdcaafb27a56e17ea1bd556741e34ba70880599 100644 (file)
@@ -116,6 +116,7 @@ class groupRssAction extends Rss10Action
             return null;
         }
 
+        $notices = array();
         $notice = $group->getNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit);
 
         while ($notice->fetch()) {
index 26b52a5fcd4dde75618205ccb3716900a8a4d6b8..3d62843ed678d54d08c9fd3ccb5ca897568a5c75 100644 (file)
@@ -100,11 +100,13 @@ class GroupsAction extends Action
 
     function showContent()
     {
-        $this->elementStart('p', array('id' => 'new_group'));
-        $this->element('a', array('href' => common_local_url('newgroup'),
-                                  'class' => 'more'),
-                       _('Create a new group'));
-        $this->elementEnd('p');
+        if (common_logged_in()) {
+            $this->elementStart('p', array('id' => 'new_group'));
+            $this->element('a', array('href' => common_local_url('newgroup'),
+                                      'class' => 'more'),
+                           _('Create a new group'));
+            $this->elementEnd('p');
+        }
 
         $offset = ($this->page-1) * GROUPS_PER_PAGE;
         $limit =  GROUPS_PER_PAGE + 1;
@@ -113,6 +115,7 @@ class GroupsAction extends Action
         $groups->orderBy('created DESC');
         $groups->limit($offset, $limit);
 
+        $cnt = 0;
         if ($groups->find()) {
             $gl = new GroupList($groups, null, $this);
             $cnt = $gl->show();
index 06b4a77550cf33bd672207ace03e135e31754c93..c50466ce62a4f65f77ec730b58b2784756f66613 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/actions/groupunblock.php b/actions/groupunblock.php
new file mode 100644 (file)
index 0000000..6beb463
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+/**
+ * Block a user from a group action class.
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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);
+}
+
+/**
+ * Unlock a user from a group
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class GroupunblockAction extends Action
+{
+    var $profile = null;
+    var $group = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'));
+            return false;
+        }
+        $token = $this->trimmed('token');
+        if (empty($token) || $token != common_session_token()) {
+            $this->clientError(_('There was a problem with your session token. Try again, please.'));
+            return;
+        }
+        $id = $this->trimmed('unblockto');
+        if (empty($id)) {
+            $this->clientError(_('No profile specified.'));
+            return false;
+        }
+        $this->profile = Profile::staticGet('id', $id);
+        if (empty($this->profile)) {
+            $this->clientError(_('No profile with that ID.'));
+            return false;
+        }
+        $group_id = $this->trimmed('unblockgroup');
+        if (empty($group_id)) {
+            $this->clientError(_('No group specified.'));
+            return false;
+        }
+        $this->group = User_group::staticGet('id', $group_id);
+        if (empty($this->group)) {
+            $this->clientError(_('No such group.'));
+            return false;
+        }
+        $user = common_current_user();
+        if (!$user->isAdmin($this->group)) {
+            $this->clientError(_('Only an admin can unblock group members.'), 401);
+            return false;
+        }
+        if (!Group_block::isBlocked($this->group, $this->profile)) {
+            $this->clientError(_('User is not blocked from group.'));
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Handle request
+     *
+     * @param array $args $_REQUEST args; handled in prepare()
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $this->unblockProfile();
+        }
+    }
+
+    /**
+     * Unblock a user.
+     *
+     * @return void
+     */
+
+    function unblockProfile()
+    {
+        $result = Group_block::unblockProfile($this->group, $this->profile);
+
+        if (!$result) {
+            $this->serverError(_('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), 303);
+        } else {
+            common_redirect(common_local_url('blockedfromgroup',
+                                             array('nickname' => $this->group->nickname)),
+                            303);
+        }
+    }
+}
+
index 7e52cdbcc6af6d20b88be1944f038a306d999c0c..bdea4807d8a2f2ac0f7f8c1043d35c6d56d6595d 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -19,7 +19,7 @@
 
 if (!defined('LACONICA')) { exit(1); }
 
-class InviteAction extends Action
+class InviteAction extends CurrentUserDesignAction
 {
     var $mode = null;
     var $error = null;
@@ -35,7 +35,9 @@ class InviteAction extends Action
     function handle($args)
     {
         parent::handle($args);
-        if (!common_logged_in()) {
+        if (!common_config('invite', 'enabled')) {
+            $this->clientError(_('Invites have been disabled.'));
+        } else if (!common_logged_in()) {
             $this->clientError(sprintf(_('You must be logged in to invite other users to use %s'),
                                         common_config('site', 'name')));
             return;
index a5d82ddc7780e3afeda2f38f9c16030773931fd1..0e4f96eaf5002e9bb2fd749beb7c49df8f9f1bad 100644 (file)
@@ -96,6 +96,11 @@ class JoingroupAction extends Action
             return false;
         }
 
+        if (Group_block::isBlocked($this->group, $cur->getProfile())) {
+            $this->clientError(_('You have been blocked from that group by the admin.'), 403);
+            return false;
+        }
+
         return true;
     }
 
index 9f3bfe2470a2253d0e93acd9ca3ec2bf976c5423..3fcfb4f4ef2570352d1c28f888c09329182feced 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -70,10 +70,20 @@ class LogoutAction extends Action
         if (!common_logged_in()) {
             $this->clientError(_('Not logged in.'));
         } else {
-            common_set_user(null);
-            common_real_login(false); // not logged in
-            common_forgetme(); // don't log back in!
+            if (Event::handle('StartLogout', array($this))) {
+                $this->logout();
+            }
+            Event::handle('EndLogout', array($this));
+
             common_redirect(common_local_url('public'), 303);
         }
     }
+
+    function logout()
+    {
+        common_set_user(null);
+        common_real_login(false); // not logged in
+        common_forgetme(); // don't log back in!
+    }
+
 }
diff --git a/actions/makeadmin.php b/actions/makeadmin.php
new file mode 100644 (file)
index 0000000..6fc2cf9
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Make another user an admin of a group
+ *
+ * PHP version 5
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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);
+}
+
+/**
+ * Make another user an admin of a group
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ */
+
+class MakeadminAction extends Action
+{
+    var $profile = null;
+    var $group = null;
+
+    /**
+     * Take arguments for running
+     *
+     * @param array $args $_REQUEST args
+     *
+     * @return boolean success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'));
+            return false;
+        }
+        $token = $this->trimmed('token');
+        if (empty($token) || $token != common_session_token()) {
+            $this->clientError(_('There was a problem with your session token. Try again, please.'));
+            return;
+        }
+        $id = $this->trimmed('profileid');
+        if (empty($id)) {
+            $this->clientError(_('No profile specified.'));
+            return false;
+        }
+        $this->profile = Profile::staticGet('id', $id);
+        if (empty($this->profile)) {
+            $this->clientError(_('No profile with that ID.'));
+            return false;
+        }
+        $group_id = $this->trimmed('groupid');
+        if (empty($group_id)) {
+            $this->clientError(_('No group specified.'));
+            return false;
+        }
+        $this->group = User_group::staticGet('id', $group_id);
+        if (empty($this->group)) {
+            $this->clientError(_('No such group.'));
+            return false;
+        }
+        $user = common_current_user();
+        if (!$user->isAdmin($this->group)) {
+            $this->clientError(_('Only an admin can make another user an admin.'), 401);
+            return false;
+        }
+        if ($this->profile->isAdmin($this->group)) {
+            $this->clientError(sprintf(_('%s is already an admin for group "%s".'),
+                                       $this->profile->getBestName(),
+                                       $this->group->getBestName()),
+                               401);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Handle request
+     *
+     * @param array $args $_REQUEST args; handled in prepare()
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            $this->makeAdmin();
+        }
+    }
+
+    /**
+     * Make user an admin
+     *
+     * @return void
+     */
+
+    function makeAdmin()
+    {
+        $member = Group_member::pkeyGet(array('group_id' => $this->group->id,
+                                              'profile_id' => $this->profile->id));
+
+        if (empty($member)) {
+            $this->serverError(_('Can\'t get membership record for %s in group %s'),
+                               $this->profile->getBestName(),
+                               $this->group->getBestName());
+        }
+
+        $orig = clone($member);
+
+        $member->is_admin = 1;
+
+        $result = $member->update($orig);
+
+        if (!$result) {
+            common_log_db_error($member, 'UPDATE', __FILE__);
+            $this->serverError(_('Can\'t make %s an admin for group %s'),
+                               $this->profile->getBestName(),
+                               $this->group->getBestName());
+        }
+
+        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), 303);
+        } else {
+            common_redirect(common_local_url('groupmembers',
+                                             array('nickname' => $this->group->nickname)),
+                            303);
+        }
+    }
+}
index 0b408ec953aff275b5523fbd3281b0f917de0564..6884a919a89eb82a343183c4c8e8844be2c96d40 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 67cd6b2f18005f3efc3878a71bec65cbe92a5365..0289e77c2511a7aebd096a4d78e66d54ac72dbe3 100644 (file)
@@ -123,6 +123,7 @@ class NewgroupAction extends Action
         $homepage    = $this->trimmed('homepage');
         $description = $this->trimmed('description');
         $location    = $this->trimmed('location');
+        $aliasstring = $this->trimmed('aliases');
 
         if (!Validate::string($nickname, array('min_length' => 1,
                                                'max_length' => 64,
@@ -153,6 +154,37 @@ class NewgroupAction extends Action
             return;
         }
 
+        if (!empty($aliasstring)) {
+            $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
+        } else {
+            $aliases = array();
+        }
+
+        if (count($aliases) > common_config('group', 'maxaliases')) {
+            $this->showForm(sprintf(_('Too many aliases! Maximum %d.'),
+                                    common_config('group', 'maxaliases')));
+            return;
+        }
+
+        foreach ($aliases as $alias) {
+            if (!Validate::string($alias, array('min_length' => 1,
+                                                'max_length' => 64,
+                                                'format' => NICKNAME_FMT))) {
+                $this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
+                return;
+            }
+            if ($this->nicknameExists($alias)) {
+                $this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
+                                        $alias));
+                return;
+            }
+            // XXX assumes alphanum nicknames
+            if (strcmp($alias, $nickname) == 0) {
+                $this->showForm(_('Alias can\'t be the same as nickname.'));
+                return;
+            }
+        }
+
         $cur = common_current_user();
 
         // Checked in prepare() above
@@ -177,6 +209,12 @@ class NewgroupAction extends Action
             $this->serverError(_('Could not create group.'));
         }
 
+        $result = $group->setAliases($aliases);
+
+        if (!$result) {
+            $this->serverError(_('Could not create aliases.'));
+        }
+
         $member = new Group_member();
 
         $member->group_id   = $group->id;
@@ -199,7 +237,18 @@ class NewgroupAction extends Action
     function nicknameExists($nickname)
     {
         $group = User_group::staticGet('nickname', $nickname);
-        return (!is_null($group) && $group != false);
+
+        if (!empty($group)) {
+            return true;
+        }
+
+        $alias = Group_alias::staticGet('alias', $nickname);
+
+        if (!empty($alias)) {
+            return true;
+        }
+
+        return false;
     }
 }
 
index cbd04c58b226c0a609c44996f7048bcf28f7abcd..e254eac4999852389b8623b36db62d10826f5bb3 100644 (file)
@@ -84,20 +84,24 @@ class NewnoticeAction extends Action
 
     function handle($args)
     {
-        parent::handle($args);
-
         if (!common_logged_in()) {
             $this->clientError(_('Not logged in.'));
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            // check for this before token since all POST and FILES data
+            // is losts when size is exceeded
+            if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
+                $this->clientError(sprintf(_('The server was unable to handle ' .
+                    'that much POST data (%s bytes) due to its current configuration.'),
+                    $_SERVER['CONTENT_LENGTH']));
+            }
+            parent::handle($args);
 
             // CSRF protection
             $token = $this->trimmed('token');
             if (!$token || $token != common_session_token()) {
                 $this->clientError(_('There was a problem with your session token. '.
                                      'Try again, please.'));
-                return;
             }
-
             try {
                 $this->saveNewNotice();
             } catch (Exception $e) {
@@ -109,6 +113,33 @@ class NewnoticeAction extends Action
         }
     }
 
+    function getUploadedFileType() {
+        require_once 'MIME/Type.php';
+
+        $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+        $cmd = common_config('attachments', 'filecommand');
+
+        $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
+        if (in_array($filetype, common_config('attachments', 'supported'))) {
+            return $filetype;
+        }
+        $media = MIME_Type::getMedia($filetype);
+        if ('application' !== $media) {
+            $hint = sprintf(_(' Try using another %s format.'), $media);
+        } else {
+            $hint = '';
+        }
+        $this->clientError(sprintf(
+            _('%s is not a supported filetype on this server.'), $filetype) . $hint);
+    }
+
+    function isRespectsQuota($user) {
+        $file = new File;
+        $ret = $file->isRespectsQuota($user,$_FILES['attach']['size']);
+        if (true === $ret) return true;
+        $this->clientError($ret);
+    }
+
     /**
      * Save a new notice, based on arguments
      *
@@ -131,7 +162,6 @@ class NewnoticeAction extends Action
             $this->clientError(_('No content!'));
         } else {
             $content_shortened = common_shorten_links($content);
-
             if (mb_strlen($content_shortened) > 140) {
                 $this->clientError(_('That\'s too long. '.
                                      'Max notice size is 140 chars.'));
@@ -158,12 +188,80 @@ class NewnoticeAction extends Action
             $replyto = 'false';
         }
 
-        $notice = Notice::saveNew($user->id, $content, 'web', 1,
+        if (isset($_FILES['attach']['error'])) {
+            switch ($_FILES['attach']['error']) {
+                case UPLOAD_ERR_NO_FILE:
+                    // no file uploaded, nothing to do
+                    break;
+
+                case UPLOAD_ERR_OK:
+                    $mimetype = $this->getUploadedFileType();
+                    if (!$this->isRespectsQuota($user)) {
+                        die('clientError() should trigger an exception before reaching here.');
+                    }
+                    break;
+
+                case UPLOAD_ERR_INI_SIZE:
+                    $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
+
+                case UPLOAD_ERR_FORM_SIZE:
+                    $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
+
+                case UPLOAD_ERR_PARTIAL:
+                    $this->clientError(_('The uploaded file was only partially uploaded.'));
+
+                case  UPLOAD_ERR_NO_TMP_DIR:
+                    $this->clientError(_('Missing a temporary folder.'));
+
+                case UPLOAD_ERR_CANT_WRITE:
+                    $this->clientError(_('Failed to write file to disk.'));
+
+                case UPLOAD_ERR_EXTENSION:
+                    $this->clientError(_('File upload stopped by extension.'));
+
+                default:
+                    die('Should never reach here.');
+            }
+        }
+
+        if (isset($mimetype)) {
+            $filename = $this->saveFile($mimetype);
+            if (empty($filename)) {
+                $this->clientError(_('Couldn\'t save file.'));
+            }
+
+            $fileRecord = $this->storeFile($filename, $mimetype);
+
+            $fileurl = common_local_url('attachment',
+                array('attachment' => $fileRecord->id));
+
+            // not sure this is necessary -- Zach
+            $this->maybeAddRedir($fileRecord->id, $fileurl);
+
+            $short_fileurl = common_shorten_url($fileurl);
+            $content_shortened .= ' ' . $short_fileurl;
+
+            if (mb_strlen($content_shortened) > 140) {
+                $this->deleteFile($filename);
+                $this->clientError(_('Max notice size is 140 chars, including attachment URL.'));
+            }
+
+            // Also, not sure this is necessary -- Zach
+            $this->maybeAddRedir($fileRecord->id, $short_fileurl);
+        }
+
+        $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
                                   ($replyto == 'false') ? null : $replyto);
 
         if (is_string($notice)) {
+            if (isset($filename)) {
+                $this->deleteFile($filename);
+            }
             $this->clientError($notice);
-            return;
+        }
+
+        if (isset($mimetype)) {
+            $this->attachFile($notice, $fileRecord);
         }
 
         common_broadcast_notice($notice);
@@ -191,6 +289,87 @@ class NewnoticeAction extends Action
         }
     }
 
+    function saveFile($mimetype) {
+
+        $cur = common_current_user();
+
+        if (empty($cur)) {
+            $this->serverError(_('Somehow lost the login in saveFile'));
+        }
+
+        $basename = basename($_FILES['attach']['name']);
+
+        $filename = File::filename($cur->getProfile(), $basename, $mimetype);
+
+        $filepath = File::path($filename);
+
+        if (move_uploaded_file($_FILES['attach']['tmp_name'], $filepath)) {
+            return $filename;
+        } else {
+            $this->clientError(_('File could not be moved to destination directory.'));
+        }
+    }
+
+    function deleteFile($filename)
+    {
+        $filepath = File::path($filename);
+        @unlink($filepath);
+    }
+
+    function storeFile($filename, $mimetype) {
+
+        $file = new File;
+        $file->filename = $filename;
+
+        $file->url = File::url($filename);
+
+        $filepath = File::path($filename);
+
+        $file->size = filesize($filepath);
+        $file->date = time();
+        $file->mimetype = $mimetype;
+
+        $file_id = $file->insert();
+
+        if (!$file_id) {
+            common_log_db_error($file, "INSERT", __FILE__);
+            $this->clientError(_('There was a database error while saving your file. Please try again.'));
+        }
+
+        return $file;
+    }
+
+    function rememberFile($file, $short)
+    {
+        $this->maybeAddRedir($file->id, $short);
+    }
+
+    function maybeAddRedir($file_id, $url)
+    {
+        $file_redir = File_redirection::staticGet('url', $url);
+
+        if (empty($file_redir)) {
+            $file_redir = new File_redirection;
+            $file_redir->url = $url;
+            $file_redir->file_id = $file_id;
+
+            $result = $file_redir->insert();
+
+            if (!$result) {
+                common_log_db_error($file_redir, "INSERT", __FILE__);
+                $this->clientError(_('There was a database error while saving your file. Please try again.'));
+            }
+        }
+    }
+
+    function attachFile($notice, $filerec)
+    {
+        File_to_post::processNew($filerec->id, $notice->id);
+
+        $this->maybeAddRedir($filerec->id,
+            common_local_url('file', array('notice' => $notice->id)));
+    }
+
     /**
      * Show an Ajax-y error message
      *
@@ -295,3 +474,4 @@ class NewnoticeAction extends Action
         $nli->show();
     }
 }
+
index d996998fc6026328cc6a8271756393203b2202fa..49b473d9e9cd542c03be950ff5fc93a60afb111b 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index f6da969ee47bc29a0257ac3772320874a943299c..2a4b2060d3fa3f2d3781d429f27cbd5a372ce9a3 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -67,11 +67,16 @@ class NoticesearchrssAction extends Rss10Action
 
         if (!$limit) $limit = 20;
         $search_engine->limit(0, $limit, true);
-        $search_engine->query($q);
-        $notice->find();
+        if (false === $search_engine->query($q)) {
+            $cnt = 0;
+        } else {
+            $cnt = $notice->find();
+        }
 
-        while ($notice->fetch()) {
-            $notices[] = clone($notice);
+        if ($cnt > 0) {
+            while ($notice->fetch()) {
+                $notices[] = clone($notice);
+            }
         }
 
         return $notices;
index c23d3e64356f4af88ea166663e9d61d56cc95c09..78c0ee566b55923b7fbcddbaf07f81d86fae1f44 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 1a4372d73e76722dfe5f60d30b220b6ba191b3f0..a8d052096ca470712c43b78e0a2988c6546a5605 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index d1f4895ce4a2d569aa2263eaf3bce37a9d8f0ede..4fe95c93b5cf318443d1c663638e1bb525021c61 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index b542233ca79ee72dc5bde18531822288c572f4ac..1277f80527f2c3e94c52e59c6176973bc3868fb0 100644 (file)
@@ -83,14 +83,12 @@ class OthersettingsAction extends AccountSettingsAction
     {
         $user = common_current_user();
 
-
         $this->elementStart('form', array('method' => 'post',
                                           'id' => 'form_settings_other',
                                           'class' => 'form_settings',
                                           'action' =>
                                           common_local_url('othersettings')));
         $this->elementStart('fieldset');
-        $this->element('legend', null, _('URL Auto-shortening'));
         $this->hidden('token', common_session_token());
 
         // I18N
@@ -109,10 +107,14 @@ class OthersettingsAction extends AccountSettingsAction
 
         $this->elementStart('ul', 'form_data');
         $this->elementStart('li');
-        $this->dropdown('urlshorteningservice', _('Service'),
+        $this->dropdown('urlshorteningservice', _('Shorten URLs with'),
                         $services, _('Automatic shortening service to use.'),
                         false, $user->urlshorteningservice);
         $this->elementEnd('li');
+        $this->elementStart('li');
+        $this->checkbox('viewdesigns', _('View profile designs'),
+                        $user->viewdesigns, _('Show or hide profile designs.'));
+        $this->elementEnd('li');
         $this->elementEnd('ul');
         $this->submit('save', _('Save'));
         $this->elementEnd('fieldset');
@@ -145,6 +147,8 @@ class OthersettingsAction extends AccountSettingsAction
             return;
         }
 
+        $viewdesigns = $this->boolean('viewdesigns');
+
         $user = common_current_user();
 
         assert(!is_null($user)); // should already be checked
@@ -154,6 +158,7 @@ class OthersettingsAction extends AccountSettingsAction
         $original = clone($user);
 
         $user->urlshorteningservice = $urlshorteningservice;
+        $user->viewdesigns          = $viewdesigns;
 
         $result = $user->update($original);
 
index 65d970dd159a7fea183763d9fd8488fdde4cb2ab..60ddb6a82863d80b0b78005c6dc2d5a9474f28df 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -87,3 +87,47 @@ class PeoplesearchAction extends SearchAction
     }
 }
 
+/**
+ * People search results class
+ *
+ * Derivative of ProfileList with specialization for highlighting search terms.
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Robin Millette <millette@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link     http://laconi.ca/
+ *
+ * @see PeoplesearchAction
+ */
+
+class PeopleSearchResults extends ProfileList
+{
+    var $terms = null;
+    var $pattern = null;
+
+    function __construct($profile, $terms, $action)
+    {
+        parent::__construct($profile, $action);
+
+        $this->terms = array_map('preg_quote',
+                                 array_map('htmlspecialchars', $terms));
+
+        $this->pattern = '/('.implode('|',$terms).')/i';
+    }
+
+    function newProfileItem($profile)
+    {
+        return new PeopleSearchResultItem($profile, $this->action);
+    }
+}
+
+class PeopleSearchResultItem extends ProfileListItem
+{
+    function highlight($text)
+    {
+        return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
+    }
+}
+
index 5add75485808d08f704fa6dfa73faa34593605d6..dd3c1c0899a7befec824afeae83e4c8f19434633 100644 (file)
@@ -124,7 +124,7 @@ class PeopletagAction extends Action
 
         $profile->query(sprintf($qry, $this->tag, $lim));
 
-        $pl  = new ProfileList($profile, null, $this);
+        $pl  = new ProfileList($profile, $this);
         $cnt = $pl->show();
 
         $this->pagination($this->page > 1,
index 3e98b3cd5535efe46c946df4cc1e664dc7fbc6f2..eb2d63b61cf08119ba2911b130a6aaf50ee508fd 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 27153f13159daf2c990ba7937cc697e0724ad27f..ef9ef0d1ab0dc4ea451d29f1c1a016359533501c 100644 (file)
@@ -35,6 +35,10 @@ require_once INSTALLDIR.'/lib/publicgroupnav.php';
 require_once INSTALLDIR.'/lib/noticelist.php';
 require_once INSTALLDIR.'/lib/feedlist.php';
 
+// Farther than any human will go
+
+define('MAX_PUBLIC_PAGE', 100);
+
 /**
  * Action for displaying the public stream
  *
@@ -74,6 +78,10 @@ class PublicAction extends Action
         parent::prepare($args);
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
 
+        if ($this->page > MAX_PUBLIC_PAGE) {
+            $this->clientError(sprintf(_("Beyond the page limit (%s)"), MAX_PUBLIC_PAGE));
+        }
+
         common_set_returnto($this->selfUrl());
 
         return true;
@@ -174,8 +182,10 @@ class PublicAction extends Action
             $message .= _('Be the first to post!');
         }
         else {
-            $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
-        }
+            if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
+                $message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
+            }
+       }
 
         $this->elementStart('div', 'guide');
         $this->raw(common_markup_to_html($message));
index bc52f29522778483220c7994605b5b552c928502..7e8df9625113345e521b7083b68176b469c51e59 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 283a932ca56772b4191a5bc6a5fa7e2c5618085c..0a14215502e1201a720ec4b9f2c737d7a4c0f292 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 82263fcd59903cc921e42f4c55bc14c3cf2c94a4..2afd052a78f3fbb8e5d00c63339aadf56aa3712e 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 033cf557f8959d7ded047794425207e82a1b82ca..dcbbbdb6a6de9afb238715b7ff1cac3d78f40b7d 100644 (file)
@@ -382,6 +382,19 @@ class RegisterAction extends Action
 
     function showFormContent()
     {
+        $code = $this->trimmed('code');
+
+        $invite = null;
+
+        if ($code) {
+            $invite = Invitation::staticGet($code);
+        }
+
+        if (common_config('site', 'inviteonly') && !($code && $invite)) {
+            $this->clientError(_('Sorry, only invited people can register.'));
+            return;
+        }
+
         $this->elementStart('form', array('method' => 'post',
                                           'id' => 'form_register',
                                           'class' => 'form_settings',
index 0b117489621d7307776b5266177e21e7b35be788..e658f8d3748ed290159222c5ed13867187e3be3e 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index eac4d0a3aeeda3622eec4bcbaec8a787fbbe82ac..d7ed440e9237d0a32fd814eb1dd99269ad6393f9 100644 (file)
@@ -45,9 +45,8 @@ require_once INSTALLDIR.'/lib/feedlist.php';
  * @link     http://laconi.ca/
  */
 
-class RepliesAction extends Action
+class RepliesAction extends OwnerDesignAction
 {
-    var $user = null;
     var $page = null;
 
     /**
index 2017c43094a5f40f176d48efb3c3ff4601aba8c0..a87e2870dc090c3bae2f23cc7aac1b128b9e0287 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 9507e3d6c9c3a2c897276d31a794188a1620d0bb..8d1e3f004342ea399a9a35efb80386b57063bc58 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 865045337aa9c07e8421f4801aeaa4f584f7e718..8efe9d30aa461ff48fb589b4f0c310653ed9a047 100644 (file)
@@ -45,7 +45,7 @@ require_once INSTALLDIR.'/lib/feedlist.php';
  * @link     http://laconi.ca/
  */
 
-class ShowfavoritesAction extends Action
+class ShowfavoritesAction extends OwnerDesignAction
 {
     /** User we're getting the faves of */
     var $user = null;
@@ -191,10 +191,21 @@ class ShowfavoritesAction extends Action
 
     function showContent()
     {
-        $notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
-                                               NOTICES_PER_PAGE + 1);
+        $cur = common_current_user();
 
-        if (!$notice) {
+        if (!empty($cur) && $cur->id == $this->user->id) {
+
+            // Show imported/gateway notices as well as local if
+            // the user is looking at his own favorites
+
+            $notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
+                                                   NOTICES_PER_PAGE + 1, true);
+        } else {
+            $notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
+                                                   NOTICES_PER_PAGE + 1, false);
+        }
+
+        if (empty($notice)) {
             $this->serverError(_('Could not retrieve favorite notices.'));
             return;
         }
index 29b6fa1e61be25ee576a7256a4d89eb1bb88daa3..ce11d574e94c7be3577902b15d5ef51590fa94f7 100644 (file)
@@ -47,10 +47,9 @@ define('MEMBERS_PER_SECTION', 27);
  * @link     http://laconi.ca/
  */
 
-class ShowgroupAction extends Action
+class ShowgroupAction extends GroupDesignAction
 {
-    /** group we're viewing. */
-    var $group = null;
+
     /** page we're viewing. */
     var $page = null;
 
@@ -272,6 +271,17 @@ class ShowgroupAction extends Action
             $this->elementEnd('dl');
         }
 
+        if (common_config('group', 'maxaliases') > 0) {
+            $aliases = $this->group->getAliases();
+
+            if (!empty($aliases)) {
+                $this->elementStart('dl', 'entity_aliases');
+                $this->element('dt', null, _('Aliases'));
+                $this->element('dd', 'aliases', implode(' ', $aliases));
+                $this->elementEnd('dl');
+            }
+        }
+
         $this->elementEnd('div');
 
         $this->elementStart('div', 'entity_actions');
@@ -283,7 +293,7 @@ class ShowgroupAction extends Action
             if ($cur->isMember($this->group)) {
                 $lf = new LeaveForm($this, $this->group);
                 $lf->show();
-            } else {
+            } else if (!Group_block::isBlocked($this->group, $cur->getProfile())) {
                 $jf = new JoinForm($this, $this->group);
                 $jf->show();
             }
@@ -321,6 +331,7 @@ class ShowgroupAction extends Action
     {
         $this->showMembers();
         $this->showStatistics();
+        $this->showAdmins();
         $cloud = new GroupTagCloudSection($this, $this->group);
         $cloud->show();
     }
@@ -344,7 +355,7 @@ class ShowgroupAction extends Action
 
         $this->element('h2', null, _('Members'));
 
-        $pml = new ProfileMiniList($member, null, $this);
+        $pml = new ProfileMiniList($member, $this);
         $cnt = $pml->show();
         if ($cnt == 0) {
              $this->element('p', null, _('(None)'));
@@ -359,6 +370,18 @@ class ShowgroupAction extends Action
         $this->elementEnd('div');
     }
 
+    /**
+     * Show list of admins
+     *
+     * @return void
+     */
+
+    function showAdmins()
+    {
+        $adminSection = new GroupAdminSection($this, $this->group);
+        $adminSection->show();
+    }
+
     /**
      * Show some statistics
      *
@@ -413,3 +436,34 @@ class ShowgroupAction extends Action
         $this->elementEnd('div');
     }
 }
+
+class GroupAdminSection extends ProfileSection
+{
+    var $group;
+
+    function __construct($out, $group)
+    {
+        parent::__construct($out);
+        $this->group = $group;
+    }
+
+    function getProfiles()
+    {
+        return $this->group->getAdmins();
+    }
+
+    function title()
+    {
+        return _('Admins');
+    }
+
+    function divId()
+    {
+        return 'group_admins';
+    }
+
+    function moreUrl()
+    {
+        return null;
+    }
+}
\ No newline at end of file
index 1be1e2414c9cc0d10986c3f89985af07a47d674d..1ec38a76bcf8cf7208dc94055f202f626a66bd18 100644 (file)
@@ -45,7 +45,7 @@ require_once INSTALLDIR.'/lib/feedlist.php';
  * @link     http://laconi.ca/
  */
 
-class ShownoticeAction extends Action
+class ShownoticeAction extends OwnerDesignAction
 {
     /**
      * Notice object to show
@@ -83,18 +83,25 @@ class ShownoticeAction extends Action
 
         $this->notice = Notice::staticGet($id);
 
-        if (!$this->notice) {
+        if (empty($this->notice)) {
             $this->clientError(_('No such notice.'), 404);
             return false;
         }
 
         $this->profile = $this->notice->getProfile();
 
-        if (!$this->profile) {
+        if (empty($this->profile)) {
             $this->serverError(_('Notice has no profile'), 500);
             return false;
         }
 
+        $this->user = User::staticGet('id', $this->profile->id);
+
+        if (empty($this->user)) {
+            $this->serverError(_('Not a local notice'), 500);
+            return false;
+        }
+
         $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE);
 
         return true;
@@ -158,8 +165,14 @@ class ShownoticeAction extends Action
 
     function title()
     {
+        if (!empty($this->profile->fullname)) {
+            $base = $this->profile->fullname . ' (' . $this->user->nickname . ') ';
+        } else {
+            $base = $this->user->nickname;
+        }
+
         return sprintf(_('%1$s\'s status on %2$s'),
-                       $this->profile->nickname,
+                       $base,
                        common_exact_date($this->notice->created));
     }
 
@@ -208,10 +221,10 @@ class ShownoticeAction extends Action
 
     function showContent()
     {
-        $this->elementStart('ul', array('class' => 'notices'));
-        $nli = new NoticeListItem($this->notice, $this);
+        $this->elementStart('ol', array('class' => 'notices xoxo'));
+        $nli = new SingleNoticeItem($this->notice, $this);
         $nli->show();
-        $this->elementEnd('ul');
+        $this->elementEnd('ol');
     }
 
     /**
@@ -264,3 +277,29 @@ class ShownoticeAction extends Action
         }
     }
 }
+
+class SingleNoticeItem extends NoticeListItem
+{
+    /**
+     * recipe function for displaying a single notice.
+     *
+     * We overload to show attachments.
+     *
+     * @return void
+     */
+
+    function show()
+    {
+        $this->showStart();
+        $this->showNotice();
+        $this->showNoticeAttachments();
+        $this->showNoticeInfo();
+        $this->showNoticeOptions();
+        $this->showEnd();
+    }
+
+    function showNoticeAttachments() {
+        $al = new AttachmentList($this->notice, $this->out);
+        $al->show();
+    }
+}
index 641228bc731d90f97139ea8316e95f0203dee69c..cd5d4bb7013bfb84f87d8eca580ddc0ba8c74bb6 100644 (file)
@@ -68,6 +68,9 @@ class ShowstreamAction extends ProfileAction
         } else {
             $base = $this->user->nickname;
         }
+        if (!empty($this->tag)) {
+            $base .= sprintf(_(' tagged %s'), $this->tag);
+        }
 
         if ($this->page == 1) {
             return $base;
@@ -110,6 +113,15 @@ class ShowstreamAction extends ProfileAction
 
     function getFeeds()
     {
+        if (!empty($this->tag)) {
+            return array(new Feed(Feed::RSS1,
+                common_local_url('userrss',
+                    array('nickname' => $this->user->nickname,
+                        'tag' => $this->tag)),
+                sprintf(_('Notice feed for %s tagged %s (RSS 1.0)'),
+                    $this->user->nickname, $this->tag)));
+        }
+
         return array(new Feed(Feed::RSS1,
                               common_local_url('userrss',
                                                array('nickname' => $this->user->nickname)),
@@ -356,7 +368,9 @@ class ShowstreamAction extends ProfileAction
 
     function showNotices()
     {
-        $notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        $notice = empty($this->tag)
+            ? $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1)
+            : $this->user->getTaggedNotices($this->tag, ($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null);
 
         $pnl = new ProfileNoticeList($notice, $this);
         $cnt = $pnl->show();
index 8ca2d791465ce34ce9e067051c18df0f1682321f..2e1bf553880144a0519dc1e1fed98c29ce196afe 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 0bc522867e39ca4a62ffd12a5ebf417a85441ba5..15b89a3122f95eeecce93fec73cff71f858141eb 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 4482de9a7c74279b000e042012fbb927a86b5dbd..66ac00fb19aa26d64da5441d07fafec8bc08f36a 100644 (file)
@@ -130,18 +130,34 @@ class SubscribersAction extends GalleryAction
     }
 }
 
-class SubscribersList extends ProfileList
+class SubscribersList extends SubscriptionList
 {
-    function showBlockForm()
+    function newListItem($profile)
     {
-        $bf = new BlockForm($this->out, $this->profile,
-                            array('action' => 'subscribers',
-                                  'nickname' => $this->owner->nickname));
-        $bf->show();
+        return new SubscribersListItem($profile, $this->owner, $this->action);
     }
+}
 
-    function isReadOnly($args)
+class SubscribersListItem extends SubscriptionListItem
+{
+    function showActions()
     {
-        return true;
+        $this->startActions();
+        $this->showSubscribeButton();
+        // Relevant code!
+        $this->showBlockForm();
+        $this->endActions();
+    }
+
+    function showBlockForm()
+    {
+        $user = common_current_user();
+
+        if (!empty($user) && $this->owner->id == $user->id) {
+            $bf = new BlockForm($this->out, $this->profile,
+                                array('action' => 'subscribers',
+                                      'nickname' => $this->owner->nickname));
+            $bf->show();
+        }
     }
 }
index 095b18ad87db49b97f1c16746d3a7366f6b0f7a4..42bdae10f78c160846de3f97d364bddfa88f390a 100644 (file)
@@ -137,22 +137,49 @@ class SubscriptionsAction extends GalleryAction
     }
 }
 
-class SubscriptionsList extends ProfileList
+// XXX SubscriptionsList and SubscriptionList are dangerously close
+
+class SubscriptionsList extends SubscriptionList
+{
+    function newListItem($profile)
+    {
+        return new SubscriptionsListItem($profile, $this->owner, $this->action);
+    }
+}
+
+class SubscriptionsListItem extends SubscriptionListItem
 {
-    function showOwnerControls($profile)
+    function showProfile()
+    {
+        $this->startProfile();
+        $this->showAvatar();
+        $this->showFullName();
+        $this->showLocation();
+        $this->showHomepage();
+        $this->showBio();
+        $this->showTags();
+        // Relevant portion!
+        $cur = common_current_user();
+        if (!empty($cur) && $cur->id == $this->owner->id) {
+            $this->showOwnerControls();
+        }
+        $this->endProfile();
+    }
+
+    function showOwnerControls()
     {
         $sub = Subscription::pkeyGet(array('subscriber' => $this->owner->id,
-                                           'subscribed' => $profile->id));
+                                           'subscribed' => $this->profile->id));
         if (!$sub) {
             return;
         }
 
-        $this->out->elementStart('form', array('id' => 'subedit-' . $profile->id,
+        $this->out->elementStart('form', array('id' => 'subedit-' . $this->profile->id,
                                           'method' => 'post',
                                           'class' => 'form_subscription_edit',
                                           'action' => common_local_url('subedit')));
         $this->out->hidden('token', common_session_token());
-        $this->out->hidden('profile', $profile->id);
+        $this->out->hidden('profile', $this->profile->id);
         $this->out->checkbox('jabber', _('Jabber'), $sub->jabber);
         $this->out->checkbox('sms', _('SMS'), $sub->sms);
         $this->out->submit('save', _('Save'));
index 691153d6a3bcd845c0f3c4765cc8ee9f3040b328..a5b665562f31f6c3a0624581583e8bade61e3e29 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -63,11 +63,13 @@ class SupAction extends Action
         # XXX: cache this. Depends on how big this protocol becomes;
         # Re-doing this query every 15 seconds isn't the end of the world.
 
+        $divider = common_sql_date(time() - $seconds);
+
         $notice->query('SELECT profile_id, max(id) AS max_id ' .
                        'FROM notice ' .
                         ((common_config('db','type') == 'pgsql') ?
                        'WHERE extract(epoch from created) > (extract(epoch from now()) - ' . $seconds . ') ' :
-                       'WHERE created > (now() - ' . $seconds . ') ' ) .
+                       'WHERE created > "'.$divider.'" ' ) .
                        'GROUP BY profile_id');
 
         $updates = array();
index f5ca06f0555669df257712765c57652456f42cec..888aba0628cb39eef5236725a0aeca43bd00c236 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -76,11 +76,6 @@ class TagAction extends Action
                               sprintf(_('Feed for tag %s'), $this->tag)));
     }
 
-    function showPageNotice()
-    {
-        return sprintf(_('Messages tagged "%s", most recent first'), $this->tag);
-    }
-
     function showContent()
     {
         $notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
index 0c5bb7cf3ecacac93f311a47c401e0715499641b..96246f79903185392df174fe35a574917d0add03 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 83cf3afe2f45b7aa594adb248291117dd4d87908..f69374fcac9d932e4a1394020422d2fba39f4301 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 8b956f897a397606434d2a2d1040a71c31b937c8..f2a7534a2988ff18f6673cd61081c7337589dd3a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -51,7 +53,8 @@ class TwitapiaccountAction extends TwitterapiAction
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST.'),
+                400, $apidata['content-type']);
             return;
         }
 
@@ -60,24 +63,20 @@ class TwitapiaccountAction extends TwitterapiAction
         if (!is_null($location) && mb_strlen($location) > 255) {
 
             // XXX: But Twitter just truncates and runs with it. -- Zach
-            $this->clientError(_('That\'s too long. Max notice size is 255 chars.'), 406, $apidate['content-type']);
+            $this->clientError(_('That\'s too long. Max notice size is 255 chars.'),
+                406, $apidate['content-type']);
             return;
         }
 
-        $user = $apidata['user'];
+        $user = $apidata['user']; // Always the auth user
         $profile = $user->getProfile();
 
-        if (!$profile) {
-            $this->serverError(_('User has no profile.'));
-            return;
-        }
-
         $orig_profile = clone($profile);
         $profile->location = $location;
 
         $result = $profile->update($orig_profile);
 
-        if (!$result) {
+        if (empty($result)) {
             common_log_db_error($profile, 'UPDATE', __FILE__);
             $this->serverError(_('Couldn\'t save profile.'));
             return;
index 8135adef3c2827ed17150082782f547339801b66..d8e72efb1600cb89c23c0b25a50473ddfef6cde6 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -31,12 +33,12 @@ class TwitapiblocksAction extends TwitterapiAction
 
         $blockee = $this->get_user($apidata['api_arg'], $apidata);
 
-        if (!$blockee) {
+        if (empty($blockee)) {
             $this->clientError('Not Found', 404, $apidata['content-type']);
             return;
         }
 
-        $user = $apidata['user'];
+        $user = $apidata['user']; // Always the auth user
 
         if ($user->hasBlocked($blockee) || $user->block($blockee)) {
             $type = $apidata['content-type'];
@@ -53,7 +55,7 @@ class TwitapiblocksAction extends TwitterapiAction
         parent::handle($args);
         $blockee = $this->get_user($apidata['api_arg'], $apidata);
 
-        if (!$blockee) {
+        if (empty($blockee)) {
             $this->clientError('Not Found', 404, $apidata['content-type']);
             return;
         }
index d2dbdb619b180044a0388cd592382944eec4018a..bd27e9d20abb25976bc8c7e74b6a578bc9fc1ae8 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -38,42 +40,34 @@ class Twitapidirect_messagesAction extends TwitterapiAction
 
     function show_messages($args, $apidata, $type)
     {
-        $user = $apidata['user'];
-
-        $count = $this->arg('count');
-        $since = $this->arg('since');
-        $since_id = $this->arg('since_id');
-        $max_id = $this->arg('max_id');
-
-        $page = $this->arg('page');
-
-        if (!$page) {
-            $page = 1;
-        }
+        $user = $apidata['user']; // Always the auth user
 
-        if (!$count) {
-            $count = 20;
-        }
-
-        $message = new Message();
-
-        $title = null;
+        $message  = new Message();
+        $title    = null;
         $subtitle = null;
-        $link = null;
-        $server = common_root_url();
+        $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);
+            $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);
+            $subtitle = sprintf(_("All the direct messages sent from %s"),
+                $user->nickname);
             $link = $server . $user->nickname . '/outbox';
         }
 
+        $page     = (int)$this->arg('page', 1);
+        $count    = (int)$this->arg('count', 20);
+        $max_id   = (int)$this->arg('max_id', 0);
+        $since_id = (int)$this->arg('since_id', 0);
+        $since    = $this->arg('since');
+
         if ($max_id) {
             $message->whereAdd("id <= $max_id");
         }
@@ -82,25 +76,23 @@ class Twitapidirect_messagesAction extends TwitterapiAction
             $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->limit((($page-1)*$count), $count);
         $message->find();
 
         switch($apidata['content-type']) {
-         case 'xml':
+        case 'xml':
             $this->show_xml_dmsgs($message);
             break;
-         case 'rss':
+        case 'rss':
             $this->show_rss_dmsgs($message, $title, $link, $subtitle);
             break;
-         case 'atom':
+        case 'atom':
             $selfuri = common_root_url() . 'api/direct_messages';
             $selfuri .= ($type == 'received') ? '.atom' : '/sent.atom';
             $taguribase = common_config('integration', 'taguri');
@@ -111,12 +103,13 @@ class Twitapidirect_messagesAction extends TwitterapiAction
                 $id = "tag:$taguribase:DirectMessages:" . $user->id;
             }
 
-            $this->show_atom_dmsgs($message, $title, $link, $subtitle, $selfuri, $id);
+            $this->show_atom_dmsgs($message, $title, $link, $subtitle,
+                $selfuri, $id);
             break;
-         case 'json':
+        case 'json':
             $this->show_json_dmsgs($message);
             break;
-         default:
+        default:
             $this->clientError(_('API method not found!'), $code = 404);
         }
 
@@ -128,22 +121,24 @@ class Twitapidirect_messagesAction extends TwitterapiAction
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST.'),
+                400, $apidata['content-type']);
             return;
         }
 
         $user = $apidata['user'];
-        $source = $this->trimmed('source');     // Not supported by Twitter.
+        $source = $this->trimmed('source'); // Not supported by Twitter.
 
         $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
-        if (!$source || in_array($source, $reserved_sources)) {
+        if (empty($source) || in_array($source, $reserved_sources)) {
             $source = 'api';
         }
 
         $content = $this->trimmed('text');
 
-        if (!$content) {
-            $this->clientError(_('No message text!'), $code = 406, $apidata['content-type']);
+        if (empty($content)) {
+            $this->clientError(_('No message text!'),
+                $code = 406, $apidata['content-type']);
         } else {
             $content_shortened = common_shorten_links($content);
             if (mb_strlen($content_shortened) > 140) {
@@ -155,8 +150,9 @@ class Twitapidirect_messagesAction extends TwitterapiAction
 
         $other = $this->get_user($this->trimmed('user'));
 
-        if (!$other) {
-            $this->clientError(_('Recipient user not found.'), $code = 403, $apidata['content-type']);
+        if (empty($other)) {
+            $this->clientError(_('Recipient user not found.'),
+                $code = 403, $apidata['content-type']);
             return;
         } else if (!$user->mutuallySubscribed($other)) {
             $this->clientError(_('Can\'t send direct messages to users who aren\'t your friend.'),
index 31dce341b7ca713ff0371916d56b8875ed100d7c..8256668f3d22a0622de9e3708c64173d4eab4ad1 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -31,50 +33,48 @@ class TwitapifavoritesAction extends TwitterapiAction
         $this->auth_user = $apidata['user'];
         $user = $this->get_user($apidata['api_arg'], $apidata);
 
-        if (!$user) {
+        if (empty($user)) {
+        if ($apidata['content-type'] == 'xml') {
+            $this->show_single_xml_status($notice);
+        } elseif ($apidata['content-type'] == 'json') {
+            $this->show_single_json_status($notice);
+        }
             $this->clientError('Not Found', 404, $apidata['content-type']);
             return;
         }
 
         $profile = $user->getProfile();
 
-        if (!$profile) {
-            $this->serverError(_('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) {
-            $this->serverError(_('Could not retrieve favorite notices.'));
-            return;
-        }
-
-        $sitename = common_config('site', 'name');
-        $title = sprintf(_('%s / Favorites from %s'), $sitename, $user->nickname);
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_('%s / Favorites from %s'), $sitename,
+            $user->nickname);
         $taguribase = common_config('integration', 'taguri');
-        $id = "tag:$taguribase: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);
+        $id         = "tag:$taguribase: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);
+
+        $page     = (int)$this->arg('page', 1);
+        $count    = (int)$this->arg('count', 20);
+        $max_id   = (int)$this->arg('max_id', 0);
+        $since_id = (int)$this->arg('since_id', 0);
+        $since    = $this->arg('since');
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $user->id) {
+            $notice = $user->favoriteNotices(($page-1)*$count, $count, true);
+        } else {
+            $notice = $user->favoriteNotices(($page-1)*$count, $count, false);
+        }
 
         switch($apidata['content-type']) {
-         case 'xml':
+        case 'xml':
             $this->show_xml_timeline($notice);
             break;
-         case 'rss':
+        case 'rss':
             $this->show_rss_timeline($notice, $title, $link, $subtitle);
             break;
-         case 'atom':
+        case 'atom':
             if (isset($apidata['api_arg'])) {
                  $selfuri = $selfuri = common_root_url() .
                      'api/favorites/' . $apidata['api_arg'] . '.atom';
@@ -82,12 +82,13 @@ class TwitapifavoritesAction extends TwitterapiAction
                  $selfuri = $selfuri = common_root_url() .
                   'api/favorites.atom';
             }
-            $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, null, $selfuri);
+            $this->show_atom_timeline($notice, $title, $id, $link,
+                $subtitle, null, $selfuri);
             break;
-         case 'json':
+        case 'json':
             $this->show_json_timeline($notice);
             break;
-         default:
+        default:
             $this->clientError(_('API method not found!'), $code = 404);
         }
 
@@ -99,8 +100,8 @@ class TwitapifavoritesAction extends TwitterapiAction
 
         // Check for RESTfulness
         if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            // XXX: Twitter just prints the err msg, no XML / JSON.
-            $this->clientError(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST or DELETE.'),
+                400, $apidata['content-type']);
             return;
         }
 
@@ -109,26 +110,27 @@ class TwitapifavoritesAction extends TwitterapiAction
             return;
         }
 
-        $this->auth_user = $apidata['user'];
-        $user = $this->auth_user;
+        $user      = $apidata['user']; // Always the auth user
         $notice_id = $apidata['api_arg'];
-        $notice = Notice::staticGet($notice_id);
+        $notice    = Notice::staticGet($notice_id);
 
-        if (!$notice) {
-            $this->clientError(_('No status found with that ID.'), 404, $apidata['content-type']);
+        if (empty($notice)) {
+            $this->clientError(_('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->clientError(_('This notice is already a favorite!'), 403, $apidata['content-type']);
+            $this->clientError(_('This status is already a favorite!'),
+                403, $apidata['content-type']);
             return;
         }
 
         $fave = Fave::addNew($user, $notice);
 
-        if (!$fave) {
-            $this->serverError(_('Could not create favorite.'));
+        if (empty($fave)) {
+            $this->clientError(_('Could not create favorite.'));
             return;
         }
 
@@ -146,10 +148,59 @@ class TwitapifavoritesAction extends TwitterapiAction
     function destroy($args, $apidata)
     {
         parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
+
+        // Check for RESTfulness
+        if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
+            $this->clientError(_('This method requires a POST or DELETE.'),
+                400, $apidata['content-type']);
+            return;
+        }
+
+        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $user      = $apidata['user']; // Always the auth user
+        $notice_id = $apidata['api_arg'];
+        $notice    = Notice::staticGet($notice_id);
+
+        if (empty($notice)) {
+            $this->clientError(_('No status found with that ID.'),
+                404, $apidata['content-type']);
+            return;
+        }
+
+        $fave            = new Fave();
+        $fave->user_id   = $this->id;
+        $fave->notice_id = $notice->id;
+
+        if (!$fave->find(true)) {
+            $this->clientError(_('That status is not a favorite!'),
+                403, $apidata['content-type']);
+            return;
+        }
+
+        $result = $fave->delete();
+
+        if (!$result) {
+            common_log_db_error($fave, 'DELETE', __FILE__);
+            $this->clientError(_('Could not delete favorite.'), 404);
+            return;
+        }
+
+        $user->blowFavesCache();
+
+        if ($apidata['content-type'] == 'xml') {
+            $this->show_single_xml_status($notice);
+        } elseif ($apidata['content-type'] == 'json') {
+            $this->show_single_json_status($notice);
+        }
+
     }
 
-    // XXX: these two funcs swiped from faves.  Maybe put in util.php, or some common base class?
+    // XXX: these two funcs swiped from faves.
+    // Maybe put in util.php, or some common base class?
 
     function notify($fave, $notice, $user)
     {
index 2f8250e0dc9dfa56b55893b436a15838c8a9b39a..5fb55e9ffed488ddcd47ae6bfc08ff5ecb109e14 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -29,23 +31,25 @@ class TwitapifriendshipsAction extends TwitterapiAction
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST.'),
+                400, $apidata['content-type']);
             return;
         }
 
-        $id = $apidata['api_arg'];
-
+        $id    = $apidata['api_arg'];
         $other = $this->get_user($id);
 
-        if (!$other) {
-            $this->clientError(_('Could not follow user: User not found.'), 403, $apidata['content-type']);
+        if (empty($other)) {
+            $this->clientError(_('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);
+            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'),
+                $other->nickname);
             $this->clientError($errmsg, 403, $apidata['content-type']);
             return;
         }
@@ -60,8 +64,9 @@ class TwitapifriendshipsAction extends TwitterapiAction
 
         $result = $sub->insert();
 
-        if (!$result) {
-            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), $other->nickname);
+        if (empty($result)) {
+            $errmsg = sprintf(_('Could not follow user: %s is already on your list.'),
+                $other->nickname);
             $this->clientError($errmsg, 400, $apidata['content-type']);
             return;
         }
@@ -82,7 +87,8 @@ class TwitapifriendshipsAction extends TwitterapiAction
         parent::handle($args);
 
         if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
-            $this->clientError(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST or DELETE.'),
+                400, $apidata['content-type']);
             return;
         }
 
@@ -91,7 +97,7 @@ class TwitapifriendshipsAction extends TwitterapiAction
         # We can't subscribe to a remote person, but we can unsub
 
         $other = $this->get_profile($id);
-        $user = $apidata['user'];
+        $user = $apidata['user']; // Alwyas the auth user
 
         $sub = new Subscription();
         $sub->subscriber = $user->id;
@@ -102,7 +108,8 @@ class TwitapifriendshipsAction extends TwitterapiAction
             $sub->delete();
             $sub->query('COMMIT');
         } else {
-            $this->clientError(_('You are not friends with the specified user.'), 403, $apidata['content-type']);
+            $this->clientError(_('You are not friends with the specified user.'),
+                403, $apidata['content-type']);
             return;
         }
 
@@ -128,8 +135,9 @@ class TwitapifriendshipsAction extends TwitterapiAction
         $user_a = $this->get_user($user_a_id);
         $user_b = $this->get_user($user_b_id);
 
-        if (!$user_a || !$user_b) {
-            $this->clientError(_('Two user ids or screen_names must be supplied.'), 400, $apidata['content-type']);
+        if (empty($user_a) || empty($user_b)) {
+            $this->clientError(_('Two user ids or screen_names must be supplied.'),
+                400, $apidata['content-type']);
             return;
         }
 
@@ -152,4 +160,85 @@ class TwitapifriendshipsAction extends TwitterapiAction
 
     }
 
-}
\ No newline at end of file
+    function show($args, $apidata)
+    {
+        parent::handle($args);
+
+        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
+            $this->clientError(_('API method not found!'), $code = 404);
+            return;
+        }
+
+        $source_id          = (int)$this->trimmed('source_id');
+        $source_screen_name = $this->trimmed('source_screen_name');
+
+        // If the source is not specified for an unauthenticated request,
+        // the method will return an HTTP 403.
+
+        if (empty($source_id) && empty($source_screen_name)) {
+            if (empty($apidata['user'])) {
+                $this->clientError(_('Could not determine source user.'),
+                        $code = 403);
+                return;
+            }
+        }
+
+        $source = null;
+
+        if (!empty($source_id)) {
+            $source = User::staticGet($source_id);
+        } elseif (!empty($source_screen_name)) {
+            $source = User::staticGet('nickname', $source_screen_name);
+        } else {
+            $source = $apidata['user'];
+        }
+
+        // If a source or target is specified but does not exist,
+        // the method will return an HTTP 404.
+
+        if (empty($source)) {
+            $this->clientError(_('Could not determine source user.'),
+                $code = 404);
+            return;
+        }
+
+        $target_id          = (int)$this->trimmed('target_id');
+        $target_screen_name = $this->trimmed('target_screen_name');
+
+        $target = null;
+
+        if (!empty($target_id)) {
+            $target = User::staticGet($target_id);
+        } elseif (!empty($target_screen_name)) {
+            $target = User::staticGet('nickname', $target_screen_name);
+        } else {
+            $this->clientError(_('Target user not specified.'),
+                $code = 403);
+            return;
+        }
+
+        if (empty($target)) {
+            $this->clientError(_('Could not find target user.'),
+                $code = 404);
+            return;
+        }
+
+        $result = $this->twitter_relationship_array($source, $target);
+
+        switch ($apidata['content-type']) {
+        case 'xml':
+            $this->init_document('xml');
+            $this->show_twitter_xml_relationship($result[relationship]);
+            $this->end_document('xml');
+            break;
+        case 'json':
+            $this->init_document('json');
+            print json_encode($result);
+            $this->end_document('json');
+            break;
+        default:
+            break;
+        }
+    }
+
+}
index db5892baf2979a5c5f8a3802973082d969afa063..dab2b34f9bd086b0db9c8bd8fc0bc0d63edd4dbb 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
index 411971af16ab536f1016b5af9a7128931c342bf0..09b11766bb30abece325435adc1691487f9a4f1a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index eb9ab5d8e9425add9688045321eb62aaa3ba7b05..3678213c3a9dbddd34050b9a4936b3f49d6a573f 100644 (file)
@@ -165,24 +165,30 @@ class TwitapisearchatomAction extends TwitterapiAction
         $search_engine->set_sort_mode('chron');
         $search_engine->limit(($this->page - 1) * $this->rpp,
             $this->rpp + 1, true);
-        $search_engine->query($q);
-        $this->cnt = $notice->find();
+        if (false === $search_engine->query($q)) {
+            $this->cnt = 0;
+        } else {
+            $this->cnt = $notice->find();
+        }
 
         $cnt = 0;
+        $this->max_id = 0;
 
-        while ($notice->fetch()) {
+        if ($this->cnt > 0) {
+            while ($notice->fetch()) {
 
-            ++$cnt;
+                ++$cnt;
 
-            if (!$this->max_id) {
-                $this->max_id = $notice->id;
-            }
+                if (!$this->max_id) {
+                    $this->max_id = $notice->id;
+                }
 
-            if ($cnt > $this->rpp) {
-                break;
-            }
+                if ($cnt > $this->rpp) {
+                    break;
+                }
 
-            $notices[] = clone($notice);
+                $notices[] = clone($notice);
+            }
         }
 
         return $notices;
index b0e3be687c1118da16e0f88584911b6a7b786c2e..27a717bfc988cc0bc7b2ef40a8fa24739a032ced 100644 (file)
@@ -124,8 +124,11 @@ class TwitapisearchjsonAction extends TwitterapiAction
         $search_engine = $notice->getSearchEngine('identica_notices');
         $search_engine->set_sort_mode('chron');
         $search_engine->limit(($this->page - 1) * $this->rpp, $this->rpp + 1, true);
-        $search_engine->query($q);
-        $cnt = $notice->find();
+        if (false === $search_engine->query($q)) {
+            $cnt = 0;
+        } else {
+            $cnt = $notice->find();
+        }
 
         // TODO: since_id, lang, geocode
 
@@ -146,4 +149,4 @@ class TwitapisearchjsonAction extends TwitterapiAction
     {
         return true;
     }
-}
\ No newline at end of file
+}
index 1fbde6639f3bf32b4be7131e8a28554e1acdcb5a..c9943698dc2bc06510c2d5bafdf506e7e6b6ba43 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -26,64 +28,45 @@ class TwitapistatusesAction extends TwitterapiAction
 
     function public_timeline($args, $apidata)
     {
-        parent::handle($args);
-
-        $sitename = common_config('site', 'name');
-        $title = sprintf(_("%s public timeline"), $sitename);
-
-        $taguribase = common_config('integration', 'taguri');
-        $id = "tag:$taguribase:PublicTimeline";
-        $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
+        // XXX: 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');
-        $max_id = $this->arg('max_id');
-
-        if (!$page) {
-            $page = 1;
-        }
-        if (!$since_id) {
-            $since_id = 0;
-        }
-        if (!$max_id) {
-            $max_id = 0;
-        }
-
-        $since = strtotime($this->arg('since'));
+        parent::handle($args);
 
-        $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $max_id, $since);
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s public timeline"), $sitename);
+        $taguribase = common_config('integration', 'taguri');
+        $id         = "tag:$taguribase:PublicTimeline";
+        $link       = common_root_url();
+        $subtitle   = sprintf(_("%s updates from everyone!"), $sitename);
 
-        if ($notice) {
+        $page     = (int)$this->arg('page', 1);
+        $count    = (int)$this->arg('count', 20);
+        $max_id   = (int)$this->arg('max_id', 0);
+        $since_id = (int)$this->arg('since_id', 0);
+        $since    = $this->arg('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':
-                    $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
-                    $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, null, $selfuri);
-                    break;
-                case 'json':
-                    $this->show_json_timeline($notice);
-                    break;
-                default:
-                    $this->clientError(_('API method not found!'), $code = 404);
-                    break;
-            }
+        $notice = Notice::publicStream(($page-1)*$count, $count, $since_id,
+            $max_id, $since);
 
-        } else {
-            $this->serverError(_('Couldn\'t find any statuses.'), $code = 503);
+        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':
+            $selfuri = common_root_url() . 'api/statuses/public_timeline.atom';
+            $this->show_atom_timeline($notice, $title, $id, $link,
+                $subtitle, null, $selfuri);
+            break;
+        case 'json':
+            $this->show_json_timeline($notice);
+            break;
+        default:
+            $this->clientError(_('API method not found!'), $code = 404);
+            break;
         }
 
     }
@@ -92,68 +75,62 @@ class TwitapistatusesAction extends TwitterapiAction
     {
         parent::handle($args);
 
-        $since = $this->arg('since');
-        $since_id = $this->arg('since_id');
-        $count = $this->arg('count');
-        $page = $this->arg('page');
-        $max_id = $this->arg('max_id');
-
-        if (!$page) {
-            $page = 1;
-        }
-
-        if (!$count) {
-            $count = 20;
-        }
-
-        if (!$since_id) {
-            $since_id = 0;
-        }
-
-        if (!$max_id) {
-            $max_id = 0;
-        }
-
-        $since = strtotime($this->arg('since'));
+        $this->auth_user = $apidata['user'];
         $user = $this->get_user($apidata['api_arg'], $apidata);
-        $this->auth_user = $user;
 
         if (empty($user)) {
-             $this->clientError(_('No such user!'), 404, $apidata['content-type']);
+             $this->clientError(_('No such user!'), 404,
+             $apidata['content-type']);
             return;
         }
 
-        $profile = $user->getProfile();
-        $sitename = common_config('site', 'name');
-        $title = sprintf(_("%s and friends"), $user->nickname);
+        $profile    = $user->getProfile();
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s and friends"), $user->nickname);
         $taguribase = common_config('integration', 'taguri');
-        $id = "tag:$taguribase:FriendsTimeline:" . $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, $max_id, $since);
+        $id         = "tag:$taguribase:FriendsTimeline:" . $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);
+
+        $page     = (int)$this->arg('page', 1);
+        $count    = (int)$this->arg('count', 20);
+        $max_id   = (int)$this->arg('max_id', 0);
+        $since_id = (int)$this->arg('since_id', 0);
+        $since    = $this->arg('since');
+
+        if (!empty($this->auth_user) && $this->auth_user->id == $user->id) {
+            $notice = $user->noticeInbox(($page-1)*$count,
+                $count, $since_id, $max_id, $since);
+        } else {
+            $notice = $user->noticesWithFriends(($page-1)*$count,
+                $count, $since_id, $max_id, $since);
+        }
 
         switch($apidata['content-type']) {
-         case 'xml':
+        case 'xml':
             $this->show_xml_timeline($notice);
             break;
-         case 'rss':
+        case 'rss':
             $this->show_rss_timeline($notice, $title, $link, $subtitle);
             break;
-         case 'atom':
+        case 'atom':
             if (isset($apidata['api_arg'])) {
                 $selfuri = common_root_url() .
-                    'api/statuses/friends_timeline/' . $apidata['api_arg'] . '.atom';
+                    'api/statuses/friends_timeline/' .
+                        $apidata['api_arg'] . '.atom';
             } else {
                 $selfuri = common_root_url() .
                     'api/statuses/friends_timeline.atom';
             }
-            $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, null, $selfuri);
+            $this->show_atom_timeline($notice, $title, $id, $link,
+                $subtitle, null, $selfuri);
             break;
-         case 'json':
+        case 'json':
             $this->show_json_timeline($notice);
             break;
-         default:
+        default:
             $this->clientError(_('API method not found!'), $code = 404);
         }
 
@@ -166,48 +143,21 @@ class TwitapistatusesAction extends TwitterapiAction
         $this->auth_user = $apidata['user'];
         $user = $this->get_user($apidata['api_arg'], $apidata);
 
-        if (!$user) {
+        if (empty($user)) {
             $this->clientError('Not Found', 404, $apidata['content-type']);
             return;
         }
 
         $profile = $user->getProfile();
 
-        if (!$profile) {
-            $this->serverError(_('User has no profile.'));
-            return;
-        }
-
-        $count = $this->arg('count');
-        $since = $this->arg('since');
-        $since_id = $this->arg('since_id');
-        $page = $this->arg('page');
-        $max_id = $this->arg('max_id');
-
-        if (!$page) {
-            $page = 1;
-        }
-
-        if (!$count) {
-            $count = 20;
-        }
-
-        if (!$since_id) {
-            $since_id = 0;
-        }
-
-        if (!$max_id) {
-            $max_id = 0;
-        }
-
-        $since = strtotime($this->arg('since'));
-
-        $sitename = common_config('site', 'name');
-        $title = sprintf(_("%s timeline"), $user->nickname);
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_("%s timeline"), $user->nickname);
         $taguribase = common_config('integration', 'taguri');
-        $id = "tag:$taguribase:UserTimeline:".$user->id;
-        $link = common_local_url('showstream', array('nickname' => $user->nickname));
-        $subtitle = sprintf(_('Updates from %1$s on %2$s!'), $user->nickname, $sitename);
+        $id         = "tag:$taguribase:UserTimeline:".$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
@@ -215,26 +165,34 @@ class TwitapistatusesAction extends TwitterapiAction
         $suplink = common_local_url('sup', null, null, $user->id);
         header('X-SUP-ID: '.$suplink);
 
-        # XXX: since
+        $page     = (int)$this->arg('page', 1);
+        $count    = (int)$this->arg('count', 20);
+        $max_id   = (int)$this->arg('max_id', 0);
+        $since_id = (int)$this->arg('since_id', 0);
+        $since    = $this->arg('since');
 
-        $notice = $user->getNotices((($page-1)*20), $count, $since_id, $max_id, $since);
+        $notice = $user->getNotices(($page-1)*$count,
+            $count, $since_id, $max_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);
+            $this->show_rss_timeline($notice, $title, $link,
+                $subtitle, $suplink);
             break;
          case 'atom':
             if (isset($apidata['api_arg'])) {
                 $selfuri = common_root_url() .
-                    'api/statuses/user_timeline/' . $apidata['api_arg'] . '.atom';
+                    'api/statuses/user_timeline/' .
+                        $apidata['api_arg'] . '.atom';
             } else {
                 $selfuri = common_root_url() .
                  'api/statuses/user_timeline.atom';
             }
-            $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, $suplink, $selfuri);
+            $this->show_atom_timeline($notice, $title, $id, $link,
+                $subtitle, $suplink, $selfuri);
             break;
          case 'json':
             $this->show_json_timeline($notice);
@@ -247,7 +205,6 @@ class TwitapistatusesAction extends TwitterapiAction
 
     function update($args, $apidata)
     {
-
         parent::handle($args);
 
         if (!in_array($apidata['content-type'], array('xml', 'json'))) {
@@ -256,21 +213,24 @@ class TwitapistatusesAction extends TwitterapiAction
         }
 
         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
-            $this->clientError(_('This method requires a POST.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST.'),
+                400, $apidata['content-type']);
             return;
         }
 
-        $this->auth_user = $apidata['user'];
-        $user = $this->auth_user;
+        $user = $apidata['user'];  // Always the auth user
+
         $status = $this->trimmed('status');
         $source = $this->trimmed('source');
-        $in_reply_to_status_id = intval($this->trimmed('in_reply_to_status_id'));
+        $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)) {
+
+        if (empty($source) || in_array($source, $reserved_sources)) {
             $source = 'api';
         }
 
-        if (!$status) {
+        if (empty($status)) {
 
             // XXX: Note: In this case, Twitter simply returns '200 OK'
             // No error is given, but the status is not posted to the
@@ -288,9 +248,9 @@ class TwitapistatusesAction extends TwitterapiAction
                 // as "truncated." Sending this error may screw up some clients
                 // that assume Twitter will truncate for them.    Should we just
                 // truncate too? -- Zach
-                $this->clientError(_('That\'s too long. Max notice size is 140 chars.'), $code = 406, $apidata['content-type']);
+                $this->clientError(_('That\'s too long. Max notice size is 140 chars.'),
+                    $code = 406, $apidata['content-type']);
                 return;
-
             }
         }
 
@@ -321,13 +281,15 @@ class TwitapistatusesAction extends TwitterapiAction
                 if ($reply) {
                     $reply_to = $in_reply_to_status_id;
                 } else {
-                    $this->clientError(_('Not found'), $code = 404, $apidata['content-type']);
+                    $this->clientError(_('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);
+            $notice = Notice::saveNew($user->id,
+                html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'),
+                    $source, 1, $reply_to);
 
             if (is_string($notice)) {
                 $this->serverError($notice);
@@ -343,71 +305,55 @@ class TwitapistatusesAction extends TwitterapiAction
 
     function mentions($args, $apidata)
     {
-
         parent::handle($args);
 
-        $since = $this->arg('since');
-        $count = $this->arg('count');
-        $page = $this->arg('page');
-        $since_id = $this->arg('since_id');
-        $max_id = $this->arg('max_id');
-
         $user = $this->get_user($apidata['api_arg'], $apidata);
         $this->auth_user = $apidata['user'];
+
+        if (empty($user)) {
+             $this->clientError(_('No such user!'), 404,
+                 $apidata['content-type']);
+            return;
+        }
+
         $profile = $user->getProfile();
 
-        $sitename = common_config('site', 'name');
-        $title = sprintf(_('%1$s / Updates mentioning %2$s'),
+        $sitename   = common_config('site', 'name');
+        $title      = sprintf(_('%1$s / Updates mentioning %2$s'),
             $sitename, $user->nickname);
         $taguribase = common_config('integration', 'taguri');
-        $id = "tag:$taguribase:Mentions:".$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.'),
+        $id         = "tag:$taguribase:Mentions:".$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;
-        }
+        $page     = (int)$this->arg('page', 1);
+        $count    = (int)$this->arg('count', 20);
+        $max_id   = (int)$this->arg('max_id', 0);
+        $since_id = (int)$this->arg('since_id', 0);
+        $since    = $this->arg('since');
 
-        if (!$max_id) {
-            $max_id = 0;
-        }
-
-        $since = strtotime($this->arg('since'));
-
-        $notice = $user->getReplies((($page-1)*20),
+        $notice = $user->getReplies(($page-1)*$count,
             $count, $since_id, $max_id, $since);
-        $notices = array();
-
-        while ($notice->fetch()) {
-            $notices[] = clone($notice);
-        }
 
         switch($apidata['content-type']) {
-         case 'xml':
-            $this->show_xml_timeline($notices);
+        case 'xml':
+            $this->show_xml_timeline($notice);
             break;
-         case 'rss':
-            $this->show_rss_timeline($notices, $title, $link, $subtitle);
+        case 'rss':
+            $this->show_rss_timeline($notice, $title, $link, $subtitle);
             break;
-         case 'atom':
+        case 'atom':
             $selfuri = common_root_url() .
                 ltrim($_SERVER['QUERY_STRING'], 'p=');
-            $this->show_atom_timeline($notices, $title, $id, $link, $subtitle,
+            $this->show_atom_timeline($notice, $title, $id, $link, $subtitle,
                 null, $selfuri);
             break;
-         case 'json':
-            $this->show_json_timeline($notices);
+        case 'json':
+            $this->show_json_timeline($notice);
             break;
-         default:
+        default:
             $this->clientError(_('API method not found!'), $code = 404);
         }
 
@@ -427,9 +373,19 @@ class TwitapistatusesAction extends TwitterapiAction
             return;
         }
 
+        // 'id' is an undocumented parameter in Twitter's API. Several
+        // clients make use of it, so we support it too.
+
+        // show.json?id=12345 takes precedence over /show/12345.json
+
         $this->auth_user = $apidata['user'];
-        $notice_id = $apidata['api_arg'];
-        $notice = Notice::staticGet($notice_id);
+        $notice_id       = $this->trimmed('id');
+
+        if (empty($notice_id)) {
+            $notice_id   = $apidata['api_arg'];
+        }
+
+        $notice          = Notice::staticGet((int)$notice_id);
 
         if ($notice) {
             if ($apidata['content-type'] == 'xml') {
@@ -438,15 +394,15 @@ class TwitapistatusesAction extends TwitterapiAction
                 $this->show_single_json_status($notice);
             }
         } else {
-            // XXX: Twitter just sets a 404 header and doens't bother to return an err msg
-            $this->clientError(_('No status with that ID found.'), 404, $apidata['content-type']);
+            // XXX: Twitter just sets a 404 header and doens't bother
+            // to return an err msg
+            $this->clientError(_('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'))) {
@@ -457,17 +413,18 @@ class TwitapistatusesAction extends TwitterapiAction
         // Check for RESTfulness
         if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
             // XXX: Twitter just prints the err msg, no XML / JSON.
-            $this->clientError(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
+            $this->clientError(_('This method requires a POST or DELETE.'),
+                400, $apidata['content-type']);
             return;
         }
 
-        $this->auth_user = $apidata['user'];
-        $user = $this->auth_user;
+        $user      = $apidata['user']; // Always the auth user
         $notice_id = $apidata['api_arg'];
-        $notice = Notice::staticGet($notice_id);
+        $notice    = Notice::staticGet($notice_id);
 
-        if (!$notice) {
-            $this->clientError(_('No status found with that ID.'), 404, $apidata['content-type']);
+        if (empty($notice)) {
+            $this->clientError(_('No status found with that ID.'),
+                404, $apidata['content-type']);
             return;
         }
 
@@ -483,7 +440,8 @@ class TwitapistatusesAction extends TwitterapiAction
                 $this->show_single_json_status($notice);
             }
         } else {
-            $this->clientError(_('You may not delete another user\'s status.'), 403, $apidata['content-type']);
+            $this->clientError(_('You may not delete another user\'s status.'),
+                403, $apidata['content-type']);
         }
 
     }
@@ -514,42 +472,41 @@ class TwitapistatusesAction extends TwitterapiAction
 
     function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false)
     {
-
         $this->auth_user = $apidata['user'];
         $user = $this->get_user($apidata['api_arg'], $apidata);
 
-        if (!$user) {
+        if (empty($user)) {
             $this->clientError('Not Found', 404, $apidata['content-type']);
             return;
         }
 
-        $page = $this->trimmed('page');
-
-        if (!$page || !is_numeric($page)) {
-            $page = 1;
-        }
-
         $profile = $user->getProfile();
 
-        if (!$profile) {
-            $this->serverError(_('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');
 
+        // Normally, page 100 friends at a time
+
         if (!$onlyIDs) {
-            $sub->limit(($page-1)*100, 100);
+            $page  = $this->arg('page', 1);
+            $count = $this->arg('count', 100);
+            $sub->limit(($page-1)*$count, $count);
+        } else {
+
+            // If we're just looking at IDs, return
+            // ALL of them, unless the user specifies a page,
+            // in which case, return 500 per page.
+
+            $page = $this->arg('page');
+            if (!empty($page)) {
+                if ($page < 1) {
+                    $page = 1;
+                }
+                $count = 500;
+                $sub->limit(($page-1)*$count, $count);
+            }
         }
 
         $others = array();
@@ -578,21 +535,21 @@ class TwitapistatusesAction extends TwitterapiAction
     function show_profiles($profiles, $type)
     {
         switch ($type) {
-         case 'xml':
+        case 'xml':
             $this->elementStart('users', array('type' => 'array'));
             foreach ($profiles as $profile) {
                 $this->show_profile($profile);
             }
             $this->elementEnd('users');
             break;
-         case 'json':
+        case 'json':
             $arrays = array();
             foreach ($profiles as $profile) {
                 $arrays[] = $this->twitter_user_array($profile, true);
             }
             print json_encode($arrays);
             break;
-         default:
+        default:
             $this->clientError(_('unsupported file type'));
         }
     }
@@ -600,21 +557,21 @@ class TwitapistatusesAction extends TwitterapiAction
     function showIDs($profiles, $type)
     {
         switch ($type) {
-         case 'xml':
+        case 'xml':
             $this->elementStart('ids');
             foreach ($profiles as $profile) {
                 $this->element('id', null, $profile->id);
             }
             $this->elementEnd('ids');
             break;
-         case 'json':
+        case 'json':
             $ids = array();
             foreach ($profiles as $profile) {
                 $ids[] = (int)$profile->id;
             }
             print json_encode($ids);
             break;
-         default:
+        default:
             $this->clientError(_('unsupported file type'));
         }
     }
@@ -627,8 +584,8 @@ class TwitapistatusesAction extends TwitterapiAction
 
     function supported($cmd)
     {
-
-        $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', 'FavCommand', 'OnCommand', 'OffCommand');
+        $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand',
+            'FavCommand', 'OnCommand', 'OffCommand');
 
         if (in_array(get_class($cmd), $cmdlist)) {
             return true;
index a47fdfbc38d0f8b2e0c1c0a6a520e3b76433cd6b..fea41b3971d3f9a77b342e6304f6e83e96fa6784 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.     If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/twitterapi.php');
 
@@ -41,10 +43,10 @@ class TwitapiusersAction extends TwitterapiAction
         if ($email) {
             $user = User::staticGet('email', $email);
         } else {
-            $user = $this->get_user($apidata['api_arg']);
+            $user = $this->get_user($apidata['api_arg'], $apidata);
         }
 
-        if (!$user) {
+        if (empty($user)) {
             $this->clientError(_('Not found.'), 404, $apidata['content-type']);
             return;
         }
@@ -56,7 +58,7 @@ class TwitapiusersAction extends TwitterapiAction
             return;
         }
 
-        $twitter_user = $this->twitter_user_array($profile, true);
+        $twitter_user = $this->twitter_user_array($user->getProfile(), true);
 
         if ($apidata['content-type'] == 'xml') {
             $this->init_document('xml');
index 0b98eef591ba086a3d8346907927eb0ae7c42456..2b742788eee55419112a718d271f6e4285960163 100644 (file)
@@ -138,7 +138,7 @@ class TwittersettingsAction extends ConnectSettingsAction
 
         $this->elementStart('ul', 'form_data');
         $this->elementStart('li');
-        $this->checkbox('noticesync',
+        $this->checkbox('noticesend',
                         _('Automatically send my notices to Twitter.'),
                         ($flink) ?
                         ($flink->noticesync & FOREIGN_NOTICE_SEND) :
@@ -158,6 +158,22 @@ class TwittersettingsAction extends ConnectSettingsAction
                         ($flink->friendsync & FOREIGN_FRIEND_RECV) :
                         false);
         $this->elementEnd('li');
+
+        if (common_config('twitterbridge','enabled')) {
+            $this->elementStart('li');
+            $this->checkbox('noticerecv',
+                            _('Import my Friends Timeline.'),
+                            ($flink) ?
+                            ($flink->noticesync & FOREIGN_NOTICE_RECV) :
+                            false);
+            $this->elementEnd('li');
+        } else {
+            // preserve setting even if bidrection bridge toggled off
+            if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
+                $this->hidden('noticerecv', true, 'noticerecv');
+            }
+        }
+
         $this->elementEnd('ul');
 
         if ($flink) {
@@ -320,7 +336,8 @@ class TwittersettingsAction extends ConnectSettingsAction
     {
         $screen_name = $this->trimmed('twitter_username');
         $password    = $this->trimmed('twitter_password');
-        $noticesync  = $this->boolean('noticesync');
+        $noticesend  = $this->boolean('noticesend');
+        $noticerecv  = $this->boolean('noticerecv');
         $replysync   = $this->boolean('replysync');
         $friendsync  = $this->boolean('friendsync');
 
@@ -363,7 +380,7 @@ class TwittersettingsAction extends ConnectSettingsAction
         $flink->credentials = $password;
         $flink->created     = common_sql_now();
 
-        $flink->set_flags($noticesync, $replysync, $friendsync);
+        $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
 
         $flink_id = $flink->insert();
 
@@ -421,7 +438,8 @@ class TwittersettingsAction extends ConnectSettingsAction
 
     function savePreferences()
     {
-        $noticesync = $this->boolean('noticesync');
+        $noticesend = $this->boolean('noticesend');
+        $noticerecv = $this->boolean('noticerecv');
         $friendsync = $this->boolean('friendsync');
         $replysync  = $this->boolean('replysync');
 
@@ -450,7 +468,7 @@ class TwittersettingsAction extends ConnectSettingsAction
 
         $original = clone($flink);
 
-        $flink->set_flags($noticesync, $replysync, $friendsync);
+        $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
 
         $result = $flink->update($original);
 
index 6e671c9dd257fa7ee2e6e0e2b10a546252ae8f5c..05d57c60d5dcdbe59b6b21ba93b7b6775c5827a9 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 7dcab04c045d2bd3ba6e3bd2ac152e6571b184c2..19275041a81e438b6c4944f177fcb543b1fe5f69 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 08cb31ae037c77c55af08f5017156685ed0076a0..d8b62fb09051d59f55c5ad374b6937dec2f3b0c0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 1680191495506c043d0cdc98df116d708d0e90c5..8dc2c808d6ff849f0da742e342de137203eeacd4 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 4a985fcd7220c842f6b99efc9dcc597f21e959bd..8b686ae106379be2ac4d5faded06e80123017eed 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
 
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/actions/userdesignsettings.php b/actions/userdesignsettings.php
new file mode 100644 (file)
index 0000000..d794995
--- /dev/null
@@ -0,0 +1,285 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Change user password
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Settings
+ * @package   Laconica
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/designsettings.php';
+
+/**
+ * Set a user's design
+ *
+ * Saves a design for a given user
+ *
+ * @category Settings
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class UserDesignSettingsAction extends DesignSettingsAction
+{
+    /**
+     * Sets the right action for the form, and passes request args into
+     * the base action
+     *
+     * @param array $args misc. arguments
+     *
+     * @return boolean true
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->submitaction = common_local_url('userdesignsettings');
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Profile design');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Customize the way your profile looks ' .
+        'with a background image and a colour palette of your choice.');
+    }
+
+    /**
+     * Get the design we want to edit
+     *
+     * @return Design
+     */
+
+    function getWorkingDesign()
+    {
+
+        $user   = common_current_user();
+        $design = $user->getDesign();
+
+        if (empty($design)) {
+            $design = $this->defaultDesign();
+        }
+
+        return $design;
+    }
+
+    /**
+     * Content area of the page
+     *
+     * Shows a form for changing the design
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $this->showDesignForm($this->getWorkingDesign());
+    }
+
+    /**
+     * Save or update the user's design settings
+     *
+     * @return void
+     */
+
+    function saveDesign()
+    {
+        foreach ($this->args as $key => $val) {
+            if (preg_match('/(#ho|ho)Td.*g/i', $val)) {
+                $this->sethd();
+                return;
+            }
+        }
+
+        try {
+            $bgcolor = new WebColor($this->trimmed('design_background'));
+            $ccolor  = new WebColor($this->trimmed('design_content'));
+            $sbcolor = new WebColor($this->trimmed('design_sidebar'));
+            $tcolor  = new WebColor($this->trimmed('design_text'));
+            $lcolor  = new WebColor($this->trimmed('design_links'));
+        } catch (WebColorException $e) {
+            $this->showForm($e->getMessage());
+            return;
+        }
+
+        $onoff = $this->arg('design_background-image_onoff');
+
+        $on   = false;
+        $off  = false;
+        $tile = false;
+
+        if ($onoff == 'on') {
+            $on = true;
+        } else {
+            $off = true;
+        }
+
+        $repeat = $this->boolean('design_background-image_repeat');
+
+        if ($repeat) {
+            $tile = true;
+        }
+
+        $user   = common_current_user();
+        $design = $user->getDesign();
+
+        if (!empty($design)) {
+
+            $original = clone($design);
+
+            $design->backgroundcolor = $bgcolor->intValue();
+            $design->contentcolor    = $ccolor->intValue();
+            $design->sidebarcolor    = $sbcolor->intValue();
+            $design->textcolor       = $tcolor->intValue();
+            $design->linkcolor       = $lcolor->intValue();
+
+            $design->setDisposition($on, $off, $tile);
+
+            $result = $design->update($original);
+
+            if ($result === false) {
+                common_log_db_error($design, 'UPDATE', __FILE__);
+                $this->showForm(_('Couldn\'t update your design.'));
+                return;
+            }
+
+            // update design
+        } else {
+
+            $user->query('BEGIN');
+
+            // save new design
+            $design = new Design();
+
+            $design->backgroundcolor = $bgcolor->intValue();
+            $design->contentcolor    = $ccolor->intValue();
+            $design->sidebarcolor    = $sbcolor->intValue();
+            $design->textcolor       = $tcolor->intValue();
+            $design->linkcolor       = $lcolor->intValue();
+
+            $design->setDisposition($on, $off, $tile);
+
+            $id = $design->insert();
+
+            if (empty($id)) {
+                common_log_db_error($id, 'INSERT', __FILE__);
+                $this->showForm(_('Unable to save your design settings!'));
+                return;
+            }
+
+            $original        = clone($user);
+            $user->design_id = $id;
+            $result          = $user->update($original);
+
+            if (empty($result)) {
+                common_log_db_error($original, 'UPDATE', __FILE__);
+                $this->showForm(_('Unable to save your design settings!'));
+                $user->query('ROLLBACK');
+                return;
+            }
+
+            $user->query('COMMIT');
+
+        }
+
+        $this->saveBackgroundImage($design);
+
+        $this->showForm(_('Design preferences saved.'), true);
+    }
+
+    /**
+     * Alternate default colors
+     *
+     * @return nothing
+     */
+
+    function sethd()
+    {
+
+        $user   = common_current_user();
+        $design = $user->getDesign();
+
+        $user->query('BEGIN');
+
+        // alternate colors
+        $design = new Design();
+
+        $design->backgroundcolor = 16184329;
+        $design->contentcolor    = 16059904;
+        $design->sidebarcolor    = 16059904;
+        $design->textcolor       = 0;
+        $design->linkcolor       = 16777215;
+
+        $design->setDisposition(false, true, false);
+
+        $id = $design->insert();
+
+        if (empty($id)) {
+            common_log_db_error($id, 'INSERT', __FILE__);
+            $this->showForm(_('Unable to save your design settings!'));
+            return;
+        }
+
+        $original        = clone($user);
+        $user->design_id = $id;
+        $result          = $user->update($original);
+
+        if (empty($result)) {
+            common_log_db_error($original, 'UPDATE', __FILE__);
+            $this->showForm(_('Unable to save your design settings!'));
+            $user->query('ROLLBACK');
+            return;
+        }
+
+        $user->query('COMMIT');
+
+        $this->saveBackgroundImage($design);
+
+        $this->showForm(_('Enjoy your hotdog!'), true);
+    }
+
+}
index e3088dcbd83d6e26dc51110ecd633f626e78becc..7ead6e6e49172e4941035e0a4c5d433c210f9026 100644 (file)
@@ -46,9 +46,8 @@ require_once INSTALLDIR.'/lib/grouplist.php';
  * @link     http://laconi.ca/
  */
 
-class UsergroupsAction extends Action
+class UsergroupsAction extends OwnerDesignAction
 {
-    var $user = null;
     var $page = null;
     var $profile = null;
 
index 5861d9ee3687bd7a4e79e6088ea30a059c690a2b..8a940865f947f872e562faad8deb0bc2cb7d5fac 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -25,14 +25,15 @@ require_once(INSTALLDIR.'/lib/rssaction.php');
 
 class UserrssAction extends Rss10Action
 {
-
     var $user = null;
+    var $tag  = null;
 
     function prepare($args)
     {
         parent::prepare($args);
-        $nickname = $this->trimmed('nickname');
+        $nickname   = $this->trimmed('nickname');
         $this->user = User::staticGet('nickname', $nickname);
+        $this->tag  = $this->trimmed('tag');
 
         if (!$this->user) {
             $this->clientError(_('No such user.'));
@@ -42,6 +43,25 @@ class UserrssAction extends Rss10Action
         }
     }
 
+    function getTaggedNotices($tag = null, $limit=0)
+    {
+        $user = $this->user;
+
+        if (is_null($user)) {
+            return null;
+        }
+
+        $notice = $user->getTaggedNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit, 0, 0, null, $tag);
+
+        $notices = array();
+        while ($notice->fetch()) {
+            $notices[] = clone($notice);
+        }
+
+        return $notices;
+    }
+
+
     function getNotices($limit=0)
     {
 
index 1335b6b806653119d6ab6c0ee96c7e4c86ab5eb0..9327a3c83145ecbdf1bc354f790f5e7597e8efdc 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/background/.gitignore b/background/.gitignore
new file mode 100644 (file)
index 0000000..e69de29
index db9d78e47fc1e8f2c137ac6eb9a67d707b2a15fb..5e8b315fe6b419998b3617710d8f01baa8584651 100644 (file)
@@ -55,19 +55,43 @@ class Avatar extends Memcached_DataObject
 
     static function path($filename)
     {
-        return INSTALLDIR . '/avatar/' . $filename;
+        $dir = common_config('avatar', 'dir');
+
+        if ($dir[strlen($dir)-1] != '/') {
+            $dir .= '/';
+        }
+
+        return $dir . $filename;
     }
 
     static function url($filename)
     {
-        return common_path('avatar/'.$filename);
+        $path = common_config('avatar', 'path');
+
+        if ($path[strlen($path)-1] != '/') {
+            $path .= '/';
+        }
+
+        if ($path[0] != '/') {
+            $path = '/'.$path;
+        }
+
+        $server = common_config('avatar', 'server');
+
+        if (empty($server)) {
+            $server = common_config('site', 'server');
+        }
+
+        // XXX: protocol
+
+        return 'http://'.$server.$path.$filename;
     }
 
     function displayUrl()
     {
         $server = common_config('avatar', 'server');
         if ($server) {
-            return 'http://'.$server.'/'.$this->filename;
+            return Avatar::url($this->filename);
         } else {
             return $this->url;
         }
diff --git a/classes/Design.php b/classes/Design.php
new file mode 100644 (file)
index 0000000..0927fcd
--- /dev/null
@@ -0,0 +1,155 @@
+<?php
+/*
+ * Laconica - the distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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('BACKGROUND_ON', 1);
+define('BACKGROUND_OFF', 2);
+define('BACKGROUND_TILE', 4);
+
+/**
+ * Table Definition for design
+ */
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+require_once INSTALLDIR . '/lib/webcolor.php';
+
+class Design extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'design';                          // table name
+    public $id;                              // int(4)  primary_key not_null
+    public $backgroundcolor;                 // int(4)
+    public $contentcolor;                    // int(4)
+    public $sidebarcolor;                    // int(4)
+    public $textcolor;                       // int(4)
+    public $linkcolor;                       // int(4)
+    public $backgroundimage;                 // varchar(255)
+    public $disposition;                     // tinyint(1)   default_1
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Design',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function showCSS($out)
+    {
+        try {
+
+            $bgcolor = new WebColor($this->backgroundcolor);
+            $ccolor  = new WebColor($this->contentcolor);
+            $sbcolor = new WebColor($this->sidebarcolor);
+            $tcolor  = new WebColor($this->textcolor);
+            $lcolor  = new WebColor($this->linkcolor);
+
+        } catch (WebColorException $e) {
+            // This shouldn't happen
+            common_log(LOG_ERR, "Unable to create color for design $id.",
+                __FILE__);
+        }
+
+        $css  = 'body { background-color: #' . $bgcolor->hexValue() . ' }' . "\n";
+        $css .= '#content, #site_nav_local_views .current a { background-color: #';
+        $css .= $ccolor->hexValue() . '} '."\n";
+        $css .= '#aside_primary { background-color: #'. $sbcolor->hexValue() . ' }' . "\n";
+        $css .= 'html body { color: #'. $tcolor->hexValue() . ' }'. "\n";
+        $css .= 'a { color: #' . $lcolor->hexValue() . ' }' . "\n";
+
+        if (!empty($this->backgroundimage) &&
+            $this->disposition & BACKGROUND_ON) {
+
+           $repeat = ($this->disposition & BACKGROUND_TILE) ?
+               'background-repeat:repeat;' :
+               'background-repeat:no-repeat;';
+
+            $css .= 'body { background-image:url(' .
+                Design::url($this->backgroundimage) .
+                '); ' . $repeat . ' background-attachment:fixed; }' . "\n";
+        }
+
+        $out->element('style', array('type' => 'text/css'), $css);
+
+    }
+
+    static function filename($id, $extension, $extra=null)
+    {
+        return $id . (($extra) ? ('-' . $extra) : '') . $extension;
+    }
+
+    static function path($filename)
+    {
+        $dir = common_config('background', 'dir');
+
+        if ($dir[strlen($dir)-1] != '/') {
+            $dir .= '/';
+        }
+
+        return $dir . $filename;
+    }
+
+    static function url($filename)
+    {
+        $path = common_config('background', 'path');
+
+        if ($path[strlen($path)-1] != '/') {
+            $path .= '/';
+        }
+
+        if ($path[0] != '/') {
+            $path = '/'.$path;
+        }
+
+        $server = common_config('background', 'server');
+
+        if (empty($server)) {
+            $server = common_config('site', 'server');
+        }
+
+        // XXX: protocol
+
+        return 'http://'.$server.$path.$filename;
+    }
+
+    function setDisposition($on, $off, $tile)
+    {
+        if ($on) {
+            $this->disposition |= BACKGROUND_ON;
+        } else {
+            $this->disposition &= ~BACKGROUND_ON;
+        }
+
+        if ($off) {
+            $this->disposition |= BACKGROUND_OFF;
+        } else {
+            $this->disposition &= ~BACKGROUND_OFF;
+        }
+
+        if ($tile) {
+            $this->disposition |= BACKGROUND_TILE;
+        } else {
+            $this->disposition &= ~BACKGROUND_TILE;
+        }
+    }
+
+}
index 572334ce4f8cf17e8811cf0eab51cd77ed922549..c3ec62dcf0176c7166d4f2dbb5e69e9791b86a2e 100644 (file)
@@ -37,52 +37,62 @@ class Fave extends Memcached_DataObject
         return Memcached_DataObject::pkeyGet('Fave', $kv);
     }
 
-    function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE)
+    function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $own=false)
     {
         $ids = Notice::stream(array('Fave', '_streamDirect'),
-                              array($user_id),
+                              array($user_id, $own),
+                              ($own) ? 'fave:ids_by_user_own:'.$user_id :
                               'fave:ids_by_user:'.$user_id,
                               $offset, $limit);
         return $ids;
     }
 
-    function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
+    function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since)
     {
         $fav = new Fave();
-
-        $fav->user_id = $user_id;
-
-        $fav->selectAdd();
-        $fav->selectAdd('notice_id');
+        $qry = null;
+
+        if ($own) {
+            $qry  = 'SELECT fave.* FROM fave ';
+            $qry .= 'WHERE fave.user_id = ' . $user_id . ' ';
+        } else {
+             $qry =  'SELECT fave.* FROM fave ';
+             $qry .= 'INNER JOIN notice ON fave.notice_id = notice.id ';
+             $qry .= 'WHERE fave.user_id = ' . $user_id . ' ';
+             $qry .= 'AND notice.is_local != ' . NOTICE_GATEWAY . ' ';
+        }
 
         if ($since_id != 0) {
-            $fav->whereAdd('notice_id > ' . $since_id);
+            $qry .= 'AND notice_id > ' . $since_id . ' ';
         }
 
         if ($max_id != 0) {
-            $fav->whereAdd('notice_id <= ' . $max_id);
+            $qry .= 'AND notice_id <= ' . $max_id . ' ';
         }
 
         if (!is_null($since)) {
-            $fav->whereAdd('modified > \'' . date('Y-m-d H:i:s', $since) . '\'');
+            $qry .= 'AND modified > \'' . date('Y-m-d H:i:s', $since) . '\' ';
         }
 
         // NOTE: we sort by fave time, not by notice time!
 
-        $fav->orderBy('modified DESC');
+        $qry .= 'ORDER BY modified DESC ';
 
         if (!is_null($offset)) {
-            $fav->limit($offset, $limit);
+            $qry .= "LIMIT $offset, $limit";
         }
 
+        $fav->query($qry);
+
         $ids = array();
 
-        if ($fav->find()) {
-            while ($fav->fetch()) {
-                $ids[] = $fav->notice_id;
-            }
+        while ($fav->fetch()) {
+            $ids[] = $fav->notice_id;
         }
 
+        $fav->free();
+        unset($fav);
+
         return $ids;
     }
 }
diff --git a/classes/File.php b/classes/File.php
new file mode 100644 (file)
index 0000000..533cc6e
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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';
+require_once INSTALLDIR.'/classes/File_redirection.php';
+require_once INSTALLDIR.'/classes/File_oembed.php';
+require_once INSTALLDIR.'/classes/File_thumbnail.php';
+require_once INSTALLDIR.'/classes/File_to_post.php';
+//require_once INSTALLDIR.'/classes/File_redirection.php';
+
+/**
+ * Table Definition for file
+ */
+
+class File extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'file';                            // table name
+    public $id;                              // int(4)  primary_key not_null
+    public $url;                             // varchar(255)  unique_key
+    public $mimetype;                        // varchar(50)
+    public $size;                            // int(4)
+    public $title;                           // varchar(255)
+    public $date;                            // int(4)
+    public $protected;                       // int(4)
+    public $filename;                        // varchar(255)
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('File',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function isProtected($url) {
+        return 'http://www.facebook.com/login.php' === $url;
+    }
+
+    function getAttachments($post_id) {
+        $query = "select file.* from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $this->escape($post_id);
+        $this->query($query);
+        $att = array();
+        while ($this->fetch()) {
+            $att[] = clone($this);
+        }
+        $this->free();
+        return $att;
+    }
+
+    function saveNew($redir_data, $given_url) {
+        $x = new File;
+        $x->url = $given_url;
+        if (!empty($redir_data['protected'])) $x->protected = $redir_data['protected'];
+        if (!empty($redir_data['title'])) $x->title = $redir_data['title'];
+        if (!empty($redir_data['type'])) $x->mimetype = $redir_data['type'];
+        if (!empty($redir_data['size'])) $x->size = intval($redir_data['size']);
+        if (isset($redir_data['time']) && $redir_data['time'] > 0) $x->date = intval($redir_data['time']);
+        $file_id = $x->insert();
+
+        if (isset($redir_data['type'])
+            && ('text/html' === substr($redir_data['type'], 0, 9))
+            && ($oembed_data = File_oembed::_getOembed($given_url))
+            && isset($oembed_data['json'])) {
+            File_oembed::saveNew($oembed_data['json'], $file_id);
+        }
+        return $x;
+    }
+
+    function processNew($given_url, $notice_id) {
+        if (empty($given_url)) return -1;   // error, no url to process
+        $given_url = File_redirection::_canonUrl($given_url);
+        if (empty($given_url)) return -1;   // error, no url to process
+        $file = File::staticGet('url', $given_url);
+        if (empty($file)) {
+            $file_redir = File_redirection::staticGet('url', $given_url);
+            if (empty($file_redir)) {
+                common_debug("processNew() '$given_url' not a known redirect.\n");
+                $redir_data = File_redirection::where($given_url);
+                $redir_url = $redir_data['url'];
+                if ($redir_url === $given_url) {
+                    $x = File::saveNew($redir_data, $given_url);
+                    $file_id = $x->id;
+                } else {
+                    $x = File::processNew($redir_url, $notice_id);
+                    $file_id = $x->id;
+                    File_redirection::saveNew($redir_data, $file_id, $given_url);
+                }
+            } else {
+                $file_id = $file_redir->file_id;
+            }
+        } else {
+            $file_id = $file->id;
+            $x = $file;
+        }
+
+        if (empty($x)) {
+            $x = File::staticGet($file_id);
+            if (empty($x)) die('Impossible!');
+        }
+
+        File_to_post::processNew($file_id, $notice_id);
+        return $x;
+    }
+
+    function isRespectsQuota($user,$fileSize) {
+        if ($fileSize > common_config('attachments', 'file_quota')) {
+            return sprintf(_('No file may be larger than %d bytes ' .
+                             'and the file you sent was %d bytes. Try to upload a smaller version.'),
+                           common_config('attachments', 'file_quota'), $fileSize);
+        }
+
+        $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'";
+        $this->query($query);
+        $this->fetch();
+        $total = $this->total + $fileSize;
+        if ($total > common_config('attachments', 'user_quota')) {
+            return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'));
+        }
+
+        $query .= ' month(modified) = month(now()) and year(modified) = year(now())';
+        $this->query($query);
+        $this->fetch();
+        $total = $this->total + $fileSize;
+        if ($total > common_config('attachments', 'monthly_quota')) {
+            return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'));
+        }
+        return true;
+    }
+
+    // where should the file go?
+
+    static function filename($profile, $basename, $mimetype)
+    {
+        require_once 'MIME/Type/Extension.php';
+        $mte = new MIME_Type_Extension();
+        $ext = $mte->getExtension($mimetype);
+        $nickname = $profile->nickname;
+        $datestamp = strftime('%Y%m%dT%H%M%S', time());
+        $random = strtolower(common_confirmation_code(32));
+        return "$nickname-$datestamp-$random.$ext";
+    }
+
+    static function path($filename)
+    {
+        $dir = common_config('attachments', 'dir');
+
+        if ($dir[strlen($dir)-1] != '/') {
+            $dir .= '/';
+        }
+
+        return $dir . $filename;
+    }
+
+    static function url($filename)
+    {
+        $path = common_config('attachments', 'path');
+
+        if ($path[strlen($path)-1] != '/') {
+            $path .= '/';
+        }
+
+        if ($path[0] != '/') {
+            $path = '/'.$path;
+        }
+
+        $server = common_config('attachments', 'server');
+
+        if (empty($server)) {
+            $server = common_config('site', 'server');
+        }
+
+        // XXX: protocol
+
+        return 'http://'.$server.$path.$filename;
+    }
+}
+
diff --git a/classes/File_oembed.php b/classes/File_oembed.php
new file mode 100644 (file)
index 0000000..69230e4
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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';
+
+/**
+ * Table Definition for file_oembed
+ */
+
+class File_oembed extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'file_oembed';                     // table name
+    public $file_id;                         // int(4)  primary_key not_null
+    public $version;                         // varchar(20)
+    public $type;                            // varchar(20)
+    public $provider;                        // varchar(50)
+    public $provider_url;                    // varchar(255)
+    public $width;                           // int(4)
+    public $height;                          // int(4)
+    public $html;                            // text()
+    public $title;                           // varchar(255)
+    public $author_name;                     // varchar(50)
+    public $author_url;                      // varchar(255)
+    public $url;                             // varchar(255)
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('File_oembed',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    function _getOembed($url, $maxwidth = 500, $maxheight = 400, $format = 'json') {
+        $cmd = common_config('oohembed', 'endpoint') . '?url=' . urlencode($url);
+        if (is_int($maxwidth)) $cmd .= "&maxwidth=$maxwidth";
+        if (is_int($maxheight)) $cmd .= "&maxheight=$maxheight";
+        if (is_string($format)) $cmd .= "&format=$format";
+        $oe = @file_get_contents($cmd);
+        if (false === $oe) return false;
+        return array($format => (('json' === $format) ? json_decode($oe, true) : $oe));
+    }
+
+    function saveNew($data, $file_id) {
+        $file_oembed = new File_oembed;
+        $file_oembed->file_id = $file_id;
+        $file_oembed->version = $data['version'];
+        $file_oembed->type = $data['type'];
+        if (!empty($data['provider_name'])) $file_oembed->provider = $data['provider_name'];
+        if (!isset($file_oembed->provider) && !empty($data['provide'])) $file_oembed->provider = $data['provider'];
+        if (!empty($data['provide_url'])) $file_oembed->provider_url = $data['provider_url'];
+        if (!empty($data['width'])) $file_oembed->width = intval($data['width']);
+        if (!empty($data['height'])) $file_oembed->height = intval($data['height']);
+        if (!empty($data['html'])) $file_oembed->html = $data['html'];
+        if (!empty($data['title'])) $file_oembed->title = $data['title'];
+        if (!empty($data['author_name'])) $file_oembed->author_name = $data['author_name'];
+        if (!empty($data['author_url'])) $file_oembed->author_url = $data['author_url'];
+        if (!empty($data['url'])) $file_oembed->url = $data['url'];
+        $file_oembed->insert();
+        if (!empty($data['thumbnail_url'])) {
+            File_thumbnail::saveNew($data, $file_id);
+        }
+    }
+}
+
diff --git a/classes/File_redirection.php b/classes/File_redirection.php
new file mode 100644 (file)
index 0000000..d6fa0bc
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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';
+require_once INSTALLDIR.'/classes/File.php';
+require_once INSTALLDIR.'/classes/File_oembed.php';
+
+define('USER_AGENT', 'Laconica user agent / file probe');
+
+/**
+ * Table Definition for file_redirection
+ */
+
+class File_redirection extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'file_redirection';                // table name
+    public $url;                             // varchar(255)  primary_key not_null
+    public $file_id;                         // int(4)
+    public $redirections;                    // int(4)
+    public $httpcode;                        // int(4)
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('File_redirection',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function _commonCurl($url, $redirs) {
+        $curlh = curl_init();
+        curl_setopt($curlh, CURLOPT_URL, $url);
+        curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects
+        curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait
+        curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow
+        curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT);
+        curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
+        curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curlh, CURLOPT_FILETIME, true);
+        curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output
+        return $curlh;
+    }
+
+    function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) {
+        if ($redirs < 0) return false;
+
+        // let's see if we know this...
+        $a = File::staticGet('url', $short_url);
+
+        if (!empty($a)) {
+            // this is a direct link to $a->url
+            return $a->url;
+        } else {
+            $b = File_redirection::staticGet('url', $short_url);
+            if (!empty($b)) {
+                // this is a redirect to $b->file_id
+                $a = File::staticGet('id', $b->file_id);
+                return $a->url;
+            }
+        }
+
+        $curlh = File_redirection::_commonCurl($short_url, $redirs);
+        // Don't include body in output
+        curl_setopt($curlh, CURLOPT_NOBODY, true);
+        curl_exec($curlh);
+        $info = curl_getinfo($curlh);
+        curl_close($curlh);
+
+        if (405 == $info['http_code']) {
+            $curlh = File_redirection::_commonCurl($short_url, $redirs);
+            curl_exec($curlh);
+            $info = curl_getinfo($curlh);
+            curl_close($curlh);
+        }
+
+        if (!empty($info['redirect_count']) && File::isProtected($info['url'])) {
+            return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true);
+        }
+
+        $ret = array('code' => $info['http_code']
+                , 'redirects' => $info['redirect_count']
+                , 'url' => $info['url']);
+
+        if (!empty($info['content_type'])) $ret['type'] = $info['content_type'];
+        if ($protected) $ret['protected'] = true;
+        if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length'];
+        if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime'];
+        return $ret;
+    }
+
+    function where($in_url) {
+        $ret = File_redirection::_redirectWhere_imp($in_url);
+        return $ret;
+    }
+
+    function makeShort($long_url) {
+
+        $canon = File_redirection::_canonUrl($long_url);
+
+        $short_url = File_redirection::_userMakeShort($canon);
+
+        // Did we get one? Is it shorter?
+        if (!empty($short_url) && mb_strlen($short_url) < mb_strlen($long_url)) {
+            return $short_url;
+        } else {
+            return $long_url;
+        }
+    }
+
+    function _userMakeShort($long_url) {
+        $short_url = common_shorten_url($long_url);
+        if (!empty($short_url) && $short_url != $long_url) {
+            $short_url = (string)$short_url;
+            // store it
+            $file = File::staticGet('url', $long_url);
+            if (empty($file)) {
+                $redir_data = File_redirection::where($long_url);
+                $file = File::saveNew($redir_data, $long_url);
+                $file_id = $file->id;
+                if (!empty($redir_data['oembed']['json'])) {
+                    File_oembed::saveNew($redir_data['oembed']['json'], $file_id);
+                }
+            } else {
+                $file_id = $file->id;
+            }
+            $file_redir = File_redirection::staticGet('url', $short_url);
+            if (empty($file_redir)) {
+                $file_redir = new File_redirection;
+                $file_redir->url = $short_url;
+                $file_redir->file_id = $file_id;
+                $file_redir->insert();
+            }
+            return $short_url;
+        }
+        return null;
+    }
+
+    function _canonUrl($in_url, $default_scheme = 'http://') {
+        if (empty($in_url)) return false;
+        $out_url = $in_url;
+        $p = parse_url($out_url);
+        if (empty($p['host']) || empty($p['scheme'])) {
+            list($scheme) = explode(':', $in_url, 2);
+            switch ($scheme) {
+            case 'fax':
+            case 'tel':
+                $out_url = str_replace('.-()', '', $out_url);
+                break;
+
+            case 'mailto':
+            case 'aim':
+            case 'jabber':
+            case 'xmpp':
+                // don't touch anything
+                break;
+
+            default:
+                $out_url = $default_scheme . ltrim($out_url, '/');
+                $p = parse_url($out_url);
+                if (empty($p['scheme'])) return false;
+                break;
+            }
+        }
+
+        if (('ftp' == $p['scheme']) || ('http' == $p['scheme']) || ('https' == $p['scheme'])) {
+            if (empty($p['host'])) return false;
+            if (empty($p['path'])) {
+                $out_url .= '/';
+            }
+        }
+
+        return $out_url;
+    }
+
+    function saveNew($data, $file_id, $url) {
+        $file_redir = new File_redirection;
+        $file_redir->url = $url;
+        $file_redir->file_id = $file_id;
+        $file_redir->redirections = intval($data['redirects']);
+        $file_redir->httpcode = intval($data['code']);
+        $file_redir->insert();
+    }
+}
+
diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php
new file mode 100644 (file)
index 0000000..44b92a2
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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';
+
+/**
+ * Table Definition for file_thumbnail
+ */
+
+class File_thumbnail extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'file_thumbnail';                  // table name
+    public $file_id;                         // int(4)  primary_key not_null
+    public $url;                             // varchar(255)  unique_key
+    public $width;                           // int(4)
+    public $height;                          // int(4)
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('File_thumbnail',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    function saveNew($data, $file_id) {
+        $tn = new File_thumbnail;
+        $tn->file_id = $file_id;
+        $tn->url = $data['thumbnail_url'];
+        $tn->width = intval($data['thumbnail_width']);
+        $tn->height = intval($data['thumbnail_height']);
+        $tn->insert();
+    }
+}
+
diff --git a/classes/File_to_post.php b/classes/File_to_post.php
new file mode 100644 (file)
index 0000000..d35febb
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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';
+
+/**
+ * Table Definition for file_to_post
+ */
+
+class File_to_post extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'file_to_post';                    // table name
+    public $file_id;                         // int(4)  primary_key not_null
+    public $post_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('File_to_post',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function processNew($file_id, $notice_id) {
+        static $seen = array();
+        if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) {
+
+            $f2p = File_to_post::pkeyGet(array('post_id' => $notice_id,
+                                               'file_id' => $file_id));
+            if (empty($f2p)) {
+                $f2p = new File_to_post;
+                $f2p->file_id = $file_id;
+                $f2p->post_id = $notice_id;
+                $f2p->insert();
+            }
+
+            if (empty($seen[$notice_id])) {
+                $seen[$notice_id] = array($file_id);
+            } else {
+                $seen[$notice_id][] = $file_id;
+            }
+        }
+    }
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('File_to_post', $kv);
+    }
+}
+
index af2b3f189da54fb8cf1e0581dd3bfe3e5233b2cc..c0b356ecedd10473e13447d7a14157b8388dc63f 100644 (file)
@@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject
 
     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 $foreign_id;                      // bigint(8)  primary_key not_null unsigned
     public $service;                         // int(4)  primary_key not_null
     public $credentials;                     // varchar(255)
     public $noticesync;                      // tinyint(1)   not_null default_1
@@ -59,13 +59,19 @@ class Foreign_link extends Memcached_DataObject
         return null;
     }
 
-    function set_flags($noticesync, $replysync, $friendsync)
+    function set_flags($noticesend, $noticerecv, $replysync, $friendsync)
     {
-        if ($noticesync) {
+        if ($noticesend) {
             $this->noticesync |= FOREIGN_NOTICE_SEND;
         } else {
             $this->noticesync &= ~FOREIGN_NOTICE_SEND;
         }
+        
+        if ($noticerecv) {
+            $this->noticesync |= FOREIGN_NOTICE_RECV;
+        } else {
+            $this->noticesync &= ~FOREIGN_NOTICE_RECV;
+        }
 
         if ($replysync) {
             $this->noticesync |= FOREIGN_NOTICE_SEND_REPLY;
index 61727abe5ed5924d6a8a7bb41eb721a2d672cefc..8b3e03dfb33f3d7f5c1e39cdf2d04160c72d07de 100644 (file)
@@ -4,42 +4,41 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Foreign_user extends Memcached_DataObject 
+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 $id;                              // bigint(8)  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 $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); }
+    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) {        
+    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;        
+
+        return null;
     }
-    
+
     function updateKeys(&$orig)
     {
         $parts = array();
@@ -68,5 +67,4 @@ class Foreign_user extends Memcached_DataObject
         return $result;
     }
 
-    
 }
diff --git a/classes/Group_alias.php b/classes/Group_alias.php
new file mode 100644 (file)
index 0000000..e801e50
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Table Definition for group_alias
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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 Group_alias extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'group_alias';                     // table name
+    public $alias;                           // varchar(64)  primary_key not_null
+    public $group_id;                        // int(4)   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Group_alias',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+}
diff --git a/classes/Group_block.php b/classes/Group_block.php
new file mode 100644 (file)
index 0000000..7922c19
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+/**
+ * Table Definition for group_block
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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 Group_block extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'group_block';                     // table name
+    public $group_id;                        // int(4)  primary_key not_null
+    public $blocked;                         // int(4)  primary_key not_null
+    public $blocker;                         // int(4)   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Group_block',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Group_block', $kv);
+    }
+
+    static function isBlocked($group, $profile)
+    {
+        $block = Group_block::pkeyGet(array('group_id' => $group->id,
+                                            'blocked' => $profile->id));
+        return !empty($block);
+    }
+
+    static function blockProfile($group, $profile, $blocker)
+    {
+        // Insert the block
+
+        $block = new Group_block();
+
+        $block->query('BEGIN');
+
+        $block->group_id = $group->id;
+        $block->blocked  = $profile->id;
+        $block->blocker  = $blocker->id;
+
+        $result = $block->insert();
+
+        if (!$result) {
+            common_log_db_error($block, 'INSERT', __FILE__);
+            return null;
+        }
+
+        // Delete membership if any
+
+        $member = new Group_member();
+
+        $member->group_id   = $group->id;
+        $member->profile_id = $profile->id;
+
+        if ($member->find(true)) {
+            $result = $member->delete();
+            if (!$result) {
+                common_log_db_error($member, 'DELETE', __FILE__);
+                return null;
+            }
+        }
+
+        // Commit, since both have been done
+
+        $block->query('COMMIT');
+
+        return $block;
+    }
+
+    static function unblockProfile($group, $profile)
+    {
+        $block = Group_block::pkeyGet(array('group_id' => $group->id,
+                                            'blocked' => $profile->id));
+
+        if (empty($block)) {
+            return null;
+        }
+
+        $result = $block->delete();
+
+        if (!$result) {
+            common_log_db_error($block, 'DELETE', __FILE__);
+            return null;
+        }
+
+        return true;
+    }
+
+}
old mode 100755 (executable)
new mode 100644 (file)
index b80ba42..1af7439
@@ -14,8 +14,14 @@ class Group_inbox extends Memcached_DataObject
     public $created;                         // datetime()   not_null
 
     /* Static get */
+
     function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Group_inbox',$k,$v); }
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Group_inbox', $kv);
+    }
 }
old mode 100755 (executable)
new mode 100644 (file)
index 33ac70dd045643a5a57ff87e58d383a0bc52bd2b..f7cbb9d5b6c375b50f9c7dbc46b3a8eb2496e80b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -193,7 +193,14 @@ class Memcached_DataObject extends DB_DataObject
                 // unable to connect to sphinx' search daemon
                 if (!$connected) {
                     if ('mysql' === common_config('db', 'type')) {
-                        $search_engine = new MySQLSearch($this, $table);
+                        $type = common_config('search', 'type');
+                        if ($type == 'like') {
+                            $search_engine = new MySQLLikeSearch($this, $table);
+                        } else if ($type == 'fulltext') {
+                            $search_engine = new MySQLSearch($this, $table);
+                        } else {
+                            throw new ServerException('Unknown search type: ' . $type);
+                        }
                     } else {
                         $search_engine = new PGSearch($this, $table);
                     }
@@ -242,13 +249,16 @@ class Memcached_DataObject extends DB_DataObject
             if (common_config('db', 'type') == 'mysql' &&
                 common_config('db', 'utf8')) {
                 $conn = $DB->connection;
-                if ($DB instanceof DB_mysqli) {
-                    mysqli_set_charset($conn, 'utf8');
-                } else if ($DB instanceof DB_mysql) {
-                    mysql_set_charset('utf8', $conn);
+                if (!empty($conn)) {
+                    if ($DB instanceof DB_mysqli) {
+                        mysqli_set_charset($conn, 'utf8');
+                    } else if ($DB instanceof DB_mysql) {
+                        mysql_set_charset('utf8', $conn);
+                    }
                 }
             }
         }
         return $result;
     }
+
 }
index bca4b22c4c76c73e45f1e84fa659c2fb3f98389a..75044cf638ae1b5218a08863ceed01f2a59a4191 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -29,6 +29,13 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
 define('NOTICE_CACHE_WINDOW', 61);
 
+define('NOTICE_LOCAL_PUBLIC', 1);
+define('NOTICE_REMOTE_OMB', 0);
+define('NOTICE_LOCAL_NONPUBLIC', -1);
+define('NOTICE_GATEWAY', -2);
+
+define('MAX_BOXCARS', 128);
+
 class Notice extends Memcached_DataObject
 {
     ###START_AUTOCODE
@@ -46,6 +53,7 @@ class Notice extends Memcached_DataObject
     public $reply_to;                        // int(4)
     public $is_local;                        // tinyint(1)
     public $source;                          // varchar(32)
+    public $conversation;                    // int(4)
 
     /* Static get */
     function staticGet($k,$v=NULL) {
@@ -119,7 +127,8 @@ class Notice extends Memcached_DataObject
         }
     }
 
-    static function saveNew($profile_id, $content, $source=null, $is_local=1, $reply_to=null, $uri=null) {
+    static function saveNew($profile_id, $content, $source=null,
+                            $is_local=1, $reply_to=null, $uri=null, $created=null) {
 
         $profile = Profile::staticGet($profile_id);
 
@@ -170,12 +179,24 @@ class Notice extends Memcached_DataObject
                $notice->query('BEGIN');
 
                $notice->reply_to = $reply_to;
-               $notice->created = common_sql_now();
+        if (!empty($created)) {
+            $notice->created = $created;
+        } else {
+            $notice->created = common_sql_now();
+        }
                $notice->content = $final;
                $notice->rendered = common_render_content($final, $notice);
                $notice->source = $source;
                $notice->uri = $uri;
 
+        if (!empty($reply_to)) {
+            $reply_notice = Notice::staticGet('id', $reply_to);
+            if (!empty($reply_notice)) {
+                $notice->reply_to = $reply_to;
+                $notice->conversation = $reply_notice->conversation;
+            }
+        }
+
         if (Event::handle('StartNoticeSave', array(&$notice))) {
 
             $id = $notice->insert();
@@ -202,7 +223,14 @@ class Notice extends Memcached_DataObject
             $notice->saveTags();
 
             $notice->addToInboxes();
-            $notice->saveGroups();
+
+            $notice->saveUrls();
+            $orig2 = clone($notice);
+               $notice->rendered = common_render_content($final, $notice);
+            if (!$notice->update($orig2)) {
+                common_log_db_error($notice, 'UPDATE', __FILE__);
+                return _('Problem saving notice.');
+            }
 
             $notice->query('COMMIT');
 
@@ -217,6 +245,22 @@ class Notice extends Memcached_DataObject
         return $notice;
     }
 
+    /** save all urls in the notice to the db
+     *
+     * follow redirects and save all available file information
+     * (mimetype, date, size, oembed, etc.)
+     *
+     * @return void
+     */
+    function saveUrls() {
+        common_replace_urls_callback($this->content, array($this, 'saveUrl'), $this->id);
+    }
+
+    function saveUrl($data) {
+        list($url, $notice_id) = $data;
+        File::processNew($url, $notice_id);
+    }
+
     static function checkDupes($profile_id, $content) {
         $profile = Profile::staticGet($profile_id);
         if (!$profile) {
@@ -265,6 +309,44 @@ class Notice extends Memcached_DataObject
         return true;
     }
 
+    function getUploadedAttachment() {
+        $post = clone $this;
+        $query = 'select file.url as up, file.id as i from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"';
+        $post->query($query);
+        $post->fetch();
+        if (empty($post->up) || empty($post->i)) {
+            $ret = false;
+        } else {
+            $ret = array($post->up, $post->i);
+        }
+        $post->free();
+        return $ret;
+    }
+
+    function hasAttachments() {
+        $post = clone $this;
+        $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id);
+        $post->query($query);
+        $post->fetch();
+        $n_attachments = intval($post->n_attachments);
+        $post->free();
+        return $n_attachments;
+    }
+
+    function attachments() {
+        // XXX: cache this
+        $att = array();
+        $f2p = new File_to_post;
+        $f2p->post_id = $this->id;
+        if ($f2p->find()) {
+            while ($f2p->fetch()) {
+                $f = File::staticGet($f2p->file_id);
+                $att[] = clone($f);
+            }
+        }
+        return $att;
+    }
+
     function blowCaches($blowLast=false)
     {
         $this->blowSubsCache($blowLast);
@@ -273,6 +355,21 @@ class Notice extends Memcached_DataObject
         $this->blowPublicCache($blowLast);
         $this->blowTagCache($blowLast);
         $this->blowGroupCache($blowLast);
+        $this->blowConversationCache($blowLast);
+        $profile = Profile::staticGet($this->profile_id);
+        $profile->blowNoticeCount();
+    }
+
+    function blowConversationCache($blowLast=false)
+    {
+        $cache = common_memcache();
+        if ($cache) {
+            $ck = common_cache_key('notice:conversation_ids:'.$this->conversation);
+            $cache->delete($ck);
+            if ($blowLast) {
+                $cache->delete($ck.';last');
+            }
+        }
     }
 
     function blowGroupCache($blowLast=false)
@@ -313,6 +410,12 @@ class Notice extends Memcached_DataObject
             if ($tag->find()) {
                 while ($tag->fetch()) {
                     $tag->blowCache($blowLast);
+                    $ck = 'profile:notice_ids_tagged:' . $this->profile_id . ':' . $tag->tag;
+
+                    $cache->delete($ck);
+                    if ($blowLast) {
+                        $cache->delete($ck . ';last');
+                    }
                 }
             }
             $tag->free();
@@ -334,8 +437,10 @@ class Notice extends Memcached_DataObject
 
             while ($user->fetch()) {
                 $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id));
+                $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id));
                 if ($blowLast) {
                     $cache->delete(common_cache_key('notice_inbox:by_user:'.$user->id.';last'));
+                    $cache->delete(common_cache_key('notice_inbox:by_user_own:'.$user->id.';last'));
                 }
             }
             $user->free();
@@ -397,8 +502,10 @@ class Notice extends Memcached_DataObject
             if ($fave->find()) {
                 while ($fave->fetch()) {
                     $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id));
+                    $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id));
                     if ($blowLast) {
                         $cache->delete(common_cache_key('fave:ids_by_user:'.$fave->user_id.';last'));
+                        $cache->delete(common_cache_key('fave:by_user_own:'.$fave->user_id.';last'));
                     }
                 }
             }
@@ -601,7 +708,10 @@ class Notice extends Memcached_DataObject
         if (!empty($cache)) {
             $notices = array();
             foreach ($ids as $id) {
-                $notices[] = Notice::staticGet('id', $id);
+                $n = Notice::staticGet('id', $id);
+                if (!empty($n)) {
+                    $notices[] = $n;
+                }
             }
             return new ArrayWrapper($notices);
         } else {
@@ -670,34 +780,148 @@ class Notice extends Memcached_DataObject
         return $ids;
     }
 
+    function conversationStream($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    {
+        $ids = Notice::stream(array('Notice', '_conversationStreamDirect'),
+                              array($id),
+                              'notice:conversation_ids:'.$id,
+                              $offset, $limit, $since_id, $max_id, $since);
+
+        return Notice::getStreamByIds($ids);
+    }
+
+    function _conversationStreamDirect($id, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
+    {
+        $notice = new Notice();
+
+        $notice->selectAdd(); // clears it
+        $notice->selectAdd('id');
+
+        $notice->conversation = $id;
+
+        $notice->orderBy('id DESC');
+
+        if (!is_null($offset)) {
+            $notice->limit($offset, $limit);
+        }
+
+        if ($since_id != 0) {
+            $notice->whereAdd('id > ' . $since_id);
+        }
+
+        if ($max_id != 0) {
+            $notice->whereAdd('id <= ' . $max_id);
+        }
+
+        if (!is_null($since)) {
+            $notice->whereAdd('created > \'' . date('Y-m-d H:i:s', $since) . '\'');
+        }
+
+        $ids = array();
+
+        if ($notice->find()) {
+            while ($notice->fetch()) {
+                $ids[] = $notice->id;
+            }
+        }
+
+        $notice->free();
+        $notice = NULL;
+
+        return $ids;
+    }
+
     function addToInboxes()
     {
         $enabled = common_config('inboxes', 'enabled');
 
         if ($enabled === true || $enabled === 'transitional') {
+
+            // XXX: loads constants
+
             $inbox = new Notice_inbox();
-            $UT = common_config('db','type')=='pgsql'?'"user"':'user';
-            $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' .
-              "SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " .
-              "FROM $UT JOIN subscription ON $UT.id = subscription.subscriber " .
-              'WHERE subscription.subscribed = ' . $this->profile_id . ' ' .
-              'AND NOT EXISTS (SELECT user_id, notice_id ' .
-              'FROM notice_inbox ' .
-              "WHERE user_id = $UT.id " .
-              'AND notice_id = ' . $this->id . ' )';
-            if ($enabled === 'transitional') {
-                $qry .= " AND $UT.inboxed = 1";
-            }
-            $inbox->query($qry);
+
+            $users = $this->getSubscribedUsers();
+
+            // FIXME: kind of ignoring 'transitional'...
+            // we'll probably stop supporting inboxless mode
+            // in 0.9.x
+
+            $ni = array();
+
+            foreach ($users as $id) {
+                $ni[$id] = NOTICE_INBOX_SOURCE_SUB;
+            }
+
+            $groups = $this->saveGroups();
+
+            foreach ($groups as $group) {
+                $users = $group->getUserMembers();
+                foreach ($users as $id) {
+                    if (!array_key_exists($id, $ni)) {
+                        $ni[$id] = NOTICE_INBOX_SOURCE_GROUP;
+                    }
+                }
+            }
+
+            $cnt = 0;
+
+            $qryhdr = 'INSERT INTO notice_inbox (user_id, notice_id, source, created) VALUES ';
+            $qry = $qryhdr;
+
+            foreach ($ni as $id => $source) {
+                if ($cnt > 0) {
+                    $qry .= ', ';
+                }
+                $qry .= '('.$id.', '.$this->id.', '.$source.', "'.$this->created.'") ';
+                $cnt++;
+                if ($cnt >= MAX_BOXCARS) {
+                    $inbox = new Notice_inbox();
+                    $inbox->query($qry);
+                    $qry = $qryhdr;
+                    $cnt = 0;
+                }
+            }
+
+            if ($cnt > 0) {
+                $inbox = new Notice_inbox();
+                $inbox->query($qry);
+            }
         }
+
         return;
     }
 
+    function getSubscribedUsers()
+    {
+        $user = new User();
+
+        $qry =
+          'SELECT id ' .
+          'FROM user JOIN subscription '.
+          'ON user.id = subscription.subscriber ' .
+          'WHERE subscription.subscribed = %d ';
+
+        $user->query(sprintf($qry, $this->profile_id));
+
+        $ids = array();
+
+        while ($user->fetch()) {
+            $ids[] = $user->id;
+        }
+
+        $user->free();
+
+        return $ids;
+    }
+
     function saveGroups()
     {
+        $groups = array();
+
         $enabled = common_config('inboxes', 'enabled');
         if ($enabled !== true && $enabled !== 'transitional') {
-            return;
+            return $groups;
         }
 
         /* extract all !group */
@@ -705,7 +929,7 @@ class Notice extends Memcached_DataObject
                                 strtolower($this->content),
                                 $match);
         if (!$count) {
-            return true;
+            return $groups;
         }
 
         $profile = $this->getProfile();
@@ -714,16 +938,16 @@ class Notice extends Memcached_DataObject
 
         foreach (array_unique($match[1]) as $nickname) {
             /* XXX: remote groups. */
-            $group = User_group::staticGet('nickname', $nickname);
+            $group = User_group::getForNickname($nickname);
 
-            if (!$group) {
+            if (empty($group)) {
                 continue;
             }
 
             // we automatically add a tag for every group name, too
 
             $tag = Notice_tag::pkeyGet(array('tag' => common_canonical_tag($nickname),
-                                           'notice_id' => $this->id));
+                                             'notice_id' => $this->id));
 
             if (is_null($tag)) {
                 $this->saveTag($nickname);
@@ -731,41 +955,36 @@ class Notice extends Memcached_DataObject
 
             if ($profile->isMember($group)) {
 
-                $gi = new Group_inbox();
-
-                $gi->group_id  = $group->id;
-                $gi->notice_id = $this->id;
-                $gi->created   = common_sql_now();
-
-                $result = $gi->insert();
+                $result = $this->addToGroupInbox($group);
 
                 if (!$result) {
                     common_log_db_error($gi, 'INSERT', __FILE__);
                 }
 
-                // FIXME: do this in an offline daemon
-
-                $this->addToGroupInboxes($group);
+                $groups[] = clone($group);
             }
         }
+
+        return $groups;
     }
 
-    function addToGroupInboxes($group)
+    function addToGroupInbox($group)
     {
-        $inbox = new Notice_inbox();
-        $UT = common_config('db','type')=='pgsql'?'"user"':'user';
-        $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created, source) ' .
-          "SELECT $UT.id, " . $this->id . ", '" . $this->created . "', 2 " .
-          "FROM $UT JOIN group_member ON $UT.id = group_member.profile_id " .
-          'WHERE group_member.group_id = ' . $group->id . ' ' .
-          'AND NOT EXISTS (SELECT user_id, notice_id ' .
-          'FROM notice_inbox ' .
-          "WHERE user_id = $UT.id " .
-          'AND notice_id = ' . $this->id . ' )';
-        if ($enabled === 'transitional') {
-            $qry .= " AND $UT.inboxed = 1";
-        }
-        $result = $inbox->query($qry);
+        $gi = Group_inbox::pkeyGet(array('group_id' => $group->id,
+                                         'notice_id' => $this->id));
+
+        if (empty($gi)) {
+
+            $gi = new Group_inbox();
+
+            $gi->group_id  = $group->id;
+            $gi->notice_id = $this->id;
+            $gi->created   = $this->created;
+
+            return $gi->insert();
+        }
+
+        return true;
     }
 
     function saveReplies()
@@ -804,6 +1023,7 @@ class Notice extends Memcached_DataObject
                 if ($recipient_notice) {
                     $orig = clone($this);
                     $this->reply_to = $recipient_notice->id;
+                    $this->conversation = $recipient_notice->conversation;
                     $this->update($orig);
                 }
             }
@@ -853,6 +1073,14 @@ class Notice extends Memcached_DataObject
             }
         }
 
+        // If it's not a reply, make it the root of a new conversation
+
+        if (empty($this->conversation)) {
+            $orig = clone($this);
+            $this->conversation = $this->id;
+            $this->update($orig);
+        }
+
         foreach (array_keys($replied) as $recipient) {
             $user = User::staticGet('id', $recipient);
             if ($user) {
@@ -938,6 +1166,18 @@ class Notice extends Memcached_DataObject
         }
         $tag->free();
 
+        # Enclosures
+        $attachments = $this->attachments();
+        if($attachments){
+            foreach($attachments as $attachment){
+                $attributes = array('rel'=>'enclosure','href'=>$attachment->url,'type'=>$attachment->mimetype,'length'=>$attachment->size);
+                if($attachment->title){
+                    $attributes['title']=$attachment->title;
+                }
+                $xs->element('link', $attributes, null);
+            }
+        }
+
         $xs->elementEnd('entry');
 
         return $xs->getString();
@@ -961,6 +1201,7 @@ class Notice extends Memcached_DataObject
 
         if (empty($cache) ||
             $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) ||
+            is_null($limit) ||
             ($offset + $limit) > NOTICE_CACHE_WINDOW) {
             return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
                                                                       $max_id, $since)));
index 8a27e174785101b9b52b6e3bf9211b3867e00ab2..940381f84cd1e258ba0a8563ea79bae62c6b4b8a 100644 (file)
@@ -25,6 +25,11 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
 define('INBOX_CACHE_WINDOW', 101);
 
+define('NOTICE_INBOX_SOURCE_SUB', 1);
+define('NOTICE_INBOX_SOURCE_GROUP', 2);
+define('NOTICE_INBOX_SOURCE_REPLY', 3);
+define('NOTICE_INBOX_SOURCE_GATEWAY', -1);
+
 class Notice_inbox extends Memcached_DataObject
 {
     ###START_AUTOCODE
@@ -43,20 +48,25 @@ class Notice_inbox extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    function stream($user_id, $offset, $limit, $since_id, $max_id, $since)
+    function stream($user_id, $offset, $limit, $since_id, $max_id, $since, $own=false)
     {
         return Notice::stream(array('Notice_inbox', '_streamDirect'),
-                              array($user_id),
-                              'notice_inbox:by_user:'.$user_id,
+                              array($user_id, $own),
+                              ($own) ? 'notice_inbox:by_user:'.$user_id :
+                              'notice_inbox:by_user_own:'.$user_id,
                               $offset, $limit, $since_id, $max_id, $since);
     }
 
-    function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
+    function _streamDirect($user_id, $own, $offset, $limit, $since_id, $max_id, $since)
     {
         $inbox = new Notice_inbox();
 
         $inbox->user_id = $user_id;
 
+        if (!$own) {
+            $inbox->whereAdd('source != ' . NOTICE_INBOX_SOURCE_GATEWAY);
+        }
+
         if ($since_id != 0) {
             $inbox->whereAdd('notice_id > ' . $since_id);
         }
@@ -85,4 +95,9 @@ class Notice_inbox extends Memcached_DataObject
 
         return $ids;
     }
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Notice_inbox', $kv);
+    }
 }
index 758a6659473ec491bd625758ac697090e74aa9b8..4e52ef2697acdb1bd3bbebac196d1fabea8e8e02 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 3d13cd46af0146f946457200656e6c529a6efef9..224b61bd2ee3508f19789923879b07f5ae3582e5 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -153,18 +153,67 @@ class Profile extends Memcached_DataObject
         return null;
     }
 
-    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0)
+    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
+    {
+        $ids = Notice::stream(array($this, '_streamTaggedDirect'),
+                              array($tag),
+                              'profile:notice_ids_tagged:' . $this->id . ':' . $tag,
+                              $offset, $limit, $since_id, $max_id, $since);
+        return Notice::getStreamByIds($ids);
+    }
+
+    function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
     {
         // XXX: I'm not sure this is going to be any faster. It probably isn't.
         $ids = Notice::stream(array($this, '_streamDirect'),
                               array(),
                               'profile:notice_ids:' . $this->id,
-                              $offset, $limit, $since_id, $max_id);
+                              $offset, $limit, $since_id, $max_id, $since);
 
         return Notice::getStreamByIds($ids);
     }
 
-    function _streamDirect($offset, $limit, $since_id, $max_id, $since)
+    function _streamTaggedDirect($tag, $offset, $limit, $since_id, $max_id, $since)
+    {
+        // XXX It would be nice to do this without a join
+
+        $notice = new Notice();
+
+        $query =
+          "select id from notice join notice_tag on id=notice_id where tag='".
+          $notice->escape($tag) .
+          "' and profile_id=" . $notice->escape($this->id);
+
+        if ($since_id != 0) {
+            $query .= " and id > $since_id";
+        }
+
+        if ($max_id != 0) {
+            $query .= " and id < $max_id";
+        }
+
+        if (!is_null($since)) {
+            $query .= " and created > '" . date('Y-m-d H:i:s', $since) . "'";
+        }
+
+        $query .= ' order by id DESC';
+
+        if (!is_null($offset)) {
+            $query .= " limit $offset, $limit";
+        }
+
+        $notice->query($query);
+
+        $ids = array();
+
+        while ($notice->fetch()) {
+            $ids[] = $notice->id;
+        }
+
+        return $ids;
+    }
+
+    function _streamDirect($offset, $limit, $since_id, $max_id, $since = null)
     {
         $notice = new Notice();
 
@@ -240,4 +289,180 @@ class Profile extends Memcached_DataObject
             return Avatar::defaultImage($size);
         }
     }
+
+    function getSubscriptions($offset=0, $limit=null)
+    {
+        $qry =
+          'SELECT profile.* ' .
+          'FROM profile JOIN subscription ' .
+          'ON profile.id = subscription.subscribed ' .
+          'WHERE subscription.subscriber = %d ' .
+          'AND subscription.subscribed != subscription.subscriber ' .
+          'ORDER BY subscription.created DESC ';
+
+        if (common_config('db','type') == 'pgsql') {
+            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+        } else {
+            $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+        }
+
+        $profile = new Profile();
+
+        $profile->query(sprintf($qry, $this->id));
+
+        return $profile;
+    }
+
+    function getSubscribers($offset=0, $limit=null)
+    {
+        $qry =
+          'SELECT profile.* ' .
+          'FROM profile JOIN subscription ' .
+          'ON profile.id = subscription.subscriber ' .
+          'WHERE subscription.subscribed = %d ' .
+          'AND subscription.subscribed != subscription.subscriber ' .
+          'ORDER BY subscription.created DESC ';
+
+        if ($offset) {
+            if (common_config('db','type') == 'pgsql') {
+                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+            } else {
+                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+            }
+        }
+
+        $profile = new Profile();
+
+        $cnt = $profile->query(sprintf($qry, $this->id));
+
+        return $profile;
+    }
+
+    function subscriptionCount()
+    {
+        $c = common_memcache();
+
+        if (!empty($c)) {
+            $cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id));
+            if (is_integer($cnt)) {
+                return (int) $cnt;
+            }
+        }
+
+        $sub = new Subscription();
+        $sub->subscriber = $this->id;
+
+        $cnt = (int) $sub->count('distinct subscribed');
+
+        $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
+
+        if (!empty($c)) {
+            $c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt);
+        }
+
+        common_debug("subscriptionCount == $cnt");
+        return $cnt;
+    }
+
+    function subscriberCount()
+    {
+        $c = common_memcache();
+        if (!empty($c)) {
+            $cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id));
+            if (is_integer($cnt)) {
+                return (int) $cnt;
+            }
+        }
+
+        $sub = new Subscription();
+        $sub->subscribed = $this->id;
+
+        $cnt = (int) $sub->count('distinct subscriber');
+
+        $cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
+
+        if (!empty($c)) {
+            $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
+        }
+
+        common_debug("subscriberCount == $cnt");
+        return $cnt;
+    }
+
+    function faveCount()
+    {
+        $c = common_memcache();
+        if (!empty($c)) {
+            $cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id));
+            if (is_integer($cnt)) {
+                return (int) $cnt;
+            }
+        }
+
+        $faves = new Fave();
+        $faves->user_id = $this->id;
+        $cnt = (int) $faves->count('distinct notice_id');
+
+        if (!empty($c)) {
+            $c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
+        }
+
+        common_debug("faveCount == $cnt");
+        return $cnt;
+    }
+
+    function noticeCount()
+    {
+        $c = common_memcache();
+
+        if (!empty($c)) {
+            $cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id));
+            if (is_integer($cnt)) {
+                return (int) $cnt;
+            }
+        }
+
+        $notices = new Notice();
+        $notices->profile_id = $this->id;
+        $cnt = (int) $notices->count('distinct id');
+
+        if (!empty($c)) {
+            $c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
+        }
+
+        common_debug("noticeCount == $cnt");
+        return $cnt;
+    }
+
+    function blowSubscriberCount()
+    {
+        $c = common_memcache();
+        if (!empty($c)) {
+            $c->delete(common_cache_key('profile:subscriber_count:'.$this->id));
+        }
+    }
+
+    function blowSubscriptionCount()
+    {
+        $c = common_memcache();
+        if (!empty($c)) {
+            $c->delete(common_cache_key('profile:subscription_count:'.$this->id));
+        }
+    }
+
+    function blowFaveCount()
+    {
+        $c = common_memcache();
+        if (!empty($c)) {
+            $c->delete(common_cache_key('profile:fave_count:'.$this->id));
+        }
+    }
+
+    function blowNoticeCount()
+    {
+        $c = common_memcache();
+        if (!empty($c)) {
+            $c->delete(common_cache_key('profile:notice_count:'.$this->id));
+        }
+    }
 }
index 551e690e24a57e55c8850fb526be1848e31f4dc8..feadea42da8e474ca65b4f805d0fc77b6cab424a 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 9b909ec22ba8360ca1f39ee5f990cd47ed92f3e7..295c321b57d4615fa5a139b9e14b9752fd19a4e3 100644 (file)
@@ -4,7 +4,7 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Queue_item extends Memcached_DataObject 
+class Queue_item extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -13,7 +13,7 @@ class Queue_item extends Memcached_DataObject
     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()  
+    public $claimed;                         // datetime()
 
     /* Static get */
     function staticGet($k,$v=null)
@@ -24,7 +24,7 @@ class Queue_item extends Memcached_DataObject
 
     function sequenceKey()
     { return array(false, false); }
-    
+
     static function top($transport) {
 
         $qi = new Queue_item();
@@ -54,4 +54,9 @@ class Queue_item extends Memcached_DataObject
         $qi = null;
         return null;
     }
+
+    function &pkeyGet($kv)
+    {
+        return Memcached_DataObject::pkeyGet('Queue_item', $kv);
+    }
 }
old mode 100755 (executable)
new mode 100644 (file)
index 5aa6d913e5949465ff3b1db84684b403f8324d33..975852dd9a5fe73db00870ee95af904259b50605 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/classes/Session.php b/classes/Session.php
new file mode 100644 (file)
index 0000000..93fd99b
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Table Definition for session
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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 Session extends Memcached_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'session';                         // table name
+    public $id;                              // varchar(32)  primary_key not_null
+    public $session_data;                    // text()
+    public $created;                         // datetime()   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Session',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    static function logdeb($msg)
+    {
+        if (common_config('sessions', 'debug')) {
+            common_debug("Session: " . $msg);
+        }
+    }
+
+    static function open($save_path, $session_name)
+    {
+        return true;
+    }
+
+    static function close()
+    {
+        return true;
+    }
+
+    static function read($id)
+    {
+        self::logdeb("Fetching session '$id'");
+
+        $session = Session::staticGet('id', $id);
+
+        if (empty($session)) {
+            return '';
+        } else {
+            return (string)$session->session_data;
+        }
+    }
+
+    static function write($id, $session_data)
+    {
+        self::logdeb("Writing session '$id'");
+
+        $session = Session::staticGet('id', $id);
+
+        if (empty($session)) {
+            $session = new Session();
+
+            $session->id           = $id;
+            $session->session_data = $session_data;
+            $session->created      = common_sql_now();
+
+            return $session->insert();
+        } else {
+            $session->session_data = $session_data;
+
+            return $session->update();
+        }
+    }
+
+    static function destroy($id)
+    {
+        self::logdeb("Deleting session $id");
+
+        $session = Session::staticGet('id', $id);
+
+        if (!empty($session)) {
+            return $session->delete();
+        }
+    }
+
+    static function gc($maxlifetime)
+    {
+        self::logdeb("garbage collection (maxlifetime = $maxlifetime)");
+
+        $epoch = time() - $maxlifetime;
+
+        $qry = 'DELETE FROM session ' .
+          'WHERE modified < "'.$epoch.'"';
+
+        $session = new Session();
+
+        $result = $session->query($qry);
+
+        self::logdeb("garbage collection result = $result");
+    }
+
+    static function setSaveHandler()
+    {
+        self::logdeb("setting save handlers");
+        $result = session_set_save_handler('Session::open', 'Session::close', 'Session::read',
+                                           'Session::write', 'Session::destroy', 'Session::gc');
+        self::logdeb("save handlers result = $result");
+        return $result;
+    }
+}
diff --git a/classes/Status_network.php b/classes/Status_network.php
new file mode 100644 (file)
index 0000000..dbd722e
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+/**
+ * Table Definition for status_network
+ *
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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 Status_network extends DB_DataObject
+{
+    ###START_AUTOCODE
+    /* the code below is auto generated do not remove the above tag */
+
+    public $__table = 'status_network';                  // table name
+    public $nickname;                        // varchar(64)  primary_key not_null
+    public $hostname;                        // varchar(255)  unique_key
+    public $pathname;                        // varchar(255)  unique_key
+    public $dbhost;                          // varchar(255)
+    public $dbuser;                          // varchar(255)
+    public $dbpass;                          // varchar(255)
+    public $dbname;                          // varchar(255)
+    public $sitename;                        // varchar(255)
+    public $theme;                           // varchar(255)
+    public $logo;                            // varchar(255)
+    public $created;                         // datetime()   not_null
+    public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
+
+    /* Static get */
+    function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('Status_network',$k,$v); }
+
+    /* the code above is auto generated do not remove the tag below */
+    ###END_AUTOCODE
+
+    static $cache = null;
+    static $base = null;
+
+    static function setupDB($dbhost, $dbuser, $dbpass, $dbname, $servers)
+    {
+        global $config;
+
+        $config['db']['database_'.$dbname] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname";
+        $config['db']['ini_'.$dbname] = INSTALLDIR.'/classes/statusnet.ini';
+        $config['db']['table_status_network'] = $dbname;
+
+        self::$cache = new Memcache();
+
+        if (is_array($servers)) {
+            foreach($servers as $server) {
+                self::$cache->addServer($server);
+            }
+        } else {
+            self::$cache->addServer($servers);
+        }
+
+        self::$base = $dbname;
+    }
+
+    static function cacheKey($k, $v) {
+        return 'laconica:' . self::$base . ':status_network:'.$k.':'.$v;
+    }
+
+    static function memGet($k, $v)
+    {
+        $ck = self::cacheKey($k, $v);
+
+        $sn = self::$cache->get($ck);
+
+        if (empty($sn)) {
+            $sn = self::staticGet($k, $v);
+            if (!empty($sn)) {
+                self::$cache->set($ck, $sn);
+            }
+        }
+
+        return $sn;
+    }
+
+    function decache()
+    {
+        $keys = array('nickname', 'hostname', 'pathname');
+        foreach ($keys as $k) {
+            $ck = self::cacheKey($k, $this->$k);
+            self::$cache->delete($ck);
+        }
+    }
+
+    function update($orig=null)
+    {
+        if (is_object($orig)) {
+            $orig->decache(); # might be different keys
+        }
+        return parent::update($orig);
+    }
+
+    function delete()
+    {
+        $this->decache(); # while we still have the values!
+        return parent::delete();
+    }
+
+    static function setupSite($servername, $pathname, $wildcard)
+    {
+        global $config;
+
+        $sn = null;
+
+        // XXX I18N, probably not crucial for hostnames
+        // XXX This probably needs a tune up
+
+        if (0 == strncasecmp(strrev($wildcard), strrev($servername), strlen($wildcard))) {
+            // special case for exact match
+            if (0 == strcasecmp($servername, $wildcard)) {
+                $sn = self::memGet('nickname', '');
+            } else {
+                $parts = explode('.', $servername);
+                $sn = self::memGet('nickname', strtolower($parts[0]));
+            }
+        } else {
+            $sn = self::memGet('hostname', strtolower($servername));
+
+            if (empty($sn)) {
+                // Try for a no-www address
+                if (0 == strncasecmp($servername, 'www.', 4)) {
+                    $sn = self::memGet('hostname', strtolower(substr($servername, 4)));
+                }
+            }
+        }
+
+        if (!empty($sn)) {
+            if (!empty($sn->hostname) && 0 != strcasecmp($sn->hostname, $servername)) {
+                $sn->redirectToHostname();
+            }
+            $dbhost = (empty($sn->dbhost)) ? 'localhost' : $sn->dbhost;
+            $dbuser = (empty($sn->dbuser)) ? $sn->nickname : $sn->dbuser;
+            $dbpass = $sn->dbpass;
+            $dbname = (empty($sn->dbname)) ? $sn->nickname : $sn->dbname;
+
+            $config['db']['database'] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname";
+
+            $config['site']['name'] = $sn->sitename;
+
+            if (!empty($sn->theme)) {
+                $config['site']['theme'] = $sn->theme;
+            }
+            if (!empty($sn->logo)) {
+                $config['site']['logo'] = $sn->logo;
+            }
+
+            return $sn;
+        } else {
+            return null;
+        }
+    }
+
+    // Code partially mooked from http://www.richler.de/en/php-redirect/
+    // (C) 2006 by Heiko Richler  http://www.richler.de/
+    // LGPL
+
+    function redirectToHostname()
+    {
+        $destination = 'http://'.$this->hostname;
+        $destination .= $_SERVER['REQUEST_URI'];
+
+        $old = 'http'.
+          (($_SERVER['HTTPS'] == 'on') ? 'S' : '').
+          '://'.
+          $_SERVER['HTTP_HOST'].
+          $_SERVER['REQUEST_URI'].
+          $_SERVER['QUERY_STRING'];
+        if ($old == $destination) { // this would be a loop!
+            // error_log(...) ?
+            return false;
+        }
+
+        header('HTTP/1.1 301 Moved Permanently');
+        header("Location: $destination");
+
+        print "<a href='$destination'>$destination</a>\n";
+
+        exit;
+    }
+}
index 3fe0d167f1c094da91866c57535764fd52307cbb..d4580fcbae053b58eb4c04bdf5d56b950c12fc93 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 59451258ec881c44e6fad7886829132afc85e114..6c1f149e4ded31fca07627ab0c16210e63ece717 100644 (file)
@@ -62,14 +62,13 @@ class User extends Memcached_DataObject
     public $autosubscribe;                   // tinyint(1)
     public $urlshorteningservice;            // varchar(50)   default_ur1.ca
     public $inboxed;                         // tinyint(1)
+    public $design_id;                       // int(4)
+    public $viewdesigns;                     // tinyint(1)   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('User',$k,$v);
-    }
+    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
@@ -406,19 +405,28 @@ class User extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
+    function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) {
+        $profile = $this->getProfile();
+        if (!$profile) {
+            return null;
+        } else {
+            return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id, $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);
+            return $profile->getNotices($offset, $limit, $since_id, $before_id, $since);
         }
     }
 
-    function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE)
+    function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE, $own=false)
     {
-        $ids = Fave::stream($this->id, $offset, $limit);
+        $ids = Fave::stream($this->id, $offset, $limit, $own);
         return Notice::getStreamByIds($ids);
     }
 
@@ -429,6 +437,33 @@ class User extends Memcached_DataObject
         // 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 ' .
+              'AND notice.is_local != ' . NOTICE_GATEWAY;
+            return Notice::getStream(sprintf($qry, $this->id),
+                                     'user:notices_with_friends:' . $this->id,
+                                     $offset, $limit, $since_id, $before_id,
+                                     $order, $since);
+        } else if ($enabled === true ||
+                   ($enabled == 'transitional' && $this->inboxed == 1)) {
+
+            $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, false);
+
+            return Notice::getStreamByIds($ids);
+        }
+    }
+
+    function noticeInbox($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 =
@@ -442,7 +477,7 @@ class User extends Memcached_DataObject
         } else if ($enabled === true ||
                    ($enabled == 'transitional' && $this->inboxed == 1)) {
 
-            $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since);
+            $ids = Notice_inbox::stream($this->id, $offset, $limit, $since_id, $before_id, $since, true);
 
             return Notice::getStreamByIds($ids);
         }
@@ -456,7 +491,11 @@ class User extends Memcached_DataObject
             // ;last cache, too
             $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id));
             $cache->delete(common_cache_key('fave:ids_by_user:'.$this->id.';last'));
+            $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id));
+            $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
         }
+        $profile = $this->getProfile();
+        $profile->blowFaveCount();
     }
 
     function getSelfTags()
@@ -565,50 +604,16 @@ class User extends Memcached_DataObject
 
     function getSubscriptions($offset=0, $limit=null)
     {
-        $qry =
-          'SELECT profile.* ' .
-          'FROM profile JOIN subscription ' .
-          'ON profile.id = subscription.subscribed ' .
-          'WHERE subscription.subscriber = %d ' .
-          'AND subscription.subscribed != subscription.subscriber ' .
-          'ORDER BY subscription.created DESC ';
-
-        if (common_config('db','type') == 'pgsql') {
-            $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-        } else {
-            $qry .= ' LIMIT ' . $offset . ', ' . $limit;
-        }
-
-        $profile = new Profile();
-
-        $profile->query(sprintf($qry, $this->id));
-
-        return $profile;
+        $profile = $this->getProfile();
+        assert(!empty($profile));
+        return $profile->getSubscriptions($offset, $limit);
     }
 
     function getSubscribers($offset=0, $limit=null)
     {
-        $qry =
-          'SELECT profile.* ' .
-          'FROM profile JOIN subscription ' .
-          'ON profile.id = subscription.subscriber ' .
-          'WHERE subscription.subscribed = %d ' .
-          'AND subscription.subscribed != subscription.subscriber ' .
-          'ORDER BY subscription.created DESC ';
-
-        if ($offset) {
-            if (common_config('db','type') == 'pgsql') {
-                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-            } else {
-                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
-            }
-        }
-
-        $profile = new Profile();
-
-        $cnt = $profile->query(sprintf($qry, $this->id));
-
-        return $profile;
+        $profile = $this->getProfile();
+        assert(!empty($profile));
+        return $profile->getSubscribers($offset, $limit);
     }
 
     function getTaggedSubscribers($tag, $offset=0, $limit=null)
@@ -675,4 +680,9 @@ class User extends Memcached_DataObject
 
         return ($cnt > 0);
     }
+
+    function getDesign()
+    {
+        return Design::staticGet('id', $this->design_id);
+    }
 }
old mode 100755 (executable)
new mode 100644 (file)
index a135015..27b4447
@@ -19,6 +19,7 @@ class User_group extends Memcached_DataObject
     public $homepage_logo;                   // varchar(255)
     public $stream_logo;                     // varchar(255)
     public $mini_logo;                       // varchar(255)
+    public $design_id;                       // int(4)
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 
@@ -125,6 +126,53 @@ class User_group extends Memcached_DataObject
         return $members;
     }
 
+    function getAdmins($offset=0, $limit=null)
+    {
+        $qry =
+          'SELECT profile.* ' .
+          'FROM profile JOIN group_member '.
+          'ON profile.id = group_member.profile_id ' .
+          'WHERE group_member.group_id = %d ' .
+          'AND group_member.is_admin = 1 ' .
+          'ORDER BY group_member.modified ASC ';
+
+        if ($limit != null) {
+            if (common_config('db','type') == 'pgsql') {
+                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+            } else {
+                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+            }
+        }
+
+        $admins = new Profile();
+
+        $admins->query(sprintf($qry, $this->id));
+        return $admins;
+    }
+
+    function getBlocked($offset=0, $limit=null)
+    {
+        $qry =
+          'SELECT profile.* ' .
+          'FROM profile JOIN group_block '.
+          'ON profile.id = group_block.blocked ' .
+          'WHERE group_block.group_id = %d ' .
+          'ORDER BY group_block.modified DESC ';
+
+        if ($limit != null) {
+            if (common_config('db','type') == 'pgsql') {
+                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
+            } else {
+                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
+            }
+        }
+
+        $blocked = new Profile();
+
+        $blocked->query(sprintf($qry, $this->id));
+        return $blocked;
+    }
+
     function setOriginal($filename)
     {
         $imagefile = new ImageFile($this->id, Avatar::path($filename));
@@ -137,4 +185,113 @@ class User_group extends Memcached_DataObject
         common_debug(common_log_objstring($this));
         return $this->update($orig);
     }
+
+    function getBestName()
+    {
+        return ($this->fullname) ? $this->fullname : $this->nickname;
+    }
+
+    function getAliases()
+    {
+        $aliases = array();
+
+        // XXX: cache this
+
+        $alias = new Group_alias();
+
+        $alias->group_id = $this->id;
+
+        if ($alias->find()) {
+            while ($alias->fetch()) {
+                $aliases[] = $alias->alias;
+            }
+        }
+
+        $alias->free();
+
+        return $aliases;
+    }
+
+    function setAliases($newaliases) {
+
+        $newaliases = array_unique($newaliases);
+
+        $oldaliases = $this->getAliases();
+
+        # Delete stuff that's old that not in new
+
+        $to_delete = array_diff($oldaliases, $newaliases);
+
+        # Insert stuff that's in new and not in old
+
+        $to_insert = array_diff($newaliases, $oldaliases);
+
+        $alias = new Group_alias();
+
+        $alias->group_id = $this->id;
+
+        foreach ($to_delete as $delalias) {
+            $alias->alias = $delalias;
+            $result = $alias->delete();
+            if (!$result) {
+                common_log_db_error($alias, 'DELETE', __FILE__);
+                return false;
+            }
+        }
+
+        foreach ($to_insert as $insalias) {
+            $alias->alias = $insalias;
+            $result = $alias->insert();
+            if (!$result) {
+                common_log_db_error($alias, 'INSERT', __FILE__);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    static function getForNickname($nickname)
+    {
+        $nickname = common_canonical_nickname($nickname);
+        $group = User_group::staticGet('nickname', $nickname);
+        if (!empty($group)) {
+            return $group;
+        }
+        $alias = Group_alias::staticGet('alias', $nickname);
+        if (!empty($alias)) {
+            return User_group::staticGet('id', $alias->group_id);
+        }
+        return null;
+    }
+
+    function getDesign()
+    {
+        return Design::staticGet('id', $this->design_id);
+    }
+
+    function getUserMembers()
+    {
+        // XXX: cache this
+
+        $user = new User();
+
+        $qry =
+          'SELECT id ' .
+          'FROM user JOIN group_member '.
+          'ON user.id = group_member.profile_id ' .
+          'WHERE group_member.group_id = %d ';
+
+        $user->query(sprintf($qry, $this->id));
+
+        $ids = array();
+
+        while ($user->fetch()) {
+            $ids[] = $user->id;
+        }
+
+        $user->free();
+
+        return $ids;
+    }
 }
old mode 100755 (executable)
new mode 100644 (file)
index c054195..766bed7
@@ -38,6 +38,19 @@ modified = 384
 [consumer__keys]
 consumer_key = K
 
+[design]
+id = 129
+backgroundcolor = 1
+contentcolor = 1
+sidebarcolor = 1
+textcolor = 1
+linkcolor = 1
+backgroundimage = 2
+disposition = 17
+
+[design__keys]
+id = N
+
 [fave]
 notice_id = 129
 user_id = 129
@@ -47,6 +60,68 @@ modified = 384
 notice_id = K
 user_id = K
 
+[file]
+id = 129
+url = 2
+mimetype = 2
+size = 1
+title = 2
+date = 1
+protected = 1
+filename = 2
+modified = 384
+
+[file__keys]
+id = N
+
+[file_oembed]
+file_id = 129
+version = 2
+type = 2
+provider = 2
+provider_url = 2
+width = 1
+height = 1
+html = 34
+title = 2
+author_name = 2
+author_url = 2
+url = 2
+modified = 384
+
+[file_oembed__keys]
+file_id = K
+
+[file_redirection]
+url = 130
+file_id = 1
+redirections = 1
+httpcode = 1
+modified = 384
+
+[file_redirection__keys]
+url = K
+
+[file_thumbnail]
+file_id = 129
+url = 2
+width = 1
+height = 1
+modified = 384
+
+[file_thumbnail__keys]
+file_id = K
+url = U
+
+[file_to_post]
+file_id = 129
+post_id = 129
+modified = 384
+
+[file_to_post__keys]
+file_id = K
+post_id = K
+
 [foreign_link]
 user_id = 129
 foreign_id = 129
@@ -100,6 +175,24 @@ id = K
 service = K
 uri = U
 
+[group_alias]
+alias = 130
+group_id = 129
+modified = 384
+
+[group_alias__keys]
+alias = K
+
+[group_block]
+group_id = 129
+blocked = 129
+blocker = 129
+modified = 384
+
+[group_block__keys]
+group_id = K
+blocked = K
+
 [group_inbox]
 group_id = 129
 notice_id = 129
@@ -170,6 +263,7 @@ modified = 384
 reply_to = 1
 is_local = 17
 source = 2
+conversation = 1
 
 [notice__keys]
 id = N
@@ -286,6 +380,15 @@ replied_id = 1
 notice_id = K
 profile_id = K
 
+[session]
+id = 130
+session_data = 34
+created = 142
+modified = 384
+
+[session__keys]
+id = K
+
 [sms_carrier]
 id = 129
 name = 2
@@ -353,6 +456,8 @@ uri = 2
 autosubscribe = 17
 urlshorteningservice = 2
 inboxed = 17
+design_id = 1
+viewdesigns = 17
 created = 142
 modified = 384
 
@@ -376,6 +481,7 @@ original_logo = 2
 homepage_logo = 2
 stream_logo = 2
 mini_logo = 2
+design_id = 1
 created = 142
 modified = 384
 
index 173b187267d433c832eb542f931564bdf95276e5..95c63f3c09cc1f1f3b565dc47f730ea9427d1ecf 100644 (file)
@@ -41,3 +41,17 @@ subscribed = profile:id
 [fave]
 notice_id = notice:id
 user_id = user:id
+
+[file_oembed]
+file_id = file:id
+
+[file_redirection]
+file_id = file:id
+
+[file_thumbnail]
+file_id = file:id
+
+[file_to_post]
+file_id = file:id
+post_id = notice:id
+
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
new file mode 100644 (file)
index 0000000..8123265
--- /dev/null
@@ -0,0 +1,18 @@
+[status_network]
+nickname = 130
+hostname = 2
+pathname = 2
+dbhost = 2
+dbuser = 2
+dbpass = 2
+dbname = 2
+sitename = 2
+theme = 2
+logo = 2
+created = 142
+modified = 384
+
+[status_network__keys]
+nickname = K
+hostname = U
+pathname = U
index e70d9ab46e843bbb6c1a2fa2c98a9ae6133aedfa..57aa6a6c8cc20b620af337cb66eb750e37969f5e 100644 (file)
 
 if (!defined('LACONICA')) { exit(1); }
 
-#If you have downloaded libraries in random little places, you
-#can add the paths here
+// 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());
+// $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
+// 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';
-#To enable the built-in mobile style sheet, defaults to false.
-#$config['site']['mobile'] = true;
-#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;
-#Make the site invisible to  non-logged-in users
-#$config['site']['private'] = true;
-
-# If you want logging sent to a file instead of syslog
-#$config['site']['logfile'] = '/tmp/laconica.log';
-
-# Enables extra log information, for example full details of PEAR DB errors
-#$config['site']['logdebug'] = true;
-
-#To set your own logo, overriding the one in the theme
-#$config['site']['logo'] = '/mylogo.png';
-
-# 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['site']['fancy'] = false;
+// $config['site']['theme'] = 'default';
+// To enable the built-in mobile style sheet, defaults to false.
+// $config['site']['mobile'] = true;
+// 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;
+// Make the site invisible to  non-logged-in users
+// $config['site']['private'] = true;
+
+// If you want logging sent to a file instead of syslog
+// $config['site']['logfile'] = '/tmp/laconica.log';
+
+// Change the syslog facility that Laconica logs to (default is LOG_USER)
+// $config['syslog']['facility'] = LOG_LOCAL7;
+
+// Enables extra log information, for example full details of PEAR DB errors
+// $config['site']['logdebug'] = true;
+
+// To set your own logo, overriding the one in the theme
+// $config['site']['logo'] = '/mylogo.png';
+
+// 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['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;
-
-#Mark certain notice sources as automatic and thus not
-#appropriate for public feed
-#$config['public]['autosource'][] = 'twitterfeed';
-#$config['public]['autosource'][] = 'rssdent';
-#$config['public]['autosource'][] = 'Ping.Fm';
-#$config['public]['autosource'][] = 'HelloTxt';
-#$config['public]['autosource'][] = 'Updating.Me';
-
-#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';
-
-# Add Google Analytics
-# require_once('plugins/GoogleAnalyticsPlugin.php');
-# $ga = new GoogleAnalyticsPlugin('your secret code');
-
-# Use Templating (template: /tpl/index.php)
-# require_once('plugins/TemplatePlugin.php');
-# $tpl = new TemplatePlugin();
-
-#Don't allow saying the same thing more than once per hour
-#$config['site']['dupelimit'] = 3600;
-#Don't enforce the dupe limit
-#$config['site']['dupelimit'] = -1;
-
-#Base string for minting Tag URIs in Atom feeds. Defaults to
-#"yourserver,2009". This needs to be configured properly for your Atom
-#feeds to validate.  See: http://www.faqs.org/rfcs/rfc4151.html and
-#http://taguri.org/ Examples:
-#$config['integration']['taguri'] = 'example.net,2008';
-#$config['integration']['taguri'] = 'admin@example.net,2009-03-09'
-
-#Don't use SSL
-#$config['site']['ssl'] = 'never';
-#Use SSL only for sensitive pages (like login, password change)
-#$config['site']['ssl'] = 'sometimes';
-#Use SSL for all pages
-#$config['site']['ssl'] = 'always';
-
-#Use a different hostname for SSL-encrypted pages
-#$config['site']['sslserver'] = 'secure.example.org';
+// 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;
+
+// Turn off invites
+// $config['invite']['enabled'] = 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;
+
+// Mark certain notice sources as automatic and thus not
+// appropriate for public feed
+// $config['public]['autosource'][] = 'twitterfeed';
+// $config['public]['autosource'][] = 'rssdent';
+// $config['public]['autosource'][] = 'Ping.Fm';
+// $config['public]['autosource'][] = 'HelloTxt';
+// $config['public]['autosource'][] = 'Updating.Me';
+
+// 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;
+
+// Queue subsystem
+// subsystems: internal (default) or stomp
+// using stomp requires an external message queue server
+// $config['queue']['subsystem'] = 'stomp';
+// $config['queue']['stomp_server'] = 'tcp://localhost:61613';
+// use different queue_basename for each laconica instance managed by the server
+// $config['queue']['queue_basename'] = 'laconica';
+
+// 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';
+
+// Enable bidirectional Twitter bridge
+//
+// NOTE: if you enable this you must also set $config['avatar']['path']
+//
+// $config['twitterbridge']['enabled'] = true;
+
+// 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';
+
+// Add Google Analytics
+// require_once('plugins/GoogleAnalyticsPlugin.php');
+// $ga = new GoogleAnalyticsPlugin('your secret code');
+
+// Use Templating (template: /tpl/index.php)
+// require_once('plugins/TemplatePlugin.php');
+// $tpl = new TemplatePlugin();
+
+// Don't allow saying the same thing more than once per hour
+// $config['site']['dupelimit'] = 3600;
+// Don't enforce the dupe limit
+// $config['site']['dupelimit'] = -1;
+
+// Base string for minting Tag URIs in Atom feeds. Defaults to
+// "yourserver,2009". This needs to be configured properly for your Atom
+// feeds to validate.  See: http://www.faqs.org/rfcs/rfc4151.html and
+// http://taguri.org/ Examples:
+// $config['integration']['taguri'] = 'example.net,2008';
+// $config['integration']['taguri'] = 'admin@example.net,2009-03-09'
+
+// Don't use SSL
+// $config['site']['ssl'] = 'never';
+// Use SSL only for sensitive pages (like login, password change)
+// $config['site']['ssl'] = 'sometimes';
+// Use SSL for all pages
+// $config['site']['ssl'] = 'always';
+
+// Use a different hostname for SSL-encrypted pages
+// $config['site']['sslserver'] = 'secure.example.org';
+
+// If you have a lot of status networks on the same server, you can
+// store the site data in a database and switch as follows
+// Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet');
+// if (!Status_network::setupSite($_server, $_path)) {
+//        print "Error\n";
+//        exit(1);
+// }
+
+// How often to send snapshots; in # of web hits. Ideally,
+// try to do this once per month (that is, make this equal to number
+// of hits per month)
+// $config['snapshot']['frequency'] = 10000;
+// If you don't want to report statistics to the central server, uncomment.
+// $config['snapshot']['run'] = 'never';
+// If you want to report statistics in a cron job instead.
+// $config['snapshot']['run'] = 'cron';
+
+// Support for file uploads (attachments),
+// select supported mimetypes and quotas (in bytes)
+// $config['attachments']['supported'] = array('image/png', 'application/ogg');
+// $config['attachments']['file_quota'] = 5000000;
+// $config['attachments']['user_quota'] = 50000000;
+// $config['attachments']['monthly_quota'] = 15000000;
+// $config['attachments']['uploads'] = true;
+
+// $config['oohembed']['endpoint'] = 'http://oohembed.com/oohembed/';
diff --git a/db/074to080.sql b/db/074to080.sql
new file mode 100644 (file)
index 0000000..ff08191
--- /dev/null
@@ -0,0 +1,109 @@
+alter table user
+     add column design_id integer comment 'id of a design' references design(id),
+     add column viewdesigns tinyint default 1 comment 'whether to view user-provided designs';
+
+alter table notice add column
+     conversation integer comment 'id of root notice in this conversation' references notice (id),
+     add index notice_conversation_idx (conversation);
+
+alter table foreign_user
+     modify column id bigint not null comment 'unique numeric key on foreign service';
+
+alter table foreign_link
+     modify column foreign_id bigint unsigned comment 'link to user on foreign service, if exists';
+
+alter table user_group
+      add column design_id integer comment 'id of a design' references design(id);
+
+create table file (
+    id integer primary key auto_increment,
+    url varchar(255) comment 'destination URL after following redirections',
+    mimetype varchar(50) comment 'mime type of resource',
+    size integer comment 'size of resource when available',
+    title varchar(255) comment 'title of resource when available',
+    date integer(11) comment 'date of resource according to http query',
+    protected integer(1) comment 'true when URL is private (needs login)',
+    filename varchar(255) comment 'if a local file, name of the file',
+    modified timestamp comment 'date this record was modified',
+
+    unique(url)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+create table file_oembed (
+    file_id integer primary key comment 'oEmbed for that URL/file' references file (id),
+    version varchar(20) comment 'oEmbed spec. version',
+    type varchar(20) comment 'oEmbed type: photo, video, link, rich',
+    provider varchar(50) comment 'name of this oEmbed provider',
+    provider_url varchar(255) comment 'URL of this oEmbed provider',
+    width integer comment 'width of oEmbed resource when available',
+    height integer comment 'height of oEmbed resource when available',
+    html text comment 'html representation of this oEmbed resource when applicable',
+    title varchar(255) comment 'title of oEmbed resource when available',
+    author_name varchar(50) comment 'author name for this oEmbed resource',
+    author_url varchar(255) comment 'author URL for this oEmbed resource',
+    url varchar(255) comment 'URL for this oEmbed resource when applicable (photo, link)',
+    modified timestamp comment 'date this record was modified'
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+create table file_redirection (
+
+    url varchar(255) primary key comment 'short URL (or any other kind of redirect) for file (id)',
+    file_id integer comment 'short URL for what URL/file' references file (id),
+    redirections integer comment 'redirect count',
+    httpcode integer comment 'HTTP status code (20x, 30x, etc.)',
+    modified timestamp comment 'date this record was modified'
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table file_thumbnail (
+
+    file_id integer primary key comment 'thumbnail for what URL/file' references file (id),
+    url varchar(255) comment 'URL of thumbnail',
+    width integer comment 'width of thumbnail',
+    height integer comment 'height of thumbnail',
+    modified timestamp comment 'date this record was modified',
+
+    unique(url)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table file_to_post (
+
+    file_id integer comment 'id of URL/file' references file (id),
+    post_id integer comment 'id of the notice it belongs to' references notice (id),
+    modified timestamp comment 'date this record was modified',
+
+    constraint primary key (file_id, post_id)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table design (
+    id integer primary key auto_increment comment 'design ID',
+    backgroundcolor integer comment 'main background color',
+    contentcolor integer comment 'content area background color',
+    sidebarcolor integer comment 'sidebar background color',
+    textcolor integer comment 'text color',
+    linkcolor integer comment 'link color',
+    backgroundimage varchar(255) comment 'background image, if any',
+    disposition tinyint default 1 comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table group_block (
+   group_id integer not null comment 'group profile is blocked from' references user_group (id),
+   blocked integer not null comment 'profile that is blocked' references profile (id),
+   blocker integer not null comment 'user making the block' references user (id),
+   modified timestamp comment 'date of blocking',
+
+   constraint primary key (group_id, blocked)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table group_alias (
+
+   alias varchar(64) primary key comment 'additional nickname for the group',
+   group_id integer not null comment 'group profile is blocked from' references user_group (id),
+   modified timestamp comment 'date alias was created',
+
+   index group_alias_group_id_idx (group_id)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
index 557ede0246781024e509146d68353c55f5f02796..79c04cee56edc7e5d8b54ac823bb4d28b9ad9f33 100644 (file)
@@ -2,4 +2,5 @@ insert into foreign_service
     (id, name, description, created)
 values
     ('1','Twitter', 'Twitter Micro-blogging service', now()),
-    ('2','Facebook', 'Facebook', now());
+    ('2','Facebook', 'Facebook', now()),
+    ('3','FacebookConnect', 'Facebook Connect', now());
diff --git a/db/innodb.sql b/db/innodb.sql
new file mode 100644 (file)
index 0000000..f3ab6cd
--- /dev/null
@@ -0,0 +1,2 @@
+alter table profile drop index nickname, engine=InnoDB;
+alter table notice drop index content, engine=InnoDB;
index c9730098e3fd2934d805606f67c90c780acdbf0d..2c04f680a85d587a032cd06fada5c63c8488eaa5 100644 (file)
@@ -41,6 +41,7 @@ create table sms_carrier (
 /* 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',
@@ -69,6 +70,9 @@ create table user (
     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?',
+    design_id integer comment 'id of a design' references design(id),
+    viewdesigns tinyint default 1 comment 'whether to view user-provided designs',
+
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
 
@@ -114,8 +118,10 @@ create table notice (
     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"',
+    conversation integer comment 'id of root notice in this conversation' references notice (id),
 
     index notice_profile_id_idx (profile_id),
+    index notice_conversation_idx (conversation),
     index notice_created_idx (created),
     index notice_replyto_idx (reply_to),
     FULLTEXT(content)
@@ -271,7 +277,7 @@ create table foreign_service (
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 create table foreign_user (
-     id int not null comment 'unique numeric key on foreign service',
+     id bigint 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',
@@ -283,7 +289,7 @@ create table foreign_user (
 
 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),
+     foreign_id bigint unsigned comment 'link to user on foreign service, if exists' 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',
@@ -381,6 +387,7 @@ create table user_group (
     homepage_logo varchar(255) comment 'homepage (profile) size logo',
     stream_logo varchar(255) comment 'stream-sized logo',
     mini_logo varchar(255) comment 'mini logo',
+    design_id integer comment 'id of a design' references design(id),
 
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
@@ -423,3 +430,108 @@ create table group_inbox (
 
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
+create table file (
+
+    id integer primary key auto_increment,
+    url varchar(255) comment 'destination URL after following redirections',
+    mimetype varchar(50) comment 'mime type of resource',
+    size integer comment 'size of resource when available',
+    title varchar(255) comment 'title of resource when available',
+    date integer(11) comment 'date of resource according to http query',
+    protected integer(1) comment 'true when URL is private (needs login)',
+    filename varchar(255) comment 'if a local file, name of the file',
+
+    modified timestamp comment 'date this record was modified',
+
+    unique(url)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+create table file_oembed (
+    file_id integer primary key comment 'oEmbed for that URL/file' references file (id),
+    version varchar(20) comment 'oEmbed spec. version',
+    type varchar(20) comment 'oEmbed type: photo, video, link, rich',
+    provider varchar(50) comment 'name of this oEmbed provider',
+    provider_url varchar(255) comment 'URL of this oEmbed provider',
+    width integer comment 'width of oEmbed resource when available',
+    height integer comment 'height of oEmbed resource when available',
+    html text comment 'html representation of this oEmbed resource when applicable',
+    title varchar(255) comment 'title of oEmbed resource when available',
+    author_name varchar(50) comment 'author name for this oEmbed resource',
+    author_url varchar(255) comment 'author URL for this oEmbed resource',
+    url varchar(255) comment 'URL for this oEmbed resource when applicable (photo, link)',
+    modified timestamp comment 'date this record was modified'
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+create table file_redirection (
+
+    url varchar(255) primary key comment 'short URL (or any other kind of redirect) for file (id)',
+    file_id integer comment 'short URL for what URL/file' references file (id),
+    redirections integer comment 'redirect count',
+    httpcode integer comment 'HTTP status code (20x, 30x, etc.)',
+    modified timestamp comment 'date this record was modified'
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table file_thumbnail (
+
+    file_id integer primary key comment 'thumbnail for what URL/file' references file (id),
+    url varchar(255) comment 'URL of thumbnail',
+    width integer comment 'width of thumbnail',
+    height integer comment 'height of thumbnail',
+    modified timestamp comment 'date this record was modified',
+
+    unique(url)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table file_to_post (
+
+    file_id integer comment 'id of URL/file' references file (id),
+    post_id integer comment 'id of the notice it belongs to' references notice (id),
+    modified timestamp comment 'date this record was modified',
+
+    constraint primary key (file_id, post_id)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table design (
+    id integer primary key auto_increment comment 'design ID',
+    backgroundcolor integer comment 'main background color',
+    contentcolor integer comment 'content area background color',
+    sidebarcolor integer comment 'sidebar background color',
+    textcolor integer comment 'text color',
+    linkcolor integer comment 'link color',
+    backgroundimage varchar(255) comment 'background image, if any',
+    disposition tinyint default 1 comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table group_block (
+   group_id integer not null comment 'group profile is blocked from' references user_group (id),
+   blocked integer not null comment 'profile that is blocked' references profile (id),
+   blocker integer not null comment 'user making the block' references user (id),
+   modified timestamp comment 'date of blocking',
+
+   constraint primary key (group_id, blocked)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table group_alias (
+
+   alias varchar(64) primary key comment 'additional nickname for the group',
+   group_id integer not null comment 'group profile is blocked from' references user_group (id),
+   modified timestamp comment 'date alias was created',
+
+   index group_alias_group_id_idx (group_id)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table session (
+
+    id varchar(32) primary key comment 'session ID',
+    session_data text comment 'session data',
+    created datetime not null comment 'date this record was created',
+    modified timestamp comment 'date this record was modified',
+
+    index session_modified_idx (modified)
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
\ No newline at end of file
index a27a616f2490875bc9a9573b97baaa0668548af4..dae8b8fafe77b6e21e3d259e834d105a0b07e989 100644 (file)
@@ -116,7 +116,9 @@ create table notice (
     modified timestamp /* comment 'date this record was modified' */,\r
     reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) ,\r
     is_local integer default 0 /* comment 'notice was generated by a user' */,\r
-    source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */\r
+    source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */,\r
+    conversation integer /*id of root notice in this conversation' */ references notice (id)\r
+\r
 \r
 /*    FULLTEXT(content) */\r
 );\r
@@ -172,7 +174,7 @@ create table token (
     tok char(32) not null /* comment 'identifying value' */,\r
     secret char(32) not null /* comment 'secret value' */,\r
     type integer not null default 0 /* comment 'request or access' */,\r
-    state integer default 0 /* comment 'for requests; 0 = initial, 1 = authorized, 2 = used' */,\r
+    state integer default 0 /* comment 'for requests 0 = initial, 1 = authorized, 2 = used' */,\r
 \r
     created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,\r
     modified timestamp /* comment 'date this record was modified' */,\r
@@ -346,7 +348,7 @@ create table notice_inbox (
     user_id integer not null /* comment 'user receiving the message' */ references "user" (id),\r
     notice_id integer not null /* comment 'notice received' */ references notice (id),\r
     created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */,\r
-    source integer default 1 /* comment 'reason it is in the inbox; 1=subscription' */,\r
+    source integer default 1 /* comment 'reason it is in the inbox: 1=subscription' */,\r
 \r
     primary key (user_id, notice_id)\r
 );\r
@@ -427,6 +429,76 @@ create table group_inbox (
 );\r
 create index group_inbox_created_idx on group_inbox using btree(created);\r
 \r
+\r
+/*attachments and URLs stuff */\r
+create sequence file_seq;\r
+create table file (\r
+    id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */,\r
+    url varchar(255) unique, \r
+    mimetype varchar(50), \r
+    size integer, \r
+    title varchar(255), \r
+    date integer, \r
+    protected integer\r
+);\r
+\r
+create sequence file_oembed_seq;\r
+create table file_oembed (\r
+    id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,\r
+    file_id bigint unique,\r
+    version varchar(20),\r
+    type varchar(20),\r
+    provider varchar(50),\r
+    provider_url varchar(255),\r
+    width integer,\r
+    height integer,\r
+    html text,\r
+    title varchar(255),\r
+    author_name varchar(50), \r
+    author_url varchar(255), \r
+    url varchar(255) \r
+);\r
+\r
+create sequence file_redirection_seq;\r
+create table file_redirection (\r
+    id bigint default nextval('file_redirection_seq') primary key /* comment 'unique identifier' */,\r
+    url varchar(255) unique, \r
+    file_id bigint, \r
+    redirections integer, \r
+    httpcode integer\r
+);\r
+\r
+create sequence file_thumbnail_seq;\r
+create table file_thumbnail (\r
+    id bigint default nextval('file_thumbnail_seq') primary key /* comment 'unique identifier' */,\r
+    file_id bigint unique, \r
+    url varchar(255) unique, \r
+    width integer, \r
+    height integer \r
+);\r
+\r
+create sequence file_to_post_seq;\r
+create table file_to_post (\r
+    id bigint default nextval('file_to_post_seq') primary key /* comment 'unique identifier' */,\r
+    file_id bigint, \r
+    post_id bigint, \r
+\r
+    unique(file_id, post_id)\r
+);\r
+\r
+create sequence design_seq;\r
+create table design (\r
+    id bigint default nextval('design_seq') /* comment 'design ID'*/,\r
+    backgroundcolor integer /* comment 'main background color'*/ ,\r
+    contentcolor integer /*comment 'content area background color'*/ ,\r
+    sidebarcolor integer /*comment 'sidebar background color'*/ ,\r
+    textcolor integer /*comment 'text color'*/ ,\r
+    linkcolor integer /*comment 'link color'*/,\r
+    backgroundimage varchar(255) /*comment 'background image, if any'*/,\r
+    disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/,\r
+    primary key (id)\r
+);\r
+\r
 /* Textsearch stuff */\r
 \r
 create index textsearch_idx on profile using gist(textsearch);\r
index 4c3dc2113d2392131a87dec7544a86fac71a53a2..983ea915023ded6aeaae99a9049cfde4ec29b05e 100644 (file)
@@ -51,6 +51,7 @@ VALUES
     ('twidge','Twidge','http://software.complete.org/twidge', now()),
     ('twidroid','twidroid','http://www.twidroid.com/', now()),
     ('twittelator','Twittelator','http://www.stone.com/iPhone/Twittelator/', now()),
+    ('twitter','Twitter','http://twitter.com/', now()),
     ('twitterfeed','twitterfeed','http://twitterfeed.com/', now()),
     ('twitterphoto','TwitterPhoto','http://richfish.org/twitterphoto/', now()),
     ('twitterpm','Net::Twitter','http://search.cpan.org/dist/Net-Twitter/', now()),
diff --git a/db/site.sql b/db/site.sql
new file mode 100644 (file)
index 0000000..a9f64e5
--- /dev/null
@@ -0,0 +1,21 @@
+/* For managing multiple sites */
+
+create table status_network (
+
+    nickname varchar(64) primary key comment 'nickname',
+    hostname varchar(255) unique key comment 'alternate hostname if any',
+    pathname varchar(255) unique key comment 'alternate pathname if any',
+
+    dbhost varchar(255) comment 'database host',
+    dbuser varchar(255) comment 'database username',
+    dbpass varchar(255) comment 'database password',
+    dbname varchar(255) comment 'database name',
+
+    sitename varchar(255) comment 'display name',
+    theme varchar(255) comment 'theme name',
+    logo varchar(255) comment 'site logo',
+
+    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_general_ci;
diff --git a/doc-src/tos b/doc-src/tos
new file mode 100644 (file)
index 0000000..bcfc319
--- /dev/null
@@ -0,0 +1,300 @@
+The gist
+--------
+
+We (the folks at [%%site.broughtby%%](%%site.broughtbyurl%%)) run a
+service called %%site.name%% and would love for you to use it. Our
+service is designed to give you as much control and ownership over
+what goes in your notice stream as possible and encourage you to
+express yourself freely. However, be responsible in what you post. In
+particular, make sure that none of the prohibited items listed below
+appear in your notice stream or get linked to from your notice stream (things
+like spam, viruses, or hate content).
+
+You can review our [Public Stream](%%action.public%%) to get a sense
+of the types of notices that are welcome on our service (or not!). If
+you find a %%site.name%% account that you believe violates our terms
+of service, please check our [Contact](%%doc.contact%%) documentation.
+
+(Note: Automattic, Inc., original creators of the below Terms of
+Service, decided to make them available under a Creative Commons
+Sharealike license, which means you’re more than welcome to steal it
+and repurpose it for your own use. Just make sure to replace
+references to us with ones to you. They’d appreciate a link to
+[WordPress.com](http://www.wordpress.com/) somewhere on your site.
+They spent a lot of money and time on the below, and other people
+shouldn’t need to do the same. (We didn't!))
+
+Terms of Service
+----------------
+
+The following terms and conditions govern all use of the %%site.name%%
+website and all content, services and products available at or through
+the website (taken together, the Website). The Website is owned and
+operated by %%site.broughtby%% (“Operator”). The Website is offered
+subject to your acceptance without modification of all of the terms
+and conditions contained herein and all other operating rules,
+policies (including, without limitation, Operator’s [Privacy
+Policy](%%doc.privacy%%))
+and procedures that may be published from time to time on this Site by
+Operator (collectively, the “Agreement”).
+
+Please read this Agreement carefully before accessing or using the
+Website. By accessing or using any part of the web site, you agree to
+become bound by the terms and conditions of this agreement. If you do
+not agree to all the terms and conditions of this agreement, then you
+may not access the Website or use any services. If these terms and
+conditions are considered an offer by Operator, acceptance is
+expressly limited to these terms. The Website is available only to
+individuals who are at least 13 years old.
+
+<ol>
+
+<li><strong>Your %%site.name%% Account and Site.</strong> If you
+create a notice stream on the Website, you are responsible for
+maintaining the security of your account and notice stream, and you
+are fully responsible for all activities that occur under the account
+and any other actions taken in connection with the notice stream. You
+must not describe or assign keywords to your notice stream in a
+misleading or unlawful manner, including in a manner intended to trade
+on the name or reputation of others, and Operator may change or remove
+any description or keyword that it considers inappropriate or
+unlawful, or otherwise likely to cause Operator liability. You must
+immediately notify Operator of any unauthorized uses of your notice
+stream, your account or any other breaches of security. Operator will
+not be liable for any acts or omissions by You, including any damages
+of any kind incurred as a result of such acts or omissions.</li>
+
+<li><strong>Responsibility of Contributors.</strong> If you operate a
+notice stream, comment on a notice stream, post material to the
+Website, post links on the Website, or otherwise make (or allow any
+third party to make) material available by means of the Website (any
+such material, “Content”), You are entirely responsible for the
+content of, and any harm resulting from, that Content. That is the
+case regardless of whether the Content in question constitutes text,
+graphics, an audio file, or computer software. By making Content
+available, you represent and warrant that:
+
+<ul>
+
+<li>the downloading, copying and use of the Content will not infringe
+the proprietary rights, including but not limited to the copyright,
+patent, trademark or trade secret rights, of any third party;</li>
+
+<li>if your employer has rights to intellectual property you create,
+you have either (i) received permission from your employer to post or
+make available the Content, including but not limited to any software,
+or (ii) secured from your employer a waiver as to all rights in or to
+the Content;</li>
+
+<li>you have fully complied with any third-party licenses
+relating to the Content, and have done all things necessary to
+successfully pass through to end users any required terms;</li>
+
+<li>the Content does not contain or install any viruses, worms, malware,
+Trojan horses or other harmful or destructive content;</li>
+
+<li>the Content is not spam, and does not contain unethical or
+unwanted commercial content designed to drive traffic to third party
+sites or boost the search engine rankings of third party sites, or to
+further unlawful acts (such as phishing) or mislead recipients as to
+the source of the material (such as spoofing);</li>
+
+<li>if the Content is machine- or randomly-generated, it is for
+purposes of direct entertainment, information and/or utility for you
+or other users, and not for spam,</li>
+
+<li>the Content is not libelous or defamatory (more info on
+what that means), does not contain threats or incite violence towards
+individuals or entities, and does not violate the privacy or publicity
+rights of any third party;</li>
+
+<li>your notice stream is not getting advertised via unwanted electronic
+messages such as spam links on newsgroups, email lists, other notice streams
+and web sites, and similar unsolicited promotional methods;</li>
+
+<li>your notice stream is not named in a manner that misleads your
+readers into thinking that you are another person or company. For
+example, your notice stream’s URL or name is not the name of a person other
+than yourself or company other than your own; and</li>
+
+<li>you have, in the case of Content that includes computer code,
+accurately categorized and/or described the type, nature, uses and
+effects of the materials, whether requested to do so by Operator or
+otherwise.</li>
+
+</ul>
+
+<p>By submitting Content to Operator for inclusion on your Website, you
+grant Operator a world-wide, royalty-free, and non-exclusive license
+to reproduce, modify, adapt and publish the Content solely for the
+purpose of displaying, distributing and promoting your notice
+stream.</p>
+
+<p>By submitting Content to Operator for inclusion on your Website,
+you grant all readers the right to use, re-use, modify and/or
+re-distribute the Content under the terms of the <a
+href="%%license.url%%">%%license.title%%</a>.</p>
+
+<p>If you delete Content, Operator will use reasonable efforts to remove it from
+the Website, but you acknowledge that caching or references to the
+Content may not be made immediately unavailable.</p>
+
+<p>Without limiting any of those representations or warranties, Operator
+has the right (though not the obligation) to, in Operator’s sole
+discretion (i) refuse or remove any content that, in Operator’s
+reasonable opinion, violates any Operator policy or is in any way
+harmful or objectionable, or (ii) terminate or deny access to and use
+of the Website to any individual or entity for any reason, in
+Operator’s sole discretion.</p>
+</li>
+
+<li><strong>Responsibility of Website Visitors.</strong> Operator has not reviewed,
+and cannot review, all of the material, including computer software,
+posted to the Website, and cannot therefore be responsible for that
+material’s content, use or effects. By operating the Website,
+Operator does not represent or imply that it endorses the material
+there posted, or that it believes such material to be accurate, useful
+or non-harmful. You are responsible for taking precautions as
+necessary to protect yourself and your computer systems from viruses,
+worms, Trojan horses, and other harmful or destructive content. The
+Website may contain content that is offensive, indecent, or otherwise
+objectionable, as well as content containing technical inaccuracies,
+typographical mistakes, and other errors. The Website may also contain
+material that violates the privacy or publicity rights, or infringes
+the intellectual property and other proprietary rights, of third
+parties, or the downloading, copying or use of which is subject to
+additional terms and conditions, stated or unstated. Operator
+disclaims any responsibility for any harm resulting from the use by
+visitors of the Website, or from any downloading by those visitors of
+content there posted.</li>
+
+<li><strong>Content Posted on Other Websites.</strong> We have not reviewed, and
+cannot review, all of the material, including computer software, made
+available through the websites and webpages to which %%site.name%%
+links, and that link to %%site.name%%. Operator does not have any
+control over those external websites and webpages, and is not
+responsible for their contents or their use. By linking to a
+external website or webpage, Operator does not represent or
+imply that it endorses such website or webpage. You are responsible
+for taking precautions as necessary to protect yourself and your
+computer systems from viruses, worms, Trojan horses, and other harmful
+or destructive content. Operator disclaims any responsibility for
+any harm resulting from your use of external websites and
+webpages.</li>
+
+<li><strong>Copyright Infringement and DMCA Policy.</strong> As Operator asks
+others to respect its intellectual property rights, it respects the
+intellectual property rights of others. If you believe that material
+located on or linked to by %%site.name%% violates your copyright, you
+are encouraged to notify Operator in accordance with Operator’s
+Digital Millennium Copyright Act (”DMCA”) Policy. Operator will
+respond to all such notices, including as required or appropriate by
+removing the infringing material or disabling all links to the
+infringing material. In the case of a visitor who may infringe or
+repeatedly infringes the copyrights or other intellectual property
+rights of Operator or others, Operator may, in its discretion,
+terminate or deny access to and use of the Website. In the case of
+such termination, Operator will have no obligation to provide a
+refund of any amounts previously paid to Operator.</li>
+
+<li><strong>Intellectual Property.</strong> This Agreement does not
+transfer from Operator to you any Operator or third party intellectual
+property, and all right, title and interest in and to such property
+will remain (as between the parties) solely with Operator.
+%%site.name%%, the %%site.name%% logo, and all other trademarks,
+service marks, graphics and logos used in connection with
+%%site.name%%, or the Website are trademarks or registered trademarks
+of Operator or Operator’s licensors. Other trademarks, service marks,
+graphics and logos used in connection with the Website may be the
+trademarks of other third parties. Your use of the Website grants you
+no right or license to reproduce or otherwise use any Operator or
+third-party trademarks.</li>
+
+<li><strong>Changes.</strong> Operator reserves the right, at its sole
+discretion, to modify or replace any part of this Agreement. It is
+your responsibility to check this Agreement periodically for changes.
+Your continued use of or access to the Website following the posting
+of any changes to this Agreement constitutes acceptance of those
+changes. Operator may also, in the future, offer new services and/or
+features through the Website (including, the release of new tools and
+resources). Such new features and/or services shall be subject to the
+terms and conditions of this Agreement.</li>
+
+<li><strong>Termination.</strong> Operator may terminate your access
+to all or any part of the Website at any time, with or without cause,
+with or without notice, effective immediately. If you wish to
+terminate this Agreement or your %%site.name%% account (if you have
+one), you may simply discontinue using the Website. All provisions of
+this Agreement which by their nature should survive termination shall
+survive termination, including, without limitation, ownership
+provisions, warranty disclaimers, indemnity and limitations of
+liability.</li>
+
+<li><strong>Disclaimer of Warranties.</strong> The Website is provided
+“as is”. Operator and its suppliers and licensors hereby disclaim all
+warranties of any kind, express or implied, including, without
+limitation, the warranties of merchantability, fitness for a
+particular purpose and non-infringement. Neither Operator nor its
+suppliers and licensors, makes any warranty that the Website will be
+error free or that access thereto will be continuous or uninterrupted.
+If you’re actually reading this, here’s a treat. You understand that
+you download from, or otherwise obtain content or services through,
+the Website at your own discretion and risk.</li>
+
+<li><strong>Limitation of Liability.</strong> In no event will
+Operator, or its suppliers or licensors, be liable with respect to any
+subject matter of this agreement under any contract, negligence,
+strict liability or other legal or equitable theory for: (i) any
+special, incidental or consequential damages; (ii) the cost of
+procurement or substitute products or services; (iii) for interruption
+of use or loss or corruption of data; or (iv) for any amounts that
+exceed the fees paid by you to Operator under this agreement during
+the twelve (12) month period prior to the cause of action. Operator
+shall have no liability for any failure or delay due to matters beyond
+their reasonable control. The foregoing shall not apply to the extent
+prohibited by applicable law.</li>
+
+<li><strong>General Representation and Warranty.</strong> You
+represent and warrant that (i) your use of the Website will be in
+strict accordance with the Operator Privacy Policy, with this
+Agreement and with all applicable laws and regulations (including
+without limitation any local laws or regulations in your country,
+state, city, or other governmental area, regarding online conduct and
+acceptable content, and including all applicable laws regarding the
+transmission of technical data exported from the United States or the
+country in which you reside) and (ii) your use of the Website will not
+infringe or misappropriate the intellectual property rights of any
+third party.</li>
+
+<li><strong>Indemnification.</strong> You agree to indemnify and hold
+harmless Operator, its contractors, and its licensors, and their
+respective directors, officers, employees and agents from and against
+any and all claims and expenses, including attorneys’ fees, arising
+out of your use of the Website, including but not limited to out of
+your violation this Agreement.</li>
+
+<li><strong>Miscellaneous.</strong> This Agreement constitutes the
+entire agreement between Operator and you concerning the subject
+matter hereof, and they may only be modified by a written amendment
+signed by an authorized executive of Operator, or by the posting by
+Operator of a revised version. If any part of this Agreement is held
+invalid or unenforceable, that part will be construed to reflect the
+parties’ original intent, and the remaining portions will remain in
+full force and effect. A waiver by either party of any term or
+condition of this Agreement or any breach thereof, in any one
+instance, will not waive such term or condition or any subsequent
+breach thereof. You may assign your rights under this Agreement to any
+party that consents to, and agrees to be bound by, its terms and
+conditions; Operator may assign its rights under this Agreement
+without condition. This Agreement will be binding upon and will inure
+to the benefit of the parties, their successors and permitted
+assigns.</li> </ol>
+
+*Originally published by Automattic, Inc. as the [WordPress.com Terms
+of Service](http://en.wordpress.com/tos/) and made available by them
+under the [Creative Commons Attribution-ShareAlike 3.0
+License](http://creativecommons.org/licenses/by-sa/3.0/).
+Modifications to remove reference to "VIP services", rename "blog" to
+"notice stream", remove the choice-of-venue clause, and add variables
+specific to instances of this software made by Control Yourself, Inc.
+and made available under the terms of the same license.*
\ No newline at end of file
diff --git a/extlib/Console/Getopt.php b/extlib/Console/Getopt.php
new file mode 100644 (file)
index 0000000..bb9d69c
--- /dev/null
@@ -0,0 +1,290 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 5                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2004 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available through the world-wide-web at the following url:           |
+// | 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 world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Author: Andrei Zmievski <andrei@php.net>                             |
+// +----------------------------------------------------------------------+
+//
+// $Id: Getopt.php,v 1.4 2007/06/12 14:58:56 cellog Exp $
+
+require_once 'PEAR.php';
+
+/**
+ * Command-line options parsing class.
+ *
+ * @author Andrei Zmievski <andrei@php.net>
+ *
+ */
+class Console_Getopt {
+    /**
+     * Parses the command-line options.
+     *
+     * The first parameter to this function should be the list of command-line
+     * arguments without the leading reference to the running program.
+     *
+     * The second parameter is a string of allowed short options. Each of the
+     * option letters can be followed by a colon ':' to specify that the option
+     * requires an argument, or a double colon '::' to specify that the option
+     * takes an optional argument.
+     *
+     * The third argument is an optional array of allowed long options. The
+     * leading '--' should not be included in the option name. Options that
+     * require an argument should be followed by '=', and options that take an
+     * option argument should be followed by '=='.
+     *
+     * The return value is an array of two elements: the list of parsed
+     * options and the list of non-option command-line arguments. Each entry in
+     * the list of parsed options is a pair of elements - the first one
+     * specifies the option, and the second one specifies the option argument,
+     * if there was one.
+     *
+     * Long and short options can be mixed.
+     *
+     * Most of the semantics of this function are based on GNU getopt_long().
+     *
+     * @param array  $args           an array of command-line arguments
+     * @param string $short_options  specifies the list of allowed short options
+     * @param array  $long_options   specifies the list of allowed long options
+     *
+     * @return array two-element array containing the list of parsed options and
+     * the non-option arguments
+     *
+     * @access public
+     *
+     */
+    function getopt2($args, $short_options, $long_options = null)
+    {
+        return Console_Getopt::doGetopt(2, $args, $short_options, $long_options);
+    }
+
+    /**
+     * This function expects $args to start with the script name (POSIX-style).
+     * Preserved for backwards compatibility.
+     * @see getopt2()
+     */    
+    function getopt($args, $short_options, $long_options = null)
+    {
+        return Console_Getopt::doGetopt(1, $args, $short_options, $long_options);
+    }
+
+    /**
+     * The actual implementation of the argument parsing code.
+     */
+    function doGetopt($version, $args, $short_options, $long_options = null)
+    {
+        // in case you pass directly readPHPArgv() as the first arg
+        if (PEAR::isError($args)) {
+            return $args;
+        }
+        if (empty($args)) {
+            return array(array(), array());
+        }
+        $opts     = array();
+        $non_opts = array();
+
+        settype($args, 'array');
+
+        if ($long_options) {
+            sort($long_options);
+        }
+
+        /*
+         * Preserve backwards compatibility with callers that relied on
+         * erroneous POSIX fix.
+         */
+        if ($version < 2) {
+            if (isset($args[0]{0}) && $args[0]{0} != '-') {
+                array_shift($args);
+            }
+        }
+
+        reset($args);
+        while (list($i, $arg) = each($args)) {
+
+            /* The special element '--' means explicit end of
+               options. Treat the rest of the arguments as non-options
+               and end the loop. */
+            if ($arg == '--') {
+                $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
+                break;
+            }
+
+            if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
+                $non_opts = array_merge($non_opts, array_slice($args, $i));
+                break;
+            } elseif (strlen($arg) > 1 && $arg{1} == '-') {
+                $error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
+                if (PEAR::isError($error))
+                    return $error;
+            } elseif ($arg == '-') {
+                // - is stdin
+                $non_opts = array_merge($non_opts, array_slice($args, $i));
+                break;
+            } else {
+                $error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
+                if (PEAR::isError($error))
+                    return $error;
+            }
+        }
+
+        return array($opts, $non_opts);
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _parseShortOption($arg, $short_options, &$opts, &$args)
+    {
+        for ($i = 0; $i < strlen($arg); $i++) {
+            $opt = $arg{$i};
+            $opt_arg = null;
+
+            /* Try to find the short option in the specifier string. */
+            if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
+            {
+                return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt");
+            }
+
+            if (strlen($spec) > 1 && $spec{1} == ':') {
+                if (strlen($spec) > 2 && $spec{2} == ':') {
+                    if ($i + 1 < strlen($arg)) {
+                        /* Option takes an optional argument. Use the remainder of
+                           the arg string if there is anything left. */
+                        $opts[] = array($opt, substr($arg, $i + 1));
+                        break;
+                    }
+                } else {
+                    /* Option requires an argument. Use the remainder of the arg
+                       string if there is anything left. */
+                    if ($i + 1 < strlen($arg)) {
+                        $opts[] = array($opt,  substr($arg, $i + 1));
+                        break;
+                    } else if (list(, $opt_arg) = each($args)) {
+                        /* Else use the next argument. */;
+                        if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
+                            return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt");
+                        }
+                    } else {
+                        return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt");
+                    }
+                }
+            }
+
+            $opts[] = array($opt, $opt_arg);
+        }
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _isShortOpt($arg)
+    {
+        return strlen($arg) == 2 && $arg[0] == '-' && preg_match('/[a-zA-Z]/', $arg[1]);
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _isLongOpt($arg)
+    {
+        return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
+            preg_match('/[a-zA-Z]+$/', substr($arg, 2));
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _parseLongOption($arg, $long_options, &$opts, &$args)
+    {
+        @list($opt, $opt_arg) = explode('=', $arg, 2);
+        $opt_len = strlen($opt);
+
+        for ($i = 0; $i < count($long_options); $i++) {
+            $long_opt  = $long_options[$i];
+            $opt_start = substr($long_opt, 0, $opt_len);
+            $long_opt_name = str_replace('=', '', $long_opt);
+
+            /* Option doesn't match. Go on to the next one. */
+            if ($long_opt_name != $opt) {
+                continue;
+            }
+
+            $opt_rest  = substr($long_opt, $opt_len);
+
+            /* Check that the options uniquely matches one of the allowed
+               options. */
+            if ($i + 1 < count($long_options)) {
+                $next_option_rest = substr($long_options[$i + 1], $opt_len);
+            } else {
+                $next_option_rest = '';
+            }
+            if ($opt_rest != '' && $opt{0} != '=' &&
+                $i + 1 < count($long_options) &&
+                $opt == substr($long_options[$i+1], 0, $opt_len) &&
+                $next_option_rest != '' &&
+                $next_option_rest{0} != '=') {
+                return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous");
+            }
+
+            if (substr($long_opt, -1) == '=') {
+                if (substr($long_opt, -2) != '==') {
+                    /* Long option requires an argument.
+                       Take the next argument if one wasn't specified. */;
+                    if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
+                        return PEAR::raiseError("Console_Getopt: option --$opt requires an argument");
+                    }
+                    if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
+                        return PEAR::raiseError("Console_Getopt: option requires an argument --$opt");
+                    }
+                }
+            } else if ($opt_arg) {
+                return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument");
+            }
+
+            $opts[] = array('--' . $opt, $opt_arg);
+            return;
+        }
+
+        return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
+    }
+
+    /**
+    * Safely read the $argv PHP array across different PHP configurations.
+    * Will take care on register_globals and register_argc_argv ini directives
+    *
+    * @access public
+    * @return mixed the $argv PHP array or PEAR error if not registered
+    */
+    function readPHPArgv()
+    {
+        global $argv;
+        if (!is_array($argv)) {
+            if (!@is_array($_SERVER['argv'])) {
+                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
+                    return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)");
+                }
+                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
+            }
+            return $_SERVER['argv'];
+        }
+        return $argv;
+    }
+
+}
+
+?>
index b1a1a4e2184f77f3bb4d1c0bf22aa45e1b6b9074..0c6a13dc28f7cec5c702d2ad09fb41704bc6b018 100644 (file)
@@ -2357,6 +2357,8 @@ class DB_DataObject extends DB_DataObject_Overload
         $t= explode(' ',microtime());
         $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1];
          
+
+        do {
         
         if ($_DB_driver == 'DB') {
             $result = $DB->query($string);
@@ -2374,8 +2376,19 @@ class DB_DataObject extends DB_DataObject_Overload
                     break;
             }
         }
-        
-       
+
+          // try to reconnect, at most 3 times
+          $again = false;
+          if (is_a($result, 'PEAR_Error')
+          AND $result->getCode() == DB_ERROR_NODBSELECTED
+          AND $cpt++<3) {
+              $DB->disconnect();
+              sleep(1);
+              $DB->connect($DB->dsn);
+              $again = true;
+          }
+          
+        } while ($again);
 
         if (is_a($result,'PEAR_Error')) {
             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { 
diff --git a/extlib/HTTP/Request.php b/extlib/HTTP/Request.php
new file mode 100644 (file)
index 0000000..42eac3b
--- /dev/null
@@ -0,0 +1,1521 @@
+<?php\r
+/**\r
+ * Class for performing HTTP requests\r
+ *\r
+ * PHP versions 4 and 5\r
+ *\r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2002-2007, Richard Heyes\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ * o Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ * o Redistributions in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ * o The names of the authors may not be used to endorse or promote\r
+ *   products derived from this software without specific prior written\r
+ *   permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\r
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request\r
+ * @author      Richard Heyes <richard@phpguru.org>\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @copyright   2002-2007 Richard Heyes\r
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version     CVS: $Id: Request.php,v 1.63 2008/10/11 11:07:10 avb Exp $\r
+ * @link        http://pear.php.net/package/HTTP_Request/\r
+ */\r
+\r
+/**\r
+ * PEAR and PEAR_Error classes (for error handling)\r
+ */\r
+require_once 'PEAR.php';\r
+/**\r
+ * Socket class\r
+ */\r
+require_once 'Net/Socket.php';\r
+/**\r
+ * URL handling class\r
+ */\r
+require_once 'Net/URL.php';\r
+\r
+/**#@+\r
+ * Constants for HTTP request methods\r
+ */\r
+define('HTTP_REQUEST_METHOD_GET',     'GET',     true);\r
+define('HTTP_REQUEST_METHOD_HEAD',    'HEAD',    true);\r
+define('HTTP_REQUEST_METHOD_POST',    'POST',    true);\r
+define('HTTP_REQUEST_METHOD_PUT',     'PUT',     true);\r
+define('HTTP_REQUEST_METHOD_DELETE',  'DELETE',  true);\r
+define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true);\r
+define('HTTP_REQUEST_METHOD_TRACE',   'TRACE',   true);\r
+/**#@-*/\r
+\r
+/**#@+\r
+ * Constants for HTTP request error codes\r
+ */\r
+define('HTTP_REQUEST_ERROR_FILE',             1);\r
+define('HTTP_REQUEST_ERROR_URL',              2);\r
+define('HTTP_REQUEST_ERROR_PROXY',            4);\r
+define('HTTP_REQUEST_ERROR_REDIRECTS',        8);\r
+define('HTTP_REQUEST_ERROR_RESPONSE',        16);\r
+define('HTTP_REQUEST_ERROR_GZIP_METHOD',     32);\r
+define('HTTP_REQUEST_ERROR_GZIP_READ',       64);\r
+define('HTTP_REQUEST_ERROR_GZIP_DATA',      128);\r
+define('HTTP_REQUEST_ERROR_GZIP_CRC',       256);\r
+/**#@-*/\r
+\r
+/**#@+\r
+ * Constants for HTTP protocol versions\r
+ */\r
+define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true);\r
+define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true);\r
+/**#@-*/\r
+\r
+if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {\r
+   /**\r
+    * Whether string functions are overloaded by their mbstring equivalents\r
+    */\r
+    define('HTTP_REQUEST_MBSTRING', true);\r
+} else {\r
+   /**\r
+    * @ignore\r
+    */\r
+    define('HTTP_REQUEST_MBSTRING', false);\r
+}\r
+\r
+/**\r
+ * Class for performing HTTP requests\r
+ *\r
+ * Simple example (fetches yahoo.com and displays it):\r
+ * <code>\r
+ * $a = &new HTTP_Request('http://www.yahoo.com/');\r
+ * $a->sendRequest();\r
+ * echo $a->getResponseBody();\r
+ * </code>\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request\r
+ * @author      Richard Heyes <richard@phpguru.org>\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @version     Release: 1.4.4\r
+ */\r
+class HTTP_Request\r
+{\r
+   /**#@+\r
+    * @access private\r
+    */\r
+    /**\r
+    * Instance of Net_URL\r
+    * @var Net_URL\r
+    */\r
+    var $_url;\r
+\r
+    /**\r
+    * Type of request\r
+    * @var string\r
+    */\r
+    var $_method;\r
+\r
+    /**\r
+    * HTTP Version\r
+    * @var string\r
+    */\r
+    var $_http;\r
+\r
+    /**\r
+    * Request headers\r
+    * @var array\r
+    */\r
+    var $_requestHeaders;\r
+\r
+    /**\r
+    * Basic Auth Username\r
+    * @var string\r
+    */\r
+    var $_user;\r
+\r
+    /**\r
+    * Basic Auth Password\r
+    * @var string\r
+    */\r
+    var $_pass;\r
+\r
+    /**\r
+    * Socket object\r
+    * @var Net_Socket\r
+    */\r
+    var $_sock;\r
+\r
+    /**\r
+    * Proxy server\r
+    * @var string\r
+    */\r
+    var $_proxy_host;\r
+\r
+    /**\r
+    * Proxy port\r
+    * @var integer\r
+    */\r
+    var $_proxy_port;\r
+\r
+    /**\r
+    * Proxy username\r
+    * @var string\r
+    */\r
+    var $_proxy_user;\r
+\r
+    /**\r
+    * Proxy password\r
+    * @var string\r
+    */\r
+    var $_proxy_pass;\r
+\r
+    /**\r
+    * Post data\r
+    * @var array\r
+    */\r
+    var $_postData;\r
+\r
+   /**\r
+    * Request body\r
+    * @var string\r
+    */\r
+    var $_body;\r
+\r
+   /**\r
+    * A list of methods that MUST NOT have a request body, per RFC 2616\r
+    * @var array\r
+    */\r
+    var $_bodyDisallowed = array('TRACE');\r
+\r
+   /**\r
+    * Methods having defined semantics for request body\r
+    *\r
+    * Content-Length header (indicating that the body follows, section 4.3 of\r
+    * RFC 2616) will be sent for these methods even if no body was added\r
+    *\r
+    * @var array\r
+    */\r
+    var $_bodyRequired = array('POST', 'PUT');\r
+\r
+   /**\r
+    * Files to post\r
+    * @var array\r
+    */\r
+    var $_postFiles = array();\r
+\r
+    /**\r
+    * Connection timeout.\r
+    * @var float\r
+    */\r
+    var $_timeout;\r
+\r
+    /**\r
+    * HTTP_Response object\r
+    * @var HTTP_Response\r
+    */\r
+    var $_response;\r
+\r
+    /**\r
+    * Whether to allow redirects\r
+    * @var boolean\r
+    */\r
+    var $_allowRedirects;\r
+\r
+    /**\r
+    * Maximum redirects allowed\r
+    * @var integer\r
+    */\r
+    var $_maxRedirects;\r
+\r
+    /**\r
+    * Current number of redirects\r
+    * @var integer\r
+    */\r
+    var $_redirects;\r
+\r
+   /**\r
+    * Whether to append brackets [] to array variables\r
+    * @var bool\r
+    */\r
+    var $_useBrackets = true;\r
+\r
+   /**\r
+    * Attached listeners\r
+    * @var array\r
+    */\r
+    var $_listeners = array();\r
+\r
+   /**\r
+    * Whether to save response body in response object property\r
+    * @var bool\r
+    */\r
+    var $_saveBody = true;\r
+\r
+   /**\r
+    * Timeout for reading from socket (array(seconds, microseconds))\r
+    * @var array\r
+    */\r
+    var $_readTimeout = null;\r
+\r
+   /**\r
+    * Options to pass to Net_Socket::connect. See stream_context_create\r
+    * @var array\r
+    */\r
+    var $_socketOptions = null;\r
+   /**#@-*/\r
+\r
+    /**\r
+    * Constructor\r
+    *\r
+    * Sets up the object\r
+    * @param    string  The url to fetch/access\r
+    * @param    array   Associative array of parameters which can have the following keys:\r
+    * <ul>\r
+    *   <li>method         - Method to use, GET, POST etc (string)</li>\r
+    *   <li>http           - HTTP Version to use, 1.0 or 1.1 (string)</li>\r
+    *   <li>user           - Basic Auth username (string)</li>\r
+    *   <li>pass           - Basic Auth password (string)</li>\r
+    *   <li>proxy_host     - Proxy server host (string)</li>\r
+    *   <li>proxy_port     - Proxy server port (integer)</li>\r
+    *   <li>proxy_user     - Proxy auth username (string)</li>\r
+    *   <li>proxy_pass     - Proxy auth password (string)</li>\r
+    *   <li>timeout        - Connection timeout in seconds (float)</li>\r
+    *   <li>allowRedirects - Whether to follow redirects or not (bool)</li>\r
+    *   <li>maxRedirects   - Max number of redirects to follow (integer)</li>\r
+    *   <li>useBrackets    - Whether to append [] to array variable names (bool)</li>\r
+    *   <li>saveBody       - Whether to save response body in response object property (bool)</li>\r
+    *   <li>readTimeout    - Timeout for reading / writing data over the socket (array (seconds, microseconds))</li>\r
+    *   <li>socketOptions  - Options to pass to Net_Socket object (array)</li>\r
+    * </ul>\r
+    * @access public\r
+    */\r
+    function HTTP_Request($url = '', $params = array())\r
+    {\r
+        $this->_method         =  HTTP_REQUEST_METHOD_GET;\r
+        $this->_http           =  HTTP_REQUEST_HTTP_VER_1_1;\r
+        $this->_requestHeaders = array();\r
+        $this->_postData       = array();\r
+        $this->_body           = null;\r
+\r
+        $this->_user = null;\r
+        $this->_pass = null;\r
+\r
+        $this->_proxy_host = null;\r
+        $this->_proxy_port = null;\r
+        $this->_proxy_user = null;\r
+        $this->_proxy_pass = null;\r
+\r
+        $this->_allowRedirects = false;\r
+        $this->_maxRedirects   = 3;\r
+        $this->_redirects      = 0;\r
+\r
+        $this->_timeout  = null;\r
+        $this->_response = null;\r
+\r
+        foreach ($params as $key => $value) {\r
+            $this->{'_' . $key} = $value;\r
+        }\r
+\r
+        if (!empty($url)) {\r
+            $this->setURL($url);\r
+        }\r
+\r
+        // Default useragent\r
+        $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )');\r
+\r
+        // We don't do keep-alives by default\r
+        $this->addHeader('Connection', 'close');\r
+\r
+        // Basic authentication\r
+        if (!empty($this->_user)) {\r
+            $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass));\r
+        }\r
+\r
+        // Proxy authentication (see bug #5913)\r
+        if (!empty($this->_proxy_user)) {\r
+            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($this->_proxy_user . ':' . $this->_proxy_pass));\r
+        }\r
+\r
+        // Use gzip encoding if possible\r
+        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib')) {\r
+            $this->addHeader('Accept-Encoding', 'gzip');\r
+        }\r
+    }\r
+\r
+    /**\r
+    * Generates a Host header for HTTP/1.1 requests\r
+    *\r
+    * @access private\r
+    * @return string\r
+    */\r
+    function _generateHostHeader()\r
+    {\r
+        if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) {\r
+            $host = $this->_url->host . ':' . $this->_url->port;\r
+\r
+        } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) {\r
+            $host = $this->_url->host . ':' . $this->_url->port;\r
+\r
+        } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) {\r
+            $host = $this->_url->host . ':' . $this->_url->port;\r
+\r
+        } else {\r
+            $host = $this->_url->host;\r
+        }\r
+\r
+        return $host;\r
+    }\r
+\r
+    /**\r
+    * Resets the object to its initial state (DEPRECATED).\r
+    * Takes the same parameters as the constructor.\r
+    *\r
+    * @param  string $url    The url to be requested\r
+    * @param  array  $params Associative array of parameters\r
+    *                        (see constructor for details)\r
+    * @access public\r
+    * @deprecated deprecated since 1.2, call the constructor if this is necessary\r
+    */\r
+    function reset($url, $params = array())\r
+    {\r
+        $this->HTTP_Request($url, $params);\r
+    }\r
+\r
+    /**\r
+    * Sets the URL to be requested\r
+    *\r
+    * @param  string The url to be requested\r
+    * @access public\r
+    */\r
+    function setURL($url)\r
+    {\r
+        $this->_url = &new Net_URL($url, $this->_useBrackets);\r
+\r
+        if (!empty($this->_url->user) || !empty($this->_url->pass)) {\r
+            $this->setBasicAuth($this->_url->user, $this->_url->pass);\r
+        }\r
+\r
+        if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) {\r
+            $this->addHeader('Host', $this->_generateHostHeader());\r
+        }\r
+\r
+        // set '/' instead of empty path rather than check later (see bug #8662)\r
+        if (empty($this->_url->path)) {\r
+            $this->_url->path = '/';\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Returns the current request URL\r
+    *\r
+    * @return   string  Current request URL\r
+    * @access   public\r
+    */\r
+    function getUrl()\r
+    {\r
+        return empty($this->_url)? '': $this->_url->getUrl();\r
+    }\r
+\r
+    /**\r
+    * Sets a proxy to be used\r
+    *\r
+    * @param string     Proxy host\r
+    * @param int        Proxy port\r
+    * @param string     Proxy username\r
+    * @param string     Proxy password\r
+    * @access public\r
+    */\r
+    function setProxy($host, $port = 8080, $user = null, $pass = null)\r
+    {\r
+        $this->_proxy_host = $host;\r
+        $this->_proxy_port = $port;\r
+        $this->_proxy_user = $user;\r
+        $this->_proxy_pass = $pass;\r
+\r
+        if (!empty($user)) {\r
+            $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass));\r
+        }\r
+    }\r
+\r
+    /**\r
+    * Sets basic authentication parameters\r
+    *\r
+    * @param string     Username\r
+    * @param string     Password\r
+    */\r
+    function setBasicAuth($user, $pass)\r
+    {\r
+        $this->_user = $user;\r
+        $this->_pass = $pass;\r
+\r
+        $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass));\r
+    }\r
+\r
+    /**\r
+    * Sets the method to be used, GET, POST etc.\r
+    *\r
+    * @param string     Method to use. Use the defined constants for this\r
+    * @access public\r
+    */\r
+    function setMethod($method)\r
+    {\r
+        $this->_method = $method;\r
+    }\r
+\r
+    /**\r
+    * Sets the HTTP version to use, 1.0 or 1.1\r
+    *\r
+    * @param string     Version to use. Use the defined constants for this\r
+    * @access public\r
+    */\r
+    function setHttpVer($http)\r
+    {\r
+        $this->_http = $http;\r
+    }\r
+\r
+    /**\r
+    * Adds a request header\r
+    *\r
+    * @param string     Header name\r
+    * @param string     Header value\r
+    * @access public\r
+    */\r
+    function addHeader($name, $value)\r
+    {\r
+        $this->_requestHeaders[strtolower($name)] = $value;\r
+    }\r
+\r
+    /**\r
+    * Removes a request header\r
+    *\r
+    * @param string     Header name to remove\r
+    * @access public\r
+    */\r
+    function removeHeader($name)\r
+    {\r
+        if (isset($this->_requestHeaders[strtolower($name)])) {\r
+            unset($this->_requestHeaders[strtolower($name)]);\r
+        }\r
+    }\r
+\r
+    /**\r
+    * Adds a querystring parameter\r
+    *\r
+    * @param string     Querystring parameter name\r
+    * @param string     Querystring parameter value\r
+    * @param bool       Whether the value is already urlencoded or not, default = not\r
+    * @access public\r
+    */\r
+    function addQueryString($name, $value, $preencoded = false)\r
+    {\r
+        $this->_url->addQueryString($name, $value, $preencoded);\r
+    }\r
+\r
+    /**\r
+    * Sets the querystring to literally what you supply\r
+    *\r
+    * @param string     The querystring data. Should be of the format foo=bar&x=y etc\r
+    * @param bool       Whether data is already urlencoded or not, default = already encoded\r
+    * @access public\r
+    */\r
+    function addRawQueryString($querystring, $preencoded = true)\r
+    {\r
+        $this->_url->addRawQueryString($querystring, $preencoded);\r
+    }\r
+\r
+    /**\r
+    * Adds postdata items\r
+    *\r
+    * @param string     Post data name\r
+    * @param string     Post data value\r
+    * @param bool       Whether data is already urlencoded or not, default = not\r
+    * @access public\r
+    */\r
+    function addPostData($name, $value, $preencoded = false)\r
+    {\r
+        if ($preencoded) {\r
+            $this->_postData[$name] = $value;\r
+        } else {\r
+            $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value);\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Recursively applies the callback function to the value\r
+    *\r
+    * @param    mixed   Callback function\r
+    * @param    mixed   Value to process\r
+    * @access   private\r
+    * @return   mixed   Processed value\r
+    */\r
+    function _arrayMapRecursive($callback, $value)\r
+    {\r
+        if (!is_array($value)) {\r
+            return call_user_func($callback, $value);\r
+        } else {\r
+            $map = array();\r
+            foreach ($value as $k => $v) {\r
+                $map[$k] = $this->_arrayMapRecursive($callback, $v);\r
+            }\r
+            return $map;\r
+        }\r
+    }\r
+\r
+   /**\r
+    * Adds a file to form-based file upload\r
+    *\r
+    * Used to emulate file upload via a HTML form. The method also sets\r
+    * Content-Type of HTTP request to 'multipart/form-data'.\r
+    *\r
+    * If you just want to send the contents of a file as the body of HTTP\r
+    * request you should use setBody() method.\r
+    *\r
+    * @access public\r
+    * @param  string    name of file-upload field\r
+    * @param  mixed     file name(s)\r
+    * @param  mixed     content-type(s) of file(s) being uploaded\r
+    * @return bool      true on success\r
+    * @throws PEAR_Error\r
+    */\r
+    function addFile($inputName, $fileName, $contentType = 'application/octet-stream')\r
+    {\r
+        if (!is_array($fileName) && !is_readable($fileName)) {\r
+            return PEAR::raiseError("File '{$fileName}' is not readable", HTTP_REQUEST_ERROR_FILE);\r
+        } elseif (is_array($fileName)) {\r
+            foreach ($fileName as $name) {\r
+                if (!is_readable($name)) {\r
+                    return PEAR::raiseError("File '{$name}' is not readable", HTTP_REQUEST_ERROR_FILE);\r
+                }\r
+            }\r
+        }\r
+        $this->addHeader('Content-Type', 'multipart/form-data');\r
+        $this->_postFiles[$inputName] = array(\r
+            'name' => $fileName,\r
+            'type' => $contentType\r
+        );\r
+        return true;\r
+    }\r
+\r
+    /**\r
+    * Adds raw postdata (DEPRECATED)\r
+    *\r
+    * @param string     The data\r
+    * @param bool       Whether data is preencoded or not, default = already encoded\r
+    * @access public\r
+    * @deprecated       deprecated since 1.3.0, method setBody() should be used instead\r
+    */\r
+    function addRawPostData($postdata, $preencoded = true)\r
+    {\r
+        $this->_body = $preencoded ? $postdata : urlencode($postdata);\r
+    }\r
+\r
+   /**\r
+    * Sets the request body (for POST, PUT and similar requests)\r
+    *\r
+    * @param    string  Request body\r
+    * @access   public\r
+    */\r
+    function setBody($body)\r
+    {\r
+        $this->_body = $body;\r
+    }\r
+\r
+    /**\r
+    * Clears any postdata that has been added (DEPRECATED).\r
+    *\r
+    * Useful for multiple request scenarios.\r
+    *\r
+    * @access public\r
+    * @deprecated deprecated since 1.2\r
+    */\r
+    function clearPostData()\r
+    {\r
+        $this->_postData = null;\r
+    }\r
+\r
+    /**\r
+    * Appends a cookie to "Cookie:" header\r
+    *\r
+    * @param string $name cookie name\r
+    * @param string $value cookie value\r
+    * @access public\r
+    */\r
+    function addCookie($name, $value)\r
+    {\r
+        $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : '';\r
+        $this->addHeader('Cookie', $cookies . $name . '=' . $value);\r
+    }\r
+\r
+    /**\r
+    * Clears any cookies that have been added (DEPRECATED).\r
+    *\r
+    * Useful for multiple request scenarios\r
+    *\r
+    * @access public\r
+    * @deprecated deprecated since 1.2\r
+    */\r
+    function clearCookies()\r
+    {\r
+        $this->removeHeader('Cookie');\r
+    }\r
+\r
+    /**\r
+    * Sends the request\r
+    *\r
+    * @access public\r
+    * @param  bool   Whether to store response body in Response object property,\r
+    *                set this to false if downloading a LARGE file and using a Listener\r
+    * @return mixed  PEAR error on error, true otherwise\r
+    */\r
+    function sendRequest($saveBody = true)\r
+    {\r
+        if (!is_a($this->_url, 'Net_URL')) {\r
+            return PEAR::raiseError('No URL given', HTTP_REQUEST_ERROR_URL);\r
+        }\r
+\r
+        $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host;\r
+        $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port;\r
+\r
+        if (strcasecmp($this->_url->protocol, 'https') == 0) {\r
+            // Bug #14127, don't try connecting to HTTPS sites without OpenSSL\r
+            if (version_compare(PHP_VERSION, '4.3.0', '<') || !extension_loaded('openssl')) {\r
+                return PEAR::raiseError('Need PHP 4.3.0 or later with OpenSSL support for https:// requests',\r
+                                        HTTP_REQUEST_ERROR_URL);\r
+            } elseif (isset($this->_proxy_host)) {\r
+                return PEAR::raiseError('HTTPS proxies are not supported', HTTP_REQUEST_ERROR_PROXY);\r
+            }\r
+            $host = 'ssl://' . $host;\r
+        }\r
+\r
+        // magic quotes may fuck up file uploads and chunked response processing\r
+        $magicQuotes = ini_get('magic_quotes_runtime');\r
+        ini_set('magic_quotes_runtime', false);\r
+\r
+        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive\r
+        // connection token to a proxy server...\r
+        if (isset($this->_proxy_host) && !empty($this->_requestHeaders['connection']) &&\r
+            'Keep-Alive' == $this->_requestHeaders['connection'])\r
+        {\r
+            $this->removeHeader('connection');\r
+        }\r
+\r
+        $keepAlive = (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && empty($this->_requestHeaders['connection'])) ||\r
+                     (!empty($this->_requestHeaders['connection']) && 'Keep-Alive' == $this->_requestHeaders['connection']);\r
+        $sockets   = &PEAR::getStaticProperty('HTTP_Request', 'sockets');\r
+        $sockKey   = $host . ':' . $port;\r
+        unset($this->_sock);\r
+\r
+        // There is a connected socket in the "static" property?\r
+        if ($keepAlive && !empty($sockets[$sockKey]) &&\r
+            !empty($sockets[$sockKey]->fp))\r
+        {\r
+            $this->_sock =& $sockets[$sockKey];\r
+            $err = null;\r
+        } else {\r
+            $this->_notify('connect');\r
+            $this->_sock =& new Net_Socket();\r
+            $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions);\r
+        }\r
+        PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest());\r
+\r
+        if (!PEAR::isError($err)) {\r
+            if (!empty($this->_readTimeout)) {\r
+                $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]);\r
+            }\r
+\r
+            $this->_notify('sentRequest');\r
+\r
+            // Read the response\r
+            $this->_response = &new HTTP_Response($this->_sock, $this->_listeners);\r
+            $err = $this->_response->process(\r
+                $this->_saveBody && $saveBody,\r
+                HTTP_REQUEST_METHOD_HEAD != $this->_method\r
+            );\r
+\r
+            if ($keepAlive) {\r
+                $keepAlive = (isset($this->_response->_headers['content-length'])\r
+                              || (isset($this->_response->_headers['transfer-encoding'])\r
+                                  && strtolower($this->_response->_headers['transfer-encoding']) == 'chunked'));\r
+                if ($keepAlive) {\r
+                    if (isset($this->_response->_headers['connection'])) {\r
+                        $keepAlive = strtolower($this->_response->_headers['connection']) == 'keep-alive';\r
+                    } else {\r
+                        $keepAlive = 'HTTP/'.HTTP_REQUEST_HTTP_VER_1_1 == $this->_response->_protocol;\r
+                    }\r
+                }\r
+            }\r
+        }\r
+\r
+        ini_set('magic_quotes_runtime', $magicQuotes);\r
+\r
+        if (PEAR::isError($err)) {\r
+            return $err;\r
+        }\r
+\r
+        if (!$keepAlive) {\r
+            $this->disconnect();\r
+        // Store the connected socket in "static" property\r
+        } elseif (empty($sockets[$sockKey]) || empty($sockets[$sockKey]->fp)) {\r
+            $sockets[$sockKey] =& $this->_sock;\r
+        }\r
+\r
+        // Check for redirection\r
+        if (    $this->_allowRedirects\r
+            AND $this->_redirects <= $this->_maxRedirects\r
+            AND $this->getResponseCode() > 300\r
+            AND $this->getResponseCode() < 399\r
+            AND !empty($this->_response->_headers['location'])) {\r
+\r
+\r
+            $redirect = $this->_response->_headers['location'];\r
+\r
+            // Absolute URL\r
+            if (preg_match('/^https?:\/\//i', $redirect)) {\r
+                $this->_url = &new Net_URL($redirect);\r
+                $this->addHeader('Host', $this->_generateHostHeader());\r
+            // Absolute path\r
+            } elseif ($redirect{0} == '/') {\r
+                $this->_url->path = $redirect;\r
+\r
+            // Relative path\r
+            } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') {\r
+                if (substr($this->_url->path, -1) == '/') {\r
+                    $redirect = $this->_url->path . $redirect;\r
+                } else {\r
+                    $redirect = dirname($this->_url->path) . '/' . $redirect;\r
+                }\r
+                $redirect = Net_URL::resolvePath($redirect);\r
+                $this->_url->path = $redirect;\r
+\r
+            // Filename, no path\r
+            } else {\r
+                if (substr($this->_url->path, -1) == '/') {\r
+                    $redirect = $this->_url->path . $redirect;\r
+                } else {\r
+                    $redirect = dirname($this->_url->path) . '/' . $redirect;\r
+                }\r
+                $this->_url->path = $redirect;\r
+            }\r
+\r
+            $this->_redirects++;\r
+            return $this->sendRequest($saveBody);\r
+\r
+        // Too many redirects\r
+        } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) {\r
+            return PEAR::raiseError('Too many redirects', HTTP_REQUEST_ERROR_REDIRECTS);\r
+        }\r
+\r
+        return true;\r
+    }\r
+\r
+    /**\r
+     * Disconnect the socket, if connected. Only useful if using Keep-Alive.\r
+     *\r
+     * @access public\r
+     */\r
+    function disconnect()\r
+    {\r
+        if (!empty($this->_sock) && !empty($this->_sock->fp)) {\r
+            $this->_notify('disconnect');\r
+            $this->_sock->disconnect();\r
+        }\r
+    }\r
+\r
+    /**\r
+    * Returns the response code\r
+    *\r
+    * @access public\r
+    * @return mixed     Response code, false if not set\r
+    */\r
+    function getResponseCode()\r
+    {\r
+        return isset($this->_response->_code) ? $this->_response->_code : false;\r
+    }\r
+\r
+    /**\r
+    * Returns the response reason phrase\r
+    *\r
+    * @access public\r
+    * @return mixed     Response reason phrase, false if not set\r
+    */\r
+    function getResponseReason()\r
+    {\r
+        return isset($this->_response->_reason) ? $this->_response->_reason : false;\r
+    }\r
+\r
+    /**\r
+    * Returns either the named header or all if no name given\r
+    *\r
+    * @access public\r
+    * @param string     The header name to return, do not set to get all headers\r
+    * @return mixed     either the value of $headername (false if header is not present)\r
+    *                   or an array of all headers\r
+    */\r
+    function getResponseHeader($headername = null)\r
+    {\r
+        if (!isset($headername)) {\r
+            return isset($this->_response->_headers)? $this->_response->_headers: array();\r
+        } else {\r
+            $headername = strtolower($headername);\r
+            return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false;\r
+        }\r
+    }\r
+\r
+    /**\r
+    * Returns the body of the response\r
+    *\r
+    * @access public\r
+    * @return mixed     response body, false if not set\r
+    */\r
+    function getResponseBody()\r
+    {\r
+        return isset($this->_response->_body) ? $this->_response->_body : false;\r
+    }\r
+\r
+    /**\r
+    * Returns cookies set in response\r
+    *\r
+    * @access public\r
+    * @return mixed     array of response cookies, false if none are present\r
+    */\r
+    function getResponseCookies()\r
+    {\r
+        return isset($this->_response->_cookies) ? $this->_response->_cookies : false;\r
+    }\r
+\r
+    /**\r
+    * Builds the request string\r
+    *\r
+    * @access private\r
+    * @return string The request string\r
+    */\r
+    function _buildRequest()\r
+    {\r
+        $separator = ini_get('arg_separator.output');\r
+        ini_set('arg_separator.output', '&');\r
+        $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : '';\r
+        ini_set('arg_separator.output', $separator);\r
+\r
+        $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : '';\r
+        $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : '';\r
+        $path = $this->_url->path . $querystring;\r
+        $url  = $host . $port . $path;\r
+\r
+        if (!strlen($url)) {\r
+            $url = '/';\r
+        }\r
+\r
+        $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n";\r
+\r
+        if (in_array($this->_method, $this->_bodyDisallowed) ||\r
+            (0 == strlen($this->_body) && (HTTP_REQUEST_METHOD_POST != $this->_method ||\r
+             (empty($this->_postData) && empty($this->_postFiles)))))\r
+        {\r
+            $this->removeHeader('Content-Type');\r
+        } else {\r
+            if (empty($this->_requestHeaders['content-type'])) {\r
+                // Add default content-type\r
+                $this->addHeader('Content-Type', 'application/x-www-form-urlencoded');\r
+            } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) {\r
+                $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime());\r
+                $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary);\r
+            }\r
+        }\r
+\r
+        // Request Headers\r
+        if (!empty($this->_requestHeaders)) {\r
+            foreach ($this->_requestHeaders as $name => $value) {\r
+                $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));\r
+                $request      .= $canonicalName . ': ' . $value . "\r\n";\r
+            }\r
+        }\r
+\r
+        // Method does not allow a body, simply add a final CRLF\r
+        if (in_array($this->_method, $this->_bodyDisallowed)) {\r
+\r
+            $request .= "\r\n";\r
+\r
+        // Post data if it's an array\r
+        } elseif (HTTP_REQUEST_METHOD_POST == $this->_method &&\r
+                  (!empty($this->_postData) || !empty($this->_postFiles))) {\r
+\r
+            // "normal" POST request\r
+            if (!isset($boundary)) {\r
+                $postdata = implode('&', array_map(\r
+                    create_function('$a', 'return $a[0] . \'=\' . $a[1];'),\r
+                    $this->_flattenArray('', $this->_postData)\r
+                ));\r
+\r
+            // multipart request, probably with file uploads\r
+            } else {\r
+                $postdata = '';\r
+                if (!empty($this->_postData)) {\r
+                    $flatData = $this->_flattenArray('', $this->_postData);\r
+                    foreach ($flatData as $item) {\r
+                        $postdata .= '--' . $boundary . "\r\n";\r
+                        $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"';\r
+                        $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n";\r
+                    }\r
+                }\r
+                foreach ($this->_postFiles as $name => $value) {\r
+                    if (is_array($value['name'])) {\r
+                        $varname       = $name . ($this->_useBrackets? '[]': '');\r
+                    } else {\r
+                        $varname       = $name;\r
+                        $value['name'] = array($value['name']);\r
+                    }\r
+                    foreach ($value['name'] as $key => $filename) {\r
+                        $fp       = fopen($filename, 'r');\r
+                        $basename = basename($filename);\r
+                        $type     = is_array($value['type'])? @$value['type'][$key]: $value['type'];\r
+\r
+                        $postdata .= '--' . $boundary . "\r\n";\r
+                        $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"';\r
+                        $postdata .= "\r\nContent-Type: " . $type;\r
+                        $postdata .= "\r\n\r\n" . fread($fp, filesize($filename)) . "\r\n";\r
+                        fclose($fp);\r
+                    }\r
+                }\r
+                $postdata .= '--' . $boundary . "--\r\n";\r
+            }\r
+            $request .= 'Content-Length: ' .\r
+                        (HTTP_REQUEST_MBSTRING? mb_strlen($postdata, 'iso-8859-1'): strlen($postdata)) .\r
+                        "\r\n\r\n";\r
+            $request .= $postdata;\r
+\r
+        // Explicitly set request body\r
+        } elseif (0 < strlen($this->_body)) {\r
+\r
+            $request .= 'Content-Length: ' .\r
+                        (HTTP_REQUEST_MBSTRING? mb_strlen($this->_body, 'iso-8859-1'): strlen($this->_body)) .\r
+                        "\r\n\r\n";\r
+            $request .= $this->_body;\r
+\r
+        // No body: send a Content-Length header nonetheless (request #12900),\r
+        // but do that only for methods that require a body (bug #14740)\r
+        } else {\r
+\r
+            if (in_array($this->_method, $this->_bodyRequired)) {\r
+                $request .= "Content-Length: 0\r\n";\r
+            }\r
+            $request .= "\r\n";\r
+        }\r
+\r
+        return $request;\r
+    }\r
+\r
+   /**\r
+    * Helper function to change the (probably multidimensional) associative array\r
+    * into the simple one.\r
+    *\r
+    * @param    string  name for item\r
+    * @param    mixed   item's values\r
+    * @return   array   array with the following items: array('item name', 'item value');\r
+    * @access   private\r
+    */\r
+    function _flattenArray($name, $values)\r
+    {\r
+        if (!is_array($values)) {\r
+            return array(array($name, $values));\r
+        } else {\r
+            $ret = array();\r
+            foreach ($values as $k => $v) {\r
+                if (empty($name)) {\r
+                    $newName = $k;\r
+                } elseif ($this->_useBrackets) {\r
+                    $newName = $name . '[' . $k . ']';\r
+                } else {\r
+                    $newName = $name;\r
+                }\r
+                $ret = array_merge($ret, $this->_flattenArray($newName, $v));\r
+            }\r
+            return $ret;\r
+        }\r
+    }\r
+\r
+\r
+   /**\r
+    * Adds a Listener to the list of listeners that are notified of\r
+    * the object's events\r
+    *\r
+    * Events sent by HTTP_Request object\r
+    * - 'connect': on connection to server\r
+    * - 'sentRequest': after the request was sent\r
+    * - 'disconnect': on disconnection from server\r
+    *\r
+    * Events sent by HTTP_Response object\r
+    * - 'gotHeaders': after receiving response headers (headers are passed in $data)\r
+    * - 'tick': on receiving a part of response body (the part is passed in $data)\r
+    * - 'gzTick': on receiving a gzip-encoded part of response body (ditto)\r
+    * - 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped)\r
+    *\r
+    * @param    HTTP_Request_Listener   listener to attach\r
+    * @return   boolean                 whether the listener was successfully attached\r
+    * @access   public\r
+    */\r
+    function attach(&$listener)\r
+    {\r
+        if (!is_a($listener, 'HTTP_Request_Listener')) {\r
+            return false;\r
+        }\r
+        $this->_listeners[$listener->getId()] =& $listener;\r
+        return true;\r
+    }\r
+\r
+\r
+   /**\r
+    * Removes a Listener from the list of listeners\r
+    *\r
+    * @param    HTTP_Request_Listener   listener to detach\r
+    * @return   boolean                 whether the listener was successfully detached\r
+    * @access   public\r
+    */\r
+    function detach(&$listener)\r
+    {\r
+        if (!is_a($listener, 'HTTP_Request_Listener') ||\r
+            !isset($this->_listeners[$listener->getId()])) {\r
+            return false;\r
+        }\r
+        unset($this->_listeners[$listener->getId()]);\r
+        return true;\r
+    }\r
+\r
+\r
+   /**\r
+    * Notifies all registered listeners of an event.\r
+    *\r
+    * @param    string  Event name\r
+    * @param    mixed   Additional data\r
+    * @access   private\r
+    * @see      HTTP_Request::attach()\r
+    */\r
+    function _notify($event, $data = null)\r
+    {\r
+        foreach (array_keys($this->_listeners) as $id) {\r
+            $this->_listeners[$id]->update($this, $event, $data);\r
+        }\r
+    }\r
+}\r
+\r
+\r
+/**\r
+ * Response class to complement the Request class\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request\r
+ * @author      Richard Heyes <richard@phpguru.org>\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @version     Release: 1.4.4\r
+ */\r
+class HTTP_Response\r
+{\r
+    /**\r
+    * Socket object\r
+    * @var Net_Socket\r
+    */\r
+    var $_sock;\r
+\r
+    /**\r
+    * Protocol\r
+    * @var string\r
+    */\r
+    var $_protocol;\r
+\r
+    /**\r
+    * Return code\r
+    * @var string\r
+    */\r
+    var $_code;\r
+\r
+    /**\r
+    * Response reason phrase\r
+    * @var string\r
+    */\r
+    var $_reason;\r
+\r
+    /**\r
+    * Response headers\r
+    * @var array\r
+    */\r
+    var $_headers;\r
+\r
+    /**\r
+    * Cookies set in response\r
+    * @var array\r
+    */\r
+    var $_cookies;\r
+\r
+    /**\r
+    * Response body\r
+    * @var string\r
+    */\r
+    var $_body = '';\r
+\r
+   /**\r
+    * Used by _readChunked(): remaining length of the current chunk\r
+    * @var string\r
+    */\r
+    var $_chunkLength = 0;\r
+\r
+   /**\r
+    * Attached listeners\r
+    * @var array\r
+    */\r
+    var $_listeners = array();\r
+\r
+   /**\r
+    * Bytes left to read from message-body\r
+    * @var null|int\r
+    */\r
+    var $_toRead;\r
+\r
+    /**\r
+    * Constructor\r
+    *\r
+    * @param  Net_Socket    socket to read the response from\r
+    * @param  array         listeners attached to request\r
+    */\r
+    function HTTP_Response(&$sock, &$listeners)\r
+    {\r
+        $this->_sock      =& $sock;\r
+        $this->_listeners =& $listeners;\r
+    }\r
+\r
+\r
+   /**\r
+    * Processes a HTTP response\r
+    *\r
+    * This extracts response code, headers, cookies and decodes body if it\r
+    * was encoded in some way\r
+    *\r
+    * @access public\r
+    * @param  bool      Whether to store response body in object property, set\r
+    *                   this to false if downloading a LARGE file and using a Listener.\r
+    *                   This is assumed to be true if body is gzip-encoded.\r
+    * @param  bool      Whether the response can actually have a message-body.\r
+    *                   Will be set to false for HEAD requests.\r
+    * @throws PEAR_Error\r
+    * @return mixed     true on success, PEAR_Error in case of malformed response\r
+    */\r
+    function process($saveBody = true, $canHaveBody = true)\r
+    {\r
+        do {\r
+            $line = $this->_sock->readLine();\r
+            if (!preg_match('!^(HTTP/\d\.\d) (\d{3})(?: (.+))?!', $line, $s)) {\r
+                return PEAR::raiseError('Malformed response', HTTP_REQUEST_ERROR_RESPONSE);\r
+            } else {\r
+                $this->_protocol = $s[1];\r
+                $this->_code     = intval($s[2]);\r
+                $this->_reason   = empty($s[3])? null: $s[3];\r
+            }\r
+            while ('' !== ($header = $this->_sock->readLine())) {\r
+                $this->_processHeader($header);\r
+            }\r
+        } while (100 == $this->_code);\r
+\r
+        $this->_notify('gotHeaders', $this->_headers);\r
+\r
+        // RFC 2616, section 4.4:\r
+        // 1. Any response message which "MUST NOT" include a message-body ...\r
+        // is always terminated by the first empty line after the header fields\r
+        // 3. ... If a message is received with both a\r
+        // Transfer-Encoding header field and a Content-Length header field,\r
+        // the latter MUST be ignored.\r
+        $canHaveBody = $canHaveBody && $this->_code >= 200 &&\r
+                       $this->_code != 204 && $this->_code != 304;\r
+\r
+        // If response body is present, read it and decode\r
+        $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']);\r
+        $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']);\r
+        $hasBody = false;\r
+        if ($canHaveBody && ($chunked || !isset($this->_headers['content-length']) ||\r
+                0 != $this->_headers['content-length']))\r
+        {\r
+            if ($chunked || !isset($this->_headers['content-length'])) {\r
+                $this->_toRead = null;\r
+            } else {\r
+                $this->_toRead = $this->_headers['content-length'];\r
+            }\r
+            while (!$this->_sock->eof() && (is_null($this->_toRead) || 0 < $this->_toRead)) {\r
+                if ($chunked) {\r
+                    $data = $this->_readChunked();\r
+                } elseif (is_null($this->_toRead)) {\r
+                    $data = $this->_sock->read(4096);\r
+                } else {\r
+                    $data = $this->_sock->read(min(4096, $this->_toRead));\r
+                    $this->_toRead -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);\r
+                }\r
+                if ('' == $data && (!$this->_chunkLength || $this->_sock->eof())) {\r
+                    break;\r
+                } else {\r
+                    $hasBody = true;\r
+                    if ($saveBody || $gzipped) {\r
+                        $this->_body .= $data;\r
+                    }\r
+                    $this->_notify($gzipped? 'gzTick': 'tick', $data);\r
+                }\r
+            }\r
+        }\r
+\r
+        if ($hasBody) {\r
+            // Uncompress the body if needed\r
+            if ($gzipped) {\r
+                $body = $this->_decodeGzip($this->_body);\r
+                if (PEAR::isError($body)) {\r
+                    return $body;\r
+                }\r
+                $this->_body = $body;\r
+                $this->_notify('gotBody', $this->_body);\r
+            } else {\r
+                $this->_notify('gotBody');\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+\r
+\r
+   /**\r
+    * Processes the response header\r
+    *\r
+    * @access private\r
+    * @param  string    HTTP header\r
+    */\r
+    function _processHeader($header)\r
+    {\r
+        if (false === strpos($header, ':')) {\r
+            return;\r
+        }\r
+        list($headername, $headervalue) = explode(':', $header, 2);\r
+        $headername  = strtolower($headername);\r
+        $headervalue = ltrim($headervalue);\r
+\r
+        if ('set-cookie' != $headername) {\r
+            if (isset($this->_headers[$headername])) {\r
+                $this->_headers[$headername] .= ',' . $headervalue;\r
+            } else {\r
+                $this->_headers[$headername]  = $headervalue;\r
+            }\r
+        } else {\r
+            $this->_parseCookie($headervalue);\r
+        }\r
+    }\r
+\r
+\r
+   /**\r
+    * Parse a Set-Cookie header to fill $_cookies array\r
+    *\r
+    * @access private\r
+    * @param  string    value of Set-Cookie header\r
+    */\r
+    function _parseCookie($headervalue)\r
+    {\r
+        $cookie = array(\r
+            'expires' => null,\r
+            'domain'  => null,\r
+            'path'    => null,\r
+            'secure'  => false\r
+        );\r
+\r
+        // Only a name=value pair\r
+        if (!strpos($headervalue, ';')) {\r
+            $pos = strpos($headervalue, '=');\r
+            $cookie['name']  = trim(substr($headervalue, 0, $pos));\r
+            $cookie['value'] = trim(substr($headervalue, $pos + 1));\r
+\r
+        // Some optional parameters are supplied\r
+        } else {\r
+            $elements = explode(';', $headervalue);\r
+            $pos = strpos($elements[0], '=');\r
+            $cookie['name']  = trim(substr($elements[0], 0, $pos));\r
+            $cookie['value'] = trim(substr($elements[0], $pos + 1));\r
+\r
+            for ($i = 1; $i < count($elements); $i++) {\r
+                if (false === strpos($elements[$i], '=')) {\r
+                    $elName  = trim($elements[$i]);\r
+                    $elValue = null;\r
+                } else {\r
+                    list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));\r
+                }\r
+                $elName = strtolower($elName);\r
+                if ('secure' == $elName) {\r
+                    $cookie['secure'] = true;\r
+                } elseif ('expires' == $elName) {\r
+                    $cookie['expires'] = str_replace('"', '', $elValue);\r
+                } elseif ('path' == $elName || 'domain' == $elName) {\r
+                    $cookie[$elName] = urldecode($elValue);\r
+                } else {\r
+                    $cookie[$elName] = $elValue;\r
+                }\r
+            }\r
+        }\r
+        $this->_cookies[] = $cookie;\r
+    }\r
+\r
+\r
+   /**\r
+    * Read a part of response body encoded with chunked Transfer-Encoding\r
+    *\r
+    * @access private\r
+    * @return string\r
+    */\r
+    function _readChunked()\r
+    {\r
+        // at start of the next chunk?\r
+        if (0 == $this->_chunkLength) {\r
+            $line = $this->_sock->readLine();\r
+            if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) {\r
+                $this->_chunkLength = hexdec($matches[1]);\r
+                // Chunk with zero length indicates the end\r
+                if (0 == $this->_chunkLength) {\r
+                    $this->_sock->readLine(); // make this an eof()\r
+                    return '';\r
+                }\r
+            } else {\r
+                return '';\r
+            }\r
+        }\r
+        $data = $this->_sock->read($this->_chunkLength);\r
+        $this->_chunkLength -= HTTP_REQUEST_MBSTRING? mb_strlen($data, 'iso-8859-1'): strlen($data);\r
+        if (0 == $this->_chunkLength) {\r
+            $this->_sock->readLine(); // Trailing CRLF\r
+        }\r
+        return $data;\r
+    }\r
+\r
+\r
+   /**\r
+    * Notifies all registered listeners of an event.\r
+    *\r
+    * @param    string  Event name\r
+    * @param    mixed   Additional data\r
+    * @access   private\r
+    * @see HTTP_Request::_notify()\r
+    */\r
+    function _notify($event, $data = null)\r
+    {\r
+        foreach (array_keys($this->_listeners) as $id) {\r
+            $this->_listeners[$id]->update($this, $event, $data);\r
+        }\r
+    }\r
+\r
+\r
+   /**\r
+    * Decodes the message-body encoded by gzip\r
+    *\r
+    * The real decoding work is done by gzinflate() built-in function, this\r
+    * method only parses the header and checks data for compliance with\r
+    * RFC 1952\r
+    *\r
+    * @access   private\r
+    * @param    string  gzip-encoded data\r
+    * @return   string  decoded data\r
+    */\r
+    function _decodeGzip($data)\r
+    {\r
+        if (HTTP_REQUEST_MBSTRING) {\r
+            $oldEncoding = mb_internal_encoding();\r
+            mb_internal_encoding('iso-8859-1');\r
+        }\r
+        $length = strlen($data);\r
+        // If it doesn't look like gzip-encoded data, don't bother\r
+        if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {\r
+            return $data;\r
+        }\r
+        $method = ord(substr($data, 2, 1));\r
+        if (8 != $method) {\r
+            return PEAR::raiseError('_decodeGzip(): unknown compression method', HTTP_REQUEST_ERROR_GZIP_METHOD);\r
+        }\r
+        $flags = ord(substr($data, 3, 1));\r
+        if ($flags & 224) {\r
+            return PEAR::raiseError('_decodeGzip(): reserved bits are set', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+        }\r
+\r
+        // header is 10 bytes minimum. may be longer, though.\r
+        $headerLength = 10;\r
+        // extra fields, need to skip 'em\r
+        if ($flags & 4) {\r
+            if ($length - $headerLength - 2 < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $extraLength = unpack('v', substr($data, 10, 2));\r
+            if ($length - $headerLength - 2 - $extraLength[1] < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $headerLength += $extraLength[1] + 2;\r
+        }\r
+        // file name, need to skip that\r
+        if ($flags & 8) {\r
+            if ($length - $headerLength - 1 < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $filenameLength = strpos(substr($data, $headerLength), chr(0));\r
+            if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $headerLength += $filenameLength + 1;\r
+        }\r
+        // comment, need to skip that also\r
+        if ($flags & 16) {\r
+            if ($length - $headerLength - 1 < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $commentLength = strpos(substr($data, $headerLength), chr(0));\r
+            if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $headerLength += $commentLength + 1;\r
+        }\r
+        // have a CRC for header. let's check\r
+        if ($flags & 1) {\r
+            if ($length - $headerLength - 2 < 8) {\r
+                return PEAR::raiseError('_decodeGzip(): data too short', HTTP_REQUEST_ERROR_GZIP_DATA);\r
+            }\r
+            $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));\r
+            $crcStored = unpack('v', substr($data, $headerLength, 2));\r
+            if ($crcReal != $crcStored[1]) {\r
+                return PEAR::raiseError('_decodeGzip(): header CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);\r
+            }\r
+            $headerLength += 2;\r
+        }\r
+        // unpacked data CRC and size at the end of encoded data\r
+        $tmp = unpack('V2', substr($data, -8));\r
+        $dataCrc  = $tmp[1];\r
+        $dataSize = $tmp[2];\r
+\r
+        // finally, call the gzinflate() function\r
+        // don't pass $dataSize to gzinflate, see bugs #13135, #14370\r
+        $unpacked = gzinflate(substr($data, $headerLength, -8));\r
+        if (false === $unpacked) {\r
+            return PEAR::raiseError('_decodeGzip(): gzinflate() call failed', HTTP_REQUEST_ERROR_GZIP_READ);\r
+        } elseif ($dataSize != strlen($unpacked)) {\r
+            return PEAR::raiseError('_decodeGzip(): data size check failed', HTTP_REQUEST_ERROR_GZIP_READ);\r
+        } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {\r
+            return PEAR::raiseError('_decodeGzip(): data CRC check failed', HTTP_REQUEST_ERROR_GZIP_CRC);\r
+        }\r
+        if (HTTP_REQUEST_MBSTRING) {\r
+            mb_internal_encoding($oldEncoding);\r
+        }\r
+        return $unpacked;\r
+    }\r
+} // End class HTTP_Response\r
+?>\r
diff --git a/extlib/HTTP/Request/Listener.php b/extlib/HTTP/Request/Listener.php
new file mode 100644 (file)
index 0000000..b4fe444
--- /dev/null
@@ -0,0 +1,106 @@
+<?php\r
+/**\r
+ * Listener for HTTP_Request and HTTP_Response objects\r
+ *\r
+ * PHP versions 4 and 5\r
+ * \r
+ * LICENSE:\r
+ *\r
+ * Copyright (c) 2002-2007, Richard Heyes\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions\r
+ * are met:\r
+ *\r
+ * o Redistributions of source code must retain the above copyright\r
+ *   notice, this list of conditions and the following disclaimer.\r
+ * o Redistributions in binary form must reproduce the above copyright\r
+ *   notice, this list of conditions and the following disclaimer in the\r
+ *   documentation and/or other materials provided with the distribution.\r
+ * o The names of the authors may not be used to endorse or promote\r
+ *   products derived from this software without specific prior written\r
+ *   permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\r
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @copyright   2002-2007 Richard Heyes\r
+ * @license     http://opensource.org/licenses/bsd-license.php New BSD License\r
+ * @version     CVS: $Id: Listener.php,v 1.3 2007/05/18 10:33:31 avb Exp $\r
+ * @link        http://pear.php.net/package/HTTP_Request/ \r
+ */\r
+\r
+/**\r
+ * Listener for HTTP_Request and HTTP_Response objects\r
+ *\r
+ * This class implements the Observer part of a Subject-Observer\r
+ * design pattern.\r
+ *\r
+ * @category    HTTP\r
+ * @package     HTTP_Request\r
+ * @author      Alexey Borzov <avb@php.net>\r
+ * @version     Release: 1.4.4\r
+ */\r
+class HTTP_Request_Listener \r
+{\r
+   /**\r
+    * A listener's identifier\r
+    * @var string\r
+    */\r
+    var $_id;\r
+\r
+   /**\r
+    * Constructor, sets the object's identifier\r
+    *\r
+    * @access public\r
+    */\r
+    function HTTP_Request_Listener()\r
+    {\r
+        $this->_id = md5(uniqid('http_request_', 1));\r
+    }\r
+\r
+\r
+   /**\r
+    * Returns the listener's identifier\r
+    *\r
+    * @access public\r
+    * @return string\r
+    */\r
+    function getId()\r
+    {\r
+        return $this->_id;\r
+    }\r
+\r
+\r
+   /**\r
+    * This method is called when Listener is notified of an event\r
+    *\r
+    * @access   public\r
+    * @param    object  an object the listener is attached to\r
+    * @param    string  Event name\r
+    * @param    mixed   Additional data\r
+    * @abstract\r
+    */\r
+    function update(&$subject, $event, $data = null)\r
+    {\r
+        echo "Notified of event: '$event'\n";\r
+        if (null !== $data) {\r
+            echo "Additional data: ";\r
+            var_dump($data);\r
+        }\r
+    }\r
+}\r
+?>\r
diff --git a/extlib/MIME/Type.php b/extlib/MIME/Type.php
new file mode 100644 (file)
index 0000000..c335f8d
--- /dev/null
@@ -0,0 +1,523 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002, 2008 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.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/3_0.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: Ian Eure <ieure@php.net>                                    |
+// +----------------------------------------------------------------------+
+//
+// $Id: Type.php,v 1.6 2009/01/16 11:49:45 cweiske Exp $
+
+require_once 'PEAR.php';
+
+$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+$_fileCmd = 'file';
+
+/**
+ * Class for working with MIME types
+ *
+ * @category MIME
+ * @package  MIME_Type
+ * @license  PHP License 3.0
+ * @version  1.2.0
+ * @link     http://pear.php.net/package/MIME_Type
+ * @author   Ian Eure <ieure@php.net>
+ */
+class MIME_Type
+{
+    /**
+     * The MIME media type
+     *
+     * @var string
+     */
+    var $media = '';
+
+    /**
+     * The MIME media sub-type
+     *
+     * @var string
+     */
+    var $subType = '';
+
+    /**
+     * Optional MIME parameters
+     *
+     * @var array
+     */
+    var $parameters = array();
+
+    /**
+     * List of valid media types.
+     * A media type is the string in front of the slash.
+     * The media type of "text/xml" would be "text".
+     *
+     * @var array
+     */
+    var $validMediaTypes = array(
+        'text',
+        'image',
+        'audio',
+        'video',
+        'application',
+        'multipart',
+        'message'
+    );
+
+
+    /**
+     * Constructor.
+     *
+     * If $type is set, if will be parsed and the appropriate class vars set.
+     * If not, you get an empty class.
+     * This is useful, but not quite as useful as parsing a type.
+     *
+     * @param string $type MIME type
+     *
+     * @return void
+     */
+    function MIME_Type($type = false)
+    {
+        if ($type) {
+            $this->parse($type);
+        }
+    }
+
+
+    /**
+     * Parse a mime-type and set the class variables.
+     *
+     * @param string $type MIME type to parse
+     *
+     * @return void
+     */
+    function parse($type)
+    {
+        $this->media      = $this->getMedia($type);
+        $this->subType    = $this->getSubType($type);
+        $this->parameters = array();
+        if (MIME_Type::hasParameters($type)) {
+            require_once 'MIME/Type/Parameter.php';
+            foreach (MIME_Type::getParameters($type) as $param) {
+                $param = new MIME_Type_Parameter($param);
+                $this->parameters[$param->name] = $param;
+            }
+        }
+    }
+
+
+    /**
+     * Does this type have any parameters?
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type has parameters, false otherwise
+     * @static
+     */
+    function hasParameters($type)
+    {
+        if (strstr($type, ';')) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Get a MIME type's parameters
+     *
+     * @param string $type MIME type to get parameters of
+     *
+     * @return array $type's parameters
+     * @static
+     */
+    function getParameters($type)
+    {
+        $params = array();
+        $tmp    = explode(';', $type);
+        for ($i = 1; $i < count($tmp); $i++) {
+            $params[] = trim($tmp[$i]);
+        }
+        return $params;
+    }
+
+
+    /**
+     * Strip parameters from a MIME type string.
+     *
+     * @param string $type MIME type string
+     *
+     * @return string MIME type with parameters removed
+     * @static
+     */
+    function stripParameters($type)
+    {
+        if (strstr($type, ';')) {
+            return substr($type, 0, strpos($type, ';'));
+        }
+        return $type;
+    }
+
+
+    /**
+     * Removes comments from a media type, subtype or parameter.
+     *
+     * @param string $string   String to strip comments from
+     * @param string &$comment Comment is stored in there.
+     *
+     * @return string   String without comments
+     * @static
+     */
+    function stripComments($string, &$comment)
+    {
+        if (strpos($string, '(') === false) {
+            return $string;
+        }
+
+        $inquote   = false;
+        $quoting   = false;
+        $incomment = 0;
+        $newstring = '';
+
+        for ($n = 0; $n < strlen($string); $n++) {
+            if ($quoting) {
+                if ($incomment == 0) {
+                    $newstring .= $string[$n];
+                } else if ($comment !== null) {
+                    $comment .= $string[$n];
+                }
+                $quoting = false;
+            } else if ($string[$n] == '\\') {
+                $quoting = true;
+            } else if (!$inquote && $incomment > 0 && $string[$n] == ')') {
+                $incomment--;
+                if ($incomment == 0 && $comment !== null) {
+                    $comment .= ' ';
+                }
+            } else if (!$inquote && $string[$n] == '(') {
+                $incomment++;
+            } else if ($string[$n] == '"') {
+                if ($inquote) {
+                    $inquote = false;
+                } else {
+                    $inquote = true;
+                }
+            } else if ($incomment == 0) {
+                $newstring .= $string[$n];
+            } else if ($comment !== null) {
+                $comment .= $string[$n];
+            }
+        }
+
+        if ($comment !== null) {
+            $comment = trim($comment);
+        }
+
+        return $newstring;
+    }
+
+
+    /**
+     * Get a MIME type's media
+     *
+     * @note 'media' refers to the portion before the first slash
+     *
+     * @param string $type MIME type to get media of
+     *
+     * @return string $type's media
+     * @static
+     */
+    function getMedia($type)
+    {
+        $tmp = explode('/', $type);
+        return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
+    }
+
+
+    /**
+     * Get a MIME type's subtype
+     *
+     * @param string $type MIME type to get subtype of
+     *
+     * @return string $type's subtype, null if invalid mime type
+     * @static
+     */
+    function getSubType($type)
+    {
+        $tmp = explode('/', $type);
+        if (!isset($tmp[1])) {
+            return null;
+        }
+        $tmp = explode(';', $tmp[1]);
+        return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
+    }
+
+
+    /**
+     * Create a textual MIME type from object values
+     *
+     * This function performs the opposite function of parse().
+     *
+     * @return string MIME type string
+     */
+    function get()
+    {
+        $type = strtolower($this->media . '/' . $this->subType);
+        if (count($this->parameters)) {
+            foreach ($this->parameters as $key => $null) {
+                $type .= '; ' . $this->parameters[$key]->get();
+            }
+        }
+        return $type;
+    }
+
+
+    /**
+     * Is this type experimental?
+     *
+     * @note Experimental types are denoted by a leading 'x-' in the media or
+     *       subtype, e.g. text/x-vcard or x-world/x-vrml.
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type is experimental, false otherwise
+     * @static
+     */
+    function isExperimental($type)
+    {
+        if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' ||
+            substr(MIME_Type::getSubType($type), 0, 2) == 'x-') {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Is this a vendor MIME type?
+     *
+     * @note Vendor types are denoted with a leading 'vnd. in the subtype.
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type is a vendor type, false otherwise
+     * @static
+     */
+    function isVendor($type)
+    {
+        if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Is this a wildcard type?
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type is a wildcard, false otherwise
+     * @static
+     */
+    function isWildcard($type)
+    {
+        if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Perform a wildcard match on a MIME type
+     *
+     * Example:
+     * MIME_Type::wildcardMatch('image/*', 'image/png')
+     *
+     * @param string $card Wildcard to check against
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if there was a match, false otherwise
+     * @static
+     */
+    function wildcardMatch($card, $type)
+    {
+        if (!MIME_Type::isWildcard($card)) {
+            return false;
+        }
+
+        if ($card == '*/*') {
+            return true;
+        }
+
+        if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Add a parameter to this type
+     *
+     * @param string $name    Attribute name
+     * @param string $value   Attribute value
+     * @param string $comment Comment for this parameter
+     *
+     * @return void
+     */
+    function addParameter($name, $value, $comment = false)
+    {
+        $tmp = new MIME_Type_Parameter();
+
+        $tmp->name               = $name;
+        $tmp->value              = $value;
+        $tmp->comment            = $comment;
+        $this->parameters[$name] = $tmp;
+    }
+
+
+    /**
+     * Remove a parameter from this type
+     *
+     * @param string $name Parameter name
+     *
+     * @return void
+     */
+    function removeParameter($name)
+    {
+        unset($this->parameters[$name]);
+    }
+
+
+    /**
+     * Autodetect a file's MIME-type
+     *
+     * This function may be called staticly.
+     *
+     * @internal Tries to use fileinfo extension at first. If that
+     *  does not work, mime_magic is used. If this is also not available
+     *  or does not succeed, "file" command is tried to be executed with
+     *  System_Command. When that fails, too, then we use our in-built
+     *  extension-to-mimetype-mapping list.
+     *
+     * @param string $file   Path to the file to get the type of
+     * @param bool   $params Append MIME parameters if true
+     *
+     * @return string $file's MIME-type on success, PEAR_Error otherwise
+     *
+     * @since 1.0.0beta1
+     * @static
+     */
+    function autoDetect($file, $params = false)
+    {
+        // Sanity checks
+        if (!file_exists($file)) {
+            return PEAR::raiseError("File \"$file\" doesn't exist");
+        }
+
+        if (!is_readable($file)) {
+            return PEAR::raiseError("File \"$file\" is not readable");
+        }
+
+        if (function_exists('finfo_file')) {
+            $finfo = finfo_open(FILEINFO_MIME);
+            $type  = finfo_file($finfo, $file);
+            finfo_close($finfo);
+            if ($type !== false && $type !== '') {
+                return MIME_Type::_handleDetection($type, $params);
+            }
+        }
+
+        if (function_exists('mime_content_type')) {
+            $type = mime_content_type($file);
+            if ($type !== false && $type !== '') {
+                return MIME_Type::_handleDetection($type, $params);
+            }
+        }
+
+        @include_once 'System/Command.php';
+        if (class_exists('System_Command')) {
+            return MIME_Type::_handleDetection(
+                MIME_Type::_fileAutoDetect($file),
+                $params
+            );
+        }
+
+        require_once 'MIME/Type/Extension.php';
+        $mte = new MIME_Type_Extension();
+        return $mte->getMIMEType($file);
+    }
+
+
+    /**
+     * Handles a detected MIME type and modifies it if necessary.
+     *
+     * @param string $type   MIME Type of a file
+     * @param bool   $params Append MIME parameters if true
+     *
+     * @return string $file's MIME-type on success, PEAR_Error otherwise
+     */
+    function _handleDetection($type, $params)
+    {
+        // _fileAutoDetect() may have returned an error.
+        if (PEAR::isError($type)) {
+            return $type;
+        }
+
+        // Don't return an empty string
+        if (!$type || !strlen($type)) {
+            return PEAR::raiseError("Sorry, couldn't determine file type.");
+        }
+
+        // Strip parameters if present & requested
+        if (MIME_Type::hasParameters($type) && !$params) {
+            $type = MIME_Type::stripParameters($type);
+        }
+
+        return $type;
+    }
+
+
+    /**
+     * Autodetect a file's MIME-type with 'file' and System_Command
+     *
+     * This function may be called staticly.
+     *
+     * @param string $file Path to the file to get the type of
+     *
+     * @return string $file's MIME-type
+     *
+     * @since 1.0.0beta1
+     * @static
+     */
+    function _fileAutoDetect($file)
+    {
+        $cmd = new System_Command();
+
+        // Make sure we have the 'file' command.
+        $fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+        if (!$cmd->which($fileCmd)) {
+            unset($cmd);
+            return PEAR::raiseError("Can't find file command \"{$fileCmd}\"");
+        }
+
+        $cmd->pushCommand($fileCmd, "-bi " . escapeshellarg($file));
+        $res = $cmd->execute();
+        unset($cmd);
+
+        return $res;
+    }
+}
+
diff --git a/extlib/MIME/Type/Extension.php b/extlib/MIME/Type/Extension.php
new file mode 100644 (file)
index 0000000..1987e2a
--- /dev/null
@@ -0,0 +1,298 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2009 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.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/3_0.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: Christian Schmidt <schmidt@php.net>                         |
+// +----------------------------------------------------------------------+
+//
+// $Id: Extension.php,v 1.1 2009/01/16 11:49:45 cweiske Exp $
+
+require_once 'PEAR.php';
+
+/**
+ * Class for mapping file extensions to MIME types.
+ *
+ * @category MIME
+ * @package  MIME_Type
+ * @author   Christian Schmidt <schmidt@php.net>
+ * @license  PHP License 3.0
+ * @version  1.2.0
+ * @link     http://pear.php.net/package/MIME_Type
+ */
+class MIME_Type_Extension
+{
+    /**
+     * Mapping between file extension and MIME type.
+     *
+     * @internal The array is sorted alphabetically by value and with primary
+     *  extension first. Be careful about not adding duplicate keys - PHP
+     *  silently ignores duplicates. The following command can be used for
+     *  checking for duplicates:
+     *    grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d
+     *  application/octet-stream is generally used as fallback when no other
+     *  MIME-type can be found, but the array does not contain a lot of such
+     *  unknown extension. One entry exists, though, to allow detection of
+     *  file extension for this MIME-type.
+     *
+     * @var array
+     */
+    var $extensionToType = array (
+        'ez'        => 'application/andrew-inset',
+        'atom'      => 'application/atom+xml',
+        'jar'       => 'application/java-archive',
+        'hqx'       => 'application/mac-binhex40',
+        'cpt'       => 'application/mac-compactpro',
+        'mathml'    => 'application/mathml+xml',
+        'doc'       => 'application/msword',
+        'dat'       => 'application/octet-stream',
+        'oda'       => 'application/oda',
+        'ogg'       => 'application/ogg',
+        'pdf'       => 'application/pdf',
+        'ai'        => 'application/postscript',
+        'eps'       => 'application/postscript',
+        'ps'        => 'application/postscript',
+        'rdf'       => 'application/rdf+xml',
+        'rss'       => 'application/rss+xml',
+        'smi'       => 'application/smil',
+        'smil'      => 'application/smil',
+        'gram'      => 'application/srgs',
+        'grxml'     => 'application/srgs+xml',
+        'kml'       => 'application/vnd.google-earth.kml+xml',
+        'kmz'       => 'application/vnd.google-earth.kmz',
+        'mif'       => 'application/vnd.mif',
+        'xul'       => 'application/vnd.mozilla.xul+xml',
+        'xls'       => 'application/vnd.ms-excel',
+        'xlb'       => 'application/vnd.ms-excel',
+        'xlt'       => 'application/vnd.ms-excel',
+        'xlam'      => 'application/vnd.ms-excel.addin.macroEnabled.12',
+        'xlsb'      => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+        'xlsm'      => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+        'xltm'      => 'application/vnd.ms-excel.template.macroEnabled.12',
+        'docm'      => 'application/vnd.ms-word.document.macroEnabled.12',
+        'dotm'      => 'application/vnd.ms-word.template.macroEnabled.12',
+        'ppam'      => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+        'pptm'      => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+        'ppsm'      => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+        'potm'      => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+        'ppt'       => 'application/vnd.ms-powerpoint',
+        'pps'       => 'application/vnd.ms-powerpoint',
+        'odc'       => 'application/vnd.oasis.opendocument.chart',
+        'odb'       => 'application/vnd.oasis.opendocument.database',
+        'odf'       => 'application/vnd.oasis.opendocument.formula',
+        'odg'       => 'application/vnd.oasis.opendocument.graphics',
+        'otg'       => 'application/vnd.oasis.opendocument.graphics-template',
+        'odi'       => 'application/vnd.oasis.opendocument.image',
+        'odp'       => 'application/vnd.oasis.opendocument.presentation',
+        'otp'       => 'application/vnd.oasis.opendocument.presentation-template',
+        'ods'       => 'application/vnd.oasis.opendocument.spreadsheet',
+        'ots'       => 'application/vnd.oasis.opendocument.spreadsheet-template',
+        'odt'       => 'application/vnd.oasis.opendocument.text',
+        'odm'       => 'application/vnd.oasis.opendocument.text-master',
+        'ott'       => 'application/vnd.oasis.opendocument.text-template',
+        'oth'       => 'application/vnd.oasis.opendocument.text-web',
+        'potx'      => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+        'ppsx'      => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+        'pptx'      => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+        'xlsx'      => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+        'xltx'      => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+        'docx'      => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+        'dotx'      => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+        'vsd'       => 'application/vnd.visio',
+        'wbxml'     => 'application/vnd.wap.wbxml',
+        'wmlc'      => 'application/vnd.wap.wmlc',
+        'wmlsc'     => 'application/vnd.wap.wmlscriptc',
+        'vxml'      => 'application/voicexml+xml',
+        'bcpio'     => 'application/x-bcpio',
+        'vcd'       => 'application/x-cdlink',
+        'pgn'       => 'application/x-chess-pgn',
+        'cpio'      => 'application/x-cpio',
+        'csh'       => 'application/x-csh',
+        'dcr'       => 'application/x-director',
+        'dir'       => 'application/x-director',
+        'dxr'       => 'application/x-director',
+        'dvi'       => 'application/x-dvi',
+        'spl'       => 'application/x-futuresplash',
+        'tgz'       => 'application/x-gtar',
+        'gtar'      => 'application/x-gtar',
+        'hdf'       => 'application/x-hdf',
+        'js'        => 'application/x-javascript',
+        'skp'       => 'application/x-koan',
+        'skd'       => 'application/x-koan',
+        'skt'       => 'application/x-koan',
+        'skm'       => 'application/x-koan',
+        'latex'     => 'application/x-latex',
+        'nc'        => 'application/x-netcdf',
+        'cdf'       => 'application/x-netcdf',
+        'sh'        => 'application/x-sh',
+        'shar'      => 'application/x-shar',
+        'swf'       => 'application/x-shockwave-flash',
+        'sit'       => 'application/x-stuffit',
+        'sv4cpio'   => 'application/x-sv4cpio',
+        'sv4crc'    => 'application/x-sv4crc',
+        'tar'       => 'application/x-tar',
+        'tcl'       => 'application/x-tcl',
+        'tex'       => 'application/x-tex',
+        'texinfo'   => 'application/x-texinfo',
+        'texi'      => 'application/x-texinfo',
+        't'         => 'application/x-troff',
+        'tr'        => 'application/x-troff',
+        'roff'      => 'application/x-troff',
+        'man'       => 'application/x-troff-man',
+        'me'        => 'application/x-troff-me',
+        'ms'        => 'application/x-troff-ms',
+        'ustar'     => 'application/x-ustar',
+        'src'       => 'application/x-wais-source',
+        'xhtml'     => 'application/xhtml+xml',
+        'xht'       => 'application/xhtml+xml',
+        'xslt'      => 'application/xslt+xml',
+        'xml'       => 'application/xml',
+        'xsl'       => 'application/xml',
+        'dtd'       => 'application/xml-dtd',
+        'zip'       => 'application/zip',
+        'au'        => 'audio/basic',
+        'snd'       => 'audio/basic',
+        'mid'       => 'audio/midi',
+        'midi'      => 'audio/midi',
+        'kar'       => 'audio/midi',
+        'mpga'      => 'audio/mpeg',
+        'mp2'       => 'audio/mpeg',
+        'mp3'       => 'audio/mpeg',
+        'aif'       => 'audio/x-aiff',
+        'aiff'      => 'audio/x-aiff',
+        'aifc'      => 'audio/x-aiff',
+        'm3u'       => 'audio/x-mpegurl',
+        'wma'       => 'audio/x-ms-wma',
+        'wax'       => 'audio/x-ms-wax',
+        'ram'       => 'audio/x-pn-realaudio',
+        'ra'        => 'audio/x-pn-realaudio',
+        'rm'        => 'application/vnd.rn-realmedia',
+        'wav'       => 'audio/x-wav',
+        'pdb'       => 'chemical/x-pdb',
+        'xyz'       => 'chemical/x-xyz',
+        'bmp'       => 'image/bmp',
+        'cgm'       => 'image/cgm',
+        'gif'       => 'image/gif',
+        'ief'       => 'image/ief',
+        'jpeg'      => 'image/jpeg',
+        'jpg'       => 'image/jpeg',
+        'jpe'       => 'image/jpeg',
+        'png'       => 'image/png',
+        'svg'       => 'image/svg+xml',
+        'tiff'      => 'image/tiff',
+        'tif'       => 'image/tiff',
+        'djvu'      => 'image/vnd.djvu',
+        'djv'       => 'image/vnd.djvu',
+        'wbmp'      => 'image/vnd.wap.wbmp',
+        'ras'       => 'image/x-cmu-raster',
+        'ico'       => 'image/x-icon',
+        'pnm'       => 'image/x-portable-anymap',
+        'pbm'       => 'image/x-portable-bitmap',
+        'pgm'       => 'image/x-portable-graymap',
+        'ppm'       => 'image/x-portable-pixmap',
+        'rgb'       => 'image/x-rgb',
+        'xbm'       => 'image/x-xbitmap',
+        'psd'       => 'image/x-photoshop',
+        'xpm'       => 'image/x-xpixmap',
+        'xwd'       => 'image/x-xwindowdump',
+        'eml'       => 'message/rfc822',
+        'igs'       => 'model/iges',
+        'iges'      => 'model/iges',
+        'msh'       => 'model/mesh',
+        'mesh'      => 'model/mesh',
+        'silo'      => 'model/mesh',
+        'wrl'       => 'model/vrml',
+        'vrml'      => 'model/vrml',
+        'ics'       => 'text/calendar',
+        'ifb'       => 'text/calendar',
+        'css'       => 'text/css',
+        'csv'       => 'text/csv',
+        'html'      => 'text/html',
+        'htm'       => 'text/html',
+        'txt'       => 'text/plain',
+        'asc'       => 'text/plain',
+        'rtx'       => 'text/richtext',
+        'rtf'       => 'text/rtf',
+        'sgml'      => 'text/sgml',
+        'sgm'       => 'text/sgml',
+        'tsv'       => 'text/tab-separated-values',
+        'wml'       => 'text/vnd.wap.wml',
+        'wmls'      => 'text/vnd.wap.wmlscript',
+        'etx'       => 'text/x-setext',
+        'mpeg'      => 'video/mpeg',
+        'mpg'       => 'video/mpeg',
+        'mpe'       => 'video/mpeg',
+        'qt'        => 'video/quicktime',
+        'mov'       => 'video/quicktime',
+        'mxu'       => 'video/vnd.mpegurl',
+        'm4u'       => 'video/vnd.mpegurl',
+        'flv'       => 'video/x-flv',
+        'asf'       => 'video/x-ms-asf',
+        'asx'       => 'video/x-ms-asf',
+        'wmv'       => 'video/x-ms-wmv',
+        'wm'        => 'video/x-ms-wm',
+        'wmx'       => 'video/x-ms-wmx',
+        'avi'       => 'video/x-msvideo',
+        'ogv'       => 'video/ogg',
+        'movie'     => 'video/x-sgi-movie',
+        'ice'       => 'x-conference/x-cooltalk',
+    );
+
+
+
+    /**
+     * Autodetect a file's MIME-type.
+     *
+     * @param string $file Path to the file to get the type of
+     *
+     * @return string $file's MIME-type on success, PEAR_Error otherwise
+     */
+    function getMIMEType($file)
+    {
+        $extension = substr(strrchr($file, '.'), 1);
+        if ($extension === false) {
+            return PEAR::raiseError("File has no extension.");
+        }
+
+        if (!isset($this->extensionToType[$extension])) {
+            return PEAR::raiseError("Sorry, couldn't determine file type.");
+        }
+
+        return $this->extensionToType[$extension];
+    }
+
+
+
+    /**
+     * Return default MIME-type for the specified extension.
+     *
+     * @param string $type MIME-type
+     *
+     * @return string A file extension without leading period.
+     */
+    function getExtension($type)
+    {
+        require_once 'MIME/Type.php';
+        // Strip parameters and comments.
+        $type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type);
+
+        $extension = array_search($type, $this->extensionToType);
+        if ($extension === false) {
+            return PEAR::raiseError("Sorry, couldn't determine extension.");
+        }
+        return $extension;
+    }
+
+}
+
+?>
\ No newline at end of file
diff --git a/extlib/MIME/Type/Parameter.php b/extlib/MIME/Type/Parameter.php
new file mode 100644 (file)
index 0000000..399d3dd
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.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/3_0.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: Ian Eure <ieure@php.net>                                    |
+// +----------------------------------------------------------------------+
+//
+// $Id: Parameter.php,v 1.1 2007/03/25 10:10:21 cweiske Exp $
+
+/**
+ * Class for working with MIME type parameters
+ *
+ * @version 1.2.0
+ * @package MIME_Type
+ * @author Ian Eure <ieure@php.net>
+ */
+class MIME_Type_Parameter {
+    /**
+     * Parameter name
+     *
+     * @var string
+     */
+    var $name;
+
+    /**
+     * Parameter value
+     *
+     * @var string
+     */
+    var $value;
+
+    /**
+     * Parameter comment
+     *
+     * @var string
+     */
+    var $comment;
+
+
+    /**
+     * Constructor.
+     *
+     * @param  string $param MIME parameter to parse, if set.
+     * @return void
+     */
+    function MIME_Type_Parameter($param = false)
+    {
+        if ($param) {
+            $this->parse($param);
+        }
+    }
+
+
+    /**
+     * Parse a MIME type parameter and set object fields
+     *
+     * @param  string $param MIME type parameter to parse
+     * @return void
+     */
+    function parse($param)
+    {
+        $comment = '';
+        $param   = MIME_Type::stripComments($param, $comment);
+        $this->name    = $this->getAttribute($param);
+        $this->value   = $this->getValue($param);
+        $this->comment = $comment;
+    }
+
+
+    /**
+     * Get a parameter attribute (e.g. name)
+     *
+     * @param  string MIME type parameter
+     * @return string Attribute name
+     * @static
+     */
+    function getAttribute($param)
+    {
+        $tmp = explode('=', $param);
+        return trim($tmp[0]);
+    }
+
+
+    /**
+     * Get a parameter value
+     *
+     * @param  string $param MIME type parameter
+     * @return string Value
+     * @static
+     */
+    function getValue($param)
+    {
+        $tmp = explode('=', $param, 2);
+        $value = $tmp[1];
+        $value = trim($value);
+        if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
+            $value = substr($value, 1, -1);
+        }
+        $value = str_replace('\\"', '"', $value);
+        return $value;
+    }
+
+
+    /**
+     * Get a parameter comment
+     *
+     * @param  string $param MIME type parameter
+     * @return string Parameter comment
+     * @see getComment()
+     * @static
+     */
+    function getComment($param)
+    {
+        $cs = strpos($param, '(');
+        $comment = substr($param, $cs);
+        return trim($comment, '() ');
+    }
+
+
+    /**
+     * Does this parameter have a comment?
+     *
+     * @param  string  $param MIME type parameter
+     * @return boolean true if $param has a comment, false otherwise
+     * @static
+     */
+    function hasComment($param)
+    {
+        if (strstr($param, '(')) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Get a string representation of this parameter
+     *
+     * This function performs the oppsite of parse()
+     *
+     * @return string String representation of parameter
+     */
+    function get()
+    {
+        $val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"';
+        if ($this->comment) {
+            $val .= ' (' . $this->comment . ')';
+        }
+        return $val;
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/Mail/mimeDecode.php b/extlib/Mail/mimeDecode.php
new file mode 100644 (file)
index 0000000..aaa870c
--- /dev/null
@@ -0,0 +1,849 @@
+<?php
+/**
+ * The Mail_mimeDecode class is used to decode mail/mime messages
+ *
+ * This class will parse a raw mime email and return
+ * the structure. Returned structure is similar to
+ * that returned by imap_fetchstructure().
+ *
+ *  +----------------------------- IMPORTANT ------------------------------+
+ *  | Usage of this class compared to native php extensions such as        |
+ *  | mailparse or imap, is slow and may be feature deficient. If available|
+ *  | you are STRONGLY recommended to use the php extensions.              |
+ *  +----------------------------------------------------------------------+
+ *
+ * Compatible with PHP versions 4 and 5
+ *
+ * LICENSE: This LICENSE is in the BSD license style.
+ * Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
+ * Copyright (c) 2003-2006, PEAR <pear-group@php.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 of the authors, 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.
+ *
+ * @category   Mail
+ * @package    Mail_Mime
+ * @author     Richard Heyes  <richard@phpguru.org>
+ * @author     George Schlossnagle <george@omniti.com>
+ * @author     Cipriano Groenendal <cipri@php.net>
+ * @author     Sean Coates <sean@php.net>
+ * @copyright  2003-2006 PEAR <pear-group@php.net>
+ * @license    http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version    CVS: $Id: mimeDecode.php,v 1.48 2006/12/03 13:43:33 cipri Exp $
+ * @link       http://pear.php.net/package/Mail_mime
+ */
+
+
+/**
+ * require PEAR
+ *
+ * This package depends on PEAR to raise errors.
+ */
+require_once 'PEAR.php';
+
+
+/**
+ * The Mail_mimeDecode class is used to decode mail/mime messages
+ *
+ * This class will parse a raw mime email and return the structure.
+ * Returned structure is similar to that returned by imap_fetchstructure().
+ *
+ *  +----------------------------- IMPORTANT ------------------------------+
+ *  | Usage of this class compared to native php extensions such as        |
+ *  | mailparse or imap, is slow and may be feature deficient. If available|
+ *  | you are STRONGLY recommended to use the php extensions.              |
+ *  +----------------------------------------------------------------------+
+ *
+ * @category   Mail
+ * @package    Mail_Mime
+ * @author     Richard Heyes  <richard@phpguru.org>
+ * @author     George Schlossnagle <george@omniti.com>
+ * @author     Cipriano Groenendal <cipri@php.net>
+ * @author     Sean Coates <sean@php.net>
+ * @copyright  2003-2006 PEAR <pear-group@php.net>
+ * @license    http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version    Release: @package_version@
+ * @link       http://pear.php.net/package/Mail_mime
+ */
+class Mail_mimeDecode extends PEAR
+{
+    /**
+     * The raw email to decode
+     *
+     * @var    string
+     * @access private
+     */
+    var $_input;
+
+    /**
+     * The header part of the input
+     *
+     * @var    string
+     * @access private
+     */
+    var $_header;
+
+    /**
+     * The body part of the input
+     *
+     * @var    string
+     * @access private
+     */
+    var $_body;
+
+    /**
+     * If an error occurs, this is used to store the message
+     *
+     * @var    string
+     * @access private
+     */
+    var $_error;
+
+    /**
+     * Flag to determine whether to include bodies in the
+     * returned object.
+     *
+     * @var    boolean
+     * @access private
+     */
+    var $_include_bodies;
+
+    /**
+     * Flag to determine whether to decode bodies
+     *
+     * @var    boolean
+     * @access private
+     */
+    var $_decode_bodies;
+
+    /**
+     * Flag to determine whether to decode headers
+     *
+     * @var    boolean
+     * @access private
+     */
+    var $_decode_headers;
+
+    /**
+     * Constructor.
+     *
+     * Sets up the object, initialise the variables, and splits and
+     * stores the header and body of the input.
+     *
+     * @param string The input to decode
+     * @access public
+     */
+    function Mail_mimeDecode($input)
+    {
+        list($header, $body)   = $this->_splitBodyHeader($input);
+
+        $this->_input          = $input;
+        $this->_header         = $header;
+        $this->_body           = $body;
+        $this->_decode_bodies  = false;
+        $this->_include_bodies = true;
+    }
+
+    /**
+     * Begins the decoding process. If called statically
+     * it will create an object and call the decode() method
+     * of it.
+     *
+     * @param array An array of various parameters that determine
+     *              various things:
+     *              include_bodies - Whether to include the body in the returned
+     *                               object.
+     *              decode_bodies  - Whether to decode the bodies
+     *                               of the parts. (Transfer encoding)
+     *              decode_headers - Whether to decode headers
+     *              input          - If called statically, this will be treated
+     *                               as the input
+     * @return object Decoded results
+     * @access public
+     */
+    function decode($params = null)
+    {
+        // determine if this method has been called statically
+        $isStatic = !(isset($this) && get_class($this) == __CLASS__);
+
+        // Have we been called statically?
+       // If so, create an object and pass details to that.
+        if ($isStatic AND isset($params['input'])) {
+
+            $obj = new Mail_mimeDecode($params['input']);
+            $structure = $obj->decode($params);
+
+        // Called statically but no input
+        } elseif ($isStatic) {
+            return PEAR::raiseError('Called statically and no input given');
+
+        // Called via an object
+        } else {
+            $this->_include_bodies = isset($params['include_bodies']) ?
+                                    $params['include_bodies'] : false;
+            $this->_decode_bodies  = isset($params['decode_bodies']) ?
+                                    $params['decode_bodies']  : false;
+            $this->_decode_headers = isset($params['decode_headers']) ?
+                                    $params['decode_headers'] : false;
+
+            $structure = $this->_decode($this->_header, $this->_body);
+            if ($structure === false) {
+                $structure = $this->raiseError($this->_error);
+            }
+        }
+
+        return $structure;
+    }
+
+    /**
+     * Performs the decoding. Decodes the body string passed to it
+     * If it finds certain content-types it will call itself in a
+     * recursive fashion
+     *
+     * @param string Header section
+     * @param string Body section
+     * @return object Results of decoding process
+     * @access private
+     */
+    function _decode($headers, $body, $default_ctype = 'text/plain')
+    {
+        $return = new stdClass;
+        $return->headers = array();
+        $headers = $this->_parseHeaders($headers);
+
+        foreach ($headers as $value) {
+            if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
+                $return->headers[strtolower($value['name'])]   = array($return->headers[strtolower($value['name'])]);
+                $return->headers[strtolower($value['name'])][] = $value['value'];
+
+            } elseif (isset($return->headers[strtolower($value['name'])])) {
+                $return->headers[strtolower($value['name'])][] = $value['value'];
+
+            } else {
+                $return->headers[strtolower($value['name'])] = $value['value'];
+            }
+        }
+
+        reset($headers);
+        while (list($key, $value) = each($headers)) {
+            $headers[$key]['name'] = strtolower($headers[$key]['name']);
+            switch ($headers[$key]['name']) {
+
+                case 'content-type':
+                    $content_type = $this->_parseHeaderValue($headers[$key]['value']);
+
+                    if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
+                        $return->ctype_primary   = $regs[1];
+                        $return->ctype_secondary = $regs[2];
+                    }
+
+                    if (isset($content_type['other'])) {
+                        while (list($p_name, $p_value) = each($content_type['other'])) {
+                            $return->ctype_parameters[$p_name] = $p_value;
+                        }
+                    }
+                    break;
+
+                case 'content-disposition':
+                    $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
+                    $return->disposition   = $content_disposition['value'];
+                    if (isset($content_disposition['other'])) {
+                        while (list($p_name, $p_value) = each($content_disposition['other'])) {
+                            $return->d_parameters[$p_name] = $p_value;
+                        }
+                    }
+                    break;
+
+                case 'content-transfer-encoding':
+                    $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
+                    break;
+            }
+        }
+
+        if (isset($content_type)) {
+            switch (strtolower($content_type['value'])) {
+                case 'text/plain':
+                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
+                    break;
+
+                case 'text/html':
+                    $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding) : $body) : null;
+                    break;
+
+                case 'multipart/parallel':
+                case 'multipart/appledouble': // Appledouble mail
+                case 'multipart/report': // RFC1892
+                case 'multipart/signed': // PGP
+                case 'multipart/digest':
+                case 'multipart/alternative':
+                case 'multipart/related':
+                case 'multipart/mixed':
+                    if(!isset($content_type['other']['boundary'])){
+                        $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
+                        return false;
+                    }
+
+                    $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
+
+                    $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
+                    for ($i = 0; $i < count($parts); $i++) {
+                        list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
+                        $part = $this->_decode($part_header, $part_body, $default_ctype);
+                        if($part === false)
+                            $part = $this->raiseError($this->_error);
+                        $return->parts[] = $part;
+                    }
+                    break;
+
+                case 'message/rfc822':
+                    $obj = &new Mail_mimeDecode($body);
+                    $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
+                                                                             'decode_bodies'  => $this->_decode_bodies,
+                                                                                                                 'decode_headers' => $this->_decode_headers));
+                    unset($obj);
+                    break;
+
+                default:
+                    if(!isset($content_transfer_encoding['value']))
+                        $content_transfer_encoding['value'] = '7bit';
+                    $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value']) : $body) : null;
+                    break;
+            }
+
+        } else {
+            $ctype = explode('/', $default_ctype);
+            $return->ctype_primary   = $ctype[0];
+            $return->ctype_secondary = $ctype[1];
+            $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
+        }
+
+        return $return;
+    }
+
+    /**
+     * Given the output of the above function, this will return an
+     * array of references to the parts, indexed by mime number.
+     *
+     * @param  object $structure   The structure to go through
+     * @param  string $mime_number Internal use only.
+     * @return array               Mime numbers
+     */
+    function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
+    {
+        $return = array();
+        if (!empty($structure->parts)) {
+            if ($mime_number != '') {
+                $structure->mime_id = $prepend . $mime_number;
+                $return[$prepend . $mime_number] = &$structure;
+            }
+            for ($i = 0; $i < count($structure->parts); $i++) {
+
+            
+                if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
+                    $prepend      = $prepend . $mime_number . '.';
+                    $_mime_number = '';
+                } else {
+                    $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
+                }
+
+                $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
+                foreach ($arr as $key => $val) {
+                    $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
+                }
+            }
+        } else {
+            if ($mime_number == '') {
+                $mime_number = '1';
+            }
+            $structure->mime_id = $prepend . $mime_number;
+            $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
+        }
+        
+        return $return;
+    }
+
+    /**
+     * Given a string containing a header and body
+     * section, this function will split them (at the first
+     * blank line) and return them.
+     *
+     * @param string Input to split apart
+     * @return array Contains header and body section
+     * @access private
+     */
+    function _splitBodyHeader($input)
+    {
+        if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
+            return array($match[1], $match[2]);
+        }
+        $this->_error = 'Could not split header and body';
+        return false;
+    }
+
+    /**
+     * Parse headers given in $input and return
+     * as assoc array.
+     *
+     * @param string Headers to parse
+     * @return array Contains parsed headers
+     * @access private
+     */
+    function _parseHeaders($input)
+    {
+
+        if ($input !== '') {
+            // Unfold the input
+            $input   = preg_replace("/\r?\n/", "\r\n", $input);
+            $input   = preg_replace("/\r\n(\t| )+/", ' ', $input);
+            $headers = explode("\r\n", trim($input));
+
+            foreach ($headers as $value) {
+                $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
+                $hdr_value = substr($value, $pos+1);
+                if($hdr_value[0] == ' ')
+                    $hdr_value = substr($hdr_value, 1);
+
+                $return[] = array(
+                                  'name'  => $hdr_name,
+                                  'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
+                                 );
+            }
+        } else {
+            $return = array();
+        }
+
+        return $return;
+    }
+
+    /**
+     * Function to parse a header value,
+     * extract first part, and any secondary
+     * parts (after ;) This function is not as
+     * robust as it could be. Eg. header comments
+     * in the wrong place will probably break it.
+     *
+     * @param string Header value to parse
+     * @return array Contains parsed result
+     * @access private
+     */
+    function _parseHeaderValue($input)
+    {
+
+        if (($pos = strpos($input, ';')) !== false) {
+
+            $return['value'] = trim(substr($input, 0, $pos));
+            $input = trim(substr($input, $pos+1));
+
+            if (strlen($input) > 0) {
+
+                // This splits on a semi-colon, if there's no preceeding backslash
+                // Now works with quoted values; had to glue the \; breaks in PHP
+                // the regex is already bordering on incomprehensible
+                $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
+                preg_match_all($splitRegex, $input, $matches);
+                $parameters = array();
+                for ($i=0; $i<count($matches[0]); $i++) {
+                    $param = $matches[0][$i];
+                    while (substr($param, -2) == '\;') {
+                        $param .= $matches[0][++$i];
+                    }
+                    $parameters[] = $param;
+                }
+
+                for ($i = 0; $i < count($parameters); $i++) {
+                    $param_name  = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
+                    $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
+                    if ($param_value[0] == '"') {
+                        $param_value = substr($param_value, 1, -1);
+                    }
+                    $return['other'][$param_name] = $param_value;
+                    $return['other'][strtolower($param_name)] = $param_value;
+                }
+            }
+        } else {
+            $return['value'] = trim($input);
+        }
+
+        return $return;
+    }
+
+    /**
+     * This function splits the input based
+     * on the given boundary
+     *
+     * @param string Input to parse
+     * @return array Contains array of resulting mime parts
+     * @access private
+     */
+    function _boundarySplit($input, $boundary)
+    {
+        $parts = array();
+
+        $bs_possible = substr($boundary, 2, -2);
+        $bs_check = '\"' . $bs_possible . '\"';
+
+        if ($boundary == $bs_check) {
+            $boundary = $bs_possible;
+        }
+
+        $tmp = explode('--' . $boundary, $input);
+
+        for ($i = 1; $i < count($tmp) - 1; $i++) {
+            $parts[] = $tmp[$i];
+        }
+
+        return $parts;
+    }
+
+    /**
+     * Given a header, this function will decode it
+     * according to RFC2047. Probably not *exactly*
+     * conformant, but it does pass all the given
+     * examples (in RFC2047).
+     *
+     * @param string Input header value to decode
+     * @return string Decoded header value
+     * @access private
+     */
+    function _decodeHeader($input)
+    {
+        // Remove white space between encoded-words
+        $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
+
+        // For each encoded-word...
+        while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches)) {
+
+            $encoded  = $matches[1];
+            $charset  = $matches[2];
+            $encoding = $matches[3];
+            $text     = $matches[4];
+
+            switch (strtolower($encoding)) {
+                case 'b':
+                    $text = base64_decode($text);
+                    break;
+
+                case 'q':
+                    $text = str_replace('_', ' ', $text);
+                    preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
+                    foreach($matches[1] as $value)
+                        $text = str_replace('='.$value, chr(hexdec($value)), $text);
+                    break;
+            }
+
+            $input = str_replace($encoded, $text, $input);
+        }
+
+        return $input;
+    }
+
+    /**
+     * Given a body string and an encoding type,
+     * this function will decode and return it.
+     *
+     * @param  string Input body to decode
+     * @param  string Encoding type to use.
+     * @return string Decoded body
+     * @access private
+     */
+    function _decodeBody($input, $encoding = '7bit')
+    {
+        switch (strtolower($encoding)) {
+            case '7bit':
+                return $input;
+                break;
+
+            case 'quoted-printable':
+                return $this->_quotedPrintableDecode($input);
+                break;
+
+            case 'base64':
+                return base64_decode($input);
+                break;
+
+            default:
+                return $input;
+        }
+    }
+
+    /**
+     * Given a quoted-printable string, this
+     * function will decode and return it.
+     *
+     * @param  string Input body to decode
+     * @return string Decoded body
+     * @access private
+     */
+    function _quotedPrintableDecode($input)
+    {
+        // Remove soft line breaks
+        $input = preg_replace("/=\r?\n/", '', $input);
+
+        // Replace encoded characters
+               $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
+
+        return $input;
+    }
+
+    /**
+     * Checks the input for uuencoded files and returns
+     * an array of them. Can be called statically, eg:
+     *
+     * $files =& Mail_mimeDecode::uudecode($some_text);
+     *
+     * It will check for the begin 666 ... end syntax
+     * however and won't just blindly decode whatever you
+     * pass it.
+     *
+     * @param  string Input body to look for attahcments in
+     * @return array  Decoded bodies, filenames and permissions
+     * @access public
+     * @author Unknown
+     */
+    function &uudecode($input)
+    {
+        // Find all uuencoded sections
+        preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
+
+        for ($j = 0; $j < count($matches[3]); $j++) {
+
+            $str      = $matches[3][$j];
+            $filename = $matches[2][$j];
+            $fileperm = $matches[1][$j];
+
+            $file = '';
+            $str = preg_split("/\r?\n/", trim($str));
+            $strlen = count($str);
+
+            for ($i = 0; $i < $strlen; $i++) {
+                $pos = 1;
+                $d = 0;
+                $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
+
+                while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
+                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+                    $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
+                    $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
+                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
+
+                    $file .= chr(((($c2 - ' ') & 077) << 6) |  (($c3 - ' ') & 077));
+
+                    $pos += 4;
+                    $d += 3;
+                }
+
+                if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
+                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+                    $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
+                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+                    $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
+
+                    $pos += 3;
+                    $d += 2;
+                }
+
+                if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
+                    $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+                    $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+                    $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+                }
+            }
+            $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
+        }
+
+        return $files;
+    }
+
+    /**
+     * getSendArray() returns the arguments required for Mail::send()
+     * used to build the arguments for a mail::send() call 
+     *
+     * Usage:
+     * $mailtext = Full email (for example generated by a template)
+     * $decoder = new Mail_mimeDecode($mailtext);
+     * $parts =  $decoder->getSendArray();
+     * if (!PEAR::isError($parts) {
+     *     list($recipents,$headers,$body) = $parts;
+     *     $mail = Mail::factory('smtp');
+     *     $mail->send($recipents,$headers,$body);
+     * } else {
+     *     echo $parts->message;
+     * }
+     * @return mixed   array of recipeint, headers,body or Pear_Error
+     * @access public
+     * @author Alan Knowles <alan@akbkhome.com>
+     */
+    function getSendArray()
+    {
+        // prevent warning if this is not set
+        $this->_decode_headers = FALSE;
+        $headerlist =$this->_parseHeaders($this->_header);
+        $to = "";
+        if (!$headerlist) {
+            return $this->raiseError("Message did not contain headers");
+        }
+        foreach($headerlist as $item) {
+            $header[$item['name']] = $item['value'];
+            switch (strtolower($item['name'])) {
+                case "to":
+                case "cc":
+                case "bcc":
+                    $to = ",".$item['value'];
+                default:
+                   break;
+            }
+        }
+        if ($to == "") {
+            return $this->raiseError("Message did not contain any recipents");
+        }
+        $to = substr($to,1);
+        return array($to,$header,$this->_body);
+    } 
+
+    /**
+     * Returns a xml copy of the output of
+     * Mail_mimeDecode::decode. Pass the output in as the
+     * argument. This function can be called statically. Eg:
+     *
+     * $output = $obj->decode();
+     * $xml    = Mail_mimeDecode::getXML($output);
+     *
+     * The DTD used for this should have been in the package. Or
+     * alternatively you can get it from cvs, or here:
+     * http://www.phpguru.org/xmail/xmail.dtd.
+     *
+     * @param  object Input to convert to xml. This should be the
+     *                output of the Mail_mimeDecode::decode function
+     * @return string XML version of input
+     * @access public
+     */
+    function getXML($input)
+    {
+        $crlf    =  "\r\n";
+        $output  = '<?xml version=\'1.0\'?>' . $crlf .
+                   '<!DOCTYPE email SYSTEM "http://www.phpguru.org/xmail/xmail.dtd">' . $crlf .
+                   '<email>' . $crlf .
+                   Mail_mimeDecode::_getXML($input) .
+                   '</email>';
+
+        return $output;
+    }
+
+    /**
+     * Function that does the actual conversion to xml. Does a single
+     * mimepart at a time.
+     *
+     * @param  object  Input to convert to xml. This is a mimepart object.
+     *                 It may or may not contain subparts.
+     * @param  integer Number of tabs to indent
+     * @return string  XML version of input
+     * @access private
+     */
+    function _getXML($input, $indent = 1)
+    {
+        $htab    =  "\t";
+        $crlf    =  "\r\n";
+        $output  =  '';
+        $headers = @(array)$input->headers;
+
+        foreach ($headers as $hdr_name => $hdr_value) {
+
+            // Multiple headers with this name
+            if (is_array($headers[$hdr_name])) {
+                for ($i = 0; $i < count($hdr_value); $i++) {
+                    $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
+                }
+
+            // Only one header of this sort
+            } else {
+                $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
+            }
+        }
+
+        if (!empty($input->parts)) {
+            for ($i = 0; $i < count($input->parts); $i++) {
+                $output .= $crlf . str_repeat($htab, $indent) . '<mimepart>' . $crlf .
+                           Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
+                           str_repeat($htab, $indent) . '</mimepart>' . $crlf;
+            }
+        } elseif (isset($input->body)) {
+            $output .= $crlf . str_repeat($htab, $indent) . '<body><![CDATA[' .
+                       $input->body . ']]></body>' . $crlf;
+        }
+
+        return $output;
+    }
+
+    /**
+     * Helper function to _getXML(). Returns xml of a header.
+     *
+     * @param  string  Name of header
+     * @param  string  Value of header
+     * @param  integer Number of tabs to indent
+     * @return string  XML version of input
+     * @access private
+     */
+    function _getXML_helper($hdr_name, $hdr_value, $indent)
+    {
+        $htab   = "\t";
+        $crlf   = "\r\n";
+        $return = '';
+
+        $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
+        $new_hdr_name  = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
+
+        // Sort out any parameters
+        if (!empty($new_hdr_value['other'])) {
+            foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
+                $params[] = str_repeat($htab, $indent) . $htab . '<parameter>' . $crlf .
+                            str_repeat($htab, $indent) . $htab . $htab . '<paramname>' . htmlspecialchars($paramname) . '</paramname>' . $crlf .
+                            str_repeat($htab, $indent) . $htab . $htab . '<paramvalue>' . htmlspecialchars($paramvalue) . '</paramvalue>' . $crlf .
+                            str_repeat($htab, $indent) . $htab . '</parameter>' . $crlf;
+            }
+
+            $params = implode('', $params);
+        } else {
+            $params = '';
+        }
+
+        $return = str_repeat($htab, $indent) . '<header>' . $crlf .
+                  str_repeat($htab, $indent) . $htab . '<headername>' . htmlspecialchars($new_hdr_name) . '</headername>' . $crlf .
+                  str_repeat($htab, $indent) . $htab . '<headervalue>' . htmlspecialchars($new_hdr_value['value']) . '</headervalue>' . $crlf .
+                  $params .
+                  str_repeat($htab, $indent) . '</header>' . $crlf;
+
+        return $return;
+    }
+
+} // End of class
diff --git a/extlib/Net/URL2.php b/extlib/Net/URL2.php
new file mode 100644 (file)
index 0000000..7a654ae
--- /dev/null
@@ -0,0 +1,813 @@
+<?php
+// +-----------------------------------------------------------------------+
+// | Copyright (c) 2007-2008, Christian Schmidt, Peytz & Co. A/S           |
+// | 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.  |
+// |                                                                       |
+// +-----------------------------------------------------------------------+
+// | Author: Christian Schmidt <schmidt at php dot net>                    |
+// +-----------------------------------------------------------------------+
+//
+// $Id: URL2.php,v 1.10 2008/04/26 21:57:08 schmidt Exp $
+//
+// Net_URL2 Class (PHP5 Only)
+
+// This code is released under the BSD License - http://www.opensource.org/licenses/bsd-license.php
+/**
+ * @license BSD License
+ */
+class Net_URL2
+{
+    /**
+     * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
+     * is true.
+     */
+    const OPTION_STRICT           = 'strict';
+
+    /**
+     * Represent arrays in query using PHP's [] notation. Default is true.
+     */
+    const OPTION_USE_BRACKETS     = 'use_brackets';
+
+    /**
+     * URL-encode query variable keys. Default is true.
+     */
+    const OPTION_ENCODE_KEYS      = 'encode_keys';
+
+    /**
+     * Query variable separators when parsing the query string. Every character
+     * is considered a separator. Default is specified by the
+     * arg_separator.input php.ini setting (this defaults to "&").
+     */
+    const OPTION_SEPARATOR_INPUT  = 'input_separator';
+
+    /**
+     * Query variable separator used when generating the query string. Default
+     * is specified by the arg_separator.output php.ini setting (this defaults
+     * to "&").
+     */
+    const OPTION_SEPARATOR_OUTPUT = 'output_separator';
+
+    /**
+     * Default options corresponds to how PHP handles $_GET.
+     */
+    private $options = array(
+        self::OPTION_STRICT           => true,
+        self::OPTION_USE_BRACKETS     => true,
+        self::OPTION_ENCODE_KEYS      => true,
+        self::OPTION_SEPARATOR_INPUT  => 'x&',
+        self::OPTION_SEPARATOR_OUTPUT => 'x&',
+        );
+
+    /**
+     * @var  string|bool
+     */
+    private $scheme = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $userinfo = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $host = false;
+
+    /**
+     * @var  int|bool
+     */
+    private $port = false;
+
+    /**
+     * @var  string
+     */
+    private $path = '';
+
+    /**
+     * @var  string|bool
+     */
+    private $query = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $fragment = false;
+
+    /**
+     * @param string $url     an absolute or relative URL
+     * @param array  $options
+     */
+    public function __construct($url, $options = null)
+    {
+        $this->setOption(self::OPTION_SEPARATOR_INPUT,
+                         ini_get('arg_separator.input'));
+        $this->setOption(self::OPTION_SEPARATOR_OUTPUT,
+                         ini_get('arg_separator.output'));
+        if (is_array($options)) {
+            foreach ($options as $optionName => $value) {
+                $this->setOption($optionName);
+            }
+        }
+
+        if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) {
+            $this->scheme = $reg[1];
+            $url = substr($url, strlen($reg[0]));
+        }
+
+        if (preg_match('@^//([^/#?]+)@', $url, $reg)) {
+            $this->setAuthority($reg[1]);
+            $url = substr($url, strlen($reg[0]));
+        }
+
+        $i = strcspn($url, '?#');
+        $this->path = substr($url, 0, $i);
+        $url = substr($url, $i);
+
+        if (preg_match('@^\?([^#]*)@', $url, $reg)) {
+            $this->query = $reg[1];
+            $url = substr($url, strlen($reg[0]));
+        }
+
+        if ($url) {
+            $this->fragment = substr($url, 1);
+        }
+    }
+
+    /**
+     * Returns the scheme, e.g. "http" or "urn", or false if there is no
+     * scheme specified, i.e. if this is a relative URL.
+     *
+     * @return  string|bool
+     */
+    public function getScheme()
+    {
+        return $this->scheme;
+    }
+
+    /**
+     * @param string|bool $scheme
+     *
+     * @return void
+     * @see    getScheme()
+     */
+    public function setScheme($scheme)
+    {
+        $this->scheme = $scheme;
+    }
+
+    /**
+     * Returns the user part of the userinfo part (the part preceding the first
+     *  ":"), or false if there is no userinfo part.
+     *
+     * @return  string|bool
+     */
+    public function getUser()
+    {
+        return $this->userinfo !== false ? preg_replace('@:.*$@', '', $this->userinfo) : false;
+    }
+
+    /**
+     * Returns the password part of the userinfo part (the part after the first
+     *  ":"), or false if there is no userinfo part (i.e. the URL does not
+     * contain "@" in front of the hostname) or the userinfo part does not
+     * contain ":".
+     *
+     * @return  string|bool
+     */
+    public function getPassword()
+    {
+        return $this->userinfo !== false ? substr(strstr($this->userinfo, ':'), 1) : false;
+    }
+
+    /**
+     * Returns the userinfo part, or false if there is none, i.e. if the
+     * authority part does not contain "@".
+     *
+     * @return  string|bool
+     */
+    public function getUserinfo()
+    {
+        return $this->userinfo;
+    }
+
+    /**
+     * Sets the userinfo part. If two arguments are passed, they are combined
+     * in the userinfo part as username ":" password.
+     *
+     * @param string|bool $userinfo userinfo or username
+     * @param string|bool $password
+     *
+     * @return void
+     */
+    public function setUserinfo($userinfo, $password = false)
+    {
+        $this->userinfo = $userinfo;
+        if ($password !== false) {
+            $this->userinfo .= ':' . $password;
+        }
+    }
+
+    /**
+     * Returns the host part, or false if there is no authority part, e.g.
+     * relative URLs.
+     *
+     * @return  string|bool
+     */
+    public function getHost()
+    {
+        return $this->host;
+    }
+
+    /**
+     * @param string|bool $host
+     *
+     * @return void
+     */
+    public function setHost($host)
+    {
+        $this->host = $host;
+    }
+
+    /**
+     * Returns the port number, or false if there is no port number specified,
+     * i.e. if the default port is to be used.
+     *
+     * @return  int|bool
+     */
+    public function getPort()
+    {
+        return $this->port;
+    }
+
+    /**
+     * @param int|bool $port
+     *
+     * @return void
+     */
+    public function setPort($port)
+    {
+        $this->port = intval($port);
+    }
+
+    /**
+     * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
+     * false if there is no authority none.
+     *
+     * @return string|bool
+     */
+    public function getAuthority()
+    {
+        if (!$this->host) {
+            return false;
+        }
+
+        $authority = '';
+
+        if ($this->userinfo !== false) {
+            $authority .= $this->userinfo . '@';
+        }
+
+        $authority .= $this->host;
+
+        if ($this->port !== false) {
+            $authority .= ':' . $this->port;
+        }
+
+        return $authority;
+    }
+
+    /**
+     * @param string|false $authority
+     *
+     * @return void
+     */
+    public function setAuthority($authority)
+    {
+        $this->user = false;
+        $this->pass = false;
+        $this->host = false;
+        $this->port = false;
+        if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
+            if ($reg[1]) {
+                $this->userinfo = $reg[2];
+            }
+
+            $this->host = $reg[3];
+            if (isset($reg[5])) {
+                $this->port = intval($reg[5]);
+            }
+        }
+    }
+
+    /**
+     * Returns the path part (possibly an empty string).
+     *
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * @param string $path
+     *
+     * @return void
+     */
+    public function setPath($path)
+    {
+        $this->path = $path;
+    }
+
+    /**
+     * Returns the query string (excluding the leading "?"), or false if "?"
+     * isn't present in the URL.
+     *
+     * @return  string|bool
+     * @see     self::getQueryVariables()
+     */
+    public function getQuery()
+    {
+        return $this->query;
+    }
+
+    /**
+     * @param string|bool $query
+     *
+     * @return void
+     * @see   self::setQueryVariables()
+     */
+    public function setQuery($query)
+    {
+        $this->query = $query;
+    }
+
+    /**
+     * Returns the fragment name, or false if "#" isn't present in the URL.
+     *
+     * @return  string|bool
+     */
+    public function getFragment()
+    {
+        return $this->fragment;
+    }
+
+    /**
+     * @param string|bool $fragment
+     *
+     * @return void
+     */
+    public function setFragment($fragment)
+    {
+        $this->fragment = $fragment;
+    }
+
+    /**
+     * Returns the query string like an array as the variables would appear in
+     * $_GET in a PHP script.
+     *
+     * @return  array
+     */
+    public function getQueryVariables()
+    {
+        $pattern = '/[' .
+                   preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
+                   ']/';
+        $parts   = preg_split($pattern, $this->query, -1, PREG_SPLIT_NO_EMPTY);
+        $return  = array();
+
+        foreach ($parts as $part) {
+            if (strpos($part, '=') !== false) {
+                list($key, $value) = explode('=', $part, 2);
+            } else {
+                $key   = $part;
+                $value = null;
+            }
+
+            if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
+                $key = rawurldecode($key);
+            }
+            $value = rawurldecode($value);
+
+            if ($this->getOption(self::OPTION_USE_BRACKETS) &&
+                preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
+
+                $key = $matches[1];
+                $idx = $matches[2];
+
+                // Ensure is an array
+                if (empty($return[$key]) || !is_array($return[$key])) {
+                    $return[$key] = array();
+                }
+
+                // Add data
+                if ($idx === '') {
+                    $return[$key][] = $value;
+                } else {
+                    $return[$key][$idx] = $value;
+                }
+            } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
+                      && !empty($return[$key])
+            ) {
+                $return[$key]   = (array) $return[$key];
+                $return[$key][] = $value;
+            } else {
+                $return[$key] = $value;
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+     * @param array $array (name => value) array
+     *
+     * @return void
+     */
+    public function setQueryVariables(array $array)
+    {
+        if (!$array) {
+            $this->query = false;
+        } else {
+            foreach ($array as $name => $value) {
+                if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
+                    $name = rawurlencode($name);
+                }
+
+                if (is_array($value)) {
+                    foreach ($value as $k => $v) {
+                        $parts[] = $this->getOption(self::OPTION_USE_BRACKETS)
+                            ? sprintf('%s[%s]=%s', $name, $k, $v)
+                            : ($name . '=' . $v);
+                    }
+                } elseif (!is_null($value)) {
+                    $parts[] = $name . '=' . $value;
+                } else {
+                    $parts[] = $name;
+                }
+            }
+            $this->query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
+                                   $parts);
+        }
+    }
+
+    /**
+     * @param string $name
+     * @param mixed  $value
+     *
+     * @return  array
+     */
+    public function setQueryVariable($name, $value)
+    {
+        $array = $this->getQueryVariables();
+        $array[$name] = $value;
+        $this->setQueryVariables($array);
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return void
+     */
+    public function unsetQueryVariable($name)
+    {
+        $array = $this->getQueryVariables();
+        unset($array[$name]);
+        $this->setQueryVariables($array);
+    }
+
+    /**
+     * Returns a string representation of this URL.
+     *
+     * @return  string
+     */
+    public function getURL()
+    {
+        // See RFC 3986, section 5.3
+        $url = "";
+
+        if ($this->scheme !== false) {
+            $url .= $this->scheme . ':';
+        }
+
+        $authority = $this->getAuthority();
+        if ($authority !== false) {
+            $url .= '//' . $authority;
+        }
+        $url .= $this->path;
+
+        if ($this->query !== false) {
+            $url .= '?' . $this->query;
+        }
+
+        if ($this->fragment !== false) {
+            $url .= '#' . $this->fragment;
+        }
+    
+        return $url;
+    }
+
+    /** 
+     * Returns a normalized string representation of this URL. This is useful
+     * for comparison of URLs.
+     *
+     * @return  string
+     */
+    public function getNormalizedURL()
+    {
+        $url = clone $this;
+        $url->normalize();
+        return $url->getUrl();
+    }
+
+    /** 
+     * Returns a normalized Net_URL2 instance.
+     *
+     * @return  Net_URL2
+     */
+    public function normalize()
+    {
+        // See RFC 3886, section 6
+
+        // Schemes are case-insensitive
+        if ($this->scheme) {
+            $this->scheme = strtolower($this->scheme);
+        }
+
+        // Hostnames are case-insensitive
+        if ($this->host) {
+            $this->host = strtolower($this->host);
+        }
+
+        // Remove default port number for known schemes (RFC 3986, section 6.2.3)
+        if ($this->port &&
+            $this->scheme &&
+            $this->port == getservbyname($this->scheme, 'tcp')) {
+
+            $this->port = false;
+        }
+
+        // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
+        foreach (array('userinfo', 'host', 'path') as $part) {
+            if ($this->$part) {
+                $this->$part  = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $this->$part);
+            }
+        }
+
+        // Path segment normalization (RFC 3986, section 6.2.2.3)
+        $this->path = self::removeDotSegments($this->path);
+
+        // Scheme based normalization (RFC 3986, section 6.2.3)
+        if ($this->host && !$this->path) {
+            $this->path = '/';
+        }
+    }
+
+    /**
+     * Returns whether this instance represents an absolute URL.
+     *
+     * @return  bool
+     */
+    public function isAbsolute()
+    {
+        return (bool) $this->scheme;
+    }
+
+    /**
+     * Returns an Net_URL2 instance representing an absolute URL relative to
+     * this URL.
+     *
+     * @param Net_URL2|string $reference relative URL
+     *
+     * @return Net_URL2
+     */
+    public function resolve($reference)
+    {
+        if (is_string($reference)) {
+            $reference = new self($reference);
+        }
+        if (!$this->isAbsolute()) {
+            throw new Exception('Base-URL must be absolute');
+        }
+
+        // A non-strict parser may ignore a scheme in the reference if it is
+        // identical to the base URI's scheme.
+        if (!$this->getOption(self::OPTION_STRICT) && $reference->scheme == $this->scheme) {
+            $reference->scheme = false;
+        }
+
+        $target = new self('');
+        if ($reference->scheme !== false) {
+            $target->scheme = $reference->scheme;
+            $target->setAuthority($reference->getAuthority());
+            $target->path  = self::removeDotSegments($reference->path);
+            $target->query = $reference->query;
+        } else {
+            $authority = $reference->getAuthority();
+            if ($authority !== false) {
+                $target->setAuthority($authority);
+                $target->path  = self::removeDotSegments($reference->path);
+                $target->query = $reference->query;
+            } else {
+                if ($reference->path == '') {
+                    $target->path = $this->path;
+                    if ($reference->query !== false) {
+                        $target->query = $reference->query;
+                    } else {
+                        $target->query = $this->query;
+                    }
+                } else {
+                    if (substr($reference->path, 0, 1) == '/') {
+                        $target->path = self::removeDotSegments($reference->path);
+                    } else {
+                        // Merge paths (RFC 3986, section 5.2.3)
+                        if ($this->host !== false && $this->path == '') {
+                            $target->path = '/' . $this->path;
+                        } else {
+                            $i = strrpos($this->path, '/');
+                            if ($i !== false) {
+                                $target->path = substr($this->path, 0, $i + 1);
+                            }
+                            $target->path .= $reference->path;
+                        }
+                        $target->path = self::removeDotSegments($target->path);
+                    }
+                    $target->query = $reference->query;
+                }
+                $target->setAuthority($this->getAuthority());
+            }
+            $target->scheme = $this->scheme;
+        }
+
+        $target->fragment = $reference->fragment;
+
+        return $target;
+    }
+
+    /**
+     * Removes dots as described in RFC 3986, section 5.2.4, e.g.
+     * "/foo/../bar/baz" => "/bar/baz"
+     *
+     * @param string $path a path
+     *
+     * @return string a path
+     */
+    private static function removeDotSegments($path)
+    {
+        $output = '';
+
+        // Make sure not to be trapped in an infinite loop due to a bug in this
+        // method
+        $j = 0; 
+        while ($path && $j++ < 100) {
+            // Step A
+            if (substr($path, 0, 2) == './') {
+                $path = substr($path, 2);
+            } elseif (substr($path, 0, 3) == '../') {
+                $path = substr($path, 3);
+
+            // Step B
+            } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
+                $path = '/' . substr($path, 3);
+
+            // Step C
+            } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
+                $path = '/' . substr($path, 4);
+                $i = strrpos($output, '/');
+                $output = $i === false ? '' : substr($output, 0, $i);
+
+            // Step D
+            } elseif ($path == '.' || $path == '..') {
+                $path = '';
+
+            // Step E
+            } else {
+                $i = strpos($path, '/');
+                if ($i === 0) {
+                    $i = strpos($path, '/', 1);
+                }
+                if ($i === false) {
+                    $i = strlen($path);
+                }
+                $output .= substr($path, 0, $i);
+                $path = substr($path, $i);
+            }
+        }
+
+        return $output;
+    }
+
+    /**
+     * Returns a Net_URL2 instance representing the canonical URL of the
+     * currently executing PHP script.
+     * 
+     * @return  string
+     */
+    public static function getCanonical()
+    {
+        if (!isset($_SERVER['REQUEST_METHOD'])) {
+            // ALERT - no current URL
+            throw new Exception('Script was not called through a webserver');
+        }
+
+        // Begin with a relative URL
+        $url = new self($_SERVER['PHP_SELF']);
+        $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        $url->host = $_SERVER['SERVER_NAME'];
+        $port = intval($_SERVER['SERVER_PORT']);
+        if ($url->scheme == 'http' && $port != 80 ||
+            $url->scheme == 'https' && $port != 443) {
+
+            $url->port = $port;
+        }
+        return $url;
+    }
+
+    /**
+     * Returns the URL used to retrieve the current request.
+     *
+     * @return  string
+     */
+    public static function getRequestedURL()
+    {
+        return self::getRequested()->getUrl();
+    }
+
+    /**
+     * Returns a Net_URL2 instance representing the URL used to retrieve the
+     * current request.
+     *
+     * @return  Net_URL2
+     */
+    public static function getRequested()
+    {
+        if (!isset($_SERVER['REQUEST_METHOD'])) {
+            // ALERT - no current URL
+            throw new Exception('Script was not called through a webserver');
+        }
+
+        // Begin with a relative URL
+        $url = new self($_SERVER['REQUEST_URI']);
+        $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        // Set host and possibly port
+        $url->setAuthority($_SERVER['HTTP_HOST']);
+        return $url;
+    }
+
+    /**
+     * Sets the specified option.
+     *
+     * @param string $optionName a self::OPTION_ constant
+     * @param mixed  $value      option value  
+     *
+     * @return void
+     * @see  self::OPTION_STRICT
+     * @see  self::OPTION_USE_BRACKETS
+     * @see  self::OPTION_ENCODE_KEYS
+     */
+    function setOption($optionName, $value)
+    {
+        if (!array_key_exists($optionName, $this->options)) {
+            return false;
+        }
+        $this->options[$optionName] = $value;
+    }
+
+    /**
+     * Returns the value of the specified option.
+     *
+     * @param string $optionName The name of the option to retrieve
+     *
+     * @return  mixed
+     */
+    function getOption($optionName)
+    {
+        return isset($this->options[$optionName])
+            ? $this->options[$optionName] : false;
+    }
+}
diff --git a/extlib/Services/oEmbed.php b/extlib/Services/oEmbed.php
new file mode 100644 (file)
index 0000000..5d38ed8
--- /dev/null
@@ -0,0 +1,357 @@
+<?php
+
+/**
+ * An interface for oEmbed consumption
+ *
+ * PHP version 5.1.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Validate.php';
+require_once 'Net/URL2.php';
+require_once 'HTTP/Request.php';
+require_once 'Services/oEmbed/Exception.php';
+require_once 'Services/oEmbed/Exception/NoSupport.php';
+require_once 'Services/oEmbed/Object.php';
+
+/**
+ * Base class for consuming oEmbed objects
+ *
+ * <code>
+ * <?php
+ * 
+ * require_once 'Services/oEmbed.php';
+ *
+ * // The URL that we'd like to find out more information about.
+ * $url = 'http://flickr.com/photos/joestump/2848795611/';
+ *
+ * // The oEmbed API URI. Not all providers support discovery yet so we're
+ * // explicitly providing one here. If one is not provided Services_oEmbed
+ * // attempts to discover it. If none is found an exception is thrown.
+ * $oEmbed = new Services_oEmbed($url, array(
+ *     Services_oEmbed::OPTION_API => 'http://www.flickr.com/services/oembed/'
+ * ));
+ * $object = $oEmbed->getObject();
+ *
+ * // All of the objects have somewhat sane __toString() methods that allow
+ * // you to output them directly.
+ * echo (string)$object;
+ * 
+ * ?>
+ * </code> 
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed
+{
+    /**
+     * HTTP timeout in seconds
+     * 
+     * All HTTP requests made by Services_oEmbed will respect this timeout. 
+     * This can be passed to {@link Services_oEmbed::setOption()} or to the
+     * options parameter in {@link Services_oEmbed::__construct()}.
+     * 
+     * @var string OPTION_TIMEOUT Timeout in seconds 
+     */
+    const OPTION_TIMEOUT = 'http_timeout';
+
+    /**
+     * HTTP User-Agent 
+     *
+     * All HTTP requests made by Services_oEmbed will be sent with the 
+     * string set by this option.
+     *
+     * @var string OPTION_USER_AGENT The HTTP User-Agent string
+     */
+    const OPTION_USER_AGENT = 'http_user_agent';
+
+    /**
+     * The API's URI
+     *
+     * If the API is known ahead of time this option can be used to explicitly
+     * set it. If not present then the API is attempted to be discovered 
+     * through the auto-discovery mechanism.
+     *
+     * @var string OPTION_API
+     */
+    const OPTION_API = 'oembed_api';
+
+    /**
+     * Options for oEmbed requests
+     *
+     * @var array $options The options for making requests
+     */
+    protected $options = array(
+        self::OPTION_TIMEOUT    => 3,
+        self::OPTION_API        => null,
+        self::OPTION_USER_AGENT => 'Services_oEmbed 0.1.0'
+    );
+
+    /**
+     * URL of object to get embed information for
+     *
+     * @var object $url {@link Net_URL2} instance of URL of object
+     */
+    protected $url = null;
+
+    /**
+     * Constructor
+     *
+     * @param string $url     The URL to fetch an oEmbed for
+     * @param array  $options A list of options for the oEmbed lookup
+     *
+     * @throws {@link Services_oEmbed_Exception} if the $url is invalid
+     * @throws {@link Services_oEmbed_Exception} when no valid API is found
+     * @return void
+     */
+    public function __construct($url, array $options = array())
+    {
+        if (Validate::uri($url)) {
+            $this->url = new Net_URL2($url);
+        } else {
+            throw new Services_oEmbed_Exception('URL is invalid');
+        }
+
+        if (count($options)) {
+            foreach ($options as $key => $val) {
+                $this->setOption($key, $val);
+            }
+        }
+
+        if ($this->options[self::OPTION_API] === null) {
+            $this->options[self::OPTION_API] = $this->discover();
+        } 
+    }
+
+    /**
+     * Set an option for the oEmbed request
+     * 
+     * @param mixed $option The option name
+     * @param mixed $value  The option value
+     *
+     * @see Services_oEmbed::OPTION_API, Services_oEmbed::OPTION_TIMEOUT
+     * @throws {@link Services_oEmbed_Exception} on invalid option
+     * @access public
+     * @return void
+     */
+    public function setOption($option, $value)
+    {
+        switch ($option) {
+        case self::OPTION_API:
+        case self::OPTION_TIMEOUT:
+            break;
+        default:
+            throw new Services_oEmbed_Exception(
+                'Invalid option "' . $option . '"'
+            );
+        }
+
+        $func = '_set_' . $option;
+        if (method_exists($this, $func)) {
+            $this->options[$option] = $this->$func($value);
+        } else {
+            $this->options[$option] = $value;
+        }
+    }
+
+    /**
+     * Set the API option
+     * 
+     * @param string $value The API's URI
+     *
+     * @throws {@link Services_oEmbed_Exception} on invalid API URI
+     * @see Validate::uri()
+     * @return string
+     */
+    protected function _set_oembed_api($value)
+    {
+        if (!Validate::uri($value)) {
+            throw new Services_oEmbed_Exception(
+                'API URI provided is invalid'
+            );
+        }
+
+        return $value;
+    }
+
+    /**
+     * Get the oEmbed response
+     *
+     * @param array $params Optional parameters for 
+     * 
+     * @throws {@link Services_oEmbed_Exception} on cURL errors
+     * @throws {@link Services_oEmbed_Exception} on HTTP errors
+     * @throws {@link Services_oEmbed_Exception} when result is not parsable 
+     * @return object The oEmbed response as an object
+     */
+    public function getObject(array $params = array())
+    {
+        $params['url'] = $this->url->getURL();
+        if (!isset($params['format'])) {
+            $params['format'] = 'json';
+        }
+
+        $sets = array();
+        foreach ($params as $var => $val) {
+            $sets[] = $var . '=' . urlencode($val);
+        }
+
+        $url = $this->options[self::OPTION_API] . '?' . implode('&', $sets);
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_HEADER, false);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options[self::OPTION_TIMEOUT]);
+        $result = curl_exec($ch);
+
+        if (curl_errno($ch)) {
+            throw new Services_oEmbed_Exception(
+                curl_error($ch), curl_errno($ch)
+            );
+        }
+
+        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        if (substr($code, 0, 1) != '2') {
+            throw new Services_oEmbed_Exception('Non-200 code returned');
+        }
+
+        curl_close($ch);
+
+        switch ($params['format']) {
+        case 'json':
+            $res = json_decode($result);
+            if (!is_object($res)) {
+                throw new Services_oEmbed_Exception(
+                    'Could not parse JSON response'
+                );
+            }
+            break;
+        case 'xml':
+            libxml_use_internal_errors(true);
+            $res = simplexml_load_string($result);
+            if (!$res instanceof SimpleXMLElement) {
+                $errors = libxml_get_errors();
+                $err    = array_shift($errors);
+                libxml_clear_errors();
+                libxml_use_internal_errors(false);
+                throw new Services_oEmbed_Exception(
+                    $err->message, $error->code
+                );
+            }
+            break;
+        }
+
+        return Services_oEmbed_Object::factory($res);
+    }
+
+    /**
+     * Discover an oEmbed API 
+     *
+     * @param string $url The URL to attempt to discover oEmbed for
+     *
+     * @throws {@link Services_oEmbed_Exception} if the $url is invalid
+     * @return string The oEmbed API endpoint discovered
+     */
+    protected function discover($url)
+    {
+        $body = $this->sendRequest($url);
+
+        // Find all <link /> tags that have a valid oembed type set. We then
+        // extract the href attribute for each type.
+        $regexp = '#<link([^>]*)type="' . 
+                  '(application/json|text/xml)\+oembed"([^>]*)>#i';
+
+        $m = $ret = array();
+        if (!preg_match_all($regexp, $body, $m)) {
+            throw new Services_oEmbed_Exception_NoSupport(
+                'No valid oEmbed links found on page'
+            );
+        }
+
+        foreach ($m[0] as $i => $link) {
+            $h = array();
+            if (preg_match('/href="([^"]+)"/i', $link, $h)) {
+                $ret[$m[2][$i]] = $h[1];
+            }
+        } 
+
+        return (isset($ret['json']) ? $ret['json'] : array_pop($ret));
+    }
+
+    /**
+     * Send a GET request to the provider
+     * 
+     * @param mixed $url The URL to send the request to
+     *
+     * @throws {@link Services_oEmbed_Exception} on HTTP errors
+     * @return string The contents of the response
+     */
+    private function sendRequest($url)
+    {
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_HEADER, false);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->options[self::OPTION_TIMEOUT]);
+        curl_setopt($ch, CURLOPT_USERAGENT, $this->options[self::OPTION_USER_AGENT]);
+        $result = curl_exec($ch);
+        if (curl_errno($ch)) {
+            throw new Services_oEmbed_Exception(
+                curl_error($ch), curl_errno($ch)
+            );
+        }
+
+        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        if (substr($code, 0, 1) != '2') {
+            throw new Services_oEmbed_Exception('Non-200 code returned');
+        }
+
+        return $result;
+    }
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Exception.php b/extlib/Services/oEmbed/Exception.php
new file mode 100644 (file)
index 0000000..446ac2a
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Base exception class for {@link Services_oEmbed}
+ *
+ * PHP version 5.1.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'PEAR/Exception.php';
+
+/**
+ * Base exception class for {@link Services_oEmbed}
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Exception extends PEAR_Exception
+{
+
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Exception/NoSupport.php b/extlib/Services/oEmbed/Exception/NoSupport.php
new file mode 100644 (file)
index 0000000..384c719
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * Exception class when no oEmbed support is discovered
+ *
+ * PHP version 5.2.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+/**
+ * Exception class when no oEmbed support is discovered
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Exception_NoSupport extends Services_oEmbed_Exception
+{
+    
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object.php b/extlib/Services/oEmbed/Object.php
new file mode 100644 (file)
index 0000000..9eedd7e
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * An interface for oEmbed consumption
+ *
+ * PHP version 5.1.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Services/oEmbed/Object/Exception.php';
+
+/**
+ * Base class for consuming oEmbed objects
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+abstract class Services_oEmbed_Object 
+{
+    
+    /**
+     * Valid oEmbed object types
+     *
+     * @var array $types Array of valid object types
+     * @see Services_oEmbed_Object::factory()
+     */
+    static protected $types = array(
+        'photo' => 'Photo',
+        'video' => 'Video',
+        'link'  => 'Link',
+        'rich'  => 'Rich'
+    );
+
+    /**
+     * Create an oEmbed object from result
+     *
+     * @param object $object Raw object returned from API
+     *
+     * @throws {@link Services_oEmbed_Object_Exception} on object error
+     * @return object Instance of object driver
+     * @see Services_oEmbed_Object_Link, Services_oEmbed_Object_Photo
+     * @see Services_oEmbed_Object_Rich, Services_oEmbed_Object_Video
+     */
+    static public function factory($object)
+    {
+        if (!isset($object->type)) {
+            throw new Services_oEmbed_Object_Exception(
+                'Object has no type'
+            );
+        }
+
+        $type = (string)$object->type;
+        if (!isset(self::$types[$type])) {
+            throw new Services_oEmbed_Object_Exception(
+                'Object type is unknown or invalid: ' . $type
+            );
+        }
+
+        $file = 'Services/oEmbed/Object/' . self::$types[$type] . '.php';
+        include_once $file;
+
+        $class = 'Services_oEmbed_Object_' . self::$types[$type];
+        if (!class_exists($class)) {
+            throw new Services_oEmbed_Object_Exception(
+                'Object class is invalid or not present'
+            );
+        }
+
+        $instance = new $class($object);
+        return $instance;
+    }
+
+    /**
+     * Instantiation is not allowed
+     *
+     * @return void
+     */
+    private function __construct()
+    {
+    
+    }
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object/Common.php b/extlib/Services/oEmbed/Object/Common.php
new file mode 100644 (file)
index 0000000..f568ec8
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+
+/**
+ * Base class for oEmbed objects
+ *
+ * PHP version 5.1.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+/**
+ * Base class for oEmbed objects
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+abstract class Services_oEmbed_Object_Common
+{
+    /**
+     * Raw object returned from API
+     *
+     * @var object $object The raw object from the API
+     */
+    protected $object = null;
+
+    /**
+     * Required fields per the specification
+     *
+     * @var array $required Array of required fields
+     * @link http://oembed.com
+     */
+    protected $required = array();
+
+    /**
+     * Constructor
+     *
+     * @param object $object Raw object returned from the API
+     *
+     * @throws {@link Services_oEmbed_Object_Exception} on missing fields
+     * @return void
+     */
+    public function __construct($object)
+    {
+        $this->object = $object;
+
+        $this->required[] = 'version';
+        foreach ($this->required as $field) {
+            if (!isset($this->$field)) {
+                throw new Services_oEmbed_Object_Exception(
+                    'Object is missing required ' . $field . ' attribute'
+                );
+            }
+        }
+    }
+
+    /**
+     * Get object variable
+     *
+     * @param string $var Variable to get
+     *
+     * @see Services_oEmbed_Object_Common::$object
+     * @return mixed Attribute's value or null if it's not set/exists
+     */
+    public function __get($var)
+    {
+        if (property_exists($this->object, $var)) {
+            return $this->object->$var;
+        }
+
+        return null;
+    }
+
+    /**
+     * Is variable set?
+     *
+     * @param string $var Variable name to check
+     * 
+     * @return boolean True if set, false if not
+     * @see Services_oEmbed_Object_Common::$object
+     */
+    public function __isset($var)
+    {
+        if (property_exists($this->object, $var)) {
+            return (isset($this->object->$var));
+        }
+
+        return false;
+    }
+
+    /**
+     * Require a sane __toString for all objects
+     *
+     * @return string
+     */
+    abstract public function __toString();
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object/Exception.php b/extlib/Services/oEmbed/Object/Exception.php
new file mode 100644 (file)
index 0000000..6025ffd
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Exception for {@link Services_oEmbed_Object}
+ *
+ * PHP version 5.1.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Services/oEmbed/Exception.php';
+
+/**
+ * Exception for {@link Services_oEmbed_Object}
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Object_Exception extends Services_oEmbed_Exception
+{
+
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object/Link.php b/extlib/Services/oEmbed/Object/Link.php
new file mode 100644 (file)
index 0000000..9b627a8
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * Link object for {@link Services_oEmbed}
+ *
+ * PHP version 5.2.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Services/oEmbed/Object/Common.php';
+
+/**
+ * Link object for {@link Services_oEmbed}
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Object_Link extends Services_oEmbed_Object_Common
+{
+    /**
+     * Output a sane link
+     *
+     * @return string An HTML link of the object
+     */
+    public function __toString()
+    {
+        return '<a href="' . $this->url . '">' . $this->title . '</a>';
+    }
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object/Photo.php b/extlib/Services/oEmbed/Object/Photo.php
new file mode 100644 (file)
index 0000000..5fbf429
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * Photo object for {@link Services_oEmbed}
+ *
+ * PHP version 5.2.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Services/oEmbed/Object/Common.php';
+
+/**
+ * Photo object for {@link Services_oEmbed}
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Object_Photo extends Services_oEmbed_Object_Common
+{
+    /**
+     * Required fields for photo objects
+     *
+     * @var array $required Required fields
+     */
+    protected $required = array(
+        'url', 'width', 'height'
+    );
+    /**
+     * Output a valid HTML tag for image
+     *
+     * @return string HTML <img /> tag for Photo
+     */
+    public function __toString()
+    {
+        $img = '<img src="' . $this->url . '" width="' . $this->width . '" ' .
+               'height="' . $this->height . '"';
+
+        if (isset($this->title)) {
+            $img .= ' alt="' . $this->title . '"';
+        }
+
+        return $img . ' />';
+    }
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object/Rich.php b/extlib/Services/oEmbed/Object/Rich.php
new file mode 100644 (file)
index 0000000..dbf6933
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * Photo object for {@link Services_oEmbed}
+ *
+ * PHP version 5.2.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Services/oEmbed/Object/Common.php';
+
+/**
+ * Photo object for {@link Services_oEmbed}
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Object_Rich extends Services_oEmbed_Object_Common
+{
+    /**
+     * Required fields for rich objects
+     *
+     * @var array $required Required fields
+     */
+    protected $required = array(
+        'html', 'width', 'height'
+    );
+    /**
+     * Output a the HTML tag for rich object
+     *
+     * @return string HTML for rich object
+     */
+    public function __toString()
+    {
+        return $this->html;        
+    }
+}
+
+?>
diff --git a/extlib/Services/oEmbed/Object/Video.php b/extlib/Services/oEmbed/Object/Video.php
new file mode 100644 (file)
index 0000000..7461081
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * Photo object for {@link Services_oEmbed}
+ *
+ * PHP version 5.2.0+
+ *
+ * Copyright (c) 2008, Digg.com, 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:
+ *
+ *  - 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 of Digg.com, Inc. 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.
+ *
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   SVN: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+
+require_once 'Services/oEmbed/Object/Common.php';
+
+/**
+ * Photo object for {@link Services_oEmbed}
+ * 
+ * @category  Services
+ * @package   Services_oEmbed
+ * @author    Joe Stump <joe@joestump.net> 
+ * @copyright 2008 Digg.com, Inc.
+ * @license   http://tinyurl.com/42zef New BSD License
+ * @version   Release: @version@
+ * @link      http://code.google.com/p/digg
+ * @link      http://oembed.com
+ */
+class Services_oEmbed_Object_Video extends Services_oEmbed_Object_Common
+{
+    /**
+     * Required fields for video objects
+     *
+     * @var array $required Required fields
+     */
+    protected $required = array(
+        'html', 'width', 'height'
+    );
+    /**
+     * Output a valid embed tag for video
+     *
+     * @return string HTML for video
+     */
+    public function __toString()
+    {
+        return $this->html;        
+    }
+}
+
+?>
diff --git a/extlib/Stomp.php b/extlib/Stomp.php
new file mode 100644 (file)
index 0000000..9e1c97b
--- /dev/null
@@ -0,0 +1,594 @@
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
+
+require_once 'Stomp/Frame.php';
+
+/**
+ * A Stomp Connection
+ *
+ *
+ * @package Stomp
+ * @author Hiram Chirino <hiram@hiramchirino.com>
+ * @author Dejan Bosanac <dejan@nighttale.net> 
+ * @author Michael Caplan <mcaplan@labnet.net>
+ * @version $Revision: 43 $
+ */
+class Stomp
+{
+    /**
+     * Perform request synchronously
+     *
+     * @var boolean
+     */
+    public $sync = false;
+
+    /**
+     * Default prefetch size
+     *
+     * @var int
+     */
+       public $prefetchSize = 1;
+    
+       /**
+     * Client id used for durable subscriptions
+     *
+     * @var string
+     */
+       public $clientId = null;
+    
+    protected $_brokerUri = null;
+    protected $_socket = null;
+    protected $_hosts = array();
+    protected $_params = array();
+    protected $_subscriptions = array();
+    protected $_defaultPort = 61613;
+    protected $_currentHost = - 1;
+    protected $_attempts = 10;
+    protected $_username = '';
+    protected $_password = '';
+    protected $_sessionId;
+    protected $_read_timeout_seconds = 60;
+    protected $_read_timeout_milliseconds = 0;
+    
+    /**
+     * Constructor
+     *
+     * @param string $brokerUri Broker URL
+     * @throws Stomp_Exception
+     */
+    public function __construct ($brokerUri)
+    {
+        $this->_brokerUri = $brokerUri;
+        $this->_init();
+    }
+    /**
+     * Initialize connection
+     *
+     * @throws Stomp_Exception
+     */
+    protected function _init ()
+    {
+        $pattern = "|^(([a-zA-Z]+)://)+\(*([a-zA-Z0-9\.:/i,-]+)\)*\??([a-zA-Z0-9=]*)$|i";
+        if (preg_match($pattern, $this->_brokerUri, $regs)) {
+            $scheme = $regs[2];
+            $hosts = $regs[3];
+            $params = $regs[4];
+            if ($scheme != "failover") {
+                $this->_processUrl($this->_brokerUri);
+            } else {
+                $urls = explode(",", $hosts);
+                foreach ($urls as $url) {
+                    $this->_processUrl($url);
+                }
+            }
+            if ($params != null) {
+                parse_str($params, $this->_params);
+            }
+        } else {
+            require_once 'Stomp/Exception.php';
+            throw new Stomp_Exception("Bad Broker URL {$this->_brokerUri}");
+        }
+    }
+    /**
+     * Process broker URL
+     *
+     * @param string $url Broker URL
+     * @throws Stomp_Exception
+     * @return boolean
+     */
+    protected function _processUrl ($url)
+    {
+        $parsed = parse_url($url);
+        if ($parsed) {
+            array_push($this->_hosts, array($parsed['host'] , $parsed['port'] , $parsed['scheme']));
+        } else {
+            require_once 'Stomp/Exception.php';
+            throw new Stomp_Exception("Bad Broker URL $url");
+        }
+    }
+    /**
+     * Make socket connection to the server
+     *
+     * @throws Stomp_Exception
+     */
+    protected function _makeConnection ()
+    {
+        if (count($this->_hosts) == 0) {
+            require_once 'Stomp/Exception.php';
+            throw new Stomp_Exception("No broker defined");
+        }
+        
+        // force disconnect, if previous established connection exists
+        $this->disconnect();
+        
+        $i = $this->_currentHost;
+        $att = 0;
+        $connected = false;
+        while (! $connected && $att ++ < $this->_attempts) {
+            if (isset($this->_params['randomize']) && $this->_params['randomize'] == 'true') {
+                $i = rand(0, count($this->_hosts) - 1);
+            } else {
+                $i = ($i + 1) % count($this->_hosts);
+            }
+            $broker = $this->_hosts[$i];
+            $host = $broker[0];
+            $port = $broker[1];
+            $scheme = $broker[2];
+            if ($port == null) {
+                $port = $this->_defaultPort;
+            }
+            if ($this->_socket != null) {
+                fclose($this->_socket);
+                $this->_socket = null;
+            }
+            $this->_socket = @fsockopen($scheme . '://' . $host, $port);
+            if (!is_resource($this->_socket) && $att >= $this->_attempts && !array_key_exists($i + 1, $this->_hosts)) {
+                require_once 'Stomp/Exception.php';
+                throw new Stomp_Exception("Could not connect to $host:$port ($att/{$this->_attempts})");
+            } else if (is_resource($this->_socket)) {
+                $connected = true;
+                $this->_currentHost = $i;
+                break;
+            }
+        }
+        if (! $connected) {
+            require_once 'Stomp/Exception.php';
+            throw new Stomp_Exception("Could not connect to a broker");
+        }
+    }
+    /**
+     * Connect to server
+     *
+     * @param string $username
+     * @param string $password
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    public function connect ($username = '', $password = '')
+    {
+        $this->_makeConnection();
+        if ($username != '') {
+            $this->_username = $username;
+        }
+        if ($password != '') {
+            $this->_password = $password;
+        }
+               $headers = array('login' => $this->_username , 'passcode' => $this->_password);
+               if ($this->clientId != null) {
+                       $headers["client-id"] = $this->clientId;
+               }
+               $frame = new Stomp_Frame("CONNECT", $headers);
+        $this->_writeFrame($frame);
+        $frame = $this->readFrame();
+        if ($frame instanceof Stomp_Frame && $frame->command == 'CONNECTED') {
+            $this->_sessionId = $frame->headers["session"];
+            return true;
+        } else {
+            require_once 'Stomp/Exception.php';
+            if ($frame instanceof Stomp_Frame) {
+                throw new Stomp_Exception("Unexpected command: {$frame->command}", 0, $frame->body);
+            } else {
+                throw new Stomp_Exception("Connection not acknowledged");
+            }
+        }
+    }
+    
+    /**
+     * Check if client session has ben established
+     *
+     * @return boolean
+     */
+    public function isConnected ()
+    {
+        return !empty($this->_sessionId) && is_resource($this->_socket);
+    }
+    /**
+     * Current stomp session ID
+     *
+     * @return string
+     */
+    public function getSessionId()
+    {
+        return $this->_sessionId;
+    }
+    /**
+     * Send a message to a destination in the messaging system 
+     *
+     * @param string $destination Destination queue
+     * @param string|Stomp_Frame $msg Message
+     * @param array $properties
+     * @param boolean $sync Perform request synchronously
+     * @return boolean
+     */
+    public function send ($destination, $msg, $properties = null, $sync = null)
+    {
+        if ($msg instanceof Stomp_Frame) {
+            $msg->headers['destination'] = $destination;
+            $msg->headers = array_merge($msg->headers, $properties);
+            $frame = $msg;
+        } else {
+            $headers = $properties;
+            $headers['destination'] = $destination;
+            $frame = new Stomp_Frame('SEND', $headers, $msg);
+        }
+        $this->_prepareReceipt($frame, $sync);
+        $this->_writeFrame($frame);
+        return $this->_waitForReceipt($frame, $sync);
+    }
+    /**
+     * Prepair frame receipt
+     *
+     * @param Stomp_Frame $frame
+     * @param boolean $sync
+     */
+    protected function _prepareReceipt (Stomp_Frame $frame, $sync)
+    {
+        $receive = $this->sync;
+        if ($sync !== null) {
+            $receive = $sync;
+        }
+        if ($receive == true) {
+            $frame->headers['receipt'] = md5(microtime());
+        }
+    }
+    /**
+     * Wait for receipt
+     *
+     * @param Stomp_Frame $frame
+     * @param boolean $sync
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    protected function _waitForReceipt (Stomp_Frame $frame, $sync)
+    {
+
+        $receive = $this->sync;
+        if ($sync !== null) {
+            $receive = $sync;
+        }
+        if ($receive == true) {
+            $id = (isset($frame->headers['receipt'])) ? $frame->headers['receipt'] : null;
+            if ($id == null) {
+                return true;
+            }
+            $frame = $this->readFrame();
+            if ($frame instanceof Stomp_Frame && $frame->command == 'RECEIPT') {
+                if ($frame->headers['receipt-id'] == $id) {
+                    return true;
+                } else {
+                    require_once 'Stomp/Exception.php';
+                    throw new Stomp_Exception("Unexpected receipt id {$frame->headers['receipt-id']}", 0, $frame->body);
+                }
+            } else {
+                require_once 'Stomp/Exception.php';
+                if ($frame instanceof Stomp_Frame) {
+                    throw new Stomp_Exception("Unexpected command {$frame->command}", 0, $frame->body);
+                } else {
+                    throw new Stomp_Exception("Receipt not received");
+                }
+            }
+        }
+        return true;
+    }
+    /**
+     * Register to listen to a given destination
+     *
+     * @param string $destination Destination queue
+     * @param array $properties
+     * @param boolean $sync Perform request synchronously
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    public function subscribe ($destination, $properties = null, $sync = null)
+    {
+        $headers = array('ack' => 'client');
+               $headers['activemq.prefetchSize'] = $this->prefetchSize;
+               if ($this->clientId != null) {
+                       $headers["activemq.subcriptionName"] = $this->clientId;
+               }
+        if (isset($properties)) {
+            foreach ($properties as $name => $value) {
+                $headers[$name] = $value;
+            }
+        }
+        $headers['destination'] = $destination;
+        $frame = new Stomp_Frame('SUBSCRIBE', $headers);
+        $this->_prepareReceipt($frame, $sync);
+        $this->_writeFrame($frame);
+        if ($this->_waitForReceipt($frame, $sync) == true) {
+            $this->_subscriptions[$destination] = $properties;
+            return true;
+        } else {
+            return false;
+        }
+    }
+    /**
+     * Remove an existing subscription
+     *
+     * @param string $destination
+     * @param array $properties
+     * @param boolean $sync Perform request synchronously
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    public function unsubscribe ($destination, $properties = null, $sync = null)
+    {
+        $headers = array();
+        if (isset($properties)) {
+            foreach ($properties as $name => $value) {
+                $headers[$name] = $value;
+            }
+        }
+        $headers['destination'] = $destination;
+        $frame = new Stomp_Frame('UNSUBSCRIBE', $headers);
+        $this->_prepareReceipt($frame, $sync);
+        $this->_writeFrame($frame);
+        if ($this->_waitForReceipt($frame, $sync) == true) {
+            unset($this->_subscriptions[$destination]);
+            return true;
+        } else {
+            return false;
+        }
+    }
+    /**
+     * Start a transaction
+     *
+     * @param string $transactionId
+     * @param boolean $sync Perform request synchronously
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    public function begin ($transactionId = null, $sync = null)
+    {
+        $headers = array();
+        if (isset($transactionId)) {
+            $headers['transaction'] = $transactionId;
+        }
+        $frame = new Stomp_Frame('BEGIN', $headers);
+        $this->_prepareReceipt($frame, $sync);
+        $this->_writeFrame($frame);
+        return $this->_waitForReceipt($frame, $sync);
+    }
+    /**
+     * Commit a transaction in progress
+     *
+     * @param string $transactionId
+     * @param boolean $sync Perform request synchronously
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    public function commit ($transactionId = null, $sync = null)
+    {
+        $headers = array();
+        if (isset($transactionId)) {
+            $headers['transaction'] = $transactionId;
+        }
+        $frame = new Stomp_Frame('COMMIT', $headers);
+        $this->_prepareReceipt($frame, $sync);
+        $this->_writeFrame($frame);
+        return $this->_waitForReceipt($frame, $sync);
+    }
+    /**
+     * Roll back a transaction in progress
+     *
+     * @param string $transactionId
+     * @param boolean $sync Perform request synchronously
+     */
+    public function abort ($transactionId = null, $sync = null)
+    {
+        $headers = array();
+        if (isset($transactionId)) {
+            $headers['transaction'] = $transactionId;
+        }
+        $frame = new Stomp_Frame('ABORT', $headers);
+        $this->_prepareReceipt($frame, $sync);
+        $this->_writeFrame($frame);
+        return $this->_waitForReceipt($frame, $sync);
+    }
+    /**
+     * Acknowledge consumption of a message from a subscription
+        * Note: This operation is always asynchronous
+     *
+     * @param string|Stomp_Frame $messageMessage ID
+     * @param string $transactionId
+     * @return boolean
+     * @throws Stomp_Exception
+     */
+    public function ack ($message, $transactionId = null)
+    {
+        if ($message instanceof Stomp_Frame) {
+            $frame = new Stomp_Frame('ACK', $message->headers);
+            $this->_writeFrame($frame);
+            return true;
+        } else {
+            $headers = array();
+            if (isset($transactionId)) {
+                $headers['transaction'] = $transactionId;
+            }
+            $headers['message-id'] = $message;
+            $frame = new Stomp_Frame('ACK', $headers);
+            $this->_writeFrame($frame);
+            return true;
+        }
+    }
+    /**
+     * Graceful disconnect from the server
+     *
+     */
+    public function disconnect ()
+    {
+               $header = array();
+
+               if ($this->clientId != null) {
+                       $headers["client-id"] = $this->clientId;
+               }
+
+        if (is_resource($this->_socket)) {
+            $this->_writeFrame(new Stomp_Frame('DISCONNECT', $headers));
+            fclose($this->_socket);
+        }
+        $this->_socket = null;
+        $this->_sessionId = null;
+        $this->_currentHost = -1;
+        $this->_subscriptions = array();
+        $this->_username = '';
+        $this->_password = '';
+    }
+    /**
+     * Write frame to server
+     *
+     * @param Stomp_Frame $stompFrame
+     */
+    protected function _writeFrame (Stomp_Frame $stompFrame)
+    {
+        if (!is_resource($this->_socket)) {
+            require_once 'Stomp/Exception.php';
+            throw new Stomp_Exception('Socket connection hasn\'t been established');
+        }
+
+        $data = $stompFrame->__toString();
+        $r = fwrite($this->_socket, $data, strlen($data));
+        if ($r === false || $r == 0) {
+            $this->_reconnect();
+            $this->_writeFrame($stompFrame);
+        }
+    }
+    
+    /**
+     * Set timeout to wait for content to read
+     *
+     * @param int $seconds_to_wait  Seconds to wait for a frame
+     * @param int $milliseconds Milliseconds to wait for a frame
+     */
+    public function setReadTimeout($seconds, $milliseconds = 0) 
+    {
+        $this->_read_timeout_seconds = $seconds;
+        $this->_read_timeout_milliseconds = $milliseconds;
+    }
+    
+    /**
+     * Read responce frame from server
+     *
+     * @return Stomp_Frame|Stomp_Message_Map|boolean False when no frame to read
+     */
+    public function readFrame ()
+    {
+        if (!$this->hasFrameToRead()) {
+            return false;
+        }
+        
+        $rb = 1024;
+        $data = '';
+        do {
+            $read = fgets($this->_socket, $rb);
+            if ($read === false) {
+                $this->_reconnect();
+                return $this->readFrame();
+            }
+            $data .= $read;
+            $len = strlen($data);
+        } while (($len < 2 || ! ($data[$len - 2] == "\x00" && $data[$len - 1] == "\n")));
+        
+        list ($header, $body) = explode("\n\n", $data, 2);
+        $header = explode("\n", $header);
+        $headers = array();
+        $command = null;
+        foreach ($header as $v) {
+            if (isset($command)) {
+                list ($name, $value) = explode(':', $v, 2);
+                $headers[$name] = $value;
+            } else {
+                $command = $v;
+            }
+        }
+        $frame = new Stomp_Frame($command, $headers, trim($body));
+        if (isset($frame->headers['amq-msg-type']) && $frame->headers['amq-msg-type'] == 'MapMessage') {
+            require_once 'Stomp/Message/Map.php';
+            return new Stomp_Message_Map($frame);
+        } else {
+            return $frame;
+        }
+    }
+    
+    /**
+     * Check if there is a frame to read
+     *
+     * @return boolean
+     */
+    public function hasFrameToRead()
+    {
+        $read = array($this->_socket);
+        $write = null;
+        $except = null;
+        
+        $has_frame_to_read = stream_select($read, $write, $except, $this->_read_timeout_seconds, $this->_read_timeout_milliseconds);
+
+        if ($has_frame_to_read === false) {
+            throw new Stomp_Exception('Check failed to determin if the socket is readable');
+        } else if ($has_frame_to_read > 0) {
+            return true;
+        } else {
+            return false; 
+        }
+    }
+    
+    /**
+     * Reconnects and renews subscriptions (if there were any)
+     * Call this method when you detect connection problems     
+     */
+    protected function _reconnect ()
+    {
+        $subscriptions = $this->_subscriptions;
+        
+        $this->connect($this->_username, $this->_password);
+        foreach ($subscriptions as $dest => $properties) {
+            $this->subscribe($dest, $properties);
+        }
+    }
+    /**
+     * Graceful object desruction
+     *
+     */
+    public function __destruct()
+    {
+        $this->disconnect();
+    }
+}
+?>
diff --git a/extlib/Stomp/Exception.php b/extlib/Stomp/Exception.php
new file mode 100644 (file)
index 0000000..e6870bc
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
+
+/**
+ * A Stomp Connection
+ *
+ *
+ * @package Stomp
+ * @author Michael Caplan <mcaplan@labnet.net>
+ * @version $Revision: 23 $
+ */\r
+class Stomp_Exception extends Exception\r
+{
+    protected $_details;
+    
+    /**
+     * Constructor
+     *
+     * @param string $message Error message
+     * @param int $code Error code
+     * @param string $details Stomp server error details
+     */
+    public function __construct($message = null, $code = 0, $details = '')
+    {
+        $this->_details = $details;
+        
+        parent::__construct($message, $code);
+    }
+    
+    /**
+     * Stomp server error details
+     *
+     * @return string
+     */
+    public function getDetails()
+    {
+        return $this->_details;
+    }
+}\r
+?>
\ No newline at end of file
diff --git a/extlib/Stomp/Frame.php b/extlib/Stomp/Frame.php
new file mode 100644 (file)
index 0000000..dc59c1c
--- /dev/null
@@ -0,0 +1,80 @@
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
+\r
+/**\r
+ * Stomp Frames are messages that are sent and received on a StompConnection.\r
+ *\r
+ * @package Stomp\r
+ * @author Hiram Chirino <hiram@hiramchirino.com>\r
+ * @author Dejan Bosanac <dejan@nighttale.net>\r
+ * @author Michael Caplan <mcaplan@labnet.net>\r
+ * @version $Revision: 36 $\r
+ */\r
+class Stomp_Frame\r
+{\r
+    public $command;\r
+    public $headers = array();\r
+    public $body;\r
+    \r
+    /**\r
+     * Constructor\r
+     *\r
+     * @param string $command\r
+     * @param array $headers\r
+     * @param string $body\r
+     */\r
+    public function __construct ($command = null, $headers = null, $body = null)\r
+    {\r
+        $this->_init($command, $headers, $body);\r
+    }\r
+    \r
+    protected function _init ($command = null, $headers = null, $body = null)\r
+    {\r
+        $this->command = $command;\r
+        if ($headers != null) {\r
+            $this->headers = $headers;\r
+        }\r
+        $this->body = $body;\r
+        \r
+        if ($this->command == 'ERROR') {\r
+            require_once 'Stomp/Exception.php';\r
+            throw new Stomp_Exception($this->headers['message'], 0, $this->body);\r
+        }\r
+    }
+    
+    /**
+     * Convert frame to transportable string
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        $data = $this->command . "\n";
+        
+        foreach ($this->headers as $name => $value) {
+            $data .= $name . ": " . $value . "\n";
+        }
+        
+        $data .= "\n";
+        $data .= $this->body;
+        return $data .= "\x00\n";
+    }\r
+}\r
+?>
\ No newline at end of file
diff --git a/extlib/Stomp/Message.php b/extlib/Stomp/Message.php
new file mode 100644 (file)
index 0000000..6bcad3e
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
+
+require_once 'Stomp/Frame.php';
+
+/**
+ * Basic text stomp message
+ *
+ * @package Stomp
+ * @author Dejan Bosanac <dejan@nighttale.net>
+ * @version $Revision: 23 $
+ */
+class Stomp_Message extends Stomp_Frame
+{
+    public function __construct ($body, $headers = null)
+    {
+        $this->_init("SEND", $headers, $body);
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/Stomp/Message/Bytes.php b/extlib/Stomp/Message/Bytes.php
new file mode 100644 (file)
index 0000000..c75f23e
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
+
+require_once 'Stomp/Message.php';
+
+/**
+ * Message that contains a stream of uninterpreted bytes
+ *
+ * @package Stomp
+ * @author Dejan Bosanac <dejan@nighttale.net>
+ * @version $Revision: 23 $
+ */
+class Stomp_Message_Bytes extends Stomp_Message
+{
+    /**
+     * Constructor
+     *
+     * @param string $body
+     * @param array $headers
+     */
+    function __construct ($body, $headers = null)
+    {
+        $this->_init("SEND", $headers, $body);
+        if ($this->headers == null) {
+            $this->headers = array();
+        }
+        $this->headers['content-length'] = count($body);
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/Stomp/Message/Map.php b/extlib/Stomp/Message/Map.php
new file mode 100644 (file)
index 0000000..288456a
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ *
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * 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.
+ */
+
+/* vim: set expandtab tabstop=3 shiftwidth=3: */
+
+require_once 'Stomp/Message.php';
+
+/**
+ * Message that contains a set of name-value pairs
+ *
+ * @package Stomp
+ * @author Dejan Bosanac <dejan@nighttale.net>
+ * @version $Revision: 23 $
+ */
+class Stomp_Message_Map extends Stomp_Message
+{
+    public $map;
+    
+    /**
+     * Constructor
+     *
+     * @param Stomp_Frame|string $msg
+     * @param array $headers
+     */
+    function __construct ($msg, $headers = null)
+    {
+        if ($msg instanceof Stomp_Frame) {
+            $this->_init($msg->command, $msg->headers, $msg->body);
+            $this->map = json_decode($msg->body);
+        } else {
+            $this->_init("SEND", $headers, $msg);
+            if ($this->headers == null) {
+                $this->headers = array();
+            }
+            $this->headers['amq-msg-type'] = 'MapMessage';
+            $this->body = json_encode($msg);
+        }
+    }
+}
+?>
\ No newline at end of file
diff --git a/extlib/System/Command.php b/extlib/System/Command.php
new file mode 100644 (file)
index 0000000..f5c3ec6
--- /dev/null
@@ -0,0 +1,587 @@
+<?php
+// {{{ license
+
+// +----------------------------------------------------------------------+
+// | PHP Version 4.0                                                      |
+// +----------------------------------------------------------------------+
+// | 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: Anders Johannsen <anders@johannsen.com>                      |
+// | Author: Dan Allen <dan@mojavelinux.com>
+// +----------------------------------------------------------------------+
+
+// $Id: Command.php,v 1.9 2007/04/20 21:08:48 cconstantine Exp $
+
+// }}}
+// {{{ includes
+
+require_once 'PEAR.php';
+require_once 'System.php';
+
+// }}}
+// {{{ constants
+
+define('SYSTEM_COMMAND_OK',                 1);
+define('SYSTEM_COMMAND_ERROR',             -1);
+define('SYSTEM_COMMAND_NO_SHELL',          -2);
+define('SYSTEM_COMMAND_INVALID_SHELL',     -3);
+define('SYSTEM_COMMAND_TMPDIR_ERROR',      -4);
+define('SYSTEM_COMMAND_INVALID_OPERATOR',  -5);
+define('SYSTEM_COMMAND_INVALID_COMMAND',   -6);
+define('SYSTEM_COMMAND_OPERATOR_PLACEMENT',-7);
+define('SYSTEM_COMMAND_COMMAND_PLACEMENT', -8);
+define('SYSTEM_COMMAND_NOHUP_MISSING',     -9);
+define('SYSTEM_COMMAND_NO_OUTPUT',        -10);
+define('SYSTEM_COMMAND_STDERR',           -11);
+define('SYSTEM_COMMAND_NONZERO_EXIT',     -12);
+
+// }}}
+
+// {{{ class System_Command
+
+/**
+ * The System_Command:: class implements an abstraction for various ways 
+ * of executing commands (directly using the backtick operator,
+ * as a background task after the script has terminated using
+ * register_shutdown_function() or as a detached process using nohup).
+ *
+ * @author  Anders Johannsen <anders@johannsen.com>
+ * @author  Dan Allen <dan@mojavelinux.com>
+ * @version $Revision: 1.9 $
+ */
+
+// }}}
+class System_Command {
+    // {{{ properties
+
+    /**
+     * Array of settings used when creating the shell command
+     *
+     * @var array
+     * @access private
+     */
+    var $options = array();
+
+    /**
+     * Array of available shells to use to execute the command
+     *
+     * @var array
+     * @access private
+     */
+    var $shells = array();
+
+    /**
+     * Array of available control operators used between commands
+     *
+     * @var array
+     * @access private
+     */
+    var $controlOperators = array();
+
+    /**
+     * The system command to be executed
+     *
+     * @var string
+     * @access private
+     */
+    var $systemCommand = null;
+
+    /**
+     * Previously added part to the command string
+     *
+     * @var string
+     * @access private
+     */
+    var $previousElement = null;
+
+    /**
+     * Directory for writing stderr output
+     *
+     * @var string
+     * @access private
+     */
+    var $tmpDir = null;
+
+    /**
+     * To allow the pear error object to accumulate when building
+     * the command, we use the command status to keep track when
+     * a pear error is raised
+     *
+     * @var int
+     * @access private
+     */
+    var $commandStatus = 0;
+    
+    /**
+     * Hold initialization PEAR_Error
+     *
+     * @var object
+     * @access private
+     **/
+    var $_initError = null;
+        
+    // }}}
+    // {{{ constructor
+
+    /**
+     * Class constructor
+     * 
+     * Defines all necessary constants and sets defaults
+     * 
+     * @access public
+     */
+    function System_Command($in_shell = null)
+    {
+        // Defining constants
+        $this->options = array(
+            'SEQUENCE'   => true,
+            'SHUTDOWN'   => false,
+            'SHELL'      => $this->which($in_shell),
+            'OUTPUT'     => true,
+            'NOHUP'      => false,
+            'BACKGROUND' => false,
+            'STDERR'     => false
+        );
+
+        // prepare the available control operators
+        $this->controlOperators = array(
+            'PIPE'  => '|',
+            'AND'   => '&&',
+            'OR'    => '||',
+            'GROUP' => ';',
+            'LFIFO' => '<',
+            'RFIFO' => '>',
+        );
+                
+        // List of allowed/available shells
+        $this->shells = array(
+            'sh',
+            'bash',
+            'zsh',
+            'tcsh',
+            'csh',
+            'ash',
+            'sash',
+            'esh',
+            'ksh'
+        );
+                                   
+        // Find the first available shell
+        if (empty($this->options['SHELL'])) {
+            foreach ($this->shells as $shell) {
+                if ($this->options['SHELL'] = $this->which($shell)) {
+                    break;
+                }
+            }
+
+            // see if we still have no shell
+            if (empty($this->options['SHELL'])) {
+               $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_WARNING, null, 'System_Command_Error', true);
+                return;
+            }
+        }
+
+        // Caputre a temporary directory for capturing stderr from commands
+        $this->tmpDir = System::tmpdir();
+        if (!System::mkDir("-p {$this->tmpDir}")) {
+            $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_TMPDIR_ERROR, null, E_USER_WARNING, null, 'System_Command_Error', true);
+            return;
+        }
+    }
+        
+    // }}}
+    // {{{ setOption()
+
+    /**
+     * Sets the value for an option. Each option should be set to true
+     * or false; except the 'SHELL' option which should be a string
+     * naming a shell. The options are:
+     * 
+     * 'SEQUENCE'   Allow a sequence command or not (right now this is always on);
+     *
+     * 'SHUTDOWN'   Execute commands via a shutdown function;
+     *
+     * 'SHELL'      Path to shell;
+     *
+     * 'OUTPUT'     Output stdout from process;
+     *
+     * 'NOHUP'      Use nohup to detach process;
+     *
+     * 'BACKGROUND' Run as a background process with &;
+     *
+     * 'STDERR'     Output on stderr will raise an error, even if
+     *              the command's exit value is zero. The output from
+     *              stderr can be retrieved using the getDebugInfo()
+     *              method of the Pear_ERROR object returned by
+     *              execute().;
+     *
+     * @param string $in_option is a case-sensitive string,
+     *                          corresponding to the option
+     *                          that should be changed
+     * @param mixed $in_setting is the new value for the option
+     * @access public
+     * @return bool true if succes, else false
+     */
+    function setOption($in_option, $in_setting)
+    {
+       if ($this->_initError) {
+            return $this->_initError;
+        }
+
+        $option = strtoupper($in_option);
+
+        if (!isset($this->options[$option])) {
+            PEAR::raiseError(null, SYSTEM_COMMAND_ERROR, null, E_USER_NOTICE, null, 'System_Command_Error', true);
+            return false;
+        }
+                
+        switch ($option) {
+            case 'OUTPUT':
+            case 'SHUTDOWN':
+            case 'SEQUENCE':
+            case 'BACKGROUND':
+            case 'STDERR':
+                $this->options[$option] = !empty($in_setting);
+                return true;
+            break;
+                
+            case 'SHELL':
+                if (($shell = $this->which($in_setting)) !== false) {
+                    $this->options[$option] = $shell;
+                    return true;
+                } 
+                else {
+                    PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_NOTICE, $in_setting, 'System_Command_Error', true);
+                    return false;
+                }
+            break;
+                        
+            case 'NOHUP':
+                if (empty($in_setting)) {
+                    $this->options[$option] = false;
+                } 
+                else if ($location = $this->which('nohup')) {
+                    $this->options[$option] = $location;
+                } 
+                else {
+                    PEAR::raiseError(null, SYSTEM_COMMAND_NOHUP_MISSING, null, E_USER_NOTICE, null, 'System_Command_Error', true);
+                    return false;
+                }
+            break;
+        }
+    }
+    
+    // }}}
+    // {{{ pushCommand()
+
+    /**
+     * Used to push a command onto the running command to be executed
+     *
+     * @param  string $in_command binary to be run
+     * @param  string $in_argument either an option or argument value, to be handled appropriately
+     * @param  string $in_argument
+     * @param  ...
+     *
+     * @access public
+     * @return boolean true on success {or System_Command_Error Exception}
+     */
+    function pushCommand($in_command)
+    {
+       if ($this->_initError) {
+            return $this->_initError;
+        }
+        
+        if (!is_null($this->previousElement) && !in_array($this->previousElement, $this->controlOperators)) {
+            $this->commandStatus = -1;
+            $error = PEAR::raiseError(null, SYSTEM_COMMAND_COMMAND_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+        }
+
+        // check for error here
+        $command = escapeshellcmd($this->which($in_command));
+        if ($command === false) {
+            $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, null, 'System_Command_Error', true);
+        }
+
+        $argv = func_get_args();
+        array_shift($argv);
+        foreach($argv as $arg) {
+            if (strpos($arg, '-') === 0) {
+                $command .= ' ' . $arg; 
+            }
+            elseif ($arg != '') {
+                $command .= ' ' . escapeshellarg($arg);
+            }
+        }
+
+        $this->previousElement = $command;
+        $this->systemCommand .= $command;
+
+        return isset($error) ? $error : true;
+    }
+
+    // }}}
+    // {{{ pushOperator()
+
+    /**
+     * Used to push an operator onto the running command to be executed
+     *
+     * @param  string $in_operator Either string reprentation of operator or system character
+     *
+     * @access public
+     * @return boolean true on success {or System_Command_Error Exception}
+     */
+    function pushOperator($in_operator)
+    {
+       if ($this->_initError) {
+            return $this->_initError;
+        }
+
+        $operator = isset($this->controlOperators[$in_operator]) ? $this->controlOperators[$in_operator] : $in_operator;
+
+        if (is_null($this->previousElement) || in_array($this->previousElement, $this->controlOperators)) {
+            $this->commandStatus = -1;
+            $error = PEAR::raiseError(null, SYSTEM_COMMAND_OPERATOR_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+        }
+        elseif (!in_array($operator, $this->controlOperators)) {
+            $this->commandStatus = -1;
+            $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_OPERATOR, null, E_USER_WARNING, $operator, 'System_Command_Error', true);
+        }
+
+        $this->previousElement = $operator;
+        $this->systemCommand .= ' ' . $operator . ' ';
+        return isset($error) ? $error : true;
+    }
+
+    // }}}
+    // {{{ execute()
+
+    /**
+     * Executes the code according to given options
+     *
+     * @return bool true if success {or System_Command_Exception}
+     *
+     * @access public
+     */
+    function execute() 
+    {
+       if ($this->_initError) {
+            return $this->_initError;
+        }
+
+        // if the command is empty or if the last element was a control operator, we can't continue
+        if (is_null($this->previousElement) || $this->commandStatus == -1 || in_array($this->previousElement, $this->controlOperators)) {
+            return PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, $this->systemCommand, 'System_Command_Error', true);
+        }
+
+        // Warning about impossible mix of options
+        if (!empty($this->options['OUTPUT'])) {
+            if (!empty($this->options['SHUTDOWN']) || !empty($this->options['NOHUP'])) {
+                return PEAR::raiseError(null, SYSTEM_COMMAND_NO_OUTPUT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+            }
+        }
+                
+        // if this is not going to stdout, then redirect to /dev/null
+        if (empty($this->options['OUTPUT'])) {
+            $this->systemCommand .= ' >/dev/null';
+        }
+                
+        $suffix = '';
+        // run a command immune to hangups, with output to a non-tty
+        if (!empty($this->options['NOHUP'])) {
+            $this->systemCommand = $this->options['NOHUP'] . $this->systemCommand;
+        }
+        // run a background process (only if not nohup)
+        elseif (!empty($this->options['BACKGROUND'])) {
+            $suffix = ' &';
+        }
+                
+        // Register to be run on shutdown
+        if (!empty($this->options['SHUTDOWN'])) {
+            $line = "system(\"{$this->systemCommand}$suffix\");";
+            $function = create_function('', $line);
+            register_shutdown_function($function);
+            return true;
+        } 
+        else {
+            // send stderr to a file so that we can reap the error message
+            $tmpFile = tempnam($this->tmpDir, 'System_Command-');
+            $this->systemCommand .= ' 2>' . $tmpFile . $suffix;
+            $shellPipe = $this->which('echo') . ' ' . escapeshellarg($this->systemCommand) . ' | ' . $this->options['SHELL'];
+            exec($shellPipe, $result, $returnVal);
+
+            if ($returnVal !== 0) {
+                // command returned nonzero; that's always an error
+                $return = PEAR::raiseError(null, SYSTEM_COMMAND_NONZERO_EXIT, null, E_USER_WARNING, null, 'System_Command_Error', true);
+            }
+            else if (!$this->options['STDERR']) {
+                // caller does not care about stderr; return success
+                $return = implode("\n", $result);
+            }
+            else {
+                // our caller cares about stderr; check stderr output
+                clearstatcache();
+                if (filesize($tmpFile) > 0) {
+                    // the command actually wrote to stderr
+                    $stderr_output = file_get_contents($tmpFile);
+                    $return = PEAR::raiseError(null, SYSTEM_COMMAND_STDERR, null, E_USER_WARNING, $stderr_output, 'System_Command_Error', true);
+                } else {
+                    // total success; return stdout gathered by exec()
+                    $return = implode("\n", $result);
+                }
+            }
+
+            unlink($tmpFile);
+            return $return;
+        }
+    }
+
+    // }}}
+    // {{{ which()
+
+    /**
+     * Functionality similiar to unix 'which'. Searches the path
+     * for the specified program. 
+     *
+     * @param $cmd name of the executable to search for 
+     *
+     * @access private
+     * @return string returns the full path if found, false if not
+     */
+    function which($in_cmd)
+    {
+        // only pass non-empty strings to System::which()
+        if (!is_string($in_cmd) || '' === $in_cmd) {
+            return(false);
+        }
+
+        // explicitly pass false as fallback value
+        return System::which($in_cmd, false);
+    }   
+
+    // }}}
+    // {{{ reset()
+
+    /**
+     * Prepare for a new command to be built
+     *
+     * @access public
+     * @return void
+     */
+    function reset()
+    {
+        $this->previousElement = null;
+        $this->systemCommand = null;
+        $this->commandStatus = 0;
+    }
+
+    // }}}
+    // {{{ errorMessage()
+
+    /**
+     * Return a textual error message for a System_Command error code
+     *
+     * @param integer error code
+     *
+     * @return string error message, or false if the error code was
+     * not recognized
+     */
+    function errorMessage($in_value)
+    {
+        static $errorMessages;
+        if (!isset($errorMessages)) {
+            $errorMessages = array(
+                SYSTEM_COMMAND_OK                     => 'no error',
+                SYSTEM_COMMAND_ERROR                  => 'unknown error',
+                SYSTEM_COMMAND_NO_SHELL               => 'no shell found',
+                SYSTEM_COMMAND_INVALID_SHELL          => 'invalid shell',
+                SYSTEM_COMMAND_TMPDIR_ERROR           => 'could not create temporary directory',
+                SYSTEM_COMMAND_INVALID_OPERATOR       => 'control operator invalid',
+                SYSTEM_COMMAND_INVALID_COMMAND        => 'invalid system command',
+                SYSTEM_COMMAND_OPERATOR_PLACEMENT     => 'invalid placement of control operator',
+                SYSTEM_COMMAND_COMMAND_PLACEMENT      => 'invalid placement of command',
+                SYSTEM_COMMAND_NOHUP_MISSING          => 'nohup not found on system',
+                SYSTEM_COMMAND_NO_OUTPUT              => 'output not allowed',
+                SYSTEM_COMMAND_STDERR                 => 'command wrote to stderr',
+                SYSTEM_COMMAND_NONZERO_EXIT           => 'non-zero exit value from command',
+            );
+        }
+
+        if (System_Command::isError($in_value)) {
+            $in_value = $in_value->getCode();
+        }
+
+        return isset($errorMessages[$in_value]) ? $errorMessages[$in_value] : $errorMessages[SYSTEM_COMMAND_ERROR];
+    }
+
+    // }}}
+    // {{{ isError()
+
+    /**
+     * Tell whether a result code from a System_Command method is an error
+     *
+     * @param int result code
+     *
+     * @return bool whether $in_value is an error
+     *
+     * @access public
+     */
+    function isError($in_value)
+    {
+        return (is_object($in_value) &&
+                (strtolower(get_class($in_value)) == 'system_command_error' ||
+                 is_subclass_of($in_value, 'system_command_error')));
+    }
+    
+    // }}}
+}
+
+// {{{ class System_Command_Error
+
+/**
+ * System_Command_Error constructor.
+ *
+ * @param mixed      System_Command error code, or string with error message.
+ * @param integer    what "error mode" to operate in
+ * @param integer    what error level to use for $mode & PEAR_ERROR_TRIGGER
+ * @param mixed      additional debug info, such as the last query
+ *
+ * @access public
+ *
+ * @see PEAR_Error
+ */
+
+// }}}
+class System_Command_Error extends PEAR_Error
+{
+    // {{{ properties
+
+    /**
+     * Message in front of the error message
+     * @var string $error_message_prefix
+     */
+    var $error_message_prefix = 'System_Command Error: ';
+
+    // }}}
+    // {{{ constructor
+
+    function System_Command_Error($code = SYSTEM_COMMAND_ERROR, $mode = PEAR_ERROR_RETURN,
+              $level = E_USER_NOTICE, $debuginfo = null)
+    {
+        if (is_int($code)) {
+            $this->PEAR_Error(System_Command::errorMessage($code), $code, $mode, $level, $debuginfo);
+        } else {
+            $this->PEAR_Error("Invalid error code: $code", SYSTEM_COMMAND_ERROR, $mode, $level, $debuginfo);
+        }
+    }
+    
+    // }}}
+}
+?>
index 4c05506b3d896a7961d0cac1ff2e914bf3fcbcda..3d8bc23f216c5867b0b1107be3a637ae33b344a8 100644 (file)
-<?php\r
-/* vim: set expandtab tabstop=4 shiftwidth=4: */\r
-// +----------------------------------------------------------------------+\r
-// | Copyright (c) 1997-2006 Pierre-Alain Joye,Tomas V.V.Cox, Amir Saied  |\r
-// +----------------------------------------------------------------------+\r
-// | This source file is subject to the New BSD license, That is bundled  |\r
-// | with this package in the file LICENSE, and is available through      |\r
-// | the world-wide-web at                                                |\r
-// | http://www.opensource.org/licenses/bsd-license.php                   |\r
-// | If you did not receive a copy of the new BSDlicense and are unable   |\r
-// | to obtain it through the world-wide-web, please send a note to       |\r
-// | pajoye@php.net so we can mail you a copy immediately.                |\r
-// +----------------------------------------------------------------------+\r
-// | Author: Tomas V.V.Cox  <cox@idecnet.com>                             |\r
-// |         Pierre-Alain Joye <pajoye@php.net>                           |\r
-// |         Amir Mohammad Saied <amir@php.net>                           |\r
-// +----------------------------------------------------------------------+\r
-//\r
-/**\r
- * Validation class\r
- *\r
- * Package to validate various datas. It includes :\r
- *   - numbers (min/max, decimal or not)\r
- *   - email (syntax, domain check)\r
- *   - string (predifined type alpha upper and/or lowercase, numeric,...)\r
- *   - date (min, max, rfc822 compliant)\r
- *   - uri (RFC2396)\r
- *   - possibility valid multiple data with a single method call (::multiple)\r
- *\r
- * @category   Validate\r
- * @package    Validate\r
- * @author     Tomas V.V.Cox <cox@idecnet.com>\r
- * @author     Pierre-Alain Joye <pajoye@php.net>\r
- * @author     Amir Mohammad Saied <amir@php.net>\r
- * @copyright  1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied\r
- * @license    http://www.opensource.org/licenses/bsd-license.php  New BSD License\r
- * @version    CVS: $Id: Validate.php,v 1.123 2007/12/12 16:45:51 davidc Exp $\r
- * @link       http://pear.php.net/package/Validate\r
- */\r
-\r
-/**\r
- * Methods for common data validations\r
- */\r
-define('VALIDATE_NUM',          '0-9');\r
-define('VALIDATE_SPACE',        '\s');\r
-define('VALIDATE_ALPHA_LOWER',  'a-z');\r
-define('VALIDATE_ALPHA_UPPER',  'A-Z');\r
-define('VALIDATE_ALPHA',        VALIDATE_ALPHA_LOWER . VALIDATE_ALPHA_UPPER);\r
-define('VALIDATE_EALPHA_LOWER', VALIDATE_ALPHA_LOWER . 'áéíóúýàèìòùäëïöüÿâêîôûãñõ¨åæç½ðøþß');\r
-define('VALIDATE_EALPHA_UPPER', VALIDATE_ALPHA_UPPER . 'ÁÉÍÓÚÝÀÈÌÒÙÄËÏÖܾÂÊÎÔÛÃÑÕ¦ÅÆǼÐØÞ');\r
-define('VALIDATE_EALPHA',       VALIDATE_EALPHA_LOWER . VALIDATE_EALPHA_UPPER);\r
-define('VALIDATE_PUNCTUATION',  VALIDATE_SPACE . '\.,;\:&"\'\?\!\(\)');\r
-define('VALIDATE_NAME',         VALIDATE_EALPHA . VALIDATE_SPACE . "'" . "-");\r
-define('VALIDATE_STREET',       VALIDATE_NUM . VALIDATE_NAME . "/\\ºª\.");\r
-\r
-define('VALIDATE_ITLD_EMAILS',  1);\r
-define('VALIDATE_GTLD_EMAILS',  2);\r
-define('VALIDATE_CCTLD_EMAILS', 4);\r
-define('VALIDATE_ALL_EMAILS',   8);\r
-\r
-/**\r
- * Validation class\r
- *\r
- * Package to validate various datas. It includes :\r
- *   - numbers (min/max, decimal or not)\r
- *   - email (syntax, domain check)\r
- *   - string (predifined type alpha upper and/or lowercase, numeric,...)\r
- *   - date (min, max)\r
- *   - uri (RFC2396)\r
- *   - possibility valid multiple data with a single method call (::multiple)\r
- *\r
- * @category   Validate\r
- * @package    Validate\r
- * @author     Tomas V.V.Cox <cox@idecnet.com>\r
- * @author     Pierre-Alain Joye <pajoye@php.net>\r
- * @author     Amir Mohammad Saied <amir@php.net>\r
- * @copyright  1997-2006 Pierre-Alain Joye,Tomas V.V.Cox,Amir Mohammad Saied\r
- * @license    http://www.opensource.org/licenses/bsd-license.php  New BSD License\r
- * @version    Release: @package_version@\r
- * @link       http://pear.php.net/package/Validate\r
- */\r
-class Validate\r
-{\r
-    /**\r
-     * International Top-Level Domain\r
-     *\r
-     * This is an array of the known international\r
-     * top-level domain names.\r
-     *\r
-     * @access protected\r
-     * @var    array     $_iTld (International top-level domains)\r
-     */\r
-    var $_itld = array(\r
-        'arpa',\r
-        'root',\r
-    );\r
-\r
-    /**\r
-     * Generic top-level domain\r
-     *\r
-     * This is an array of the official\r
-     * generic top-level domains.\r
-     *\r
-     * @access protected\r
-     * @var    array     $_gTld (Generic top-level domains)\r
-     */\r
-    var $_gtld = array(\r
-        'aero',\r
-        'biz',\r
-        'cat',\r
-        'com',\r
-        'coop',\r
-        'edu',\r
-        'gov',\r
-        'info',\r
-        'int',\r
-        'jobs',\r
-        'mil',\r
-        'mobi',\r
-        'museum',\r
-        'name',\r
-        'net',\r
-        'org',\r
-        'pro',\r
-        'travel',\r
-        'asia',\r
-        'post',\r
-        'tel',\r
-        'geo',\r
-    );\r
-\r
-    /**\r
-     * Country code top-level domains\r
-     *\r
-     * This is an array of the official country\r
-     * codes top-level domains\r
-     *\r
-     * @access protected\r
-     * @var    array     $_ccTld (Country Code Top-Level Domain)\r
-     */\r
-    var $_cctld = array(\r
-        'ac',\r
-        'ad','ae','af','ag',\r
-        'ai','al','am','an',\r
-        'ao','aq','ar','as',\r
-        'at','au','aw','ax',\r
-        'az','ba','bb','bd',\r
-        'be','bf','bg','bh',\r
-        'bi','bj','bm','bn',\r
-        'bo','br','bs','bt',\r
-        'bu','bv','bw','by',\r
-        'bz','ca','cc','cd',\r
-        'cf','cg','ch','ci',\r
-        'ck','cl','cm','cn',\r
-        'co','cr','cs','cu',\r
-        'cv','cx','cy','cz',\r
-        'de','dj','dk','dm',\r
-        'do','dz','ec','ee',\r
-        'eg','eh','er','es',\r
-        'et','eu','fi','fj',\r
-        'fk','fm','fo','fr',\r
-        'ga','gb','gd','ge',\r
-        'gf','gg','gh','gi',\r
-        'gl','gm','gn','gp',\r
-        'gq','gr','gs','gt',\r
-        'gu','gw','gy','hk',\r
-        'hm','hn','hr','ht',\r
-        'hu','id','ie','il',\r
-        'im','in','io','iq',\r
-        'ir','is','it','je',\r
-        'jm','jo','jp','ke',\r
-        'kg','kh','ki','km',\r
-        'kn','kp','kr','kw',\r
-        'ky','kz','la','lb',\r
-        'lc','li','lk','lr',\r
-        'ls','lt','lu','lv',\r
-        'ly','ma','mc','md',\r
-        'me','mg','mh','mk',\r
-        'ml','mm','mn','mo',\r
-        'mp','mq','mr','ms',\r
-        'mt','mu','mv','mw',\r
-        'mx','my','mz','na',\r
-        'nc','ne','nf','ng',\r
-        'ni','nl','no','np',\r
-        'nr','nu','nz','om',\r
-        'pa','pe','pf','pg',\r
-        'ph','pk','pl','pm',\r
-        'pn','pr','ps','pt',\r
-        'pw','py','qa','re',\r
-        'ro','rs','ru','rw',\r
-        'sa','sb','sc','sd',\r
-        'se','sg','sh','si',\r
-        'sj','sk','sl','sm',\r
-        'sn','so','sr','st',\r
-        'su','sv','sy','sz',\r
-        'tc','td','tf','tg',\r
-        'th','tj','tk','tl',\r
-        'tm','tn','to','tp',\r
-        'tr','tt','tv','tw',\r
-        'tz','ua','ug','uk',\r
-        'us','uy','uz','va',\r
-        'vc','ve','vg','vi',\r
-        'vn','vu','wf','ws',\r
-        'ye','yt','yu','za',\r
-        'zm','zw',\r
-    );\r
-\r
-\r
-    /**\r
-     * Validate a number\r
-     *\r
-     * @param string    $number     Number to validate\r
-     * @param array     $options    array where:\r
-     *                              'decimal'   is the decimal char or false when decimal not allowed\r
-     *                                          i.e. ',.' to allow both ',' and '.'\r
-     *                              'dec_prec'  Number of allowed decimals\r
-     *                              'min'       minimum value\r
-     *                              'max'       maximum value\r
-     *\r
-     * @return boolean true if valid number, false if not\r
-     *\r
-     * @access public\r
-     */\r
-    function number($number, $options = array())\r
-    {\r
-        $decimal = $dec_prec = $min = $max = null;\r
-        if (is_array($options)) {\r
-            extract($options);\r
-        }\r
-\r
-        $dec_prec   = $dec_prec ? "{1,$dec_prec}" : '+';\r
-        $dec_regex  = $decimal  ? "[$decimal][0-9]$dec_prec" : '';\r
-\r
-        if (!preg_match("|^[-+]?\s*[0-9]+($dec_regex)?\$|", $number)) {\r
-            return false;\r
-        }\r
-\r
-        if ($decimal != '.') {\r
-            $number = strtr($number, $decimal, '.');\r
-        }\r
-\r
-        $number = (float)str_replace(' ', '', $number);\r
-        if ($min !== null && $min > $number) {\r
-            return false;\r
-        }\r
-\r
-        if ($max !== null && $max < $number) {\r
-            return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Converting a string to UTF-7 (RFC 2152)\r
-     *\r
-     * @param   $string     string to be converted\r
-     *\r
-     * @return  string  converted string\r
-     *\r
-     * @access  private\r
-     */\r
-    function __stringToUtf7($string) {\r
-        $return = '';\r
-        $utf7 = array(\r
-                        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',\r
-                        'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',\r
-                        'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',\r
-                        'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',\r
-                        's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',\r
-                        '3', '4', '5', '6', '7', '8', '9', '+', ','\r
-                    );\r
-\r
-        $state = 0;\r
-        if (!empty($string)) {\r
-            $i = 0;\r
-            while ($i <= strlen($string)) {\r
-                $char = substr($string, $i, 1);\r
-                if ($state == 0) {\r
-                    if ((ord($char) >= 0x7F) || (ord($char) <= 0x1F)) {\r
-                        if ($char) {\r
-                            $return .= '&';\r
-                        }\r
-                        $state = 1;\r
-                    } elseif ($char == '&') {\r
-                        $return .= '&-';\r
-                    } else {\r
-                        $return .= $char;\r
-                    }\r
-                } elseif (($i == strlen($string) ||\r
-                            !((ord($char) >= 0x7F)) || (ord($char) <= 0x1F))) {\r
-                    if ($state != 1) {\r
-                        if (ord($char) > 64) {\r
-                            $return .= '';\r
-                        } else {\r
-                            $return .= $utf7[ord($char)];\r
-                        }\r
-                    }\r
-                    $return .= '-';\r
-                    $state = 0;\r
-                } else {\r
-                    switch($state) {\r
-                        case 1:\r
-                            $return .= $utf7[ord($char) >> 2];\r
-                            $residue = (ord($char) & 0x03) << 4;\r
-                            $state = 2;\r
-                            break;\r
-                        case 2:\r
-                            $return .= $utf7[$residue | (ord($char) >> 4)];\r
-                            $residue = (ord($char) & 0x0F) << 2;\r
-                            $state = 3;\r
-                            break;\r
-                        case 3:\r
-                            $return .= $utf7[$residue | (ord($char) >> 6)];\r
-                            $return .= $utf7[ord($char) & 0x3F];\r
-                            $state = 1;\r
-                            break;\r
-                    }\r
-                }\r
-                $i++;\r
-            }\r
-            return $return;\r
-        }\r
-        return '';\r
-    }\r
-\r
-    /**\r
-     * Validate an email according to full RFC822 (inclusive human readable part)\r
-     *\r
-     * @param string $email email to validate,\r
-     *                      will return the address for optional dns validation\r
-     * @param array $options email() options\r
-     *\r
-     * @return boolean true if valid email, false if not\r
-     *\r
-     * @access private\r
-     */\r
-    function __emailRFC822(&$email, &$options)\r
-    {\r
-        if (Validate::__stringToUtf7($email) != $email) {\r
-            return false;\r
-        }\r
-        static $address = null;\r
-        static $uncomment = null;\r
-        if (!$address) {\r
-            // atom        =  1*<any CHAR except specials, SPACE and CTLs>\r
-            $atom = '[^][()<>@,;:\\".\s\000-\037\177-\377]+\s*';\r
-            // qtext       =  <any CHAR excepting <">,     ; => may be folded\r
-            //         "\" & CR, and including linear-white-space>\r
-            $qtext = '[^"\\\\\r]';\r
-            // quoted-pair =  "\" CHAR                     ; may quote any char\r
-            $quoted_pair = '\\\\.';\r
-            // quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or\r
-            //                                             ;   quoted chars.\r
-            $quoted_string = '"(?:' . $qtext . '|' . $quoted_pair . ')*"\s*';\r
-            // word        =  atom / quoted-string\r
-            $word = '(?:' . $atom . '|' . $quoted_string . ')';\r
-            // local-part  =  word *("." word)             ; uninterpreted\r
-            //                                             ; case-preserved\r
-            $local_part = $word . '(?:\.\s*' . $word . ')*';\r
-            // dtext       =  <any CHAR excluding "[",     ; => may be folded\r
-            //         "]", "\" & CR, & including linear-white-space>\r
-            $dtext = '[^][\\\\\r]';\r
-            // domain-literal =  "[" *(dtext / quoted-pair) "]"\r
-            $domain_literal = '\[(?:' . $dtext . '|' . $quoted_pair . ')*\]\s*';\r
-            // sub-domain  =  domain-ref / domain-literal\r
-            // domain-ref  =  atom                         ; symbolic reference\r
-            $sub_domain = '(?:' . $atom . '|' . $domain_literal . ')';\r
-            // domain      =  sub-domain *("." sub-domain)\r
-            $domain = $sub_domain . '(?:\.\s*' . $sub_domain . ')*';\r
-            // addr-spec   =  local-part "@" domain        ; global address\r
-            $addr_spec = $local_part . '@\s*' . $domain;\r
-            // route       =  1#("@" domain) ":"           ; path-relative\r
-            $route = '@' . $domain . '(?:,@\s*' . $domain . ')*:\s*';\r
-            // route-addr  =  "<" [route] addr-spec ">"\r
-            $route_addr = '<\s*(?:' . $route . ')?' . $addr_spec . '>\s*';\r
-            // phrase      =  1*word                       ; Sequence of words\r
-            $phrase = $word  . '+';\r
-            // mailbox     =  addr-spec                    ; simple address\r
-            //             /  phrase route-addr            ; name & addr-spec\r
-            $mailbox = '(?:' . $addr_spec . '|' . $phrase . $route_addr . ')';\r
-            // group       =  phrase ":" [#mailbox] ";"\r
-            $group = $phrase . ':\s*(?:' . $mailbox . '(?:,\s*' . $mailbox . ')*)?;\s*';\r
-            //     address     =  mailbox                      ; one addressee\r
-            //                 /  group                        ; named list\r
-            $address = '/^\s*(?:' . $mailbox . '|' . $group . ')$/';\r
-            $uncomment =\r
-            '/((?:(?:\\\\"|[^("])*(?:' . $quoted_string .\r
-                                             ')?)*)((?<!\\\\)\((?:(?2)|.)*?(?<!\\\\)\))/';\r
-        }\r
-        // strip comments\r
-        $email = preg_replace($uncomment, '$1 ', $email);\r
-        return preg_match($address, $email);\r
-    }\r
-\r
-    /**\r
-     * Full TLD Validation function\r
-     *\r
-     * This function is used to make a much more proficient validation\r
-     * against all types of official domain names.\r
-     *\r
-     * @access protected\r
-     * @param  string    $email    The email address to check.\r
-     * @param  array     $options  The options for validation\r
-     * @return bool      True if validating succeeds\r
-     */\r
-    function _fullTLDValidation($email, $options)\r
-    {\r
-        $validate = array();\r
-\r
-        switch ($options) {\r
-            /** 1 */\r
-            case VALIDATE_ITLD_EMAILS:\r
-                array_push($validate, 'itld');\r
-                break;\r
-\r
-            /** 2 */\r
-            case VALIDATE_GTLD_EMAILS:\r
-                array_push($validate, 'gtld');\r
-                break;\r
-\r
-            /** 3 */\r
-            case VALIDATE_ITLD_EMAILS | VALIDATE_GTLD_EMAILS:\r
-                array_push($validate, 'itld');\r
-                array_push($validate, 'gtld');\r
-                break;\r
-\r
-            /** 4 */\r
-            case VALIDATE_CCTLD_EMAILS:\r
-                array_push($validate, 'cctld');\r
-                break;\r
-\r
-            /** 5 */\r
-            case VALIDATE_CCTLD_EMAILS | VALIDATE_ITLD_EMAILS:\r
-                array_push($validate, 'cctld');\r
-                array_push($validate, 'itld');\r
-                break;\r
-\r
-            /** 6 */\r
-            case VALIDATE_CCTLD_EMAILS ^ VALIDATE_ITLD_EMAILS:\r
-                array_push($validate, 'cctld');\r
-                array_push($validate, 'itld');\r
-                break;\r
-\r
-            /** 7 - 8 */\r
-            case VALIDATE_CCTLD_EMAILS | VALIDATE_ITLD_EMAILS | VALIDATE_GTLD_EMAILS:\r
-            case VALIDATE_ALL_EMAILS:\r
-                array_push($validate, 'cctld');\r
-                array_push($validate, 'itld');\r
-                array_push($validate, 'gtld');\r
-                break;\r
-        }\r
-\r
-        /**\r
-         * Debugging still, not implemented but code is somewhat here.\r
-         */\r
-\r
-        $self = new Validate;\r
-\r
-        $toValidate = array();\r
-\r
-        foreach ($validate as $valid) {\r
-            $tmpVar = '_' . (string)$valid;\r
-            $toValidate[$valid] = $self->{$tmpVar};\r
-        }\r
-\r
-        $e = $self->executeFullEmailValidation($email, $toValidate);\r
-\r
-        return $e;\r
-    }\r
-    // {{{ protected function executeFullEmailValidation\r
-    /**\r
-     * Execute the validation\r
-     *\r
-     * This function will execute the full email vs tld\r
-     * validation using an array of tlds passed to it.\r
-     *\r
-     * @access public\r
-     * @param  string $email       The email to validate.\r
-     * @param  array  $arrayOfTLDs The array of the TLDs to validate\r
-     * @return true or false (Depending on if it validates or if it does not)\r
-     */\r
-    function executeFullEmailValidation($email, $arrayOfTLDs)\r
-    {\r
-        $emailEnding = explode('.', $email);\r
-        $emailEnding = $emailEnding[count($emailEnding)-1];\r
-        \r
-        foreach ($arrayOfTLDs as $validator => $keys) {\r
-            if (in_array($emailEnding, $keys)) {\r
-                return true;\r
-            }\r
-        }\r
-        return false;\r
-    }\r
-    // }}}\r
-\r
-    /**\r
-     * Validate an email\r
-     *\r
-     * @param string $email email to validate\r
-     * @param mixed boolean (BC) $check_domain   Check or not if the domain exists\r
-     *              array $options associative array of options\r
-     *              'check_domain' boolean Check or not if the domain exists\r
-     *              'use_rfc822' boolean Apply the full RFC822 grammar\r
-     *\r
-     * @return boolean true if valid email, false if not\r
-     *\r
-     * @access public\r
-     */\r
-    function email($email, $options = null)\r
-    {\r
-        $check_domain = false;\r
-        $use_rfc822 = false;\r
-        if (is_bool($options)) {\r
-            $check_domain = $options;\r
-        } elseif (is_array($options)) {\r
-            extract($options);\r
-        }\r
-\r
-        /**\r
-         * @todo Fix bug here.. even if it passes this, it won't be passing\r
-         *       The regular expression below\r
-         */\r
-        if (isset($fullTLDValidation)) {\r
-            $valid = Validate::_fullTLDValidation($email, $fullTLDValidation);\r
-\r
-            if (!$valid) {\r
-                return false;\r
-            }\r
-        }\r
-\r
-        // the base regexp for address\r
-        $regex = '&^(?:                                               # recipient:\r
-         ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                          #1 quoted name\r
-         ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*)) #2 OR dot-atom\r
-         @(((\[)?                     #3 domain, 4 as IPv4, 5 optionally bracketed\r
-         (?:(?:(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))\.){3}\r
-               (?:(?:25[0-5])|(?:2[0-4][0-9])|(?:[0-1]?[0-9]?[0-9]))))(?(5)\])|\r
-         ((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)  #6 domain as hostname\r
-         \.((?:([^- ])[-a-z]*[-a-z]))) #7 TLD \r
-         $&xi';\r
-        \r
-        if ($use_rfc822? Validate::__emailRFC822($email, $options) :\r
-            preg_match($regex, $email)) {\r
-            if ($check_domain && function_exists('checkdnsrr')) {\r
-                list (, $domain)  = explode('@', $email);\r
-                if (checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A')) {\r
-                    return true;\r
-                }\r
-                return false;\r
-            }\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-   /**\r
-     * Validate a string using the given format 'format'\r
-     *\r
-     * @param string    $string     String to validate\r
-     * @param array     $options    Options array where:\r
-     *                              'format' is the format of the string\r
-     *                                  Ex: VALIDATE_NUM . VALIDATE_ALPHA (see constants)\r
-     *                              'min_length' minimum length\r
-     *                              'max_length' maximum length\r
-     *\r
-     * @return boolean true if valid string, false if not\r
-     *\r
-     * @access public\r
-     */\r
-    function string($string, $options)\r
-    {\r
-        $format = null;\r
-        $min_length = $max_length = 0;\r
-        if (is_array($options)) {\r
-            extract($options);\r
-        }\r
-        if ($format && !preg_match("|^[$format]*\$|s", $string)) {\r
-            return false;\r
-        }\r
-        if ($min_length && strlen($string) < $min_length) {\r
-            return false;\r
-        }\r
-        if ($max_length && strlen($string) > $max_length) {\r
-            return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Validate an URI (RFC2396)\r
-     * This function will validate 'foobarstring' by default, to get it to validate\r
-     * only http, https, ftp and such you have to pass it in the allowed_schemes\r
-     * option, like this:\r
-     * <code>\r
-     * $options = array('allowed_schemes' => array('http', 'https', 'ftp'))\r
-     * var_dump(Validate::uri('http://www.example.org', $options));\r
-     * </code>\r
-     *\r
-     * NOTE 1: The rfc2396 normally allows middle '-' in the top domain\r
-     *         e.g. http://example.co-m should be valid\r
-     *         However, as '-' is not used in any known TLD, it is invalid\r
-     * NOTE 2: As double shlashes // are allowed in the path part, only full URIs\r
-     *         including an authority can be valid, no relative URIs\r
-     *         the // are mandatory (optionally preceeded by the 'sheme:' )\r
-     * NOTE 3: the full complience to rfc2396 is not achieved by default\r
-     *         the characters ';/?:@$,' will not be accepted in the query part\r
-     *         if not urlencoded, refer to the option "strict'"\r
-     *\r
-     * @param string    $url        URI to validate\r
-     * @param array     $options    Options used by the validation method.\r
-     *                              key => type\r
-     *                              'domain_check' => boolean\r
-     *                                  Whether to check the DNS entry or not\r
-     *                              'allowed_schemes' => array, list of protocols\r
-     *                                  List of allowed schemes ('http',\r
-     *                                  'ssh+svn', 'mms')\r
-     *                              'strict' => string the refused chars\r
-     *                                   in query and fragment parts\r
-     *                                   default: ';/?:@$,'\r
-     *                                   empty: accept all rfc2396 foreseen chars\r
-     *\r
-     * @return boolean true if valid uri, false if not\r
-     *\r
-     * @access public\r
-     */\r
-    function uri($url, $options = null)\r
-    {\r
-        $strict = ';/?:@$,';\r
-        $domain_check = false;\r
-        $allowed_schemes = null;\r
-        if (is_array($options)) {\r
-            extract($options);\r
-        }\r
-        if (preg_match(\r
-             '&^(?:([a-z][-+.a-z0-9]*):)?                             # 1. scheme\r
-              (?://                                                   # authority start\r
-              (?:((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();:\&=+$,])*)@)?    # 2. authority-userinfo\r
-              (?:((?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*[a-z](?:[a-z0-9]+)?\.?)  # 3. authority-hostname OR\r
-              |([0-9]{1,3}(?:\.[0-9]{1,3}){3}))                       # 4. authority-ipv4\r
-              (?::([0-9]*))?)                                        # 5. authority-port\r
-              ((?:/(?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'():@\&=+$,;])*)*/?)? # 6. path\r
-              (?:\?([^#]*))?                                          # 7. query\r
-              (?:\#((?:%[0-9a-f]{2}|[-a-z0-9_.!~*\'();/?:@\&=+$,])*))? # 8. fragment\r
-              $&xi', $url, $matches)) {\r
-            $scheme = isset($matches[1]) ? $matches[1] : '';\r
-            $authority = isset($matches[3]) ? $matches[3] : '' ;\r
-            if (is_array($allowed_schemes) &&\r
-                !in_array($scheme,$allowed_schemes)\r
-            ) {\r
-                return false;\r
-            }\r
-            if (!empty($matches[4])) {\r
-                $parts = explode('.', $matches[4]);\r
-                foreach ($parts as $part) {\r
-                    if ($part > 255) {\r
-                        return false;\r
-                    }\r
-                }\r
-            } elseif ($domain_check && function_exists('checkdnsrr')) {\r
-                if (!checkdnsrr($authority, 'A')) {\r
-                    return false;\r
-                }\r
-            }\r
-            if ($strict) {\r
-                $strict = '#[' . preg_quote($strict, '#') . ']#';\r
-                if ((!empty($matches[7]) && preg_match($strict, $matches[7]))\r
-                 || (!empty($matches[8]) && preg_match($strict, $matches[8]))) {\r
-                    return false;\r
-                }\r
-            }\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-    /**\r
-     * Validate date and times. Note that this method need the Date_Calc class\r
-     *\r
-     * @param string    $date   Date to validate\r
-     * @param array     $options array options where :\r
-     *                          'format' The format of the date (%d-%m-%Y)\r
-     *                                   or rfc822_compliant\r
-     *                          'min' The date has to be greater\r
-     *                                than this array($day, $month, $year)\r
-     *                                or PEAR::Date object\r
-     *                          'max' The date has to be smaller than\r
-     *                                this array($day, $month, $year)\r
-     *                                or PEAR::Date object\r
-     *\r
-     * @return boolean true if valid date/time, false if not\r
-     *\r
-     * @access public\r
-     */\r
-    function date($date, $options)\r
-    {\r
-        $max = $min = false;\r
-        $format = '';\r
-        if (is_array($options)) {\r
-            extract($options);\r
-        }\r
-\r
-        if (strtolower($format) == 'rfc822_compliant') {\r
-            $preg = '&^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),) \s+\r
-                    (?:(\d{2})?) \s+\r
-                    (?:(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)?) \s+\r
-                    (?:(\d{2}(\d{2})?)?) \s+\r
-                    (?:(\d{2}?)):(?:(\d{2}?))(:(?:(\d{2}?)))? \s+\r
-                    (?:[+-]\d{4}|UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Za-ik-z])$&xi';\r
-\r
-            if (!preg_match($preg, $date, $matches)) {\r
-                return false;\r
-            }\r
-\r
-            $year   = (int)$matches[4];\r
-            $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\r
-                            'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');\r
-            $month  = array_keys($months, $matches[3]);\r
-            $month  = (int)$month[0]+1;\r
-            $day    = (int)$matches[2];\r
-            $weekday= $matches[1];\r
-            $hour   = (int)$matches[6];\r
-            $minute = (int)$matches[7];\r
-            isset($matches[9]) ? $second = (int)$matches[9] : $second = 0;\r
-\r
-            if ((strlen($year) != 4)        ||\r
-                ($day    > 31   || $day < 1)||\r
-                ($hour   > 23)  ||\r
-                ($minute > 59)  ||\r
-                ($second > 59)) {\r
-                    return false;\r
-            }\r
-        } else {\r
-            $date_len = strlen($format);\r
-            for ($i = 0; $i < $date_len; $i++) {\r
-                $c = $format{$i};\r
-                if ($c == '%') {\r
-                    $next = $format{$i + 1};\r
-                    switch ($next) {\r
-                        case 'j':\r
-                        case 'd':\r
-                            if ($next == 'j') {\r
-                                $day = (int)Validate::_substr($date, 1, 2);\r
-                            } else {\r
-                                $day = (int)Validate::_substr($date, 0, 2);\r
-                            }\r
-                            if ($day < 1 || $day > 31) {\r
-                                return false;\r
-                            }\r
-                            break;\r
-                        case 'm':\r
-                        case 'n':\r
-                            if ($next == 'm') {\r
-                                $month = (int)Validate::_substr($date, 0, 2);\r
-                            } else {\r
-                                $month = (int)Validate::_substr($date, 1, 2);\r
-                            }\r
-                            if ($month < 1 || $month > 12) {\r
-                                return false;\r
-                            }\r
-                            break;\r
-                        case 'Y':\r
-                        case 'y':\r
-                            if ($next == 'Y') {\r
-                                $year = Validate::_substr($date, 4);\r
-                                $year = (int)$year?$year:'';\r
-                            } else {\r
-                                $year = (int)(substr(date('Y'), 0, 2) .\r
-                                              Validate::_substr($date, 2));\r
-                            }\r
-                            if (strlen($year) != 4 || $year < 0 || $year > 9999) {\r
-                                return false;\r
-                            }\r
-                            break;\r
-                        case 'g':\r
-                        case 'h':\r
-                            if ($next == 'g') {\r
-                                $hour = Validate::_substr($date, 1, 2);\r
-                            } else {\r
-                                $hour = Validate::_substr($date, 2);\r
-                            }\r
-                            if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 12) {\r
-                                return false;\r
-                            }\r
-                            break;\r
-                        case 'G':\r
-                        case 'H':\r
-                            if ($next == 'G') {\r
-                                $hour = Validate::_substr($date, 1, 2);\r
-                            } else {\r
-                                $hour = Validate::_substr($date, 2);\r
-                            }\r
-                            if (!preg_match('/^\d+$/', $hour) || $hour < 0 || $hour > 24) {\r
-                                return false;\r
-                            }\r
-                            break;\r
-                        case 's':\r
-                        case 'i':\r
-                            $t = Validate::_substr($date, 2);\r
-                            if (!preg_match('/^\d+$/', $t) || $t < 0 || $t > 59) {\r
-                                return false;\r
-                            }\r
-                            break;\r
-                        default:\r
-                            trigger_error("Not supported char `$next' after % in offset " . ($i+2), E_USER_WARNING);\r
-                    }\r
-                    $i++;\r
-                } else {\r
-                    //literal\r
-                    if (Validate::_substr($date, 1) != $c) {\r
-                        return false;\r
-                    }\r
-                }\r
-            }\r
-        }\r
-        // there is remaing data, we don't want it\r
-        if (strlen($date) && (strtolower($format) != 'rfc822_compliant')) {\r
-            return false;\r
-        }\r
-\r
-        if (isset($day) && isset($month) && isset($year)) {\r
-            if (!checkdate($month, $day, $year)) {\r
-                return false;\r
-            }\r
-\r
-            if (strtolower($format) == 'rfc822_compliant') {\r
-                if ($weekday != date("D", mktime(0, 0, 0, $month, $day, $year))) {\r
-                    return false;\r
-                }\r
-            }\r
-\r
-            if ($min) {\r
-                include_once 'Date/Calc.php';\r
-                if (is_a($min, 'Date') &&\r
-                    (Date_Calc::compareDates($day, $month, $year,\r
-                                             $min->getDay(), $min->getMonth(), $min->getYear()) < 0))\r
-                {\r
-                    return false;\r
-                } elseif (is_array($min) &&\r
-                        (Date_Calc::compareDates($day, $month, $year,\r
-                                             $min[0], $min[1], $min[2]) < 0))\r
-                {\r
-                    return false;\r
-                }\r
-            }\r
-\r
-            if ($max) {\r
-                include_once 'Date/Calc.php';\r
-                if (is_a($max, 'Date') &&\r
-                    (Date_Calc::compareDates($day, $month, $year,\r
-                                             $max->getDay(), $max->getMonth(), $max->getYear()) > 0))\r
-                {\r
-                    return false;\r
-                } elseif (is_array($max) &&\r
-                        (Date_Calc::compareDates($day, $month, $year,\r
-                                                 $max[0], $max[1], $max[2]) > 0))\r
-                {\r
-                    return false;\r
-                }\r
-            }\r
-        }\r
-\r
-        return true;\r
-    }\r
-\r
-    function _substr(&$date, $num, $opt = false)\r
-    {\r
-        if ($opt && strlen($date) >= $opt && preg_match('/^[0-9]{'.$opt.'}/', $date, $m)) {\r
-            $ret = $m[0];\r
-        } else {\r
-            $ret = substr($date, 0, $num);\r
-        }\r
-        $date = substr($date, strlen($ret));\r
-        return $ret;\r
-    }\r
-\r
-    function _modf($val, $div) {\r
-        if (function_exists('bcmod')) {\r
-            return bcmod($val, $div);\r
-        } elseif (function_exists('fmod')) {\r
-            return fmod($val, $div);\r
-        }\r
-        $r = $val / $div;\r
-        $i = intval($r);\r
-        return intval($val - $i * $div + .1);\r
-    }\r
-\r
-    /**\r
-     * Calculates sum of product of number digits with weights\r
-     *\r
-     * @param string $number number string\r
-     * @param array $weights reference to array of weights\r
-     *\r
-     * @returns int returns product of number digits with weights\r
-     *\r
-     * @access protected\r
-     */\r
-    function _multWeights($number, &$weights) {\r
-        if (!is_array($weights)) {\r
-            return -1;\r
-        }\r
-        $sum = 0;\r
-\r
-        $count = min(count($weights), strlen($number));\r
-        if ($count == 0)  { // empty string or weights array\r
-            return -1;\r
-        }\r
-        for ($i = 0; $i < $count; ++$i) {\r
-            $sum += intval(substr($number, $i, 1)) * $weights[$i];\r
-        }\r
-\r
-        return $sum;\r
-    }\r
-\r
-    /**\r
-     * Calculates control digit for a given number\r
-     *\r
-     * @param string $number number string\r
-     * @param array $weights reference to array of weights\r
-     * @param int $modulo (optionsl) number\r
-     * @param int $subtract (optional) number\r
-     * @param bool $allow_high (optional) true if function can return number higher than 10\r
-     *\r
-     * @returns int -1 calculated control number is returned\r
-     *\r
-     * @access protected\r
-     */\r
-    function _getControlNumber($number, &$weights, $modulo = 10, $subtract = 0, $allow_high = false) {\r
-        // calc sum\r
-        $sum = Validate::_multWeights($number, $weights);\r
-        if ($sum == -1) {\r
-            return -1;\r
-        }\r
-        $mod = Validate::_modf($sum, $modulo);  // calculate control digit\r
-\r
-        if ($subtract > $mod && $mod > 0) {\r
-            $mod = $subtract - $mod;\r
-        }\r
-        if ($allow_high === false) {\r
-            $mod %= 10;           // change 10 to zero\r
-        }\r
-        return $mod;\r
-    }\r
-\r
-    /**\r
-     * Validates a number\r
-     *\r
-     * @param string $number number to validate\r
-     * @param array $weights reference to array of weights\r
-     * @param int $modulo (optionsl) number\r
-     * @param int $subtract (optional) numbier\r
-     *\r
-     * @returns bool true if valid, false if not\r
-     *\r
-     * @access protected\r
-     */\r
-    function _checkControlNumber($number, &$weights, $modulo = 10, $subtract = 0) {\r
-        if (strlen($number) < count($weights)) {\r
-            return false;\r
-        }\r
-        $target_digit  = substr($number, count($weights), 1);\r
-        $control_digit = Validate::_getControlNumber($number, $weights, $modulo, $subtract, $modulo > 10);\r
-\r
-        if ($control_digit == -1) {\r
-            return false;\r
-        }\r
-        if ($target_digit === 'X' && $control_digit == 10) {\r
-            return true;\r
-        }\r
-        if ($control_digit != $target_digit) {\r
-            return false;\r
-        }\r
-        return true;\r
-    }\r
-\r
-    /**\r
-     * Bulk data validation for data introduced in the form of an\r
-     * assoc array in the form $var_name => $value.\r
-     * Can be used on any of Validate subpackages\r
-     *\r
-     * @param  array   $data     Ex: array('name' => 'toto', 'email' => 'toto@thing.info');\r
-     * @param  array   $val_type Contains the validation type and all parameters used in.\r
-     *                           'val_type' is not optional\r
-     *                           others validations properties must have the same name as the function\r
-     *                           parameters.\r
-     *                           Ex: array('toto'=>array('type'=>'string','format'='toto@thing.info','min_length'=>5));\r
-     * @param  boolean $remove if set, the elements not listed in data will be removed\r
-     *\r
-     * @return array   value name => true|false    the value name comes from the data key\r
-     *\r
-     * @access public\r
-     */\r
-    function multiple(&$data, &$val_type, $remove = false)\r
-    {\r
-        $keys = array_keys($data);\r
-        $valid = array();\r
-        foreach ($keys as $var_name) {\r
-            if (!isset($val_type[$var_name])) {\r
-                if ($remove) {\r
-                    unset($data[$var_name]);\r
-                }\r
-                continue;\r
-            }\r
-            $opt = $val_type[$var_name];\r
-            $methods = get_class_methods('Validate');\r
-            $val2check = $data[$var_name];\r
-            // core validation method\r
-            if (in_array(strtolower($opt['type']), $methods)) {\r
-                //$opt[$opt['type']] = $data[$var_name];\r
-                $method = $opt['type'];\r
-                unset($opt['type']);\r
-\r
-                if (sizeof($opt) == 1 && is_array(reset($opt))) {\r
-                    $opt = array_pop($opt);\r
-                }\r
-                $valid[$var_name] = call_user_func(array('Validate', $method), $val2check, $opt);\r
-\r
-            /**\r
-             * external validation method in the form:\r
-             * "<class name><underscore><method name>"\r
-             * Ex: us_ssn will include class Validate/US.php and call method ssn()\r
-             */\r
-            } elseif (strpos($opt['type'], '_') !== false) {\r
-                $validateType = explode('_', $opt['type']);\r
-                $method       = array_pop($validateType);\r
-                $class        = implode('_', $validateType);\r
-                $classPath    = str_replace('_', DIRECTORY_SEPARATOR, $class);\r
-                $class        = 'Validate_' . $class;\r
-                if (!@include_once "Validate/$classPath.php") {\r
-                    trigger_error("$class isn't installed or you may have some permissoin issues", E_USER_ERROR);\r
-                }\r
-\r
-                $ce = substr(phpversion(), 0, 1) > 4 ? class_exists($class, false) : class_exists($class);\r
-                if (!$ce ||\r
-                    !in_array($method, get_class_methods($class)))\r
-                {\r
-                    trigger_error("Invalid validation type $class::$method", E_USER_WARNING);\r
-                    continue;\r
-                }\r
-                unset($opt['type']);\r
-                if (sizeof($opt) == 1) {\r
-                    $opt = array_pop($opt);\r
-                }\r
-                $valid[$var_name] = call_user_func(array($class, $method), $data[$var_name], $opt);\r
-            } else {\r
-                trigger_error("Invalid validation type {$opt['type']}", E_USER_WARNING);\r
-            }\r
-        }\r
-        return $valid;\r
-    }\r
-}\r
-\r
+<?php
+/**
+ * Validation class
+ *
+ * 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>                           
+ *
+ *
+ * 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.134 2009/01/28 12:27:33 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 tag URI (RFC4151)
+     *
+     * @param string $uri tag URI to validate
+     *
+     * @return boolean true if valid tag URI, false if not
+     *
+     * @access private
+     */
+    function __uriRFC4151($uri)
+    {
+        $datevalid = false;
+        if (preg_match(
+            '/^tag:(?<name>.*),(?<date>\d{4}-?\d{0,2}-?\d{0,2}):(?<specific>.*)(.*:)*$/', $uri, $matches)) {
+            $date  = $matches['date'];
+            $date6 = strtotime($date);
+            if ((strlen($date) == 4) && $date <= date('Y')) {
+                $datevalid = true;
+            } elseif ((strlen($date) == 7) && ($date6 < strtotime("now"))) {
+                $datevalid = true;
+            } elseif ((strlen($date) == 10) && ($date6 < strtotime("now"))) {
+                $datevalid = true;
+            }
+            if (self::email($matches['name'])) {
+                $namevalid = true;
+            } else {
+                $namevalid = self::email('info@' . $matches['name']);
+            }
+            return $datevalid && $namevalid;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 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 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)
+    {
+        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.
+     *
+     * @param string $email   The email address to check.
+     * @param array  $options The options for validation
+     *
+     * @access protected
+     *
+     * @return bool True if validating succeeds
+     */
+    function _fullTLDValidation($email, $options)
+    {
+        $validate = array();
+        if(!empty($options["VALIDATE_ITLD_EMAILS"])) array_push($validate, 'itld');
+        if(!empty($options["VALIDATE_GTLD_EMAILS"])) array_push($validate, 'gtld');
+        if(!empty($options["VALIDATE_CCTLD_EMAILS"])) array_push($validate, 'cctld');
+
+        $self = new Validate;
+
+        $toValidate = array();
+
+        foreach ($validate as $valid) {
+            $tmpVar = '_' . (string)$valid;
+
+            $toValidate[$valid] = $self->{$tmpVar};
+        }
+
+        $e = $self->executeFullEmailValidation($email, $toValidate);
+
+        return $e;
+    }
+    
+    /**
+     * Execute the validation
+     *
+     * This function will execute the full email vs tld
+     * validation using an array of tlds passed to it.
+     *
+     * @param string $email       The email to validate.
+     * @param array  $arrayOfTLDs The array of the TLDs to validate
+     *
+     * @access public
+     *
+     * @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
+     *
+     * Ex.
+     *  $options = array(
+     *      'check_domain' => 'true',
+     *      'fullTLDValidation' => 'true',
+     *      'use_rfc822' => 'true',
+     *      'VALIDATE_GTLD_EMAILS' => 'true',
+     *      'VALIDATE_CCTLD_EMAILS' => 'true',
+     *      'VALIDATE_ITLD_EMAILS' => 'true',           
+     *      );
+     *
+     * @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);
+        }
+
+        /**
+         * Check for IDN usage so we can encode the domain as Punycode
+         * before continuing.
+         */
+        $hasIDNA = false;
+
+        if (@include_once('Net/IDNA.php')) {
+            $hasIDNA = true;
+        }
+
+        if ($hasIDNA === true) {
+            if (strpos($email, '@') !== false) {
+                list($name, $domain) = explode('@', $email, 2);
+
+                // Check if the domain contains characters > 127 which means 
+                // it's an idn domain name.
+                $chars = count_chars($domain, 1);
+                if (!empty($chars) && max(array_keys($chars)) > 127) {
+                    $idna   =& Net_IDNA::singleton();
+                    $domain = $idna->encode($domain);
+                }
+
+                $email = "$name@$domain";
+            }
+        }
+        
+        /**
+         * @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);
+            $valid = Validate::_fullTLDValidation($email, $options);
+
+            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';
+
+        //checks if exists the domain (MX or A)
+        if ($use_rfc822? Validate::__emailRFC822($email, $options) :
+                preg_match($regex, $email)) {
+            if ($check_domain && function_exists('checkdnsrr')) {
+                list ($account, $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 = 0;
+        $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 (is_array($allowed_schemes) &&
+            in_array("tag", $allowed_schemes)
+        ) {
+            if (strpos($url, "tag:") === 0) {
+                return self::__uriRFC4151($url);
+            }
+        }
+
+        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    = false;
+        $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;
+    }
+
+    /**
+     * Substr
+     *
+     * @param string &$date Date
+     * @param string $num   Length
+     * @param string $opt   Unknown   
+     *
+     * @access private
+     * @return string
+     */
+    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
+     *
+     * @access protected
+     *
+     * @return int returns product of number digits with weights
+     */
+    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
+     *
+     * @access protected
+     *
+     * @return  int -1 calculated control number is returned
+     */
+    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   (optional) number
+     * @param int    $subtract (optional) number
+     *
+     * @access protected
+     *
+     * @return  bool true if valid, false if not
+     */
+    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;
+    }
+}
+
index b147443d79bcb28ac88e67bef52dbe0d64c71c36..befaf60a77151cc533743ab5adb4211a05971d94 100644 (file)
@@ -27,7 +27,7 @@
  */
 
 /** XMPPHP_XMLStream */
-require_once "XMPP.php";
+require_once dirname(__FILE__) . "/XMPP.php";
 
 /**
  * XMPPHP Main Class
index 0fcfea375e1d4110925dbe63a34310985e232dbb..d33411ec54140dafc1c527c946d05cbc931807c9 100644 (file)
  */
 
 /** XMPPHP_Exception */
-require_once 'Exception.php';
+require_once dirname(__FILE__) . '/Exception.php';
 
 /** XMPPHP_XMLObj */
-require_once 'XMLObj.php';
+require_once dirname(__FILE__) . '/XMLObj.php';
 
 /** XMPPHP_Log */
-require_once 'Log.php';
+require_once dirname(__FILE__) . '/Log.php';
 
 /**
  * XMPPHP XML Stream
@@ -375,7 +375,7 @@ class XMPPHP_XMLStream {
         * integer -> process for this amount of time 
         */
        
-       private function __process($maximum=0) {
+       private function __process($maximum=5) {
                
                $remaining = $maximum;
                
index 73fbd265840b878e176aba334a7cf984bca72689..429f45e565eb57648930988c8133b73bd6d26c5d 100644 (file)
@@ -27,8 +27,8 @@
  */
 
 /** XMPPHP_XMLStream */
-require_once "XMLStream.php";
-require_once "Roster.php";
+require_once dirname(__FILE__) . "/XMLStream.php";
+require_once dirname(__FILE__) . "/Roster.php";
 
 /**
  * XMPPHP Main Class
@@ -208,6 +208,15 @@ class XMPPHP_XMPP extends XMPPHP_XMLStream {
                
                $this->send($out);
        }
+       /**
+        * Send Auth request
+        *
+        * @param string $jid
+        */
+       public function subscribe($jid) {
+               $this->send("<presence type='subscribe' to='{$jid}' from='{$this->fulljid}' />");
+               #$this->send("<presence type='subscribed' to='{$jid}' from='{$this->fulljid}' />");
+       }
 
        /**
         * Message handler
index 35de6be5090bba96a4038d0fbd01273cab77a1d6..fee1dd086a9600875d0b8d9e9d8cffea46c579cc 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright 2004-2008 Facebook. All Rights Reserved.
+// Copyright 2004-2009 Facebook. All Rights Reserved.
 //
 // +---------------------------------------------------------------------------+
 // | Facebook Platform PHP5 client                                             |
 // +---------------------------------------------------------------------------+
 // | 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;
@@ -213,28 +212,55 @@ class Facebook {
     }
   }
 
-  // Invalidate the session currently being used, and clear any state associated with it
+  // Invalidate the session currently being used, and clear any state associated
+  // with it. Note that the user will still remain logged into Facebook.
   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;
+      $this->clear_cookie_state();
       return true;
     } else {
       return false;
     }
   }
 
+  /** Logs the user out of all temporary application sessions as well as their
+   * Facebook session.  Note this will only work if the user has a valid current
+   * session with the application.
+   *
+   * @param string  $next  URL to redirect to upon logging out
+   *
+   */
+   public function logout($next) {
+    $logout_url = $this->get_logout_url($next);
+
+    // Clear any stored state
+    $this->clear_cookie_state();
+
+    $this->redirect($logout_url);
+  }
+
+  /**
+   *  Clears any persistent state stored about the user, including
+   *  cookies and information related to the current session in the
+   *  client.
+   *
+   */
+  public function clear_cookie_state() {
+    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;
+  }
+
   public function redirect($url) {
     if ($this->in_fb_canvas()) {
       echo '<fb:redirect url="' . $url . '"/>';
@@ -249,7 +275,8 @@ class Facebook {
   }
 
   public function in_frame() {
-    return isset($this->fb_params['in_canvas']) || isset($this->fb_params['in_iframe']);
+    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']);
@@ -296,14 +323,42 @@ class Facebook {
   }
 
   public function get_add_url($next=null) {
-    return self::get_facebook_url().'/add.php?api_key='.$this->api_key .
-      ($next ? '&next=' . urlencode($next) : '');
+    $page = self::get_facebook_url().'/add.php';
+    $params = array('api_key' => $this->api_key);
+
+    if ($next) {
+      $params['next'] = $next;
+    }
+
+    return $page . '?' . http_build_query($params);
   }
 
   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' : '');
+    $page = self::get_facebook_url().'/login.php';
+    $params = array('api_key' => $this->api_key,
+                    'v'       => '1.0');
+
+    if ($next) {
+      $params['next'] = $next;
+    }
+    if ($canvas) {
+      $params['canvas'] = '1';
+    }
+
+    return $page . '?' . http_build_query($params);
+  }
+
+  public function get_logout_url($next) {
+    $page = self::get_facebook_url().'/logout.php';
+    $params = array('app_key'     => $this->api_key,
+                    'session_key' => $this->api_client->session_key);
+
+    if ($next) {
+      $params['connect_next'] = 1;
+      $params['next'] = $next;
+    }
+
+    return $page . '?' . http_build_query($params);
   }
 
   public function set_user($user, $session_key, $expires=null, $session_secret=null) {
@@ -410,7 +465,20 @@ class Facebook {
     return $fb_params;
   }
 
-  /*
+  /**
+   *  Validates the account that a user was trying to set up an
+   *  independent account through Facebook Connect.
+   *
+   *  @param  user The user attempting to set up an independent account.
+   *  @param  hash The hash passed to the reclamation URL used.
+   *  @return bool True if the user is the one that selected the
+   *               reclamation link.
+   */
+  public function verify_account_reclamation($user, $hash) {
+    return $hash == md5($user . $this->secret);
+  }
+
+  /**
    * Validates that a given set of parameters match their signature.
    * Parameters all match a given input prefix, such as "fb_sig".
    *
@@ -422,6 +490,37 @@ class Facebook {
     return self::generate_sig($fb_params, $this->secret) == $expected_sig;
   }
 
+  /**
+   * Validate the given signed public session data structure with
+   * public key of the app that
+   * the session proof belongs to.
+   *
+   * @param $signed_data the session info that is passed by another app
+   * @param string $public_key Optional public key of the app. If this
+   *               is not passed, function will make an API call to get it.
+   * return true if the session proof passed verification.
+   */
+  public function verify_signed_public_session_data($signed_data,
+                                                    $public_key = null) {
+
+    // If public key is not already provided, we need to get it through API
+    if (!$public_key) {
+      $public_key = $this->api_client->auth_getAppPublicKey(
+        $signed_data['api_key']);
+    }
+
+    // Create data to verify
+    $data_to_serialize = $signed_data;
+    unset($data_to_serialize['sig']);
+    $serialized_data = implode('_', $data_to_serialize);
+
+    // Decode signature
+    $signature = base64_decode($signed_data['sig']);
+    $result = openssl_verify($serialized_data, $signature, $public_key,
+                             OPENSSL_ALGO_SHA1);
+    return $result == 1;
+  }
+
   /*
    * Generate a signature using the application secret key.
    *
index 90cdf66bd0777822e2acce3b9315eada561a60a7..e79a2ca3433819126fef76d29ea6251dbdaceb76 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-// Copyright 2004-2008 Facebook. All Rights Reserved.
+// Copyright 2004-2009 Facebook. All Rights Reserved.
 //
 // +---------------------------------------------------------------------------+
 // | Facebook Platform PHP5 client                                             |
old mode 100644 (file)
new mode 100755 (executable)
index 389f40a..3fec06e
@@ -1,9 +1,10 @@
 <?php
+// Copyright 2004-2009 Facebook. All Rights Reserved.
 //
 // +---------------------------------------------------------------------------+
 // | Facebook Platform PHP5 client                                             |
 // +---------------------------------------------------------------------------+
-// | Copyright (c) 2007-2008 Facebook, Inc.                                    |
+// | Copyright (c) 2007-2009 Facebook, Inc.                                    |
 // | All rights reserved.                                                      |
 // |                                                                           |
 // | Redistribution and use in source and binary forms, with or without        |
@@ -32,6 +33,7 @@
 //
 
 include_once 'jsonwrapper/jsonwrapper.php';
+
 class FacebookRestClient {
   public $secret;
   public $session_key;
@@ -50,7 +52,9 @@ class FacebookRestClient {
   public $canvas_user;
   public $batch_mode;
   private $batch_queue;
+  private $pending_batch;
   private $call_as_apikey;
+  private $use_curl_if_available;
 
   const BATCH_MODE_DEFAULT = 0;
   const BATCH_MODE_SERVER_PARALLEL = 0;
@@ -70,7 +74,8 @@ class FacebookRestClient {
     $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';
+    $this->use_curl_if_available = true;
+    $this->server_addr  = Facebook::get_facebook_url('api') . '/restserver.php';
 
     if (!empty($GLOBALS['facebook_config']['debug'])) {
       $this->cur_id = 0;
@@ -122,40 +127,62 @@ function toggleDisplay(id, type) {
     $this->user = $uid;
   }
 
+  /**
+   * Normally, if the cURL library/PHP extension is available, it is used for
+   * HTTP transactions.  This allows that behavior to be overridden, falling
+   * back to a vanilla-PHP implementation even if cURL is installed.
+   *
+   * @param $use_curl_if_available bool whether or not to use cURL if available
+   */
+  public function set_use_curl_if_available($use_curl_if_available) {
+    $this->use_curl_if_available = $use_curl_if_available;
+  }
+
   /**
    * Start a batch operation.
    */
   public function begin_batch() {
-    if($this->batch_queue !== null) {
+    if ($this->pending_batch()) {
       $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
-      throw new FacebookRestClientException($code,
-        FacebookAPIErrorCodes::$api_error_descriptions[$code]);
+      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+      throw new FacebookRestClientException($description, $code);
     }
 
     $this->batch_queue = array();
+    $this->pending_batch = true;
   }
 
   /*
    * End current batch operation
    */
   public function end_batch() {
-    if($this->batch_queue === null) {
+    if (!$this->pending_batch()) {
       $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
-      throw new FacebookRestClientException($code,
-      FacebookAPIErrorCodes::$api_error_descriptions[$code]);
+      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+      throw new FacebookRestClientException($description, $code);
     }
 
-    $this->execute_server_side_batch();
+    $this->pending_batch = false;
 
+    $this->execute_server_side_batch();
     $this->batch_queue = null;
   }
 
+  /**
+   * are we currently queueing up calls for a batch?
+   */
+  public function pending_batch() {
+    return $this->pending_batch;
+  }
+
   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 = $batch_item['m'];
+      $params = $batch_item['p'];
+      $this->finalize_params($method, $params);
+      $method_feed[] = $this->create_post_string($method, $params);
     }
 
     $method_feed_json = json_encode($method_feed);
@@ -202,6 +229,18 @@ function toggleDisplay(id, type) {
     $this->call_as_apikey = '';
   }
 
+
+  /*
+   * If a page is loaded via HTTPS, then all images and static
+   * resources need to be printed with HTTPS urls to avoid
+   * mixed content warnings. If your page loads with an HTTPS
+   * url, then call set_use_ssl_resources to retrieve the correct
+   * urls.
+   */
+  public function set_use_ssl_resources($is_ssl = true) {
+    $this->use_ssl_resources = $is_ssl;
+  }
+
   /**
    * Returns public information for an application (as shown in the application
    * directory) by either application ID, API key, or canvas page name.
@@ -231,7 +270,7 @@ function toggleDisplay(id, type) {
    * @return string  An authentication token.
    */
   public function auth_createToken() {
-    return $this->call_method('facebook.auth.createToken', array());
+    return $this->call_method('facebook.auth.createToken');
   }
 
   /**
@@ -246,8 +285,7 @@ function toggleDisplay(id, type) {
    * @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) {
+    if (!$this->pending_batch()) {
       $result = $this->call_method('facebook.auth.getSession',
           array('auth_token' => $auth_token,
                 'generate_session_secret' => $generate_session_secret));
@@ -271,7 +309,7 @@ function toggleDisplay(id, type) {
    *        API_EC_PARAM_UNKNOWN
    */
   public function auth_promoteSession() {
-      return $this->call_method('facebook.auth.promoteSession', array());
+      return $this->call_method('facebook.auth.promoteSession');
   }
 
   /**
@@ -282,7 +320,20 @@ function toggleDisplay(id, type) {
    * @return bool  true if session expiration was successful, false otherwise
    */
   public function auth_expireSession() {
-      return $this->call_method('facebook.auth.expireSession', array());
+      return $this->call_method('facebook.auth.expireSession');
+  }
+
+  /**
+   *  Revokes the given extended permission that the user granted at some
+   *  prior time (for instance, offline_access or email).  If no user is
+   *  provided, it will be revoked for the user of the current session.
+   *
+   *  @param  string  $perm  The permission to revoke
+   *  @param  int     $uid   The user for whom to revoke the permission.
+   */
+  public function auth_revokeExtendedPermission($perm, $uid=null) {
+    return $this->call_method('facebook.auth.revokeExtendedPermission',
+        array('perm' => $perm, 'uid' => $uid));
   }
 
   /**
@@ -302,6 +353,30 @@ function toggleDisplay(id, type) {
           array('uid' => $uid));
   }
 
+  /**
+   * Get public key that is needed to verify digital signature
+   * an app may pass to other apps. The public key is only used by
+   * other apps for verification purposes.
+   * @param  string  API key of an app
+   * @return string  The public key for the app.
+   */
+  public function auth_getAppPublicKey($target_app_key) {
+    return $this->call_method('facebook.auth.getAppPublicKey',
+          array('target_app_key' => $target_app_key));
+  }
+
+  /**
+   * Get a structure that can be passed to another app
+   * as proof of session. The other app can verify it using public
+   * key of this app.
+   *
+   * @return signed public session data structure.
+   */
+  public function auth_getSignedPublicSessionData() {
+    return $this->call_method('facebook.auth.getSignedPublicSessionData',
+                              array());
+  }
+
   /**
    * Returns the number of unconnected friends that exist in this application.
    * This number is determined based on the accounts registered through
@@ -363,8 +438,9 @@ function toggleDisplay(id, type) {
    *
    * @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 array/string $eids  (Optional) Filter by these event
+   *                            ids. A null parameter will get all events for
+   *                            the user. (A csv list will work but is deprecated)
    * @param int $start_time     (Optional) Filter with this unix time as lower
    *                            bound.  A null or zero parameter indicates no
    *                            lower bound.
@@ -718,12 +794,15 @@ function toggleDisplay(id, type) {
    * @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)
+   * @param string $user_message     (Optional) A user message for a short
+   *                                 story.
    *
    * @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) {
+      $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE,
+      $user_message='') {
 
     if (is_array($template_data)) {
       $template_data = json_encode($template_data);
@@ -739,7 +818,107 @@ function toggleDisplay(id, type) {
               'template_data' => $template_data,
               'target_ids' => $target_ids,
               'body_general' => $body_general,
-              'story_size' => $story_size));
+              'story_size' => $story_size,
+              'user_message' => $user_message));
+  }
+
+
+  /**
+   * Publish a post to the user's stream.
+   *
+   * @param $message        the user's message
+   * @param $attachment     the post's attachment (optional)
+   * @param $action links   the post's action links (optional)
+   * @param $target_id      the user on whose wall the post will be posted
+   *                        (optional)
+   * @param $uid            the actor (defaults to session user)
+   * @return string the post id
+   */
+  public function stream_publish(
+    $message, $attachment = null, $action_links = null, $target_id = null,
+    $uid = null) {
+
+    return $this->call_method(
+      'facebook.stream.publish',
+      array('message' => $message,
+            'attachment' => $attachment,
+            'action_links' => $action_links,
+            'target_id' => $target_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Remove a post from the user's stream.
+   * Currently, you may only remove stories you application created.
+   *
+   * @param $post_id  the post id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_remove($post_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.remove',
+      array('post_id' => $post_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Add a comment to a stream post
+   *
+   * @param $post_id  the post id
+   * @param $comment  the comment text
+   * @param $uid      the actor (defaults to session user)
+   * @return string the id of the created comment
+   */
+  public function stream_addComment($post_id, $comment, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.addComment',
+      array('post_id' => $post_id,
+            'comment' => $comment,
+            'uid' => $this->get_uid($uid)));
+  }
+
+
+  /**
+   * Remove a comment from a stream post
+   *
+   * @param $comment_id  the comment id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_removeComment($comment_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.removeComment',
+      array('comment_id' => $comment_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Add a like to a stream post
+   *
+   * @param $post_id  the post id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_addLike($post_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.addLike',
+      array('post_id' => $post_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Remove a like from a stream post
+   *
+   * @param $post_id  the post id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_removeLike($post_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.removeLike',
+      array('post_id' => $post_id,
+            'uid' => $this->get_uid($uid)));
   }
 
   /**
@@ -750,7 +929,7 @@ function toggleDisplay(id, type) {
    * @return array  An array of feed story objects.
    */
   public function &feed_getAppFriendStories() {
-    return $this->call_method('facebook.feed.getAppFriendStories', array());
+    return $this->call_method('facebook.feed.getAppFriendStories');
   }
 
   /**
@@ -771,33 +950,42 @@ function toggleDisplay(id, type) {
    * 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
+   * @param array/string $uids1  list of ids (id_1, id_2,...)
+   *                       of some length X (csv is deprecated)
+   * @param array/string $uids2  list of ids (id_A, id_B,...)
+   *                       of SAME length X (csv is deprecated)
    *
    * @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)
    *         ...)
+   * @error
+   *    API_EC_PARAM_USER_ID_LIST
    */
   public function &friends_areFriends($uids1, $uids2) {
     return $this->call_method('facebook.friends.areFriends',
-        array('uids1' => $uids1, 'uids2' => $uids2));
+                 array('uids1' => $uids1,
+                       'uids2' => $uids2));
   }
 
   /**
    * Returns the friends of the current session user.
    *
    * @param int $flid  (Optional) Only return friends on this friend list.
+   * @param int $uid   (Optional) Return friends for this user.
    *
    * @return array  An array of friends
    */
-  public function &friends_get($flid=null) {
+  public function &friends_get($flid=null, $uid = null) {
     if (isset($this->friends_list)) {
       return $this->friends_list;
     }
     $params = array();
-    if (isset($this->canvas_user)) {
-      $params['uid'] = $this->canvas_user;
+    if (!$uid && isset($this->canvas_user)) {
+      $uid = $this->canvas_user;
+    }
+    if ($uid) {
+      $params['uid'] = $uid;
     }
     if ($flid) {
       $params['flid'] = $flid;
@@ -812,7 +1000,7 @@ function toggleDisplay(id, type) {
    * @return array  An array of friend list objects
    */
   public function &friends_getLists() {
-    return $this->call_method('facebook.friends.getLists', array());
+    return $this->call_method('facebook.friends.getLists');
   }
 
   /**
@@ -822,7 +1010,7 @@ function toggleDisplay(id, type) {
    * @return array  An array of friends also using the app
    */
   public function &friends_getAppUsers() {
-    return $this->call_method('facebook.friends.getAppUsers', array());
+    return $this->call_method('facebook.friends.getAppUsers');
   }
 
   /**
@@ -830,8 +1018,9 @@ function toggleDisplay(id, type) {
    *
    * @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.
+   * @param array/string $gids (Optional) Array of group ids to query. A null
+   *                     parameter will get all groups for the user.
+   *                     (csv is deprecated)
    *
    * @return array  An array of group objects
    */
@@ -889,6 +1078,40 @@ function toggleDisplay(id, type) {
               'path' => $path));
   }
 
+  /**
+   * Retrieves links posted by the given user.
+   *
+   * @param int    $uid      The user whose links you wish to retrieve
+   * @param int    $limit    The maximimum number of links to retrieve
+   * @param array $link_ids (Optional) Array of specific link
+   *                          IDs to retrieve by this user
+   *
+   * @return array  An array of links.
+   */
+  public function &links_get($uid, $limit, $link_ids = null) {
+    return $this->call_method('links.get',
+        array('uid' => $uid,
+              'limit' => $limit,
+              'link_ids' => $link_ids));
+  }
+
+  /**
+   * Posts a link on Facebook.
+   *
+   * @param string $url     URL/link you wish to post
+   * @param string $comment (Optional) A comment about this link
+   * @param int    $uid     (Optional) User ID that is posting this link;
+   *                        defaults to current session user
+   *
+   * @return bool
+   */
+  public function &links_post($url, $comment='', $uid = null) {
+    return $this->call_method('links.post',
+        array('uid' => $uid,
+              'url' => $url,
+              'comment' => $comment));
+  }
+
   /**
    * Permissions API
    */
@@ -945,6 +1168,78 @@ function toggleDisplay(id, type) {
         array('permissions_apikey' => $permissions_apikey));
   }
 
+  /**
+   * Creates a note with the specified title and content.
+   *
+   * @param string $title   Title of the note.
+   * @param string $content Content of the note.
+   * @param int    $uid     (Optional) The user for whom you are creating a
+   *                        note; defaults to current session user
+   *
+   * @return int   The ID of the note that was just created.
+   */
+  public function &notes_create($title, $content, $uid = null) {
+    return $this->call_method('notes.create',
+        array('uid' => $uid,
+              'title' => $title,
+              'content' => $content));
+  }
+
+  /**
+   * Deletes the specified note.
+   *
+   * @param int $note_id  ID of the note you wish to delete
+   * @param int $uid      (Optional) Owner of the note you wish to delete;
+   *                      defaults to current session user
+   *
+   * @return bool
+   */
+  public function &notes_delete($note_id, $uid = null) {
+    return $this->call_method('notes.delete',
+        array('uid' => $uid,
+              'note_id' => $note_id));
+  }
+
+  /**
+   * Edits a note, replacing its title and contents with the title
+   * and contents specified.
+   *
+   * @param int    $note_id  ID of the note you wish to edit
+   * @param string $title    Replacement title for the note
+   * @param string $content  Replacement content for the note
+   * @param int    $uid      (Optional) Owner of the note you wish to edit;
+   *                         defaults to current session user
+   *
+   * @return bool
+   */
+  public function &notes_edit($note_id, $title, $content, $uid = null) {
+    return $this->call_method('notes.edit',
+        array('uid' => $uid,
+              'note_id' => $note_id,
+              'title' => $title,
+              'content' => $content));
+  }
+
+  /**
+   * Retrieves all notes by a user. If note_ids are specified,
+   * retrieves only those specific notes by that user.
+   *
+   * @param int    $uid      User whose notes you wish to retrieve
+   * @param array  $note_ids (Optional) List of specific note
+   *                         IDs by this user to retrieve
+   *
+   * @return array A list of all of the given user's notes, or an empty list
+   *               if the viewer lacks permissions or if there are no visible
+   *               notes.
+   */
+  public function &notes_get($uid, $note_ids = null) {
+
+    return $this->call_method('notes.get',
+        array('uid' => $uid,
+              'note_ids' => $note_ids));
+  }
+
+
   /**
    * Returns the outstanding notifications for the session user.
    *
@@ -954,13 +1249,15 @@ function toggleDisplay(id, type) {
    *               and an eid list of 'event_invites'
    */
   public function &notifications_get() {
-    return $this->call_method('facebook.notifications.get', array());
+    return $this->call_method('facebook.notifications.get');
   }
 
   /**
    * Sends a notification to the specified users.
    *
    * @return A comma separated list of successful recipients
+   * @error
+   *    API_EC_PARAM_USER_ID_LIST
    */
   public function &notifications_send($to_ids, $notification, $type) {
     return $this->call_method('facebook.notifications.send',
@@ -972,12 +1269,14 @@ function toggleDisplay(id, type) {
   /**
    * Sends an email to the specified user of the application.
    *
-   * @param array $recipients  id of the recipients
+   * @param array/string $recipients array of ids of the recipients (csv is deprecated)
    * @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
+   * @error
+   *    API_EC_PARAM_USER_ID_LIST
    */
   public function &notifications_sendEmail($recipients,
                                            $subject,
@@ -993,9 +1292,9 @@ function toggleDisplay(id, type) {
   /**
    * 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 array/string $page_ids  an array of page ids (csv is deprecated)
+   * @param array/string  $fields    an array of strings describing the
+   *                           info fields desired (csv is deprecated)
    * @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.
@@ -1090,7 +1389,7 @@ function toggleDisplay(id, type) {
               'tag_text' => $tag_text,
               'x' => $x,
               'y' => $y,
-              'tags' => json_encode($tags),
+              'tags' => (is_array($tags)) ? json_encode($tags) : null,
               'owner_uid' => $this->get_uid($owner_uid)));
   }
 
@@ -1128,7 +1427,8 @@ function toggleDisplay(id, type) {
    * @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
+   * @param array/string $pids   (Optional) Restrict to an array of pids
+   *                             (csv is deprecated)
    *
    * Note that at least one of these parameters needs to be specified, or an
    * error is returned.
@@ -1143,9 +1443,10 @@ function toggleDisplay(id, type) {
   /**
    * 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.
+   * @param int $uid      (Optional) The uid of the user whose albums you want.
+   *                       A null will return the albums of the session user.
+   * @param string $aids  (Optional) An array of aids to restrict
+   *                       the query. (csv is deprecated)
    *
    * Note that at least one of the (uid, aids) parameters must be specified.
    *
@@ -1171,17 +1472,67 @@ function toggleDisplay(id, type) {
       array('pids' => $pids));
   }
 
+  /**
+   * Uploads a photo.
+   *
+   * @param string $file     The location of the photo on the local filesystem.
+   * @param int $aid         (Optional) The album into which to upload the
+   *                         photo.
+   * @param string $caption  (Optional) A caption for the photo.
+   * @param int uid          (Optional) The user ID of the user whose photo you
+   *                         are uploading
+   *
+   * @return array  An array of user objects
+   */
+  public function photos_upload($file, $aid=null, $caption=null, $uid=null) {
+    return $this->call_upload_method('facebook.photos.upload',
+                                     array('aid' => $aid,
+                                           'caption' => $caption,
+                                           'uid' => $uid),
+                                     $file);
+  }
+
+
+  /**
+   * Uploads a video.
+   *
+   * @param  string $file        The location of the video on the local filesystem.
+   * @param  string $title       (Optional) A title for the video. Titles over 65 characters in length will be truncated.
+   * @param  string $description (Optional) A description for the video.
+   *
+   * @return array  An array with the video's ID, title, description, and a link to view it on Facebook.
+   */
+  public function video_upload($file, $title=null, $description=null) {
+    return $this->call_upload_method('facebook.video.upload',
+                                     array('title' => $title,
+                                           'description' => $description),
+                                     $file,
+                                     Facebook::get_facebook_url('api-video') . '/restserver.php');
+  }
+
+  /**
+   * Returns an array with the video limitations imposed on the current session's
+   * associated user. Maximum length is measured in seconds; maximum size is
+   * measured in bytes.
+   *
+   * @return array  Array with "length" and "size" keys
+   */
+  public function &video_getUploadLimits() {
+    return $this->call_method('facebook.video.getUploadLimits');
+  }
+
   /**
    * 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
+   * @param array/string $uids    An array of user ids (csv is deprecated)
+   * @param array/string $fields  An array of info field names desired (csv is deprecated)
    *
    * @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));
+                  array('uids' => $uids,
+                        'fields' => $fields));
   }
 
   /**
@@ -1194,14 +1545,15 @@ function toggleDisplay(id, type) {
    * 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
+   * @param array/string $uids    An array of user ids (csv is deprecated)
+   * @param array/string $fields  An array of info field names desired (csv is deprecated)
    *
    * @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));
+                              array('uids' => $uids,
+                                    'fields' => $fields));
   }
 
   /**
@@ -1210,7 +1562,7 @@ function toggleDisplay(id, type) {
    * @return integer  User id
    */
   public function &users_getLoggedInUser() {
-    return $this->call_method('facebook.users.getLoggedInUser', array());
+    return $this->call_method('facebook.users.getLoggedInUser');
   }
 
   /**
@@ -1238,6 +1590,17 @@ function toggleDisplay(id, type) {
     return $this->call_method('facebook.users.isAppUser', array('uid' => $uid));
   }
 
+  /**
+   * Returns whether or not the user corresponding to the current
+   * session object is verified by Facebook. See the documentation
+   * for Users.isVerified for details.
+   *
+   * @return boolean  true if the user is verified
+   */
+  public function &users_isVerified() {
+    return $this->call_method('facebook.users.isVerified');
+  }
+
   /**
    * Sets the users' current status message. Message does NOT contain the
    * word "is" , so make sure to include a verb.
@@ -1268,6 +1631,69 @@ function toggleDisplay(id, type) {
     return $this->call_method('facebook.users.setStatus', $args);
   }
 
+  /**
+   * Gets the stream on behalf of a user using a set of users. This
+   * call will return the latest $limit queries between $start_time
+   * and $end_time.
+   *
+   * @param int    $viewer_id  user making the call (def: session)
+   * @param array  $source_ids users/pages to look at (def: all connections)
+   * @param int    $start_time start time to look for stories (def: 1 day ago)
+   * @param int    $end_time   end time to look for stories (def: now)
+   * @param int    $limit      number of stories to attempt to fetch (def: 30)
+   * @param string $filter_key key returned by stream.getFilters to fetch
+   *
+   * @return array(
+   *           'posts'    => array of posts,
+   *           'profiles' => array of profile metadata of users/pages in posts
+   *           'albums'   => array of album metadata in posts
+   *         )
+   */
+  public function &stream_get($viewer_id = null,
+                              $source_ids = null,
+                              $start_time = 0,
+                              $end_time = 0,
+                              $limit = 30,
+                              $filter_key = '') {
+    $args = array(
+      'viewer_id'  => $viewer_id,
+      'source_ids' => $source_ids,
+      'start_time' => $start_time,
+      'end_time'   => $end_time,
+      'limit'      => $limit,
+      'filter_key' => $filter_key);
+    return $this->call_method('facebook.stream.get', $args);
+  }
+
+  /**
+   * Gets the filters (with relevant filter keys for stream.get) for a
+   * particular user. These filters are typical things like news feed,
+   * friend lists, networks. They can be used to filter the stream
+   * without complex queries to determine which ids belong in which groups.
+   *
+   * @param int $uid user to get filters for
+   *
+   * @return array of stream filter objects
+   */
+  public function &stream_getFilters($uid = null) {
+    $args = array('uid' => $uid);
+    return $this->call_method('facebook.stream.getFilters', $args);
+  }
+
+  /**
+   * Gets the full comments given a post_id from stream.get or the
+   * stream FQL table. Initially, only a set of preview comments are
+   * returned because some posts can have many comments.
+   *
+   * @param string $post_id id of the post to get comments for
+   *
+   * @return array of comment objects
+   */
+  public function &stream_getComments($post_id) {
+    $args = array('post_id' => $post_id);
+    return $this->call_method('facebook.stream.getComments', $args);
+  }
+
   /**
    * Sets the FBML for the profile of the user attached to this session.
    *
@@ -1690,7 +2116,7 @@ function toggleDisplay(id, type) {
    *    API_EC_DATA_UNKNOWN_ERROR
    */
   public function &data_getObjectTypes() {
-    return $this->call_method('facebook.data.getObjectTypes', array());
+    return $this->call_method('facebook.data.getObjectTypes');
   }
 
   /**
@@ -2315,12 +2741,14 @@ function toggleDisplay(id, type) {
    *
    * @param string $integration_point_name  Name of an integration point
    *                                        (see developer wiki for list).
+   * @param int    $uid                     Specific user to check the limit.
    *
    * @return int  Integration point allocation value
    */
-  public function &admin_getAllocation($integration_point_name) {
+  public function &admin_getAllocation($integration_point_name, $uid=null) {
     return $this->call_method('facebook.admin.getAllocation',
-        array('integration_point_name' => $integration_point_name));
+        array('integration_point_name' => $integration_point_name,
+              'uid' => $uid));
   }
 
   /**
@@ -2376,28 +2804,75 @@ function toggleDisplay(id, type) {
    */
   public function admin_getRestrictionInfo() {
     return json_decode(
-        $this->call_method('admin.getRestrictionInfo', array()),
+        $this->call_method('admin.getRestrictionInfo'),
         true);
   }
 
+
+  /**
+   * Bans a list of users from the app. Banned users can't
+   * access the app's canvas page and forums.
+   *
+   * @param array $uids an array of user ids
+   * @return bool true on success
+   */
+  public function admin_banUsers($uids) {
+    return $this->call_method(
+      'admin.banUsers', array('uids' => json_encode($uids)));
+  }
+
+  /**
+   * Unban users that have been previously banned with
+   * admin_banUsers().
+   *
+   * @param array $uids an array of user ids
+   * @return bool true on success
+   */
+  public function admin_unbanUsers($uids) {
+    return $this->call_method(
+      'admin.unbanUsers', array('uids' => json_encode($uids)));
+  }
+
+  /**
+   * Gets the list of users that have been banned from the application.
+   * $uids is an optional parameter that filters the result with the list
+   * of provided user ids. If $uids is provided,
+   * only banned user ids that are contained in $uids are returned.
+   *
+   * @param array $uids an array of user ids to filter by
+   * @return bool true on success
+   */
+
+  public function admin_getBannedUsers($uids = null) {
+    return $this->call_method(
+      'admin.getBannedUsers',
+      array('uids' => $uids ? json_encode($uids) : null));
+  }
+
   /* UTILITY FUNCTIONS */
 
   /**
-   * Calls the specified method with the specified parameters.
+   * Calls the specified normal POST 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
+   * @return mixed  Result of method call; this returns a reference to support
+   *                'delayed returns' when in a batch context.
+   *     See: http://wiki.developers.facebook.com/index.php/Using_batching_API
    */
-  public function & call_method($method, $params) {
-    //Check if we are in batch mode
-    if($this->batch_queue === null) {
+  public function &call_method($method, $params = array()) {
+    if (!$this->pending_batch()) {
       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);
+      $data = $this->post_request($method, $params);
+      if (empty($params['format']) || strtolower($params['format']) != 'json') {
+        $result = $this->convert_xml_to_result($data, $method, $params);
+      }
+      else {
+        $result = json_decode($data, true);
+      }
 
       if (is_array($result) && isset($result['error_code'])) {
         throw new FacebookRestClientException($result['error_msg'],
@@ -2413,11 +2888,46 @@ function toggleDisplay(id, type) {
     return $result;
   }
 
-  private function convert_xml_to_result($xml, $method, $params) {
+  /**
+   * Calls the specified file-upload POST 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
+   * @param string $file   A path to the file to upload (required)
+   *
+   * @return array A dictionary representing the response.
+   */
+  public function call_upload_method($method, $params, $file, $server_addr = null) {
+    if (!$this->pending_batch()) {
+      if (!file_exists($file)) {
+        $code =
+          FacebookAPIErrorCodes::API_EC_PARAM;
+        $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+        throw new FacebookRestClientException($description, $code);
+      }
+
+      $xml = $this->post_upload_request($method, $params, $file, $server_addr);
+      $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 {
+      $code =
+        FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
+      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+      throw new FacebookRestClientException($description, $code);
+    }
+
+    return $result;
+  }
+
+  protected 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;">';
@@ -2436,7 +2946,25 @@ function toggleDisplay(id, type) {
     return $result;
   }
 
-  private function create_post_string($method, $params) {
+  private function finalize_params($method, &$params) {
+    $this->add_standard_params($method, $params);
+    // we need to do this before signing the params
+    $this->convert_array_values_to_json($params);
+    $params['sig'] = Facebook::generate_sig($params, $this->secret);
+  }
+
+  private function convert_array_values_to_json(&$params) {
+    foreach ($params as $key => &$val) {
+      if (is_array($val)) {
+        $val = json_encode($val);
+      }
+    }
+  }
+
+  private function add_standard_params($method, &$params) {
+    if ($this->call_as_apikey) {
+      $params['call_as_apikey'] = $this->call_as_apikey;
+    }
     $params['method'] = $method;
     $params['session_key'] = $this->session_key;
     $params['api_key'] = $this->api_key;
@@ -2448,50 +2976,118 @@ function toggleDisplay(id, type) {
     if (!isset($params['v'])) {
       $params['v'] = '1.0';
     }
+    if (isset($this->use_ssl_resources) &&
+        $this->use_ssl_resources) {
+      $params['return_ssl_resources'] = true;
+    }
+  }
+
+  private function create_post_string($method, $params) {
     $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) {
+  private function run_multipart_http_transaction($method, $params, $file, $server_addr) {
 
-    $post_string = $this->create_post_string($method, $params);
+    // the format of this message is specified in RFC1867/RFC1341.
+    // we add twenty pseudo-random digits to the end of the boundary string.
+    $boundary = '--------------------------FbMuLtIpArT' .
+                sprintf("%010d", mt_rand()) .
+                sprintf("%010d", mt_rand());
+    $content_type = 'multipart/form-data; boundary=' . $boundary;
+    // within the message, we prepend two extra hyphens.
+    $delimiter = '--' . $boundary;
+    $close_delimiter = $delimiter . '--';
+    $content_lines = array();
+    foreach ($params as $key => &$val) {
+      $content_lines[] = $delimiter;
+      $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"';
+      $content_lines[] = '';
+      $content_lines[] = $val;
+    }
+    // now add the file data
+    $content_lines[] = $delimiter;
+    $content_lines[] =
+      'Content-Disposition: form-data; filename="' . $file . '"';
+    $content_lines[] = 'Content-Type: application/octet-stream';
+    $content_lines[] = '';
+    $content_lines[] = file_get_contents($file);
+    $content_lines[] = $close_delimiter;
+    $content_lines[] = '';
+    $content = implode("\r\n", $content_lines);
+    return $this->run_http_post_transaction($content_type, $content, $server_addr);
+  }
 
-    if (function_exists('curl_init')) {
-      // Use CURL if installed...
+  public function post_request($method, $params) {
+    $this->finalize_params($method, $params);
+    $post_string = $this->create_post_string($method, $params);
+    if ($this->use_curl_if_available && function_exists('curl_init')) {
       $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);
+      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
       $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' =>
+      $content = $post_string;
+      $result = $this->run_http_post_transaction($content_type,
+                                                 $content,
+                                                 $this->server_addr);
+    }
+    return $result;
+  }
+
+  private function post_upload_request($method, $params, $file, $server_addr = null) {
+    $server_addr = $server_addr ? $server_addr : $this->server_addr;
+    $this->finalize_params($method, $params);
+    if ($this->use_curl_if_available && function_exists('curl_init')) {
+      // prepending '@' causes cURL to upload the file; the key is ignored.
+      $params['_file'] = '@' . $file;
+      $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
+      $ch = curl_init();
+      curl_setopt($ch, CURLOPT_URL, $server_addr);
+      // this has to come before the POSTFIELDS set!
+      curl_setopt($ch, CURLOPT_POST, 1 );
+      // passing an array gets curl to use the multipart/form-data content type
+      curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+      curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
+      $result = curl_exec($ch);
+      curl_close($ch);
+    } else {
+      $result = $this->run_multipart_http_transaction($method, $params, $file, $server_addr);
+    }
+    return $result;
+  }
+
+  private function run_http_post_transaction($content_type, $content, $server_addr) {
+
+    $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion();
+    $content_length = strlen($content);
+    $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);
+                    'user_agent' => $user_agent,
+                    'header' => 'Content-Type: ' . $content_type . "\r\n" .
+                                'Content-Length: ' . $content_length,
+                    'content' => $content));
+    $context_id = stream_context_create($context);
+    $sock = fopen($server_addr, 'r', false, $context_id);
+
+    $result = '';
+    if ($sock) {
+      while (!feof($sock)) {
+        $result .= fgets($sock, 4096);
       }
+      fclose($sock);
     }
     return $result;
   }
@@ -2541,6 +3137,14 @@ class FacebookAPIErrorCodes {
   const API_EC_METHOD = 3;
   const API_EC_TOO_MANY_CALLS = 4;
   const API_EC_BAD_IP = 5;
+  const API_EC_HOST_API = 6;
+  const API_EC_HOST_UP = 7;
+  const API_EC_SECURE = 8;
+  const API_EC_RATE = 9;
+  const API_EC_PERMISSION_DENIED = 10;
+  const API_EC_DEPRECATED = 11;
+  const API_EC_VERSION = 12;
+  const API_EC_INTERNAL_FQL_ERROR = 13;
 
   /*
    * PARAMETER ERRORS
@@ -2550,27 +3154,121 @@ class FacebookAPIErrorCodes {
   const API_EC_PARAM_SESSION_KEY = 102;
   const API_EC_PARAM_CALL_ID = 103;
   const API_EC_PARAM_SIGNATURE = 104;
+  const API_EC_PARAM_TOO_MANY = 105;
   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_EMAIL = 113;
+  const API_EC_PARAM_USER_ID_LIST = 114;
+  const API_EC_PARAM_FIELD_LIST = 115;
   const API_EC_PARAM_ALBUM_ID = 120;
+  const API_EC_PARAM_PHOTO_ID = 121;
+  const API_EC_PARAM_FEED_PRIORITY = 130;
+  const API_EC_PARAM_CATEGORY = 140;
+  const API_EC_PARAM_SUBCATEGORY = 141;
+  const API_EC_PARAM_TITLE = 142;
+  const API_EC_PARAM_DESCRIPTION = 143;
+  const API_EC_PARAM_BAD_JSON = 144;
   const API_EC_PARAM_BAD_EID = 150;
   const API_EC_PARAM_UNKNOWN_CITY = 151;
+  const API_EC_PARAM_BAD_PAGE_TYPE = 152;
 
   /*
    * USER PERMISSIONS ERRORS
    */
   const API_EC_PERMISSION = 200;
   const API_EC_PERMISSION_USER = 210;
+  const API_EC_PERMISSION_NO_DEVELOPERS = 211;
   const API_EC_PERMISSION_ALBUM = 220;
   const API_EC_PERMISSION_PHOTO = 221;
+  const API_EC_PERMISSION_MESSAGE = 230;
+  const API_EC_PERMISSION_OTHER_USER = 240;
+  const API_EC_PERMISSION_STATUS_UPDATE = 250;
+  const API_EC_PERMISSION_PHOTO_UPLOAD = 260;
+  const API_EC_PERMISSION_VIDEO_UPLOAD = 261;
+  const API_EC_PERMISSION_SMS = 270;
+  const API_EC_PERMISSION_CREATE_LISTING = 280;
+  const API_EC_PERMISSION_CREATE_NOTE = 281;
+  const API_EC_PERMISSION_SHARE_ITEM = 282;
   const API_EC_PERMISSION_EVENT = 290;
+  const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291;
+  const API_EC_PERMISSION_LIVEMESSAGE = 292;
   const API_EC_PERMISSION_RSVP_EVENT = 299;
 
-  const FQL_EC_PARSER = 601;
+  /*
+   * DATA EDIT ERRORS
+   */
+  const API_EC_EDIT = 300;
+  const API_EC_EDIT_USER_DATA = 310;
+  const API_EC_EDIT_PHOTO = 320;
+  const API_EC_EDIT_ALBUM_SIZE = 321;
+  const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322;
+  const API_EC_EDIT_PHOTO_TAG_PHOTO = 323;
+  const API_EC_EDIT_PHOTO_FILE = 324;
+  const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325;
+  const API_EC_EDIT_PHOTO_TAG_LIMIT = 326;
+  const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327;
+  const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328;
+
+  const API_EC_MALFORMED_MARKUP = 329;
+  const API_EC_EDIT_MARKUP = 330;
+
+  const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340;
+  const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341;
+  const API_EC_EDIT_FEED_TITLE_LINK = 342;
+  const API_EC_EDIT_FEED_TITLE_LENGTH = 343;
+  const API_EC_EDIT_FEED_TITLE_NAME = 344;
+  const API_EC_EDIT_FEED_TITLE_BLANK = 345;
+  const API_EC_EDIT_FEED_BODY_LENGTH = 346;
+  const API_EC_EDIT_FEED_PHOTO_SRC = 347;
+  const API_EC_EDIT_FEED_PHOTO_LINK = 348;
+
+  const API_EC_EDIT_VIDEO_SIZE = 350;
+  const API_EC_EDIT_VIDEO_INVALID_FILE = 351;
+  const API_EC_EDIT_VIDEO_INVALID_TYPE = 352;
+  const API_EC_EDIT_VIDEO_FILE = 353;
+
+  const API_EC_EDIT_FEED_TITLE_ARRAY = 360;
+  const API_EC_EDIT_FEED_TITLE_PARAMS = 361;
+  const API_EC_EDIT_FEED_BODY_ARRAY = 362;
+  const API_EC_EDIT_FEED_BODY_PARAMS = 363;
+  const API_EC_EDIT_FEED_PHOTO = 364;
+  const API_EC_EDIT_FEED_TEMPLATE = 365;
+  const API_EC_EDIT_FEED_TARGET = 366;
+  const API_EC_EDIT_FEED_MARKUP = 367;
+
+  /**
+   * SESSION ERRORS
+   */
+  const API_EC_SESSION_TIMED_OUT = 450;
+  const API_EC_SESSION_METHOD = 451;
+  const API_EC_SESSION_INVALID = 452;
+  const API_EC_SESSION_REQUIRED = 453;
+  const API_EC_SESSION_REQUIRED_FOR_SECRET = 454;
+  const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455;
+
+
+  /**
+   * FQL ERRORS
+   */
+  const FQL_EC_UNKNOWN_ERROR = 600;
+  const FQL_EC_PARSER = 601; // backwards compatibility
+  const FQL_EC_PARSER_ERROR = 601;
   const FQL_EC_UNKNOWN_FIELD = 602;
   const FQL_EC_UNKNOWN_TABLE = 603;
-  const FQL_EC_NOT_INDEXABLE = 604;
+  const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility
+  const FQL_EC_NO_INDEX = 604;
+  const FQL_EC_UNKNOWN_FUNCTION = 605;
+  const FQL_EC_INVALID_PARAM = 606;
+  const FQL_EC_INVALID_FIELD = 607;
+  const FQL_EC_INVALID_SESSION = 608;
+  const FQL_EC_UNSUPPORTED_APP_TYPE = 609;
+  const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610;
+  const FQL_EC_DEPRECATED_TABLE = 611;
+  const FQL_EC_EXTENDED_PERMISSION = 612;
+  const FQL_EC_RATE_LIMIT_EXCEEDED = 613;
+
+  const API_EC_REF_SET_FAILED = 700;
 
   /**
    * DATA STORE API ERRORS
@@ -2581,52 +3279,122 @@ class FacebookAPIErrorCodes {
   const API_EC_DATA_OBJECT_NOT_FOUND = 803;
   const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804;
   const API_EC_DATA_DATABASE_ERROR = 805;
+  const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806;
+  const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807;
+  const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808;
+  const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809;
+  const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810;
+  const API_EC_DATA_MALFORMED_ACTION_LINK = 811;
+  const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812;
 
   /*
-   * Batch ERROR
+   * APPLICATION INFO ERRORS
    */
-  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;
+  const API_EC_NO_SUCH_APP = 900;
 
+  /*
+   * BATCH ERRORS
+   */
+  const API_EC_BATCH_TOO_MANY_ITEMS = 950;
+  const API_EC_BATCH_ALREADY_STARTED = 951;
+  const API_EC_BATCH_NOT_STARTED = 952;
+  const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953;
+
+  /*
+   * EVENT API ERRORS
+   */
+  const API_EC_EVENT_INVALID_TIME = 1000;
+
+  /*
+   * INFO BOX ERRORS
+   */
+  const API_EC_INFO_NO_INFORMATION = 1050;
+  const API_EC_INFO_SET_FAILED = 1051;
+
+  /*
+   * LIVEMESSAGE API ERRORS
+   */
+  const API_EC_LIVEMESSAGE_SEND_FAILED = 1100;
+  const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101;
+  const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102;
+
+  /*
+   * CONNECT SESSION ERRORS
+   */
+  const API_EC_CONNECT_FEED_DISABLED = 1300;
+
+  /*
+   * Platform tag bundles errors
+   */
+  const API_EC_TAG_BUNDLE_QUOTA = 1400;
+
+  /*
+   * SHARE
+   */
+  const API_EC_SHARE_BAD_URL = 1500;
+
+  /*
+   * NOTES
+   */
+  const API_EC_NOTE_CANNOT_MODIFY = 1600;
+
+  /*
+   * COMMENTS
+   */
+  const API_EC_COMMENTS_UNKNOWN = 1700;
+  const API_EC_COMMENTS_POST_TOO_LONG = 1701;
+  const API_EC_COMMENTS_DB_DOWN = 1702;
+  const API_EC_COMMENTS_INVALID_XID = 1703;
+  const API_EC_COMMENTS_INVALID_UID = 1704;
+  const API_EC_COMMENTS_INVALID_POST = 1705;
+
+  /**
+   * This array is no longer maintained; to view the description of an error
+   * code, please look at the message element of the API response or visit
+   * the developer wiki at http://wiki.developers.facebook.com/.
+   */
   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',
+      self::API_EC_SUCCESS           => 'Success',
+      self::API_EC_UNKNOWN           => 'An unknown error occurred',
+      self::API_EC_SERVICE           => 'Service temporarily unavailable',
+      self::API_EC_METHOD            => 'Unknown method',
+      self::API_EC_TOO_MANY_CALLS    => 'Application request limit reached',
+      self::API_EC_BAD_IP            => 'Unauthorized source IP address',
+      self::API_EC_PARAM             => 'Invalid parameter',
+      self::API_EC_PARAM_API_KEY     => 'Invalid API key',
+      self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
+      self::API_EC_PARAM_CALL_ID     => 'Call_id must be greater than previous',
+      self::API_EC_PARAM_SIGNATURE   => 'Incorrect signature',
+      self::API_EC_PARAM_USER_ID     => 'Invalid user id',
+      self::API_EC_PARAM_USER_FIELD  => 'Invalid user info field',
+      self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
+      self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list',
+      self::API_EC_PARAM_FIELD_LIST => 'Invalid field list',
+      self::API_EC_PARAM_ALBUM_ID    => 'Invalid album id',
+      self::API_EC_PARAM_BAD_EID     => 'Invalid eid',
+      self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
+      self::API_EC_PERMISSION        => 'Permissions error',
+      self::API_EC_PERMISSION_USER   => 'User not visible',
+      self::API_EC_PERMISSION_NO_DEVELOPERS  => 'Application has no developers',
+      self::API_EC_PERMISSION_ALBUM  => 'Album not visible',
+      self::API_EC_PERMISSION_PHOTO  => 'Photo not visible',
+      self::API_EC_PERMISSION_EVENT  => 'Creating and modifying events required the extended permission create_event',
+      self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
+      self::API_EC_EDIT_ALBUM_SIZE   => 'Album is full',
+      self::FQL_EC_PARSER            => 'FQL: Parser Error',
+      self::FQL_EC_UNKNOWN_FIELD     => 'FQL: Unknown Field',
+      self::FQL_EC_UNKNOWN_TABLE     => 'FQL: Unknown Table',
+      self::FQL_EC_NOT_INDEXABLE     => 'FQL: Statement not indexable',
+      self::FQL_EC_UNKNOWN_FUNCTION  => 'FQL: Attempted to call unknown function',
+      self::FQL_EC_INVALID_PARAM     => 'FQL: Invalid parameter passed in',
+      self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
+      self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
+      self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
+      self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
+      self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
+      self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
+      self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
+      self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch',
+      self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode'
   );
 }
index 0c69e226f759845d159d68f271c37f9c38c93b4a..5f9a048f2c7225917dcc56bfbb1b7326067ef4f9 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1,7 +1,7 @@
 <?php
 /**
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -48,22 +48,72 @@ function handleError($error)
         $logmsg .= " : ". $error->getDebugInfo();
     }
     common_log(LOG_ERR, $logmsg);
-    $msg = sprintf(_('The database for %s isn\'t responding correctly, '.
-                     'so the site won\'t work properly. '.
-                     'The site admins probably know about the problem, '.
-                     'but you can contact them at %s to make sure. '.
-                     'Otherwise, wait a few minutes and try again.'),
-                   common_config('site', 'name'),
-                   common_config('site', 'email'));
+    if(common_config('site', 'logdebug')) {
+        $bt = $error->getBacktrace();
+        foreach ($bt as $line) {
+            common_log(LOG_ERR, $line);
+        }
+    }
+    if ($error instanceof DB_DataObject_Error ||
+        $error instanceof DB_Error) {
+        $msg = sprintf(_('The database for %s isn\'t responding correctly, '.
+                         'so the site won\'t work properly. '.
+                         'The site admins probably know about the problem, '.
+                         'but you can contact them at %s to make sure. '.
+                         'Otherwise, wait a few minutes and try again.'),
+                       common_config('site', 'name'),
+                       common_config('site', 'email'));
+    } else {
+        $msg = _('An important error occured, probably related to email setup. '.
+                 'Check logfiles for more info..');
+    }
 
     $dac = new DBErrorAction($msg, 500);
     $dac->showPage();
     exit(-1);
 }
 
+function checkMirror($action_obj)
+{
+    global $config;
+
+    static $alwaysRW = array('session', 'remember_me');
+
+    if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
+        if (is_array(common_config('db', 'mirror'))) {
+            // "load balancing", ha ha
+            $arr = common_config('db', 'mirror');
+            $k = array_rand($arr);
+            $mirror = $arr[$k];
+        } else {
+            $mirror = common_config('db', 'mirror');
+        }
+
+        // We ensure that these tables always are used
+        // on the master DB
+
+        $config['db']['database_rw'] = $config['db']['database'];
+        $config['db']['ini_rw'] = INSTALLDIR.'/classes/laconica.ini';
+
+        foreach ($alwaysRW as $table) {
+            $config['db']['table_'.$table] = 'rw';
+        }
+
+        // everyone else uses the mirror
+
+        $config['db']['database'] = $mirror;
+    }
+}
+
 function main()
 {
-    global $user, $action, $config;
+    // quick check for fancy URL auto-detection support in installer.
+    if (isset($_SERVER['REDIRECT_URL']) && ((dirname($_SERVER['REQUEST_URI']) . '/check-fancy') === $_SERVER['REDIRECT_URL'])) {
+        die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
+    }
+    global $user, $action;
+
+    Snapshot::check();
 
     if (!_have_config()) {
         $msg = sprintf(_("No configuration file found. Try running ".
@@ -128,19 +178,7 @@ function main()
     } else {
         $action_obj = new $action_class();
 
-        // XXX: find somewhere for this little block to live
-
-        if (common_config('db', 'mirror') && $action_obj->isReadOnly($args)) {
-            if (is_array(common_config('db', 'mirror'))) {
-                // "load balancing", ha ha
-                $arr = common_config('db', 'mirror');
-                $k = array_rand($arr);
-                $mirror = $arr[$k];
-            } else {
-                $mirror = common_config('db', 'mirror');
-            }
-            $config['db']['database'] = $mirror;
-        }
+        checkMirror($action_obj);
 
         try {
             if ($action_obj->prepare($args)) {
index 87a99a650895b3774adc3b2e0c84cf401cb370fe..570b08edf473b13e709ece605656a279d89016de 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2009, Controlez-Vous, Inc.
+ * Copyright (C) 2009, Control Yourself, 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
@@ -35,15 +35,17 @@ function main()
 
 function checkPrereqs()
 {
+       $pass = true;
+
     if (file_exists(INSTALLDIR.'/config.php')) {
          ?><p class="error">Config file &quot;config.php&quot; already exists.</p>
          <?php
-        return false;
+        $pass = false;
     }
 
     if (version_compare(PHP_VERSION, '5.0.0', '<')) {
             ?><p class="error">Require PHP version 5 or greater.</p><?php
-                   return false;
+                   $pass = false;
     }
 
     $reqs = array('gd', 'mysql', 'curl',
@@ -52,28 +54,32 @@ function checkPrereqs()
 
     foreach ($reqs as $req) {
         if (!checkExtension($req)) {
-            ?><p class="error">Cannot load required extension &quot;<?php echo $req; ?>&quot;.</p><?php
-                   return false;
+            ?><p class="error">Cannot load required extension: <code><?php echo $req; ?></code></p><?php
+                   $pass = false;
         }
     }
 
        if (!is_writable(INSTALLDIR)) {
-         ?><p class="error">Cannot write config file to &quot;<?php echo INSTALLDIR; ?>&quot;.</p>
-              <p>On your server, try this command:</p>
-              <blockquote>chmod a+w <?php echo INSTALLDIR; ?></blockquote>
+         ?><p class="error">Cannot write config file to: <code><?php echo INSTALLDIR; ?></code></p>
+              <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?></code>
          <?php
-            return false;
+            $pass = false;
        }
 
        if (!is_writable(INSTALLDIR.'/avatar/')) {
-         ?><p class="error">Cannot write avatar directory &quot;<?php echo INSTALLDIR; ?>/avatar/&quot;.</p>
-              <p>On your server, try this command:</p>
-              <blockquote>chmod a+w <?php echo INSTALLDIR; ?>/avatar/</blockquote>
+         ?><p class="error">Cannot write avatar directory: <code><?php echo INSTALLDIR; ?>/avatar/</code></p>
+              <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/avatar/</code></p>
+         <?
+            $pass = false;
+       }
+       if (!is_writable(INSTALLDIR.'/background/')) {
+         ?><p class="error">Cannot write background directory: <code><?php echo INSTALLDIR; ?>/background/</code></p>
+              <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/background/</code></p>
          <?
-            return false;
+            $pass = false;
        }
 
-       return true;
+       return $pass;
 }
 
 function checkExtension($name)
@@ -88,96 +94,118 @@ function checkExtension($name)
 
 function showForm()
 {
-?>
-<p>Enter your database connection information below to initialize the database.</p>
-<form method='post' action='install.php'>
-       <fieldset>
-       <ul class='form_data'>
-       <li>
-       <label for='sitename'>Site name</label>
-       <input type='text' id='sitename' name='sitename' />
-       <p>The name of your site</p>
-       </li>
-       <li>
-       <li>
-       <label for='host'>Hostname</label>
-       <input type='text' id='host' name='host' />
-       <p>Database hostname</p>
-       </li>
-       <li>
-       <label for='host'>Database</label>
-       <input type='text' id='database' name='database' />
-       <p>Database name</p>
-       </li>
-       <li>
-       <label for='username'>Username</label>
-       <input type='text' id='username' name='username' />
-       <p>Database username</p>
-       </li>
-       <li>
-       <label for='password'>Password</label>
-       <input type='password' id='password' name='password' />
-       <p>Database password</p>
-       </li>
-       </ul>
-       <input type='submit' name='submit' value='Submit'>
-       </fieldset>
+    echo<<<E_O_T
+        </ul>
+    </dd>
+</dl>
+<dl id="page_notice" class="system_notice">
+    <dt>Page notice</dt>
+    <dd>
+        <div class="instructions">
+            <p>Enter your database connection information below to initialize the database.</p>
+        </div>
+    </dd>
+</dl>
+<form method="post" action="install.php" class="form_settings" id="form_install">
+    <fieldset>
+        <legend>Connection settings</legend>
+        <ul class="form_data">
+            <li>
+                <label for="sitename">Site name</label>
+                <input type="text" id="sitename" name="sitename" />
+                <p class="form_guide">The name of your site</p>
+            </li>
+            <li>
+                <label for="fancy-enable">Fancy URLs</label>
+                <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
+                <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
+                <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p>
+            </li>
+            <li>
+                <label for="host">Hostname</label>
+                <input type="text" id="host" name="host" />
+                <p class="form_guide">Database hostname</p>
+            </li>
+            <li>
+                <label for="host">Database</label>
+                <input type="text" id="database" name="database" />
+                <p class="form_guide">Database name</p>
+            </li>
+            <li>
+                <label for="username">Username</label>
+                <input type="text" id="username" name="username" />
+                <p class="form_guide">Database username</p>
+            </li>
+            <li>
+                <label for="password">Password</label>
+                <input type="password" id="password" name="password" />
+                <p class="form_guide">Database password</p>
+            </li>
+        </ul>
+        <input type="submit" name="submit" class="submit" value="Submit" />
+    </fieldset>
 </form>
-<?
+
+E_O_T;
 }
 
 function updateStatus($status, $error=false)
 {
 ?>
-       <li>
-<?
-    print $status;
-?>
-       </li>
-<?
+                <li <?php echo ($error) ? 'class="error"': ''; ?>><?print $status;?></li>
+
+<?php
 }
 
 function handlePost()
 {
 ?>
-       <ul>
-<?
-    $host = $_POST['host'];
+
+<?php
+    $host     = $_POST['host'];
     $database = $_POST['database'];
     $username = $_POST['username'];
     $password = $_POST['password'];
     $sitename = $_POST['sitename'];
+    $fancy    = !empty($_POST['fancy']);
+?>
+    <dl class="system_notice">
+        <dt>Page notice</dt>
+        <dd>
+            <ul>
+<?php
+       $fail = false;
 
     if (empty($host)) {
         updateStatus("No hostname specified.", true);
-        showForm();
-        return;
+               $fail = true;
     }
 
     if (empty($database)) {
         updateStatus("No database specified.", true);
-        showForm();
-        return;
+               $fail = true;
     }
 
     if (empty($username)) {
         updateStatus("No username specified.", true);
-        showForm();
-        return;
+               $fail = true;
     }
 
     if (empty($password)) {
         updateStatus("No password specified.", true);
-        showForm();
-        return;
+               $fail = true;
     }
 
     if (empty($sitename)) {
         updateStatus("No sitename specified.", true);
-        showForm();
-        return;
+               $fail = true;
     }
 
+       if($fail){
+               showForm();
+           return;
+       }
+
     updateStatus("Starting installation...");
     updateStatus("Checking database...");
     $conn = mysql_connect($host, $username, $password);
@@ -214,24 +242,29 @@ function handlePost()
     }
     updateStatus("Writing config file...");
     $sqlUrl = "mysqli://$username:$password@$host/$database";
-    $res = writeConf($sitename, $sqlUrl);
+    $res = writeConf($sitename, $sqlUrl, $fancy);
     if (!$res) {
         updateStatus("Can't write config file.", true);
         showForm();
         return;
     }
     updateStatus("Done!");
+    if ($path) $path .= '/';
+    updateStatus("You can visit your <a href='/$path'>new Laconica site</a>.");
 ?>
-       </ul>
-<?
+
+<?php
 }
 
-function writeConf($sitename, $sqlUrl)
+function writeConf($sitename, $sqlUrl, $fancy)
 {
     $res = file_put_contents(INSTALLDIR.'/config.php',
                              "<?php\n".
+                             "if (!defined('LACONICA')) { exit(1); }\n\n".
                              "\$config['site']['name'] = \"$sitename\";\n\n".
-                             "\$config['db']['database'] = \"$sqlUrl\";\n\n");
+                             ($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
+                             "\$config['db']['database'] = \"$sqlUrl\";\n\n".
+                             "?>");
     return $res;
 }
 
@@ -253,21 +286,37 @@ function runDbScript($filename, $conn)
 }
 
 ?>
-<html>
-<head>
-       <title>Install Laconica</title>
-       <link rel="stylesheet" type="text/css" href="theme/base/css/display.css?version=0.7.1" media="screen, projection, tv"/>
-       <link rel="stylesheet" type="text/css" href="theme/base/css/modal.css?version=0.7.1" media="screen, projection, tv"/>
-       <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.7.1" media="screen, projection, tv"/>
-</head>
-<body>
-       <div id="wrap">
-       <div id="core">
-       <div id="content">
-       <h1>Install Laconica</h1>
+<?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
+<!DOCTYPE html
+PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
+    <head>
+        <title>Install Laconica</title>
+       <link rel="shortcut icon" href="favicon.ico"/>
+        <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/>
+        <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]-->
+        <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]-->
+        <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]-->
+        <script src="js/jquery.min.js"></script>
+        <script src="js/install.js"></script>
+    </head>
+    <body id="install">
+        <div id="wrap">
+            <div id="header">
+                <address id="site_contact" class="vcard">
+                    <a class="url home bookmark" href=".">
+                        <img class="logo photo" src="theme/default/logo.png" alt="Laconica"/>
+                        <span class="fn org">Laconica</span>
+                    </a>
+                </address>
+            </div>
+            <div id="core">
+                <div id="content">
+                    <h1>Install Laconica</h1>
 <?php main(); ?>
-       </div>
-       </div>
-       </div>
-</body>
+                </div>
+            </div>
+        </div>
+    </body>
 </html>
diff --git a/js/farbtastic/LICENSE.txt b/js/farbtastic/LICENSE.txt
new file mode 100644 (file)
index 0000000..5a3cc20
--- /dev/null
@@ -0,0 +1,341 @@
+
+        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/js/farbtastic/farbtastic.js b/js/farbtastic/farbtastic.js
new file mode 100644 (file)
index 0000000..d8b5ad9
--- /dev/null
@@ -0,0 +1,345 @@
+/**
+ * Farbtastic Color Picker 1.2
+ * © 2008 Steven Wittens
+ *
+ * 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
+ */
+
+jQuery.fn.farbtastic = function (callback) {
+  $.farbtastic(this, callback);
+  return this;
+};
+
+jQuery.farbtastic = function (container, callback) {
+  var container = $(container).get(0);
+  return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback));
+}
+
+jQuery._farbtastic = function (container, callback) {
+  // Store farbtastic object
+  var fb = this;
+
+  // Insert markup
+  $(container).html('<div class="farbtastic"><div class="color"></div><div class="wheel"></div><div class="overlay"></div><div class="h-marker marker"></div><div class="sl-marker marker"></div></div>');
+  var e = $('.farbtastic', container);
+  fb.wheel = $('.wheel', container).get(0);
+  // Dimensions
+  fb.radius = 84;
+  fb.square = 100;
+  fb.width = 194;
+
+  // Fix background PNGs in IE6
+  if (navigator.appVersion.match(/MSIE [0-6]\./)) {
+    $('*', e).each(function () {
+      if (this.currentStyle.backgroundImage != 'none') {
+        var image = this.currentStyle.backgroundImage;
+        image = this.currentStyle.backgroundImage.substring(5, image.length - 2);
+        $(this).css({
+          'backgroundImage': 'none',
+          'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
+        });
+      }
+    });
+  }
+
+  /**
+   * Link to the given element(s) or callback.
+   */
+  fb.linkTo = function (callback) {
+    // Unbind previous nodes
+    if (typeof fb.callback == 'object') {
+      $(fb.callback).unbind('keyup', fb.updateValue);
+    }
+
+    // Reset color
+    fb.color = null;
+
+    // Bind callback or elements
+    if (typeof callback == 'function') {
+      fb.callback = callback;
+    }
+    else if (typeof callback == 'object' || typeof callback == 'string') {
+      fb.callback = $(callback);
+      fb.callback.bind('keyup', fb.updateValue);
+      if (fb.callback.get(0).value) {
+        fb.setColor(fb.callback.get(0).value);
+      }
+    }
+    return this;
+  }
+  fb.updateValue = function (event) {
+    if (this.value && this.value != fb.color) {
+      fb.setColor(this.value);
+    }
+  }
+
+  /**
+   * Change color with HTML syntax #123456
+   */
+  fb.setColor = function (color) {
+    var unpack = fb.unpack(color);
+    if (fb.color != color && unpack) {
+      fb.color = color;
+      fb.rgb = unpack;
+      fb.hsl = fb.RGBToHSL(fb.rgb);
+      fb.updateDisplay();
+    }
+    return this;
+  }
+
+  /**
+   * Change color with HSL triplet [0..1, 0..1, 0..1]
+   */
+  fb.setHSL = function (hsl) {
+    fb.hsl = hsl;
+    fb.rgb = fb.HSLToRGB(hsl);
+    fb.color = fb.pack(fb.rgb);
+    fb.updateDisplay();
+    return this;
+  }
+
+  /////////////////////////////////////////////////////
+
+  /**
+   * Retrieve the coordinates of the given event relative to the center
+   * of the widget.
+   */
+  fb.widgetCoords = function (event) {
+    var x, y;
+    var el = event.target || event.srcElement;
+    var reference = fb.wheel;
+
+    if (typeof event.offsetX != 'undefined') {
+      // Use offset coordinates and find common offsetParent
+      var pos = { x: event.offsetX, y: event.offsetY };
+
+      // Send the coordinates upwards through the offsetParent chain.
+      var e = el;
+      while (e) {
+        e.mouseX = pos.x;
+        e.mouseY = pos.y;
+        pos.x += e.offsetLeft;
+        pos.y += e.offsetTop;
+        e = e.offsetParent;
+      }
+
+      // Look for the coordinates starting from the wheel widget.
+      var e = reference;
+      var offset = { x: 0, y: 0 }
+      while (e) {
+        if (typeof e.mouseX != 'undefined') {
+          x = e.mouseX - offset.x;
+          y = e.mouseY - offset.y;
+          break;
+        }
+        offset.x += e.offsetLeft;
+        offset.y += e.offsetTop;
+        e = e.offsetParent;
+      }
+
+      // Reset stored coordinates
+      e = el;
+      while (e) {
+        e.mouseX = undefined;
+        e.mouseY = undefined;
+        e = e.offsetParent;
+      }
+    }
+    else {
+      // Use absolute coordinates
+      var pos = fb.absolutePosition(reference);
+      x = (event.pageX || 0*(event.clientX + $('html').get(0).scrollLeft)) - pos.x;
+      y = (event.pageY || 0*(event.clientY + $('html').get(0).scrollTop)) - pos.y;
+    }
+    // Subtract distance to middle
+    return { x: x - fb.width / 2, y: y - fb.width / 2 };
+  }
+
+  /**
+   * Mousedown handler
+   */
+  fb.mousedown = function (event) {
+    // Capture mouse
+    if (!document.dragging) {
+      $(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup);
+      document.dragging = true;
+    }
+
+    // Check which area is being dragged
+    var pos = fb.widgetCoords(event);
+    fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square;
+
+    // Process
+    fb.mousemove(event);
+    return false;
+  }
+
+  /**
+   * Mousemove handler
+   */
+  fb.mousemove = function (event) {
+    // Get coordinates relative to color picker center
+    var pos = fb.widgetCoords(event);
+
+    // Set new HSL parameters
+    if (fb.circleDrag) {
+      var hue = Math.atan2(pos.x, -pos.y) / 6.28;
+      if (hue < 0) hue += 1;
+      fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]);
+    }
+    else {
+      var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5));
+      var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5));
+      fb.setHSL([fb.hsl[0], sat, lum]);
+    }
+    return false;
+  }
+
+  /**
+   * Mouseup handler
+   */
+  fb.mouseup = function () {
+    // Uncapture mouse
+    $(document).unbind('mousemove', fb.mousemove);
+    $(document).unbind('mouseup', fb.mouseup);
+    document.dragging = false;
+  }
+
+  /**
+   * Update the markers and styles
+   */
+  fb.updateDisplay = function () {
+    // Markers
+    var angle = fb.hsl[0] * 6.28;
+    $('.h-marker', e).css({
+      left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px',
+      top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px'
+    });
+
+    $('.sl-marker', e).css({
+      left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px',
+      top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px'
+    });
+
+    // Saturation/Luminance gradient
+    $('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])));
+
+    // Linked elements or callback
+    if (typeof fb.callback == 'object') {
+      // Set background/foreground color
+      $(fb.callback).css({
+        backgroundColor: fb.color,
+        color: fb.hsl[2] > 0.5 ? '#000' : '#fff'
+      });
+
+      // Change linked value
+      $(fb.callback).each(function() {
+        if (this.value && this.value != fb.color) {
+          this.value = fb.color;
+        }
+      });
+    }
+    else if (typeof fb.callback == 'function') {
+      fb.callback.call(fb, fb.color);
+    }
+  }
+
+  /**
+   * Get absolute position of element
+   */
+  fb.absolutePosition = function (el) {
+    var r = { x: el.offsetLeft, y: el.offsetTop };
+    // Resolve relative to offsetParent
+    if (el.offsetParent) {
+      var tmp = fb.absolutePosition(el.offsetParent);
+      r.x += tmp.x;
+      r.y += tmp.y;
+    }
+    return r;
+  };
+
+  /* Various color utility functions */
+  fb.pack = function (rgb) {
+    var r = Math.round(rgb[0] * 255);
+    var g = Math.round(rgb[1] * 255);
+    var b = Math.round(rgb[2] * 255);
+    return '#' + (r < 16 ? '0' : '') + r.toString(16) +
+           (g < 16 ? '0' : '') + g.toString(16) +
+           (b < 16 ? '0' : '') + b.toString(16);
+  }
+
+  fb.unpack = function (color) {
+    if (color.length == 7) {
+      return [parseInt('0x' + color.substring(1, 3)) / 255,
+        parseInt('0x' + color.substring(3, 5)) / 255,
+        parseInt('0x' + color.substring(5, 7)) / 255];
+    }
+    else if (color.length == 4) {
+      return [parseInt('0x' + color.substring(1, 2)) / 15,
+        parseInt('0x' + color.substring(2, 3)) / 15,
+        parseInt('0x' + color.substring(3, 4)) / 15];
+    }
+  }
+
+  fb.HSLToRGB = function (hsl) {
+    var m1, m2, r, g, b;
+    var h = hsl[0], s = hsl[1], l = hsl[2];
+    m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s;
+    m1 = l * 2 - m2;
+    return [this.hueToRGB(m1, m2, h+0.33333),
+        this.hueToRGB(m1, m2, h),
+        this.hueToRGB(m1, m2, h-0.33333)];
+  }
+
+  fb.hueToRGB = function (m1, m2, h) {
+    h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
+    if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
+    if (h * 2 < 1) return m2;
+    if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6;
+    return m1;
+  }
+
+  fb.RGBToHSL = function (rgb) {
+    var min, max, delta, h, s, l;
+    var r = rgb[0], g = rgb[1], b = rgb[2];
+    min = Math.min(r, Math.min(g, b));
+    max = Math.max(r, Math.max(g, b));
+    delta = max - min;
+    l = (min + max) / 2;
+    s = 0;
+    if (l > 0 && l < 1) {
+      s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
+    }
+    h = 0;
+    if (delta > 0) {
+      if (max == r && max != g) h += (g - b) / delta;
+      if (max == g && max != b) h += (2 + (b - r) / delta);
+      if (max == b && max != r) h += (4 + (r - g) / delta);
+      h /= 6;
+    }
+    return [h, s, l];
+  }
+
+  // Install mousedown handler (the others are set on the document on-demand)
+  $('*', e).mousedown(fb.mousedown);
+
+    // Init color
+  fb.setColor('#000000');
+
+  // Set linked elements/callback
+  if (callback) {
+    fb.linkTo(callback);
+  }
+}
\ No newline at end of file
diff --git a/js/farbtastic/marker.png b/js/farbtastic/marker.png
new file mode 100755 (executable)
index 0000000..3929bbb
Binary files /dev/null and b/js/farbtastic/marker.png differ
diff --git a/js/farbtastic/mask.png b/js/farbtastic/mask.png
new file mode 100644 (file)
index 0000000..b0a4d40
Binary files /dev/null and b/js/farbtastic/mask.png differ
diff --git a/js/farbtastic/wheel.png b/js/farbtastic/wheel.png
new file mode 100644 (file)
index 0000000..97b343d
Binary files /dev/null and b/js/farbtastic/wheel.png differ
diff --git a/js/install.js b/js/install.js
new file mode 100644 (file)
index 0000000..32a5411
--- /dev/null
@@ -0,0 +1,18 @@
+$(document).ready(function(){
+    $.ajax({url:'check-fancy',
+        type:'GET',
+        success:function(data, textStatus) {
+            $('#fancy-enable').attr('checked', true);
+            $('#fancy-disable').attr('checked', false);
+            $('#fancy-form_guide').text(data);
+        },
+        error:function(XMLHttpRequest, textStatus, errorThrown) {
+            $('#fancy-enable').attr('checked', false);
+            $('#fancy-disable').attr('checked', true);
+            $('#fancy-enable').attr('disabled', true);
+            $('#fancy-disable').attr('disabled', true);
+            $('#fancy-form_guide').text("Fancy URL support detection failed, disabling this option. Make sure you renamed htaccess.sample to .htaccess.");
+        }
+    });
+});
+
index a0399d540510db1638a9f233dbeb5ef18bcc6bc1..4e1cbfd1e705810938e2a5ee07a9cf304d80c688 100644 (file)
@@ -1,39 +1,48 @@
-               $(function(){
-                       var x = ($('#avatar_crop_x').val()) ? $('#avatar_crop_x').val() : 0;
-                       var y = ($('#avatar_crop_y').val()) ? $('#avatar_crop_y').val() : 0;
-                       var w = ($('#avatar_crop_w').val()) ? $('#avatar_crop_w').val() : $("#avatar_original img").attr("width");
-                       var h = ($('#avatar_crop_h').val()) ? $('#avatar_crop_h').val() : $("#avatar_original img").attr("height");
+/** Init for Jcrop library and page setup
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
 
-                       jQuery("#avatar_original img").Jcrop({
-                               onChange: showPreview,
-                       setSelect: [ x, y, w, h ],
-                               onSelect: updateCoords,
-                               aspectRatio: 1,
-                               boxWidth: 480,
-                               boxHeight: 480,
-                               bgColor: '#000',
-                               bgOpacity: .4
-                       });
-               });
+$(function(){
+    var x = ($('#avatar_crop_x').val()) ? $('#avatar_crop_x').val() : 0;
+    var y = ($('#avatar_crop_y').val()) ? $('#avatar_crop_y').val() : 0;
+    var w = ($('#avatar_crop_w').val()) ? $('#avatar_crop_w').val() : $("#avatar_original img").attr("width");
+    var h = ($('#avatar_crop_h').val()) ? $('#avatar_crop_h').val() : $("#avatar_original img").attr("height");
 
-               function showPreview(coords) {
-                       var rx = 96 / coords.w;
-                       var ry = 96 / coords.h;
+    jQuery("#avatar_original img").Jcrop({
+        onChange: showPreview,
+        setSelect: [ x, y, w, h ],
+        onSelect: updateCoords,
+        aspectRatio: 1,
+        boxWidth: 480,
+        boxHeight: 480,
+        bgColor: '#000',
+        bgOpacity: .4
+    });
+});
 
-                       var img_width = $("#avatar_original img").attr("width");
-                       var img_height = $("#avatar_original img").attr("height");
+function showPreview(coords) {
+    var rx = 96 / coords.w;
+    var ry = 96 / coords.h;
 
-                       $('#avatar_preview img').css({
-                               width: Math.round(rx *img_width) + 'px',
-                               height: Math.round(ry * img_height) + 'px',
-                               marginLeft: '-' + Math.round(rx * coords.x) + 'px',
-                               marginTop: '-' + Math.round(ry * coords.y) + 'px'
-                       });
-               };
+    var img_width = $("#avatar_original img").attr("width");
+    var img_height = $("#avatar_original img").attr("height");
 
-               function updateCoords(c) {
-                       $('#avatar_crop_x').val(c.x);
-                       $('#avatar_crop_y').val(c.y);
-                       $('#avatar_crop_w').val(c.w);
-                       $('#avatar_crop_h').val(c.h);
-               };
+    $('#avatar_preview img').css({
+        width: Math.round(rx *img_width) + 'px',
+        height: Math.round(ry * img_height) + 'px',
+        marginLeft: '-' + Math.round(rx * coords.x) + 'px',
+        marginTop: '-' + Math.round(ry * coords.y) + 'px'
+    });
+};
+
+function updateCoords(c) {
+    $('#avatar_crop_x').val(c.x);
+    $('#avatar_crop_y').val(c.y);
+    $('#avatar_crop_w').val(c.w);
+    $('#avatar_crop_h').val(c.h);
+};
index cb8b5a6609a9c43b3804e4e90d44d5bddafbb249..936b847abe7bedc9ae44cf68cc75a48b1b94bf5c 100644 (file)
@@ -157,7 +157,7 @@ $.fn.ajaxSubmit = function(options) {
     function fileUpload() {\r
         var form = $form[0];\r
         \r
-        if ($(':input[@name=submit]', form).length) {\r
+        if ($(':input[name=submit]', form).length) {\r
             alert('Error: Form elements must not be named "submit".');\r
             return;\r
         }\r
@@ -570,7 +570,7 @@ $.fn.clearForm = function() {
 $.fn.clearFields = $.fn.clearInputs = function() {\r
     return this.each(function() {\r
         var t = this.type, tag = this.tagName.toLowerCase();\r
-        if (t == 'text' || t == 'password' || tag == 'textarea')\r
+        if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea')\r
             this.value = '';\r
         else if (t == 'checkbox' || t == 'radio')\r
             this.checked = false;\r
diff --git a/js/jquery.joverlay.js b/js/jquery.joverlay.js
new file mode 100644 (file)
index 0000000..e4effec
--- /dev/null
@@ -0,0 +1,271 @@
+/* Copyright (c) 2009 Alvaro A. Lima Jr http://alvarojunior.com/jquery/joverlay.html
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * Version: 0.7.1 (JUN 15, 2009)
+ * Requires: jQuery 1.3+
+ */
+
+(function($) {
+
+       // Global vars
+       var isIE6 = $.browser.msie && $.browser.version == 6.0; // =(
+       var JOVERLAY_TIMER = null;
+       var     JOVERLAY_ELEMENT_PREV = null;
+
+       $.fn.jOverlay = function(options) {
+
+               // Element exist?
+               if ( $('#jOverlay').length ) {$.closeOverlay();}
+
+               // Clear Element Prev
+               JOVERLAY_ELEMENT_PREV = null;
+
+               // Clear Timer
+               if (JOVERLAY_TIMER !== null) {
+                       clearTimeout( JOVERLAY_TIMER );
+               }
+
+               // Set Options
+               var options = $.extend({}, $.fn.jOverlay.options, options);
+
+               // private function
+               function center(id) {
+                       if (options.center) {
+                               $.center(id);
+                       }
+               }
+
+               var element = this.is('*') ? this : '#jOverlayContent';
+               var position = isIE6 ? 'absolute' : 'fixed';
+               var isImage = /([^\/\\]+)\.(png|gif|jpeg|jpg|bmp)$/i.test( options.url );
+
+               var imgLoading = options.imgLoading ? "<img id='jOverlayLoading' src='"+options.imgLoading+"' style='position:"+position+"; z-index:"+(options.zIndex + 9)+";'/>" : '';
+
+               $('body').prepend(imgLoading + "<div id='jOverlay' />"
+                       + "<div id='jOverlayContent' style='position:"+position+"; z-index:"+(options.zIndex + 5)+"; display:none;'/>"
+               );
+
+               // Loading Centered
+               $('#jOverlayLoading').load(function(){
+                       center(this);
+               });
+
+               //IE 6 FIX
+               if ( isIE6 ) {
+                       $('select').hide();
+                       $('#jOverlayContent select').show();
+               }
+
+               // Overlay Style
+               $('#jOverlay').css({
+                       backgroundColor : options.color,
+                       position : position,
+                       top : '0px',
+                       left : '0px',
+                       filter : 'alpha(opacity='+ (options.opacity * 100) +')', // IE =(
+                       opacity : options.opacity, // Good Browser =D
+                       zIndex : options.zIndex,
+                       width : !isIE6 ? '100%' : $(window).width() + 'px',
+                       height : !isIE6 ? '100%' : $(document).height() + 'px'
+               }).show();
+
+               // ELEMENT
+               if ( this.is('*') ) {
+
+                       JOVERLAY_ELEMENT_PREV = this.prev();
+
+                       $('#jOverlayContent').html(
+                               this.show().attr('display', options.autoHide ? 'none' : this.css('display') )
+                       );
+                       
+                       if ( !isImage ) {
+
+                               center('#jOverlayContent');
+
+                               $('#jOverlayContent').show();
+                               
+                               // Execute callback
+                               if ( !options.url && $.isFunction( options.success ) ) {
+                                       options.success( this );
+                               }
+
+                       }
+
+               }
+
+               // IMAGE
+               if ( isImage ) {
+
+                       $('<img/>').load(function(){
+                               var resize = $.resize(this.width, this.height);
+
+                               $(this).css({
+                                       width : resize.width,
+                                       height : resize.height
+                               });
+
+                               $( element ).html(this);
+
+                               center('#jOverlayContent');
+
+                               $('#jOverlayLoading').fadeOut(500);
+                               $('#jOverlayContent').show();
+
+                               // Execute callback
+                               if ( $.isFunction( options.success ) ) {
+                                       options.success( this );
+                               }
+
+                       }).error(function(){
+                               alert('Image ('+options.url+') not found.');
+                               $.closeOverlay();
+                       }).attr({'src' : options.url, 'alt' : options.url});
+
+               }
+
+               // AJAX
+               if ( options.url && !isImage ) {
+
+                       $.ajax({
+                               type: options.method,
+                               data: options.data,
+                               url: options.url,
+                               success: function(responseText) {
+
+                                       $('#jOverlayLoading').fadeOut(500);
+
+                                       $( element ).html(responseText).show();
+
+                                       center('#jOverlayContent');
+
+                                       // Execute callback
+                                       if ($.isFunction( options.success )) {
+                                               options.success(responseText);
+                                       }
+
+                               },
+                               error : function() {
+                                       alert('URL ('+options.url+') not found.');
+                                       $.closeOverlay();
+                               }
+                       });
+
+               }
+
+               // :(
+               if ( isIE6 ) {
+
+                       // Window scroll
+                       $(window).scroll(function(){
+                               center('#jOverlayContent');
+                       });
+
+                       // Window resize
+                       $(window).resize(function(){
+
+                               $('#jOverlay').css({
+                                       width: $(window).width() + 'px',
+                                       height: $(document).height() + 'px'
+                               });
+
+                               center('#jOverlayContent');
+
+                       });
+
+               }
+
+               // Press ESC to close
+               $(document).keydown(function(event){
+                       if (event.keyCode == 27) {
+                               $.closeOverlay();
+                       }
+               });
+
+               // Click to close
+               if ( options.bgClickToClose ) {
+                       $('#jOverlay').click($.closeOverlay);
+               }
+
+               // Timeout (auto-close)
+               // time in millis to wait before auto-close
+               // set to 0 to disable
+               if ( Number(options.timeout) > 0 ) {
+                       jOverlayTimer = setTimeout( $.closeOverlay, Number(options.timeout) );
+               }
+
+               // ADD CSS
+               $('#jOverlayContent').css(options.css || {});
+       };
+
+       // Resizing large images - orginal by Christian Montoya.
+       // Edited by - Cody Lindley (http://www.codylindley.com) (Thickbox 3.1)
+       $.resize = function(imageWidth, imageHeight) {
+               var x = $(window).width() - 150;
+               var y = $(window).height() - 150;
+               if (imageWidth > x) {
+                       imageHeight = imageHeight * (x / imageWidth); 
+                       imageWidth = x; 
+                       if (imageHeight > y) { 
+                               imageWidth = imageWidth * (y / imageHeight); 
+                               imageHeight = y; 
+                       }
+               } else if (imageHeight > y) { 
+                       imageWidth = imageWidth * (y / imageHeight); 
+                       imageHeight = y; 
+                       if (imageWidth > x) { 
+                               imageHeight = imageHeight * (x / imageWidth); 
+                               imageWidth = x;
+                       }
+               }
+               return {width:imageWidth, height:imageHeight};
+       };
+
+       // Centered Element
+       $.center = function(element) {
+               var element = $(element);
+               var elemWidth = element.width();
+
+               element.css({
+                       width : elemWidth + 'px',
+                       marginLeft : '-' + (elemWidth / 2) + 'px',
+                       marginTop : '-' + element.height() / 2 + 'px',
+                       height : 'auto',
+               top : !isIE6 ? '50%' : $(window).scrollTop() + ($(window).height() / 2) + 'px',
+               left : '50%'
+               });
+       };
+
+       // Options default
+       $.fn.jOverlay.options = {
+               method : 'GET',
+               data : '',
+               url : '',
+               color : '#000',
+               opacity : '0.6',
+               zIndex : 9999,
+               center : true,
+               imgLoading : '',
+               bgClickToClose : true,
+               success : null,
+               timeout : 0,
+               autoHide : true,
+               css : {}
+       };
+
+       // Close
+       $.closeOverlay = function() {
+
+               if (isIE6) { $("select").show(); }
+
+               if ( JOVERLAY_ELEMENT_PREV !== null ) {
+                       if ( JOVERLAY_ELEMENT_PREV !== null ) {
+                               var element = $('#jOverlayContent').children();
+                               JOVERLAY_ELEMENT_PREV.after( element.css('display', element.attr('display') ) );
+                               element.removeAttr('display');
+                       }
+               }
+
+               $('#jOverlayLoading, #jOverlayContent, #jOverlay').remove();
+
+       };
+
+})(jQuery);
\ No newline at end of file
diff --git a/js/jquery.joverlay.min.js b/js/jquery.joverlay.min.js
new file mode 100644 (file)
index 0000000..44cd460
--- /dev/null
@@ -0,0 +1,7 @@
+/* Copyright (c) 2009 Alvaro A. Lima Jr http://alvarojunior.com/jquery/joverlay.html
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * Version: 0.7.1 (JUN 15, 2009
+ * Requires: jQuery 1.3+
+ * Packer from http://dean.edwards.name/packer/
+ */
+(function($){var g=$.browser.msie&&$.browser.version==6.0;var h=null;var i=null;$.fn.jOverlay=function(b){if($('#jOverlay').length){$.closeOverlay()}i=null;if(h!==null){clearTimeout(h)}var b=$.extend({},$.fn.jOverlay.options,b);function center(a){if(b.center){$.center(a)}}var c=this.is('*')?this:'#jOverlayContent';var d=g?'absolute':'fixed';var e=/([^\/\\]+)\.(png|gif|jpeg|jpg|bmp)$/i.test(b.url);var f=b.imgLoading?"<img id='jOverlayLoading' src='"+b.imgLoading+"' style='position:"+d+"; z-index:"+(b.zIndex+9)+";'/>":'';$('body').prepend(f+"<div id='jOverlay' />"+"<div id='jOverlayContent' style='position:"+d+"; z-index:"+(b.zIndex+5)+"; display:none;'/>");$('#jOverlayLoading').load(function(){center(this)});if(g){$('select').hide();$('#jOverlayContent select').show()}$('#jOverlay').css({backgroundColor:b.color,position:d,top:'0px',left:'0px',filter:'alpha(opacity='+(b.opacity*100)+')',opacity:b.opacity,zIndex:b.zIndex,width:!g?'100%':$(window).width()+'px',height:!g?'100%':$(document).height()+'px'}).show();if(this.is('*')){i=this.prev();$('#jOverlayContent').html(this.show().attr('display',b.autoHide?'none':this.css('display')));if(!e){center('#jOverlayContent');$('#jOverlayContent').show();if(!b.url&&$.isFunction(b.success)){b.success(this)}}}if(e){$('<img/>').load(function(){var a=$.resize(this.width,this.height);$(this).css({width:a.width,height:a.height});$(c).html(this);center('#jOverlayContent');$('#jOverlayLoading').fadeOut(500);$('#jOverlayContent').show();if($.isFunction(b.success)){b.success(this)}}).error(function(){alert('Image ('+b.url+') not found.');$.closeOverlay()}).attr({'src':b.url,'alt':b.url})}if(b.url&&!e){$.ajax({type:b.method,data:b.data,url:b.url,success:function(a){$('#jOverlayLoading').fadeOut(500);$(c).html(a).show();center('#jOverlayContent');if($.isFunction(b.success)){b.success(a)}},error:function(){alert('URL ('+b.url+') not found.');$.closeOverlay()}})}if(g){$(window).scroll(function(){center('#jOverlayContent')});$(window).resize(function(){$('#jOverlay').css({width:$(window).width()+'px',height:$(document).height()+'px'});center('#jOverlayContent')})}$(document).keydown(function(a){if(a.keyCode==27){$.closeOverlay()}});if(b.bgClickToClose){$('#jOverlay').click($.closeOverlay)}if(Number(b.timeout)>0){jOverlayTimer=setTimeout($.closeOverlay,Number(b.timeout))}$('#jOverlayContent').css(b.css||{})};$.resize=function(a,b){var x=$(window).width()-150;var y=$(window).height()-150;if(a>x){b=b*(x/a);a=x;if(b>y){a=a*(y/b);b=y}}else if(b>y){a=a*(y/b);b=y;if(a>x){b=b*(x/a);a=x}}return{width:a,height:b}};$.center=function(a){var a=$(a);var b=a.width();a.css({width:b+'px',marginLeft:'-'+(b/2)+'px',marginTop:'-'+a.height()/2+'px',height:'auto',top:!g?'50%':$(window).scrollTop()+($(window).height()/2)+'px',left:'50%'})};$.fn.jOverlay.options={method:'GET',data:'',url:'',color:'#000',opacity:'0.6',zIndex:9999,center:true,imgLoading:'',bgClickToClose:true,success:null,timeout:0,autoHide:true,css:{}};$.closeOverlay=function(){if(g){$("select").show()}if(i!==null){if(i!==null){var a=$('#jOverlayContent').children();i.after(a.css('display',a.attr('display')));a.removeAttr('display')}}$('#jOverlayLoading, #jOverlayContent, #jOverlay').remove()}})(jQuery);
diff --git a/js/userdesign.go.js b/js/userdesign.go.js
new file mode 100644 (file)
index 0000000..dda8629
--- /dev/null
@@ -0,0 +1,99 @@
+/** Init for Farbtastic library and page setup
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+$(document).ready(function() {
+    function UpdateColors(S) {
+        C = $(S).val();
+        switch (parseInt(S.id.slice(-1))) {
+            case 1: default:
+                $('body').css({'background-color':C});
+                break;
+            case 2:
+                $('#content, #site_nav_local_views .current a').css({'background-color':C});
+                break;
+            case 3:
+                $('#aside_primary').css({'background-color':C});
+                break;
+            case 4:
+                $('html body').css({'color':C});
+                break;
+            case 5:
+                $('a').css({'color':C});
+                break;
+        }
+    }
+
+    function UpdateFarbtastic(e) {
+        f.linked = e;
+        f.setColor(e.value);
+    }
+
+    function UpdateSwatch(e) {
+        $(e).css({"background-color": e.value,
+                  "color": f.hsl[2] > 0.5 ? "#000": "#fff"});
+    }
+
+    function SynchColors(e) {
+        var S = f.linked;
+        var C = f.color;
+
+        if (S && S.value && S.value != C) {
+            S.value = C;
+            UpdateSwatch(S);
+            UpdateColors(S);
+        }
+    }
+
+    function InitFarbtastic() {
+        $('#settings_design_color').append('<div id="color-picker"></div>');
+        $('#color-picker').hide();
+
+        f = $.farbtastic('#color-picker', SynchColors);
+        swatches = $('#settings_design_color .swatch');
+
+        swatches
+            .each(SynchColors)
+            .blur(function() {
+                tv = $(this).val();
+                $(this).val(tv.toUpperCase());
+                (tv.length == 4) ? ((tv[0] == '#') ? $(this).val('#'+tv[1]+tv[1]+tv[2]+tv[2]+tv[3]+tv[3]) : '') : '';
+             })
+            .focus(function() {
+                $('#color-picker').show();
+                UpdateFarbtastic(this);
+            })
+            .change(function() {
+                UpdateFarbtastic(this);
+                UpdateSwatch(this);
+                UpdateColors(this);
+            }).change();
+    }
+
+    var f, swatches;
+    InitFarbtastic();
+    $('#form_settings_design').bind('reset', function(){
+        setTimeout(function(){
+            swatches.each(function(){UpdateColors(this);});
+            $('#color-picker').remove();
+            swatches.unbind();
+            InitFarbtastic();
+        },10);
+    });
+
+    $('#design_background-image_off').focus(function() {
+        $('body').css({'background-image':'none'});
+    });
+    $('#design_background-image_on').focus(function() {
+        $('body').css({'background-image':'url('+$('#design_background-image_onoff img')[0].src+')'});
+        $('body').css({'background-attachment': 'fixed'});
+    });
+
+    $('#design_background-image_repeat').click(function() {
+        ($(this)[0].checked) ? $('body').css({'background-repeat':'repeat'}) : $('body').css({'background-repeat':'no-repeat'});
+    });
+});
index 23abba6c2604b48bd18db8fa48b3af437fa59de0..bbcbc0bbb983ccf557bcf01268e62d5bdc55efb2 100644 (file)
@@ -49,8 +49,9 @@ $(document).ready(function(){
                // run once in case there's something in there
                counter();
 
-               // set the focus
-               $("#notice_data-text").focus();
+        if($('body')[0].id != 'conversation') {
+            $("#notice_data-text").focus();
+        }
        }
 
        // XXX: refactor this code
@@ -203,14 +204,27 @@ $(document).ready(function(){
                                                     else {
                                                          li = $("li", xml).get(0);
                                                          if ($("#"+li.id).length == 0) {
-                                                              $("#notices_primary .notices").prepend(document._importNode(li, true));
-                                                              $("#notices_primary .notice:first").css({display:"none"});
-                                                              $("#notices_primary .notice:first").fadeIn(2500);
-                                                              NoticeHover();
-                                                              NoticeReply();
+                                                            var notice_irt_value = $('#notice_in-reply-to').val();
+                                                            var notice_irt = '#notices_primary #notice-'+notice_irt_value;
+                                                            if($('body')[0].id == 'conversation') {
+                                                                if(notice_irt_value.length > 0 && $(notice_irt+' .notices').length < 1) {
+                                                                    $(notice_irt).append('<ul class="notices"></ul>');
+                                                                }
+                                                                $($(notice_irt+' .notices')[0]).append(document._importNode(li, true));
+                                                            }
+                                                            else {
+                                                                $("#notices_primary .notices").prepend(document._importNode(li, true));
+                                                            }
+                                                            $('#'+li.id).css({display:'none'});
+                                                            $('#'+li.id).fadeIn(2500);
+                                                            NoticeReply();
+                                                            NoticeAttachments();
                                                          }
                                                                                                        }
                                                                                                        $("#notice_data-text").val("");
+                                                                                               $("#notice_data-attach").val("");
+                                                                                               $("#notice_in-reply-to").val("");
+                                                    $('#notice_data-attach_selected').remove();
                                                     counter();
                                                                                                }
                                                                                                $("#form_notice").removeClass("processing");
@@ -220,28 +234,18 @@ $(document).ready(function(){
                                           };
        $("#form_notice").ajaxForm(PostNotice);
        $("#form_notice").each(addAjaxHidden);
-    NoticeHover();
     NoticeReply();
+    NoticeAttachments();
+    NoticeDataAttach();
 });
 
-function NoticeHover() {
-    $("#content .notice").hover(
-        function () {
-            $(this).addClass('hover');
-        },
-        function () {
-            $(this).removeClass('hover');
-        }
-    );
-}
-
 function NoticeReply() {
-    if ($('#notice_data-text').length > 0) {
+    if ($('#notice_data-text').length > 0 && $('#content .notice_reply').length > 0) {
         $('#content .notice').each(function() {
-            var notice = $(this);
-            $('.notice_reply', $(this)).click(function() {
-                var nickname = ($('.author .nickname', notice).length > 0) ? $('.author .nickname', notice) : $('.author .nickname');
-                NoticeReplySet(nickname.text(), $('.notice_id', notice).text());
+            var notice = $(this)[0];
+            $($('.notice_reply', notice)[0]).click(function() {
+                var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname');
+                NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text());
                 return false;
             });
         });
@@ -261,3 +265,67 @@ function NoticeReplySet(nick,id) {
        }
        return true;
 }
+
+function NoticeAttachments() {
+    $.fn.jOverlay.options = {
+        method : 'GET',
+        data : '',
+        url : '',
+        color : '#000',
+        opacity : '0.6',
+        zIndex : 99,
+        center : false,
+        imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
+        bgClickToClose : true,
+        success : function() {
+            $('#jOverlayContent').append('<button>&#215;</button>');
+            $('#jOverlayContent button').click($.closeOverlay);
+        },
+        timeout : 0,
+        autoHide : true,
+        css : {'max-width':'542px', 'top':'22.5%', 'left':'32.5%'}
+    };
+
+    $('#content .notice a.attachment').click(function() {
+        $().jOverlay({url: $('address .url')[0].href+'attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
+        return false;
+    });
+
+    var t;
+    $("body:not(#shownotice) #content .notice a.thumbnail").hover(
+        function() {
+            var anchor = $(this);
+            $("a.thumbnail").children('img').hide();
+            anchor.closest(".entry-title").addClass('ov');
+
+            if (anchor.children('img').length == 0) {
+                t = setTimeout(function() {
+                    $.get($('address .url')[0].href+'attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
+                        anchor.append(data);
+                    });
+                }, 500);
+            }
+            else {
+                anchor.children('img').show();
+            }
+        },
+        function() {
+            clearTimeout(t);
+            $("a.thumbnail").children('img').hide();
+            $(this).closest(".entry-title").removeClass('ov');
+        }
+    );
+}
+
+function NoticeDataAttach() {
+    NDA = $('#notice_data-attach');
+    NDA.change(function() {
+        S = '<div id="notice_data-attach_selected" class="success"><code>'+$(this).val()+'</code> <button>&#215;</button></div>';
+        NDAS = $('#notice_data-attach_selected');
+        (NDAS.length > 0) ? NDAS.replaceWith(S) : $('#form_notice').append(S);
+        $('#notice_data-attach_selected button').click(function(){
+            $('#notice_data-attach_selected').remove();
+            NDA.val('');
+        });
+    });
+}
index d1fc5eb6dea23dd05c81df367cfafe1e8edd025f..22d5b4cb5442e762b05d7a0d9a85c0944f4ad8e3 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -22,6 +22,7 @@ if (!defined('LACONICA')) { exit(1); }
 class ShortUrlApi
 {
     protected $service_url;
+    protected $long_limit = 27;
 
     function __construct($service_url)
     {
index 46090b8c1976573e8255a38c64132f89ce8cc87e..4ab50abcef1134a50282bbdfb64a45a4696fa972 100644 (file)
@@ -115,6 +115,9 @@ class AccountSettingsNav extends Widget
                 'openidsettings' =>
                 array(_('OpenID'),
                       _('Add or remove OpenIDs')),
+                'userdesignsettings' =>
+                array(_('Design'),
+                      _('Design your profile')),
                 'othersettings' =>
                 array(_('Other'),
                       _('Other options')));
index 7c7c52c2c19d5b8baf702d3502021e8851ff8fd7..95ee10c642b0ec22bfd7d755144d031d958fba7e 100644 (file)
@@ -242,6 +242,11 @@ class Action extends HTMLOutputter // lawsuit
                 $this->element('script', array('type' => 'text/javascript',
                                                'src' => common_path('js/jquery.form.js')),
                                ' ');
+
+                $this->element('script', array('type' => 'text/javascript',
+                                               'src' => common_path('js/jquery.joverlay.min.js')),
+                               ' ');
+
                 Event::handle('EndShowJQueryScripts', array($this));
             }
             if (Event::handle('StartShowLaconicaScripts', array($this))) {
@@ -378,15 +383,18 @@ class Action extends HTMLOutputter // lawsuit
     {
         $this->elementStart('address', array('id' => 'site_contact',
                                              'class' => 'vcard'));
-        $this->elementStart('a', array('class' => 'url home bookmark',
-                                       'href' => common_local_url('public')));
-        if (common_config('site', 'logo') || file_exists(theme_file('logo.png'))) {
-            $this->element('img', array('class' => 'logo photo',
-                                        'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : theme_path('logo.png'),
-                                        'alt' => common_config('site', 'name')));
+        if (Event::handle('StartAddressData', array($this))) {
+            $this->elementStart('a', array('class' => 'url home bookmark',
+                                           'href' => common_local_url('public')));
+            if (common_config('site', 'logo') || file_exists(theme_file('logo.png'))) {
+                $this->element('img', array('class' => 'logo photo',
+                                            'src' => (common_config('site', 'logo')) ? common_config('site', 'logo') : theme_path('logo.png'),
+                                            'alt' => common_config('site', 'name')));
+            }
+            $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
+            $this->elementEnd('a');
+            Event::handle('EndAddressData', array($this));
         }
-        $this->element('span', array('class' => 'fn org'), common_config('site', 'name'));
-        $this->elementEnd('a');
         $this->elementEnd('address');
     }
 
@@ -416,11 +424,13 @@ class Action extends HTMLOutputter // lawsuit
                     $this->menuItem(common_local_url('smssettings'),
                                     _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
                 }
-                $this->menuItem(common_local_url('invite'),
-                                _('Invite'),
-                                sprintf(_('Invite friends and colleagues to join you on %s'),
-                                        common_config('site', 'name')),
-                                false, 'nav_invitecontact');
+                if (common_config('invite', 'enabled')) {
+                    $this->menuItem(common_local_url('invite'),
+                                    _('Invite'),
+                                    sprintf(_('Invite friends and colleagues to join you on %s'),
+                                            common_config('site', 'name')),
+                                    false, 'nav_invitecontact');
+                }
                 $this->menuItem(common_local_url('logout'),
                                 _('Logout'), _('Logout from the site'), false, 'nav_logout');
             }
@@ -429,8 +439,6 @@ class Action extends HTMLOutputter // lawsuit
                     $this->menuItem(common_local_url('register'),
                                     _('Register'), _('Create an account'), false, 'nav_register');
                 }
-                $this->menuItem(common_local_url('openidlogin'),
-                                _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
                 $this->menuItem(common_local_url('login'),
                                 _('Login'), _('Login to the site'), false, 'nav_login');
             }
@@ -569,20 +577,32 @@ class Action extends HTMLOutputter // lawsuit
     /**
      * Show page notice block.
      *
+     * Only show the block if a subclassed action has overrided
+     * Action::showPageNotice(), or an event handler is registered for
+     * the StartShowPageNotice event, in which case we assume the
+     * 'page_notice' definition list is desired.  This is to prevent
+     * empty 'page_notice' definition lists from being output everywhere.
+     *
      * @return nothing
      */
     function showPageNoticeBlock()
     {
-        $this->elementStart('dl', array('id' => 'page_notice',
-                                        'class' => 'system_notice'));
-        $this->element('dt', null, _('Page notice'));
-        $this->elementStart('dd');
-        if (Event::handle('StartShowPageNotice', array($this))) {
-            $this->showPageNotice();
-            Event::handle('EndShowPageNotice', array($this));
+        $rmethod = new ReflectionMethod($this, 'showPageNotice');
+        $dclass = $rmethod->getDeclaringClass()->getName();
+
+        if ($dclass != 'Action' || Event::hasHandler('StartShowPageNotice')) {
+
+            $this->elementStart('dl', array('id' => 'page_notice',
+                                            'class' => 'system_notice'));
+            $this->element('dt', null, _('Page notice'));
+            $this->elementStart('dd');
+            if (Event::handle('StartShowPageNotice', array($this))) {
+                $this->showPageNotice();
+                Event::handle('EndShowPageNotice', array($this));
+            }
+            $this->elementEnd('dd');
+            $this->elementEnd('dl');
         }
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
     }
 
     /**
@@ -686,6 +706,11 @@ class Action extends HTMLOutputter // lawsuit
                             _('About'));
             $this->menuItem(common_local_url('doc', array('title' => 'faq')),
                             _('FAQ'));
+            $bb = common_config('site', 'broughtby');
+            if (!empty($bb)) {
+                $this->menuItem(common_local_url('doc', array('title' => 'tos')),
+                                _('TOS'));
+            }
             $this->menuItem(common_local_url('doc', array('title' => 'privacy')),
                             _('Privacy'));
             $this->menuItem(common_local_url('doc', array('title' => 'source')),
@@ -747,7 +772,9 @@ class Action extends HTMLOutputter // lawsuit
         $this->elementStart('p');
         $this->element('img', array('id' => 'license_cc',
                                     'src' => common_config('license', 'image'),
-                                    'alt' => common_config('license', 'title')));
+                                    'alt' => common_config('license', 'title'),
+                                    'width' => '80',
+                                    'height' => '15'));
         //TODO: This is dirty: i18n
         $this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
         $this->element('a', array('class' => 'license',
@@ -847,10 +874,12 @@ class Action extends HTMLOutputter // lawsuit
         }
         if ($lm) {
             header('Last-Modified: ' . date(DATE_RFC1123, $lm));
-            if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
-                $ims = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
+            if (array_key_exists('HTTP_IF_MODIFIED_SINCE', $_SERVER)) {
+                $if_modified_since = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+                $ims = strtotime($if_modified_since);
                 if ($lm <= $ims) {
-                    $if_none_match = $_SERVER['HTTP_IF_NONE_MATCH'];
+                    $if_none_match = (array_key_exists('HTTP_IF_NONE_MATCH', $_SERVER)) ?
+                      $_SERVER['HTTP_IF_NONE_MATCH'] : null;
                     if (!$if_none_match ||
                         !$etag ||
                         $this->_hasEtag($etag, $if_none_match)) {
@@ -944,12 +973,16 @@ class Action extends HTMLOutputter // lawsuit
         $action = $this->trimmed('action');
         $args   = $this->args;
         unset($args['action']);
+        if (common_config('site', 'fancy')) {
+            unset($args['p']);
+        }
         if (array_key_exists('submit', $args)) {
             unset($args['submit']);
         }
         foreach (array_keys($_COOKIE) as $cookie) {
             unset($args[$cookie]);
         }
+
         return common_local_url($action, $args);
     }
 
index ef0eeffa5ec774a372e94f70256452473987763f..a8a12b3bb35668ec1bc8e41872e8d5a7ab962b09 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
new file mode 100644 (file)
index 0000000..f6a1b59
--- /dev/null
@@ -0,0 +1,357 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * widget for displaying a list of notice attachments
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  UI
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2008 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * widget for displaying a list of notice attachments
+ *
+ * There are a number of actions that display a list of notices, in
+ * reverse chronological order. This widget abstracts out most of the
+ * code for UI for notice lists. It's overridden to hide some
+ * data for e.g. the profile page.
+ *
+ * @category UI
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ * @see      Notice
+ * @see      NoticeListItem
+ * @see      ProfileNoticeList
+ */
+
+class AttachmentList extends Widget
+{
+    /** the current stream of notices being displayed. */
+
+    var $notice = null;
+
+    /**
+     * constructor
+     *
+     * @param Notice $notice stream of notices from DB_DataObject
+     */
+
+    function __construct($notice, $out=null)
+    {
+        parent::__construct($out);
+        $this->notice = $notice;
+    }
+
+    /**
+     * show the list of notices
+     *
+     * "Uses up" the stream by looping through it. So, probably can't
+     * be called twice on the same list.
+     *
+     * @return int count of notices listed.
+     */
+
+    function show()
+    {
+        $atts = new File;
+        $att = $atts->getAttachments($this->notice->id);
+        if (empty($att)) return 0;
+        $this->out->elementStart('dl', array('id' =>'attachments',
+                                             'class' => 'entry-content'));
+        $this->out->element('dt', null, _('Attachments'));
+        $this->out->elementStart('dd');
+        $this->out->elementStart('ol', array('class' => 'attachments'));
+
+        foreach ($att as $n=>$attachment) {
+            $item = $this->newListItem($attachment);
+            $item->show();
+        }
+
+        $this->out->elementEnd('dd');
+        $this->out->elementEnd('ol');
+        $this->out->elementEnd('dl');
+
+        return count($att);
+    }
+
+    /**
+     * returns a new list item for the current notice
+     *
+     * Recipe (factory?) method; overridden by sub-classes to give
+     * a different list item class.
+     *
+     * @param Notice $notice the current notice
+     *
+     * @return NoticeListItem a list item for displaying the notice
+     */
+
+    function newListItem($attachment)
+    {
+        return new AttachmentListItem($attachment, $this->out);
+    }
+}
+
+/**
+ * widget for displaying a single notice
+ *
+ * This widget has the core smarts for showing a single notice: what to display,
+ * where, and under which circumstances. Its key method is show(); this is a recipe
+ * that calls all the other show*() methods to build up a single notice. The
+ * ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
+ * author info (since that's implicit by the data in the page).
+ *
+ * @category UI
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ * @see      NoticeList
+ * @see      ProfileNoticeListItem
+ */
+
+class AttachmentListItem extends Widget
+{
+    /** The attachment this item will show. */
+
+    var $attachment = null;
+
+    var $oembed = null;
+
+    /**
+     * constructor
+     *
+     * Also initializes the profile attribute.
+     *
+     * @param Notice $notice The notice we'll display
+     */
+
+    function __construct($attachment, $out=null)
+    {
+        parent::__construct($out);
+        $this->attachment  = $attachment;
+        $this->oembed = File_oembed::staticGet('file_id', $this->attachment->id);
+    }
+
+    function title() {
+        if (empty($this->attachment->title)) {
+            if (empty($this->oembed->title)) {
+                $title = $this->attachment->url;
+            } else {
+                $title = $this->oembed->title;
+            }
+        } else {
+            $title = $this->attachment->title;
+        }
+
+        return $title;
+    }
+
+    function linkTitle() {
+        return $this->title();
+    }
+
+    /**
+     * recipe function for displaying a single notice.
+     *
+     * This uses all the other methods to correctly display a notice. Override
+     * it or one of the others to fine-tune the output.
+     *
+     * @return void
+     */
+
+    function show()
+    {
+        $this->showStart();
+        $this->showNoticeAttachment();
+        $this->showEnd();
+    }
+
+    function linkAttr() {
+        return array('class' => 'attachment', 'href' => $this->attachment->url, 'id' => 'attachment-' . $this->attachment->id);
+    }
+
+    function showLink() {
+        $this->out->elementStart('a', $this->linkAttr());
+        $this->out->element('span', null, $this->linkTitle());
+        $this->showRepresentation();
+        $this->out->elementEnd('a');
+    }
+
+    function showNoticeAttachment()
+    {
+        $this->showLink();
+    }
+
+    function showRepresentation() {
+        $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id);
+        if (!empty($thumbnail)) {
+            $this->out->element('img', array('alt' => '', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height));
+        }
+    }
+
+    /**
+     * start a single notice.
+     *
+     * @return void
+     */
+
+    function showStart()
+    {
+        // XXX: RDFa
+        // TODO: add notice_type class e.g., notice_video, notice_image
+        $this->out->elementStart('li');
+    }
+
+    /**
+     * finish the notice
+     *
+     * Close the last elements in the notice list item
+     *
+     * @return void
+     */
+
+    function showEnd()
+    {
+        $this->out->elementEnd('li');
+    }
+}
+
+class Attachment extends AttachmentListItem
+{
+    function showLink() {
+        $this->out->elementStart('div', array('id' => 'attachment_view',
+                                              'class' => 'hentry'));
+        $this->out->elementStart('div', 'entry-title');
+        $this->out->elementStart('a', $this->linkAttr());
+        $this->out->element('span', null, $this->linkTitle());
+        $this->out->elementEnd('a');
+        $this->out->elementEnd('div');
+
+        $this->out->elementStart('div', 'entry-content');
+        $this->showRepresentation();
+        $this->out->elementEnd('div');
+
+        if (!empty($this->oembed->author_name) || !empty($this->oembed->provider)) {
+            $this->out->elementStart('div', array('id' => 'oembed_info', 
+                                                  'class' => 'entry-content'));
+            if (!empty($this->oembed->author_name)) {
+                $this->out->elementStart('dl', 'vcard author');
+                $this->out->element('dt', null, _('Author'));
+                $this->out->elementStart('dd', 'fn');
+                if (empty($this->oembed->author_url)) {
+                    $this->out->text($this->oembed->author_name);
+                } else {
+                    $this->out->element('a', array('href' => $this->oembed->author_url,
+                                                   'class' => 'url'), $this->oembed->author_name);
+                }
+                $this->out->elementEnd('dd');
+                $this->out->elementEnd('dl');
+            }
+            if (!empty($this->oembed->provider)) {
+                $this->out->elementStart('dl', 'vcard');
+                $this->out->element('dt', null, _('Provider'));
+                $this->out->elementStart('dd', 'fn');
+                if (empty($this->oembed->provider_url)) {
+                    $this->out->text($this->oembed->provider);
+                } else {
+                    $this->out->element('a', array('href' => $this->oembed->provider_url,
+                                                   'class' => 'url'), $this->oembed->provider);
+                }
+                $this->out->elementEnd('dd');
+                $this->out->elementEnd('dl');
+            }
+            $this->out->elementEnd('div');
+        }
+        $this->out->elementEnd('div');
+    }
+
+    function show() {
+        $this->showNoticeAttachment();
+    }
+
+    function linkAttr() {
+        return array('class' => 'external', 'href' => $this->attachment->url);
+    }
+
+    function linkTitle() {
+        return $this->attachment->url;
+    }
+
+    function showRepresentation() {
+        if (empty($this->oembed->type)) {
+            if (empty($this->attachment->mimetype)) {
+                $this->out->element('pre', null, 'oh well... not sure how to handle the following: ' . print_r($this->attachment, true));
+            } else {
+                switch ($this->attachment->mimetype) {
+                case 'image/gif':
+                case 'image/png':
+                case 'image/jpg':
+                case 'image/jpeg':
+                    $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
+                    break;
+
+                case 'application/ogg':
+                case 'audio/x-speex':
+                case 'video/mpeg':
+                case 'audio/mpeg':
+                case 'video/mp4':
+                case 'video/quicktime':
+                    $arr  = array('type' => $this->attachment->mimetype,
+                        'data' => $this->attachment->url,
+                        'width' => 320,
+                        'height' => 240
+                    );
+                    $this->out->elementStart('object', $arr);
+                    $this->out->element('param', array('name' => 'src', 'value' => $this->attachment->url));
+                    $this->out->element('param', array('name' => 'autoStart', 'value' => 1));
+                    $this->out->elementEnd('object');
+                    break;
+                }
+            }
+        } else {
+            switch ($this->oembed->type) {
+            case 'rich':
+            case 'video':
+            case 'link':
+                if (!empty($this->oembed->html)) {
+                    $this->out->raw($this->oembed->html);
+                }
+                break;
+
+            case 'photo':
+                $this->out->element('img', array('src' => $this->oembed->url, 'width' => $this->oembed->width, 'height' => $this->oembed->height, 'alt' => 'alt'));
+                break;
+
+            default:
+                $this->out->element('pre', null, 'oh well... not sure how to handle the following oembed: ' . print_r($this->oembed, true));
+            }
+        }
+    }
+}
+
diff --git a/lib/attachmentnoticesection.php b/lib/attachmentnoticesection.php
new file mode 100644 (file)
index 0000000..eb31763
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * FIXME
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Widget
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * FIXME
+ *
+ * These are the widgets that show interesting data about a person * group, or site.
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class AttachmentNoticeSection extends NoticeSection
+{
+    function showContent() {
+        parent::showContent();
+        return false;
+    }
+
+    function getNotices()
+    {
+        $notice = new Notice;
+        $f2p = new File_to_post;
+        $f2p->file_id = $this->out->attachment->id;
+        $notice->joinAdd($f2p);
+        $notice->orderBy('created desc');
+        $notice->selectAdd('post_id as id');
+        $notice->find();
+        return $notice; 
+    }
+
+    function title()
+    {
+        return _('Notices where this attachment appears');
+    }
+
+    function divId()
+    {
+        return 'popular_notices';
+    }
+}
+
diff --git a/lib/attachmenttagcloudsection.php b/lib/attachmenttagcloudsection.php
new file mode 100644 (file)
index 0000000..50bfcec
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Attachment tag cloud section
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Widget
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Attachment tag cloud section
+ *
+ * @category Widget
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class AttachmentTagCloudSection extends TagCloudSection
+{
+    function title()
+    {
+        return _('Tags for this attachment');
+    }
+
+    function showTag($tag, $weight, $relative)
+    {
+        if ($relative > 0.5) {
+            $rel =  'tag-cloud-7';
+        } else if ($relative > 0.4) {
+            $rel = 'tag-cloud-6';
+        } else if ($relative > 0.3) {
+            $rel = 'tag-cloud-5';
+        } else if ($relative > 0.2) {
+            $rel = 'tag-cloud-4';
+        } else if ($relative > 0.1) {
+            $rel = 'tag-cloud-3';
+        } else if ($relative > 0.05) {
+            $rel = 'tag-cloud-2';
+        } else {
+            $rel = 'tag-cloud-1';
+        }
+
+        $this->out->elementStart('li', $rel);
+        $this->out->element('a', array('href' => $this->tagUrl($tag)),
+                       $tag);
+        $this->out->elementEnd('li');
+    }
+
+    function getTags()
+    {
+        $notice_tag = new Notice_tag;
+        $query = 'select tag,count(tag) as weight from notice_tag join file_to_post on (notice_tag.notice_id=post_id) join notice on notice_id = notice.id where file_id=' . $notice_tag->escape($this->out->attachment->id) . ' group by tag order by weight desc';
+        $notice_tag->query($query);
+        return $notice_tag;
+    }
+}
+
index f1e2055466ae2f7f83fe66b39b8ca9b94f2caaac..38c1d4d67f56f37025f8c5bc4115dda5cd3614b9 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 0c48414d55f066c774c2e41a4aebc6b3ec98b0d4..7ddc35eb660459d407701581323d9dc8ff776f85 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 507990a0b3ac6a6ba6d5315a9c3c605f0050b957..4e2280bc8070d58bc5edefc5edd09230f5a7c95b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -97,18 +97,11 @@ class StatsCommand extends Command
 {
     function execute($channel)
     {
+        $profile = $this->user->getProfile();
 
-        $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();
+        $subs_count   = $profile->subscriptionCount();
+        $subbed_count = $profile->subscriberCount();
+        $notice_count = $profile->noticeCount();
 
         $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
                                    "Subscribers: %2\$s\n".
index 49c733c0339c0f2c5f29f85af847f803642dcbd8..6538d44420e9135f78d432277ac5a65c7b6c3928 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 151b31d80a53fb34e3eda35a1e250221f5c7d79a..c47702779d9fa4f8827213ccfd053345c064def2 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -19,7 +19,7 @@
 
 if (!defined('LACONICA')) { exit(1); }
 
-define('LACONICA_VERSION', '0.7.4');
+define('LACONICA_VERSION', '0.8.0');
 
 define('AVATAR_PROFILE_SIZE', 96);
 define('AVATAR_STREAM_SIZE', 48);
@@ -55,14 +55,37 @@ require_once(INSTALLDIR.'/lib/language.php');
 require_once(INSTALLDIR.'/lib/event.php');
 require_once(INSTALLDIR.'/lib/plugin.php');
 
-// try to figure out where we are
+function _sn_to_path($sn)
+{
+    $past_root = substr($sn, 1);
+    $last_slash = strrpos($past_root, '/');
+    if ($last_slash > 0) {
+        $p = substr($past_root, 0, $last_slash);
+    } else {
+        $p = '';
+    }
+    return $p;
+}
+
+// try to figure out where we are. $server and $path
+// can be set by including module, else we guess based
+// on HTTP info.
+
+if (isset($server)) {
+    $_server = $server;
+} else {
+    $_server = array_key_exists('SERVER_NAME', $_SERVER) ?
+      strtolower($_SERVER['SERVER_NAME']) :
+    null;
+}
 
-$_server = array_key_exists('SERVER_NAME', $_SERVER) ?
-  strtolower($_SERVER['SERVER_NAME']) :
-  null;
-$_path = array_key_exists('SCRIPT_NAME', $_SERVER) ?
-  substr($_SERVER['SCRIPT_NAME'], 1, strrpos($_SERVER['SCRIPT_NAME'], '/') - 1) :
-  null;
+if (isset($path)) {
+    $_path = $path;
+} else {
+    $_path = array_key_exists('SCRIPT_NAME', $_SERVER) ?
+      _sn_to_path($_SERVER['SCRIPT_NAME']) :
+    null;
+}
 
 // default configuration, overwritten in config.php
 
@@ -71,6 +94,14 @@ $config =
         array('name' => 'Just another Laconica microblog',
               'server' => $_server,
               'theme' => 'default',
+              'design' =>
+              array('backgroundcolor' => '#CEE1E9',
+                    'contentcolor' => '#FFFFFF',
+                    'sidebarcolor' => '#C8D1D5',
+                    'textcolor' => '#000000',
+                    'linkcolor' => '#002E6E',
+                    'backgroundimage' => null,
+                    'disposition' => 1),
               'path' => $_path,
               'logfile' => null,
               'logo' => null,
@@ -93,9 +124,16 @@ $config =
               'dupelimit' => 60), # default for same person saying the same thing
         'syslog' =>
         array('appname' => 'laconica', # for syslog
-              'priority' => 'debug'), # XXX: currently ignored
+              'priority' => 'debug', # XXX: currently ignored
+              'facility' => LOG_USER),
         'queue' =>
-        array('enabled' => false),
+        array('enabled' => false,
+              'subsystem' => 'db', # default to database, or 'stomp'
+              'stomp_server' => null,
+              'queue_basename' => 'laconica',
+              'stomp_username' => null,
+              'stomp_password' => null,
+              ),
         'license' =>
         array('url' => 'http://creativecommons.org/licenses/by/3.0/',
               'title' => 'Creative Commons Attribution 3.0',
@@ -109,13 +147,21 @@ $config =
         'profile' =>
         array('banned' => array()),
         'avatar' =>
-        array('server' => null),
+        array('server' => null,
+              'dir' => INSTALLDIR . '/avatar/',
+              'path' => $_path . '/avatar/'),
+        'background' =>
+        array('server' => null,
+              'dir' => INSTALLDIR . '/background/',
+              'path' => $_path . '/background/'),
         'public' =>
         array('localonly' => true,
               'blacklist' => array(),
               'autosource' => array()),
         'theme' =>
-        array('server' => null),
+        array('server' => null,
+              'dir' => null,
+              'path'=> null),
         'throttle' =>
         array('enabled' => false, // whether to throttle edits; false by default
               'count' => 20, // number of allowed messages in timespan
@@ -131,6 +177,8 @@ $config =
               'host' => null, # only set if != server
               'debug' => false, # print extra debug info
               'public' => array()), # JIDs of users who want to receive the public stream
+        'invite' =>
+        array('enabled' => true),
         'sphinx' =>
         array('enabled' => false,
               'server' => 'localhost',
@@ -143,20 +191,76 @@ $config =
         array('piddir' => '/var/run',
               'user' => false,
               'group' => false),
+        'twitterbridge' =>
+        array('enabled' => false),
         'integration' =>
         array('source' => 'Laconica', # source attribute for Twitter
               'taguri' => $_server.',2009'), # base for tag URIs
         'memcached' =>
         array('enabled' => false,
               'server' => 'localhost',
+              'base' => null,
               'port' => 11211),
                'ping' =>
         array('notify' => array()),
         'inboxes' =>
         array('enabled' => true), # on by default for new sites
         'newuser' =>
-        array('subscribe' => null,
+        array('default' => null,
               'welcome' => null),
+        'snapshot' =>
+        array('run' => 'web',
+              'frequency' => 10000,
+              'reporturl' => 'http://laconi.ca/stats/report'),
+        'attachments' =>
+        array('server' => null,
+              'dir' => INSTALLDIR . '/file/',
+              'path' => $_path . '/file/',
+              'supported' => array('image/png',
+                                   'image/jpeg',
+                                   'image/gif',
+                                   'image/svg+xml',
+                                   'audio/mpeg',
+                                   'audio/x-speex',
+                                   'application/ogg',
+                                   'application/pdf',
+                                   'application/vnd.oasis.opendocument.text',
+                                   'application/vnd.oasis.opendocument.text-template',
+                                   'application/vnd.oasis.opendocument.graphics',
+                                   'application/vnd.oasis.opendocument.graphics-template',
+                                   'application/vnd.oasis.opendocument.presentation',
+                                   'application/vnd.oasis.opendocument.presentation-template',
+                                   'application/vnd.oasis.opendocument.spreadsheet',
+                                   'application/vnd.oasis.opendocument.spreadsheet-template',
+                                   'application/vnd.oasis.opendocument.chart',
+                                   'application/vnd.oasis.opendocument.chart-template',
+                                   'application/vnd.oasis.opendocument.image',
+                                   'application/vnd.oasis.opendocument.image-template',
+                                   'application/vnd.oasis.opendocument.formula',
+                                   'application/vnd.oasis.opendocument.formula-template',
+                                   'application/vnd.oasis.opendocument.text-master',
+                                   'application/vnd.oasis.opendocument.text-web',
+                                   'application/x-zip',
+                                   'application/zip',
+                                   'text/plain',
+                                   'video/mpeg',
+                                   'video/mp4',
+                                   'video/quicktime',
+                                   'video/mpeg'),
+        'file_quota' => 5000000,
+        'user_quota' => 50000000,
+        'monthly_quota' => 15000000,
+        'uploads' => true,
+        'filecommand' => '/usr/bin/file',
+        ),
+        'group' =>
+        array('maxaliases' => 3),
+        'oohembed' => array('endpoint' => 'http://oohembed.com/oohembed/'),
+        'search' =>
+        array('type' => 'fulltext'),
+        'sessions' =>
+        array('handle' => false, // whether to handle sessions ourselves
+              'debug' => false), // debugging output for sessions
         );
 
 $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@@ -178,18 +282,55 @@ if (function_exists('date_default_timezone_set')) {
     date_default_timezone_set('UTC');
 }
 
+function addPlugin($name, $attrs = null)
+{
+    $name = ucfirst($name);
+    $pluginclass = "{$name}Plugin";
+
+    if (!class_exists($pluginclass)) {
+
+        $files = array("local/plugins/{$pluginclass}.php",
+                       "local/plugins/{$name}/{$pluginclass}.php",
+                       "local/{$pluginclass}.php",
+                       "local/{$name}/{$pluginclass}.php",
+                       "plugins/{$pluginclass}.php",
+                       "plugins/{$name}/{$pluginclass}.php");
+
+        foreach ($files as $file) {
+            $fullpath = INSTALLDIR.'/'.$file;
+            if (@file_exists($fullpath)) {
+                include_once($fullpath);
+                break;
+            }
+        }
+    }
+
+    $inst = new $pluginclass();
+
+    if (!empty($attrs)) {
+        foreach ($attrs as $aname => $avalue) {
+            $inst->$aname = $avalue;
+        }
+    }
+    return $inst;
+}
+
 // From most general to most specific:
 // server-wide, then vhost-wide, then for a path,
 // finally for a dir (usually only need one of the last two).
 
-$_config_files = array('/etc/laconica/laconica.php',
-                  '/etc/laconica/'.$_server.'.php');
+if (isset($conffile)) {
+    $_config_files = array($conffile);
+} else {
+    $_config_files = array('/etc/laconica/laconica.php',
+                           '/etc/laconica/'.$_server.'.php');
 
-if (strlen($_path) > 0) {
-    $_config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php';
-}
+    if (strlen($_path) > 0) {
+        $_config_files[] = '/etc/laconica/'.$_server.'_'.$_path.'.php';
+    }
 
-$_config_files[] = INSTALLDIR.'/config.php';
+    $_config_files[] = INSTALLDIR.'/config.php';
+}
 
 $_have_a_config = false;
 
@@ -218,19 +359,19 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
 
 // XXX: how many of these could be auto-loaded on use?
 
-require_once('Validate.php');
-require_once('markdown.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');
+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';
 
-require_once(INSTALLDIR.'/lib/clientexception.php');
-require_once(INSTALLDIR.'/lib/serverexception.php');
+require_once INSTALLDIR.'/lib/clientexception.php';
+require_once INSTALLDIR.'/lib/serverexception.php';
 
 // XXX: other formats here
 
diff --git a/lib/currentuserdesignaction.php b/lib/currentuserdesignaction.php
new file mode 100644 (file)
index 0000000..4c7e15a
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for actions that use the current user's design
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Action
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Base class for actions that use the current user's design
+ *
+ * Some pages (settings in particular) use the current user's chosen
+ * design. This superclass returns that design.
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ */
+
+class CurrentUserDesignAction extends Action
+{
+
+    /**
+      * Show the user's design stylesheet
+      *
+      * @return nothing
+      */
+
+     function showStylesheets()
+     {
+         parent::showStylesheets();
+
+         $user = common_current_user();
+
+         if (empty($user) || $user->viewdesigns) {
+             $design = $this->getDesign();
+
+             if (!empty($design)) {
+                 $design->showCSS($this);
+             }
+         }
+     }
+
+    /**
+     * A design for this action
+     *
+     * if the user attribute has been set, returns that user's
+     * design.
+     *
+     * @return Design a design object to use
+     */
+
+    function getDesign()
+    {
+        $cur = common_current_user();
+
+        if (empty($cur)) {
+            return null;
+        }
+
+        return $cur->getDesign();
+    }
+
+}
index 9c1ae50a02c56e5be81534f905d352fd287e6944..9d89c63e781eb2e25353c5501850510cb87cc612 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -23,6 +23,13 @@ if (!defined('LACONICA')) {
 
 class Daemon
 {
+    var $daemonize = true;
+
+    function __construct($daemonize = true)
+    {
+        $this->daemonize = $daemonize;
+    }
+
     function name()
     {
         return null;
@@ -129,12 +136,16 @@ class Daemon
             common_log(LOG_INFO, $this->name() . ' already running. Exiting.');
             exit(0);
         }
-        if ($this->background()) {
-            $this->writePidFile();
-            $this->changeUser();
-            $this->run();
-            $this->clearPidFile();
+
+        if ($this->daemonize) {
+            common_log(LOG_INFO, 'Backgrounding daemon "'.$this->name().'"');
+            $this->background();
         }
+
+        $this->writePidFile();
+        $this->changeUser();
+        $this->run();
+        $this->clearPidFile();
     }
 
     function run()
index 0dc92490cdc70d8265e001eb81530b60856fb173..a04e5f74f7a23e69873102dd09cce63e667ea7e9 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php
new file mode 100644 (file)
index 0000000..6e7172d
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Simple-minded queue manager for storing items in the database
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QueueManager
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+class DBQueueManager extends QueueManager
+{
+    var $qis = array();
+
+    function enqueue($object, $queue)
+    {
+        $notice = $object;
+
+        $qi = new Queue_item();
+
+        $qi->notice_id = $notice->id;
+        $qi->transport = $queue;
+        $qi->created   = $notice->created;
+        $result        = $qi->insert();
+
+        if (!$result) {
+            common_log_db_error($qi, 'INSERT', __FILE__);
+            throw new ServerException('DB error inserting queue item');
+        }
+
+        return true;
+    }
+
+    function service($queue, $handler)
+    {
+        while (true) {
+            $this->_log(LOG_DEBUG, 'Checking for notices...');
+            $notice = $this->_nextItem($queue, null);
+            if (empty($notice)) {
+                $this->_log(LOG_DEBUG, 'No notices waiting; idling.');
+                // Nothing in the queue. Do you
+                // have other tasks, like servicing your
+                // XMPP connection, to do?
+                $handler->idle(QUEUE_HANDLER_MISS_IDLE);
+            } else {
+                $this->_log(LOG_INFO, 'Got notice '. $notice->id);
+                // Yay! Got one!
+                if ($handler->handle_notice($notice)) {
+                    $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id);
+                    $this->_done($notice, $queue);
+                } else {
+                    $this->_log(LOG_INFO, 'Failed to handle notice '. $notice->id);
+                    $this->_fail($notice, $queue);
+                }
+                // Chance to e.g. service your XMPP connection
+                $this->_log(LOG_DEBUG, 'Idling after success.');
+                $handler->idle(QUEUE_HANDLER_HIT_IDLE);
+            }
+            // XXX: when do we give up?
+        }
+    }
+
+    function _nextItem($queue, $timeout=null)
+    {
+        $start = time();
+        $result = null;
+
+        do {
+            $qi = Queue_item::top($queue);
+            if (!empty($qi)) {
+                $notice = Notice::staticGet('id', $qi->notice_id);
+                if (!empty($notice)) {
+                    $result = $notice;
+                } else {
+                    $this->_log(LOG_INFO, 'dequeued non-existent notice ' . $notice->id);
+                    $qi->delete();
+                    $qi->free();
+                    $qi = null;
+                }
+            }
+        } while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout));
+
+        return $result;
+    }
+
+    function _done($object, $queue)
+    {
+        // XXX: right now, we only handle notices
+
+        $notice = $object;
+
+        $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id,
+                                        'transport' => $queue));
+
+        if (empty($qi)) {
+            $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
+        } else {
+            if (empty($qi->claimed)) {
+                $this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '.
+                           'for '.$notice->id.', queue '.$queue);
+            }
+            $qi->delete();
+            $qi->free();
+            $qi = null;
+        }
+
+        $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
+
+        $notice->free();
+        $notice = null;
+    }
+
+    function _fail($object, $queue)
+    {
+        // XXX: right now, we only handle notices
+
+        $notice = $object;
+
+        $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id,
+                                        'transport' => $queue));
+
+        if (empty($qi)) {
+            $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
+        } else {
+            if (empty($qi->claimed)) {
+                $this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '.
+                           'for '.$notice->id.', queue '.$queue);
+            } else {
+                $orig = clone($qi);
+                $qi->claimed = null;
+                $qi->update($orig);
+                $qi = null;
+            }
+        }
+
+        $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
+
+        $notice->free();
+        $notice = null;
+    }
+
+    function _log($level, $msg)
+    {
+        common_log($level, 'DBQueueManager: '.$msg);
+    }
+}
diff --git a/lib/designsettings.php b/lib/designsettings.php
new file mode 100644 (file)
index 0000000..fbffdb2
--- /dev/null
@@ -0,0 +1,477 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Change user password
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Settings
+ * @package   Laconica
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/accountsettingsaction.php';
+require_once INSTALLDIR . '/lib/webcolor.php';
+
+/**
+ * Base class for setting a user or group design
+ *
+ * Shows the design setting form and also handles some things like saving
+ * background images, and fetching a default design
+ *
+ * @category Settings
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @author   Sarven Capadisli <csarven@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class DesignSettingsAction extends AccountSettingsAction
+{
+
+    var $submitaction = null;
+
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Profile design');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Customize the way your profile looks ' .
+        'with a background image and a colour palette of your choice.');
+    }
+
+    /**
+     * Shows the design settings form
+     *
+     * @param Design $design a working design to show
+     *
+     * @return nothing
+     */
+
+    function showDesignForm($design)
+    {
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'enctype' => 'multipart/form-data',
+                                          'id' => 'form_settings_design',
+                                          'class' => 'form_settings',
+                                          'action' => $this->submitaction));
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+
+        $this->elementStart('fieldset', array('id' =>
+            'settings_design_background-image'));
+        $this->element('legend', null, _('Change background image'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->element('label', array('for' => 'design_background-image_file'),
+                                _('Upload file'));
+        $this->element('input', array('name' => 'design_background-image_file',
+                                      'type' => 'file',
+                                      'id' => 'design_background-image_file'));
+        $this->element('p', 'form_guide', _('You can upload your personal ' .
+            'background image. The maximum file size is 2Mb.'));
+        $this->element('input', array('name' => 'MAX_FILE_SIZE',
+                                      'type' => 'hidden',
+                                      'id' => 'MAX_FILE_SIZE',
+                                      'value' => ImageFile::maxFileSizeInt()));
+        $this->elementEnd('li');
+
+        if (!empty($design->backgroundimage)) {
+
+            $this->elementStart('li', array('id' =>
+                'design_background-image_onoff'));
+
+            $this->element('img', array('src' =>
+                Design::url($design->backgroundimage)));
+
+            $attrs = array('name' => 'design_background-image_onoff',
+                           'type' => 'radio',
+                           'id' => 'design_background-image_on',
+                           'class' => 'radio',
+                           'value' => 'on');
+
+            if ($design->disposition & BACKGROUND_ON) {
+                $attrs['checked'] = 'checked';
+            }
+
+            $this->element('input', $attrs);
+
+            $this->element('label', array('for' => 'design_background-image_on',
+                                          'class' => 'radio'),
+                                          _('On'));
+
+            $attrs = array('name' => 'design_background-image_onoff',
+                           'type' => 'radio',
+                           'id' => 'design_background-image_off',
+                           'class' => 'radio',
+                           'value' => 'off');
+
+            if ($design->disposition & BACKGROUND_OFF) {
+                $attrs['checked'] = 'checked';
+            }
+
+            $this->element('input', $attrs);
+
+            $this->element('label', array('for' => 'design_background-image_off',
+                                          'class' => 'radio'),
+                                          _('Off'));
+            $this->element('p', 'form_guide', _('Turn background image on or off.'));
+            $this->elementEnd('li');
+
+            $this->elementStart('li');
+            $this->checkbox('design_background-image_repeat',
+                            _('Tile background image'),
+                            ($design->disposition & BACKGROUND_TILE) ? true : false);
+            $this->elementEnd('li');
+        }
+
+        $this->elementEnd('ul');
+        $this->elementEnd('fieldset');
+
+        $this->elementStart('fieldset', array('id' => 'settings_design_color'));
+        $this->element('legend', null, _('Change colours'));
+        $this->elementStart('ul', 'form_data');
+
+        try {
+
+            $bgcolor = new WebColor($design->backgroundcolor);
+
+            $this->elementStart('li');
+            $this->element('label', array('for' => 'swatch-1'), _('Background'));
+            $this->element('input', array('name' => 'design_background',
+                                          'type' => 'text',
+                                          'id' => 'swatch-1',
+                                          'class' => 'swatch',
+                                          'maxlength' => '7',
+                                          'size' => '7',
+                                          'value' => '#' . $bgcolor->hexValue()));
+            $this->elementEnd('li');
+
+            $ccolor = new WebColor($design->contentcolor);
+
+            $this->elementStart('li');
+            $this->element('label', array('for' => 'swatch-2'), _('Content'));
+            $this->element('input', array('name' => 'design_content',
+                                          'type' => 'text',
+                                          'id' => 'swatch-2',
+                                          'class' => 'swatch',
+                                          'maxlength' => '7',
+                                          'size' => '7',
+                                          'value' => '#' . $ccolor->hexValue()));
+            $this->elementEnd('li');
+
+            $sbcolor = new WebColor($design->sidebarcolor);
+
+            $this->elementStart('li');
+            $this->element('label', array('for' => 'swatch-3'), _('Sidebar'));
+            $this->element('input', array('name' => 'design_sidebar',
+                                        'type' => 'text',
+                                        'id' => 'swatch-3',
+                                        'class' => 'swatch',
+                                        'maxlength' => '7',
+                                        'size' => '7',
+                                        'value' => '#' . $sbcolor->hexValue()));
+            $this->elementEnd('li');
+
+            $tcolor = new WebColor($design->textcolor);
+
+            $this->elementStart('li');
+            $this->element('label', array('for' => 'swatch-4'), _('Text'));
+            $this->element('input', array('name' => 'design_text',
+                                        'type' => 'text',
+                                        'id' => 'swatch-4',
+                                        'class' => 'swatch',
+                                        'maxlength' => '7',
+                                        'size' => '7',
+                                        'value' => '#' . $tcolor->hexValue()));
+            $this->elementEnd('li');
+
+            $lcolor = new WebColor($design->linkcolor);
+
+            $this->elementStart('li');
+            $this->element('label', array('for' => 'swatch-5'), _('Links'));
+            $this->element('input', array('name' => 'design_links',
+                                         'type' => 'text',
+                                         'id' => 'swatch-5',
+                                         'class' => 'swatch',
+                                         'maxlength' => '7',
+                                         'size' => '7',
+                                         'value' => '#' . $lcolor->hexValue()));
+            $this->elementEnd('li');
+
+        } catch (WebColorException $e) {
+            common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id);
+        }
+
+        $this->elementEnd('ul');
+        $this->elementEnd('fieldset');
+
+        $this->submit('defaults', _('Use defaults'), 'submit form_action-default',
+            'defaults', _('Restore default designs'));
+
+        $this->element('input', array('id' => 'settings_design_reset',
+                                     'type' => 'reset',
+                                     'value' => 'Reset',
+                                     'class' => 'submit form_action-primary',
+                                     'title' => _('Reset back to default')));
+
+        $this->submit('save', _('Save'), 'submit form_action-secondary',
+            'save', _('Save design'));
+
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Handle a post
+     *
+     * Validate input and save changes. Reload the form with a success
+     * or error message.
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // XXX: Robin's workaround for a bug in PHP where $_POST
+        // and $_FILE are empty in the case that the uploaded
+        // file is bigger than PHP is configured to handle.
+
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
+
+                $msg = _('The server was unable to handle that much POST ' .
+                    'data (%s bytes) due to its current configuration.');
+
+                $this->showForm(sprintf($msg, $_SERVER['CONTENT_LENGTH']));
+            }
+        }
+
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('save')) {
+            $this->saveDesign();
+        } else if ($this->arg('defaults')) {
+            $this->restoreDefaults();
+        } else {
+            $this->showForm(_('Unexpected form submission.'));
+        }
+    }
+
+    /**
+     * Add the Farbtastic stylesheet
+     *
+     * @return void
+     */
+
+    function showStylesheets()
+    {
+        parent::showStylesheets();
+        $farbtasticStyle =
+          common_path('theme/base/css/farbtastic.css?version='.LACONICA_VERSION);
+
+        $this->element('link', array('rel' => 'stylesheet',
+                                     'type' => 'text/css',
+                                     'href' => $farbtasticStyle,
+                                     'media' => 'screen, projection, tv'));
+    }
+
+    /**
+     * Add the Farbtastic scripts
+     *
+     * @return void
+     */
+
+    function showScripts()
+    {
+        parent::showScripts();
+
+        $farbtasticPack = common_path('js/farbtastic/farbtastic.js');
+        $userDesignGo   = common_path('js/userdesign.go.js');
+
+        $this->element('script', array('type' => 'text/javascript',
+                                       'src' => $farbtasticPack));
+        $this->element('script', array('type' => 'text/javascript',
+                                       'src' => $userDesignGo));
+    }
+
+    /**
+     * Get a default design
+     *
+     * @return Design design
+     */
+
+    function defaultDesign()
+    {
+        $defaults = common_config('site', 'design');
+
+        $design = new Design();
+
+        try {
+
+            $color = new WebColor();
+
+            $color->parseColor($defaults['backgroundcolor']);
+            $design->backgroundcolor = $color->intValue();
+
+            $color->parseColor($defaults['contentcolor']);
+            $design->contentcolor = $color->intValue();
+
+            $color->parseColor($defaults['sidebarcolor']);
+            $design->sidebarcolor = $color->intValue();
+
+            $color->parseColor($defaults['textcolor']);
+            $design->textcolor = $color->intValue();
+
+            $color->parseColor($defaults['linkcolor']);
+            $design->linkcolor = $color->intValue();
+
+            $design->backgroundimage = $defaults['backgroundimage'];
+
+            $design->disposition = $defaults['disposition'];
+
+        } catch (WebColorException $e) {
+            common_log(LOG_ERR, _('Bad default color settings: ' .
+                $e->getMessage()));
+        }
+
+        return $design;
+    }
+
+    /**
+     * Save the background image, if any, and set its disposition
+     *
+     * @param Design $design a working design to attach the img to
+     *
+     * @return nothing
+     */
+
+    function saveBackgroundImage($design)
+    {
+
+        // Now that we have a Design ID we can add a file to the design.
+        // XXX: This is an additional DB hit, but figured having the image
+        // associated with the Design rather than the User was worth
+        // it. -- Zach
+
+        if ($_FILES['design_background-image_file']['error'] ==
+            UPLOAD_ERR_OK) {
+
+            $filepath = null;
+
+            try {
+                $imagefile =
+                    ImageFile::fromUpload('design_background-image_file');
+            } catch (Exception $e) {
+                $this->showForm($e->getMessage());
+                return;
+            }
+
+            $filename = Design::filename($design->id,
+                image_type_to_extension($imagefile->type),
+                    common_timestamp());
+
+            $filepath = Design::path($filename);
+
+            move_uploaded_file($imagefile->filepath, $filepath);
+
+            // delete any old backround img laying around
+
+            if (isset($design->backgroundimage)) {
+                @unlink(Design::path($design->backgroundimage));
+            }
+
+            $original = clone($design);
+
+            $design->backgroundimage = $filename;
+
+            // default to on, no tile
+
+            $design->setDisposition(true, false, false);
+
+            $result = $design->update($original);
+
+            if ($result === false) {
+                common_log_db_error($design, 'UPDATE', __FILE__);
+                $this->showForm(_('Couldn\'t update your design.'));
+                return;
+            }
+        }
+    }
+
+    /**
+     * Restore the user or group design to system defaults
+     *
+     * @return nothing
+     */
+
+    function restoreDefaults()
+    {
+        $design   = $this->getWorkingDesign();
+        $default  = $this->defaultDesign();
+        $original = clone($design);
+
+        $design->backgroundcolor = $default->backgroundcolor;
+        $design->contentcolor    = $default->contentcolor;
+        $design->sidebarcolor    = $default->sidebarcolor;
+        $design->textcolor       = $default->textcolor;
+        $design->linkcolor       = $default->linkcolor;
+
+        $design->setDisposition(false, true, false);
+
+        $result = $design->update($original);
+
+        if ($result === false) {
+            common_log_db_error($design, 'UPDATE', __FILE__);
+            $this->showForm(_('Couldn\'t update your design.'));
+            return;
+        }
+
+        $this->showForm(_('Design defaults restored.'), true);
+    }
+
+}
index 282682133a2c64abd366ef1102fd2869c74b4648..bbf9987cff4632281c888ec0fd08c9815ad50a2a 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index d815ae54bac4a137a2f5b534cc06e8cccba066b3..4ccee17e666bc770d45b7ebac03f3b77dc918a2d 100644 (file)
@@ -110,4 +110,32 @@ class Event {
         }
         return ($result !== false);
     }
+
+    /**
+     * Check to see if an event handler exists
+     *
+     * Look to see if there's any handler for a given event, or narrow
+     * by providing the name of a specific plugin class.
+     *
+     * @param string $name Name of the event to look for
+     * @param string $plugin Optional name of the plugin class to look for
+     *
+     * @return boolean flag saying whether such a handler exists
+     *
+     */
+
+    public static function hasHandler($name, $plugin=null) {
+        if (array_key_exists($name, Event::$_handlers)) {
+            if (isset($plugin)) {
+                foreach (Event::$_handlers[$name] as $handler) {
+                    if (get_class($handler[0]) == $plugin) {
+                        return true;
+                    }
+                }
+            } else {
+                return true;
+            }
+        }
+        return false;
+    }
 }
index 043a078cd5bbd2fa8b0849fa7dc3c5238927cc3f..1ae90d53bdeb534afd8a65a1f5f51c5b90a2b480 100644 (file)
@@ -38,14 +38,14 @@ require_once INSTALLDIR.'/lib/noticeform.php';
 
 class FacebookAction extends Action
 {
-    
+
     var $facebook = null;
     var $fbuid    = null;
     var $flink    = null;
     var $action   = null;
     var $app_uri  = null;
     var $app_name = null;
-  
+
     /**
      * Constructor
      *
@@ -60,71 +60,71 @@ class FacebookAction extends Action
     function __construct($output='php://output', $indent=true, $facebook=null, $flink=null)
     {
         parent::__construct($output, $indent);
-        
+
         $this->facebook = $facebook;
         $this->flink = $flink;
-        
+
         if ($this->flink) {
-            $this->fbuid = $flink->foreign_id; 
+            $this->fbuid = $flink->foreign_id;
             $this->user = $flink->getUser();
         }
-        
+
         $this->args = array();
     }
-  
+
     function prepare($argarray)
-    {        
+    {
         parent::prepare($argarray);
-          
+
         $this->facebook = getFacebook();
         $this->fbuid = $this->facebook->require_login();
-        
+
         $this->action = $this->trimmed('action');
-        
+
         $app_props = $this->facebook->api_client->Admin_getAppProperties(
                 array('canvas_name', 'application_name'));
-        
+
         $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name'];
         $this->app_name = $app_props['application_name'];
 
         $this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
-        
+
         return true;
-        
+
     }
-  
+
     function showStylesheets()
     {
         // Add a timestamp to the file so Facebook cache wont ignore our changes
         $ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
-        
-        $this->element('link', array('rel' => 'stylesheet',
-                                     'type' => 'text/css',
-                                     'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
-                                     
+
+    $this->element('link', array('rel' => 'stylesheet',
+               'type' => 'text/css',
+               'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
+
         $theme = common_config('site', 'theme');
-        
+
         $ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css');
-                                     
+
         $this->element('link', array('rel' => 'stylesheet',
                                      'type' => 'text/css',
                                      'href' => theme_path('css/display.css', null) . '?ts=' . $ts));
-                                     
+
         $ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css');
-        
+
         $this->element('link', array('rel' => 'stylesheet',
                                      'type' => 'text/css',
                                      'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts));
     }
-  
+
     function showScripts()
     {
         // Add a timestamp to the file so Facebook cache wont ignore our changes
         $ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
-        
+
         $this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts));
     }
-    
+
     /**
      * Start an Facebook ready HTML document
      *
@@ -138,11 +138,11 @@ class FacebookAction extends Action
      * @return void
      */
 
-    function startHTML($type=null) 
-    {          
+    function startHTML($type=null)
+    {
         $this->showStylesheets();
         $this->showScripts();
-        
+
         $this->elementStart('div', array('class' => 'facebook-page'));
     }
 
@@ -177,18 +177,18 @@ class FacebookAction extends Action
         $this->showFooter();
         $this->elementEnd('div');
     }
-      
+
     function showAside()
     {
     }
 
     function showHead($error, $success)
     {
-        
+
         if ($error) {
             $this->element("h1", null, $error);
         }
-        
+
         if ($success) {
             $this->element("h1", null, $success);
         }
@@ -198,10 +198,10 @@ class FacebookAction extends Action
         $this->element('fb:add-section-button', array('section' => 'profile'));
         $this->elementEnd('span');
         $this->elementEnd('fb:if-section-not-added');
-        
+
     }
 
-    
+
     // Make this into a widget later
     function showLocalNav()
     {
@@ -213,12 +213,14 @@ class FacebookAction extends Action
             array('href' => 'index.php', 'title' => _('Home')), _('Home'));
         $this->elementEnd('li');
 
-        $this->elementStart('li',
-            array('class' =>
-                ($this->action == 'facebookinvite') ? 'current' : 'facebook_invite'));
-        $this->element('a',
-            array('href' => 'invite.php', 'title' => _('Invite')), _('Invite'));
-        $this->elementEnd('li');
+        if (common_config('invite', 'enabled')) {
+            $this->elementStart('li',
+                array('class' =>
+                    ($this->action == 'facebookinvite') ? 'current' : 'facebook_invite'));
+            $this->element('a',
+                array('href' => 'invite.php', 'title' => _('Invite')), _('Invite'));
+            $this->elementEnd('li');
+        }
 
         $this->elementStart('li',
             array('class' =>
@@ -229,8 +231,8 @@ class FacebookAction extends Action
         $this->elementEnd('li');
 
         $this->elementEnd('ul');
-    }     
-    
+    }
+
     /**
      * Show header of the page.
      *
@@ -245,7 +247,7 @@ class FacebookAction extends Action
         $this->showNoticeForm();
         $this->elementEnd('div');
     }
-    
+
     /**
      * Show page, a template method.
      *
@@ -258,7 +260,7 @@ class FacebookAction extends Action
         $this->showBody();
         $this->endHTML();
     }
-    
+
 
     function showInstructions()
     {
@@ -278,7 +280,7 @@ class FacebookAction extends Action
         $this->element('a',
             array('href' => common_local_url('register')), _('Register'));
         $this->text($loginmsg_part2);
-       $this->elementEnd('p');
+    $this->elementEnd('p');
         $this->elementEnd('dd');
 
         $this->elementEnd('dl');
@@ -317,7 +319,7 @@ class FacebookAction extends Action
         $this->elementEnd('ul');
 
         $this->submit('submit', _('Login'));
-       $this->elementEnd('fieldset');
+    $this->elementEnd('fieldset');
         $this->elementEnd('form');
 
         $this->elementStart('p');
@@ -329,73 +331,73 @@ class FacebookAction extends Action
         $this->elementEnd('div');
 
     }
-    
-    
+
+
     function updateProfileBox($notice)
     {
 
         // Need to include inline CSS for styling the Profile box
 
-       $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
-       $icon_url = $app_props['icon_url'];
+    $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
+    $icon_url = $app_props['icon_url'];
 
         $style = '<style>
-        .entry-title *,
-        .entry-content * {
-        font-size:14px;
-        font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
-        }
-        .entry-title a,
-        .entry-content a {
-        color:#002E6E;
-        }
+     .entry-title *,
+     .entry-content * {
+     font-size:14px;
+     font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
+     }
+     .entry-title a,
+     .entry-content a {
+     color:#002E6E;
+     }
 
          .entry-title .vcard .photo {
          float:left;
          display:inline;
-        margin-right:11px;
-        margin-bottom:11px
+     margin-right:11px;
+     margin-bottom:11px
          }
-        .entry-title {
-        margin-bottom:11px;
-        }
+     .entry-title {
+     margin-bottom:11px;
+     }
          .entry-title p.entry-content {
          display:inline;
-        margin-left:5px;
+     margin-left:5px;
          }
 
-        div.entry-content {
-        clear:both;
-        }
+     div.entry-content {
+     clear:both;
+     }
          div.entry-content dl,
          div.entry-content dt,
          div.entry-content dd {
          display:inline;
-        text-transform:lowercase;
+     text-transform:lowercase;
          }
 
          div.entry-content dd,
-        div.entry-content .device dt {
-        margin-left:0;
-        margin-right:5px;
+     div.entry-content .device dt {
+     margin-left:0;
+     margin-right:5px;
          }
          div.entry-content dl.timestamp dt,
-        div.entry-content dl.response dt {
+     div.entry-content dl.response dt {
          display:none;
          }
          div.entry-content dd a {
          display:inline-block;
          }
 
-        #facebook_laconica_app {
-        text-indent:-9999px;
-        height:16px;
-        width:16px;
-        display:block;
-        background:url('.$icon_url.') no-repeat 0 0;
-        float:right;
-        }
-         </style>';        
+     #facebook_laconica_app {
+     text-indent:-9999px;
+     height:16px;
+     width:16px;
+     display:block;
+     background:url('.$icon_url.') no-repeat 0 0;
+     float:right;
+     }
+         </style>';
 
         $this->xw->openMemory();
 
@@ -407,12 +409,12 @@ class FacebookAction extends Action
 
         $fbml_main = "<fb:narrow>$style " . $this->xw->outputMemory(false) . "</fb:narrow>";
 
-        $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main);  
+        $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main);
 
         $this->xw->openURI('php://output');
     }
-    
-    
+
+
     /**
      * Generate pagination links
      *
@@ -457,24 +459,24 @@ class FacebookAction extends Action
             $this->elementEnd('div');
         }
     }
-    
-    function updateFacebookStatus($notice) 
+
+    function updateFacebookStatus($notice)
     {
         $prefix = $this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $this->fbuid);
         $content = "$prefix $notice->content";
-        
+
         if ($this->facebook->api_client->users_hasAppPermission('status_update', $this->fbuid)) {
             $this->facebook->api_client->users_setStatus($content, $this->fbuid, false, true);
         }
     }
-    
+
     function saveNewNotice()
     {
 
         $user = $this->flink->getUser();
 
         $content = $this->trimmed('status_textarea');
-        
+
         if (!$content) {
             $this->showPage(_('No notice content!'));
             return;
@@ -492,9 +494,9 @@ class FacebookAction extends Action
         $cmd = $inter->handle_command($user, $content_shortened);
 
         if ($cmd) {
-            
+
             // XXX fix this
-            
+
             $cmd->execute(new WebChannel());
             return;
         }
@@ -510,20 +512,20 @@ class FacebookAction extends Action
         }
 
         common_broadcast_notice($notice);
-        
+
         // Also update the user's Facebook status
         $this->updateFacebookStatus($notice);
         $this->updateProfileBox($notice);
-        
+
     }
 
 }
 
-class FacebookNoticeForm extends NoticeForm 
+class FacebookNoticeForm extends NoticeForm
 {
-    
+
     var $post_action = null;
-    
+
     /**
      * Constructor
      *
@@ -532,13 +534,13 @@ class FacebookNoticeForm extends NoticeForm
      * @param string        $content content to pre-fill
      */
 
-    function __construct($out=null, $action=null, $content=null, 
+    function __construct($out=null, $action=null, $content=null,
         $post_action=null, $user=null)
     {
         parent::__construct($out, $action, $content, $user);
         $this->post_action = $post_action;
     }
-    
+
     /**
      * Action of the form
      *
@@ -554,7 +556,7 @@ class FacebookNoticeForm extends NoticeForm
 
 class FacebookNoticeList extends NoticeList
 {
-    
+
     /**
      * constructor
      *
@@ -565,7 +567,7 @@ class FacebookNoticeList extends NoticeList
     {
         parent::__construct($notice, $out);
     }
-    
+
     /**
      * show the list of notices
      *
@@ -619,7 +621,7 @@ class FacebookNoticeList extends NoticeList
 }
 
 class FacebookNoticeListItem extends NoticeListItem
-{    
+{
 
     /**
      * constructor
@@ -646,51 +648,19 @@ class FacebookNoticeListItem extends NoticeListItem
     function show()
     {
         $this->showStart();
+        $this->showNotice();
+        $this->showNoticeInfo();
 
-        $this->out->elementStart('div', 'entry-title');
-        $this->showAuthor();
-        $this->showContent();
-        $this->out->elementEnd('div');
-
-        $this->out->elementStart('div', 'entry-content');
-        $this->showNoticeLink();
-        $this->showNoticeSource();
-        $this->showReplyTo();
-        $this->out->elementEnd('div');
+        // XXX: Need to update to show attachements and controls
 
         $this->showEnd();
     }
 
-    function showNoticeLink()
-    {
-        $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;
-        }
-
-        $this->out->elementStart('dl', 'timestamp');
-        $this->out->element('dt', null, _('Published'));
-        $this->out->elementStart('dd', null);
-        $this->out->elementStart('a', array('rel' => 'bookmark',
-                                        'href' => $noticeurl));
-        $dt = common_date_iso8601($this->notice->created);
-        $this->out->element('abbr', array('class' => 'published',
-                                     'title' => $dt),
-        common_date_string($this->notice->created));
-        $this->out->elementEnd('a');
-        $this->out->elementEnd('dd');
-        $this->out->elementEnd('dl');
-    }
-
 }
 
-
 class FacebookProfileBoxNotice extends FacebookNoticeListItem
-{    
-    
+{
+
     /**
      * constructor
      *
@@ -703,36 +673,24 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
     {
         parent::__construct($notice, $out);
     }
-    
+
     /**
-     * Recipe function for displaying a single notice in the 
-     * Facebook App's Profile
+     * Recipe function for displaying a single notice in the
+     * Facebook App profile notice box
      *
      * @return void
      */
 
     function show()
     {
-
-        $this->out->elementStart('div', 'entry-title');
-        $this->showAuthor();
-        $this->showContent();
-        $this->out->elementEnd('div');
-
-        $this->out->elementStart('div', 'entry-content');
-
-        $this->showNoticeLink();
-        $this->showNoticeSource();
-        $this->showReplyTo();
-        $this->out->elementEnd('div');
-        
+        $this->showNotice();
+        $this->showNoticeInfo();
         $this->showAppLink();
-
     }
 
-    function showAppLink() 
+    function showAppLink()
     {
-        
+
         $this->facebook = getFacebook();
 
         $app_props = $this->facebook->api_client->Admin_getAppProperties(
@@ -740,7 +698,7 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
 
         $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name'];
         $this->app_name = $app_props['application_name'];
-        
+
         $this->out->elementStart('a', array('id' => 'facebook_laconica_app',
                                             'href' => $this->app_uri));
         $this->out->text($this->app_name);
index 242d2e06f280e1bd0bbe6587d2ece54b7ab1dd04..632ec4bade172e8f708f965e7b83d5f7a9e1d07b 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -51,6 +51,10 @@ function updateProfileBox($facebook, $flink, $notice) {
 
 function isFacebookBound($notice, $flink) {
 
+    if (empty($flink)) {
+        return false;
+    }
+
     // If the user does not want to broadcast to Facebook, move along
     if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
         common_log(LOG_INFO, "Skipping notice $notice->id " .
@@ -86,10 +90,10 @@ function isFacebookBound($notice, $flink) {
 
             if ($result != 1) {
                 $user = $flink->getUser();
-                $msg = "Can't send notice $notice->id to Facebook " .
+                $msg = "Not sending notice $notice->id to Facebook " .
                     "because user $user->nickname hasn't given the " .
                     'Facebook app \'status_update\' permission.';
-                common_log(LOG_INFO, $msg);
+                common_debug($msg);
                 $success = false;
             }
 
@@ -108,13 +112,16 @@ function facebookBroadcastNotice($notice)
 {
     $facebook = getFacebook();
     $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
-    $fbuid = $flink->foreign_id;
 
     if (isFacebookBound($notice, $flink)) {
 
         $status = null;
+        $fbuid = $flink->foreign_id;
+
+        $user = $flink->getUser();
 
         // Get the status 'verb' (prefix) the user has set
+
         try {
             $prefix = $facebook->api_client->
                 data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
@@ -122,23 +129,79 @@ function facebookBroadcastNotice($notice)
             $status = "$prefix $notice->content";
 
         } catch(FacebookRestClientException $e) {
-            common_log(LOG_ERR, $e->getMessage());
-            return false;
+            common_log(LOG_WARNING, $e->getMessage());
+            common_log(LOG_WARNING,
+                'Unable to get the status verb setting from Facebook ' .
+                "for $user->nickname (user id: $user->id).");
         }
 
-        // Okay, we're good to go!
+        // Okay, we're good to go, update the FB status
 
         try {
             $facebook->api_client->users_setStatus($status, $fbuid, false, true);
-            updateProfileBox($facebook, $flink, $notice);
         } catch(FacebookRestClientException $e) {
             common_log(LOG_ERR, $e->getMessage());
-            return false;
+            common_log(LOG_ERR,
+                'Unable to update Facebook status for ' .
+                "$user->nickname (user id: $user->id)!");
+
+            $code = $e->getCode();
 
-             // Should we remove flink if this fails?
+            if ($code >= 200) {
+
+                // 200 The application does not have permission to operate on the passed in uid parameter.
+                // 250 Updating status requires the extended permission status_update.
+                // see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML
+
+                remove_facebook_app($flink);
+            }
+
+        }
+
+        // Now try to update the profile box
+
+        try {
+            updateProfileBox($facebook, $flink, $notice);
+        } catch(FacebookRestClientException $e) {
+            common_log(LOG_WARNING, $e->getMessage());
+            common_log(LOG_WARNING,
+                'Unable to update Facebook profile box for ' .
+                "$user->nickname (user id: $user->id).");
         }
 
     }
 
     return true;
 }
+
+function remove_facebook_app($flink)
+{
+
+    $user = $flink->getUser();
+
+    common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' .
+        "user $user->nickname (user id: $user->id).");
+
+    $result = $flink->delete();
+
+    if (empty($result)) {
+        common_log(LOG_ERR, 'Could not remove Facebook App ' .
+            "Foreign_link for $user->nickname (user id: $user->id)!");
+        common_log_db_error($flink, 'DELETE', __FILE__);
+    }
+
+    // Notify the user that we are removing their FB app access
+
+    $result = mail_facebook_app_removed($user);
+
+    if (!$result) {
+
+        $msg = 'Unable to send email to notify ' .
+            "$user->nickname (user id: $user->id) " .
+            'that their Facebook app link was ' .
+            'removed!';
+
+        common_log(LOG_WARNING, $msg);
+    }
+
+}
index 5317df4715946e9638a1b32905f603f3652ca299..f872aef0b55066196d54c61456678664736c1d93 100644 (file)
@@ -52,6 +52,8 @@ require_once INSTALLDIR.'/lib/widget.php';
 
 class Form extends Widget
 {
+    var $enctype = null;
+
     /**
      * Show the form
      *
@@ -63,11 +65,15 @@ class Form extends Widget
 
     function show()
     {
-        $this->out->elementStart('form',
-                                 array('id' => $this->id(),
-                                       'class' => $this->formClass(),
-                                       'method' => 'post',
-                                       'action' => $this->action()));
+        $attributes = array('id' => $this->id(),
+            'class' => $this->formClass(),
+            'method' => 'post',
+            'action' => $this->action());
+
+        if (!empty($this->enctype)) {
+            $attributes['enctype'] = $this->enctype;
+        }
+        $this->out->elementStart('form', $attributes);
         $this->out->elementStart('fieldset');
         $this->formLegend();
         $this->sessionToken();
index 8fa11a7562752f217edb65bb206f797b5732c40a..b389fc00f805634b92f3dd1eddc52e20dcaa5ff4 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /**
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -27,10 +27,9 @@ require_once INSTALLDIR.'/lib/profilelist.php';
 
 define('AVATARS_PER_PAGE', 80);
 
-class GalleryAction extends Action
+class GalleryAction extends OwnerDesignAction
 {
     var $profile = null;
-    var $user = null;
     var $page = null;
     var $tag = null;
 
diff --git a/lib/groupdesignaction.php b/lib/groupdesignaction.php
new file mode 100644 (file)
index 0000000..58777c2
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for actions that use the current user's design
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Action
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Base class for actions that use a group's design
+ *
+ * Pages related to groups can be themed with a design.
+ * This superclass returns that design.
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ */
+class GroupDesignAction extends Action {
+
+    /** The group in question */
+    var $group = null;
+
+    /**
+      * Show the groups's design stylesheet
+      *
+      * @return nothing
+      */
+     function showStylesheets()
+     {
+         parent::showStylesheets();
+
+         $user = common_current_user();
+
+         if (empty($user) || $user->viewdesigns) {
+             $design = $this->getDesign();
+
+             if (!empty($design)) {
+                 $design->showCSS($this);
+             }
+         }
+     }
+
+    /**
+     * A design for this action
+     *
+     * if the group attribute has been set, returns that group's
+     * design.
+     *
+     * @return Design a design object to use
+     */
+
+    function getDesign()
+    {
+        if (empty($this->group)) {
+            return null;
+        }
+
+        return $this->group->getDesign();
+    }
+}
index ca674f3c8e24a925c93b194c0866ce5748fc0663..fbb39129bc3bc4f7b05f552b318bf0e94029b8c8 100644 (file)
@@ -111,7 +111,6 @@ class GroupEditForm extends Form
         }
     }
 
-
     /**
      * Name of the form
      *
@@ -131,32 +130,58 @@ class GroupEditForm extends Form
 
     function formData()
     {
+        if ($this->group) {
+            $id = $this->group->id;
+            $nickname = $this->group->nickname;
+            $fullname = $this->group->fullname;
+            $homepage = $this->group->homepage;
+            $description = $this->group->description;
+            $location = $this->group->location;
+        } else {
+            $id = '';
+            $nickname = '';
+            $fullname = '';
+            $homepage = '';
+            $description = '';
+            $location = '';
+        }
+
         $this->out->elementStart('ul', 'form_data');
         $this->out->elementStart('li');
-        $this->out->hidden('groupid', $this->group->id);
+        $this->out->hidden('groupid', $id);
         $this->out->input('nickname', _('Nickname'),
-                     ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $this->group->nickname,
+                     ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
                      _('1-64 lowercase letters or numbers, no punctuation or spaces'));
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->input('fullname', _('Full name'),
-                     ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $this->group->fullname);
+                     ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->input('homepage', _('Homepage'),
-                     ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $this->group->homepage,
+                     ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
                      _('URL of the homepage or blog of the group or topic'));
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->textarea('description', _('Description'),
-                        ($this->out->arg('description')) ? $this->out->arg('description') : $this->group->description,
+                        ($this->out->arg('description')) ? $this->out->arg('description') : $description,
                         _('Describe the group or topic in 140 chars'));
         $this->out->elementEnd('li');
         $this->out->elementStart('li');
         $this->out->input('location', _('Location'),
-                     ($this->out->arg('location')) ? $this->out->arg('location') : $this->group->location,
+                     ($this->out->arg('location')) ? $this->out->arg('location') : $location,
                      _('Location for the group, if any, like "City, State (or Region), Country"'));
         $this->out->elementEnd('li');
+        if (common_config('group', 'maxaliases') > 0) {
+            $aliases = (empty($this->group)) ? array() : $this->group->getAliases();
+            $this->out->elementStart('li');
+            $this->out->input('aliases', _('Aliases'),
+                              ($this->out->arg('aliases')) ? $this->out->arg('aliases') :
+                              (!empty($aliases)) ? implode(' ', $aliases) : '',
+                              sprintf(_('Extra nicknames for the group, comma- or space- separated, max %d'),
+                                      common_config('group', 'maxaliases')));;
+            $this->out->elementEnd('li');
+        }
         $this->out->elementEnd('ul');
     }
 
index 1b854749982198e68f89de742d2c1fd306e3ce1c..1ded5160bdcd655e2c1b2656d7eee3d8af6c3aab 100644 (file)
@@ -166,7 +166,7 @@ class GroupList extends Widget
             if ($user->isMember($this->group)) {
                 $lf = new LeaveForm($this->out, $this->group);
                 $lf->show();
-            } else {
+            } else if (!Group_block::isBlocked($this->group, $user->getProfile())) {
                 $jf = new JoinForm($this->out, $this->group);
                 $jf->show();
             }
index 90bdc10149b30cda2cb0aab8dca759d615e90c78..9e530c447ce511bca2eabb1b3d5371a10c92dc03 100644 (file)
@@ -95,6 +95,12 @@ class GroupNav extends Widget
         $cur = common_current_user();
 
         if ($cur && $cur->isAdmin($this->group)) {
+            $this->out->menuItem(common_local_url('blockedfromgroup', array('nickname' =>
+                                                                            $nickname)),
+                                 _('Blocked'),
+                                 sprintf(_('%s blocked users'), $nickname),
+                                 $action_name == 'blockedfromgroup',
+                                 'nav_group_blocked');
             $this->out->menuItem(common_local_url('editgroup', array('nickname' =>
                                                                      $nickname)),
                                  _('Admin'),
@@ -107,6 +113,12 @@ class GroupNav extends Widget
                                  sprintf(_('Add or edit %s logo'), $nickname),
                                  $action_name == 'grouplogo',
                                  'nav_group_logo');
+            $this->out->menuItem(common_local_url('groupdesignsettings', array('nickname' =>
+                                                                  $nickname)),
+                                 _('Design'),
+                                 sprintf(_('Add or edit %s design'), $nickname),
+                                 $action_name == 'groupdesignsettings',
+                                 'nav_group_design');
         }
         $this->out->elementEnd('ul');
     }
index 5d68af28bf7bdc62e8a9ba3b3892a3b03c6d8084..9b7a10f6b9ea029e45bda11f64fa7604411f04b3 100644 (file)
@@ -32,7 +32,7 @@ if (!defined('LACONICA')) {
 }
 
 /**
- * Personal tag cloud section
+ * Group tag cloud section
  *
  * @category Widget
  * @package  Laconica
@@ -64,12 +64,27 @@ class GroupTagCloudSection extends TagCloudSection
             $weightexpr='sum(exp(-(now() - notice_tag.created) / %s))';
         }
 
+        $names = $this->group->getAliases();
+
+        $names = array_merge(array($this->group->nickname), $names);
+
+        // XXX This is dumb.
+
+        $quoted = array();
+
+        foreach ($names as $name) {
+            $quoted[] = "\"$name\"";
+        }
+
+        $namestring = implode(',', $quoted);
+
         $qry = 'SELECT notice_tag.tag, '.
           $weightexpr . ' as weight ' .
           'FROM notice_tag JOIN notice ' .
           'ON notice_tag.notice_id = notice.id ' .
           'JOIN group_inbox on group_inbox.notice_id = notice.id ' .
           'WHERE group_inbox.group_id = %d ' .
+          'AND notice_tag.tag not in (%s) '.
           'GROUP BY notice_tag.tag ' .
           'ORDER BY weight DESC ';
 
@@ -85,9 +100,9 @@ class GroupTagCloudSection extends TagCloudSection
         $tag = Memcached_DataObject::cachedQuery('Notice_tag',
                                                  sprintf($qry,
                                                          common_config('tag', 'dropoff'),
-                                                         $this->group->id),
+                                                         $this->group->id,
+                                                         $namestring),
                                                  3600);
         return $tag;
     }
-
 }
index 0c93b257ed394d5433e6a2c8f147e18aad480587..52e4c4b2272d66e8a2b9e0de1612e0a73e7d308e 100644 (file)
@@ -72,7 +72,8 @@ class ImageFile
             break;
          case UPLOAD_ERR_INI_SIZE:
          case UPLOAD_ERR_FORM_SIZE:
-            throw new Exception(sprintf(_('That file is too big. The maximum file size is %d.'), $this->maxFileSize()));
+            throw new Exception(sprintf(_('That file is too big. The maximum file size is %d.'),
+                ImageFile::maxFileSize()));
             return;
          case UPLOAD_ERR_PARTIAL:
             @unlink($_FILES[$param]['tmp_name']);
index 7d584ad0164edc18155e4e541f9025b14ace0294..e15076160fc82c1bccb128e5709ded5db65af777 100644 (file)
@@ -77,6 +77,14 @@ function jabber_daemon_address()
     return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
 }
 
+class Sharing_XMPP extends XMPPHP_XMPP
+{
+    function getSocket()
+    {
+        return $this->socket;
+    }
+}
+
 /**
  * connect the configured Jabber account to the configured server
  *
@@ -89,7 +97,7 @@ function jabber_connect($resource=null)
 {
     static $conn = null;
     if (!$conn) {
-        $conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ?
+        $conn = new Sharing_XMPP(common_config('xmpp', 'host') ?
                                 common_config('xmpp', 'host') :
                                 common_config('xmpp', 'server'),
                                 common_config('xmpp', 'port'),
index f786c20a806ed53c3c55cff6eb9c0cbe0367dad5..7beea9328d27649d507b4e8a753324ae836648d5 100644 (file)
@@ -89,6 +89,7 @@ class JSONSearchResultsList
     function show()
     {
         $cnt = 0;
+        $this->max_id = 0;
 
         $time_start = microtime(true);
 
index cd6498d30b5d108677080a4b1494efa7e54d9a90..3ea3dd2aa0a5237412a980fb1266fce75d10e9e9 100644 (file)
@@ -108,7 +108,7 @@ function get_all_languages() {
                'el'      => array('q' => 0.1, 'lang' => 'el',    'name' => 'Greek', 'direction' => 'ltr'),
                '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'),
+               'en'      => array('q' => 1, 'lang' => 'en_US',    'name' => 'English (US)', 'direction' => 'ltr'),
                'es'      => array('q' => 1, 'lang' => 'es',    'name' => 'Spanish', 'direction' => 'ltr'),
                'fi'      => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'),
                'fr-fr'   => array('q' => 1, 'lang' => 'fr_FR', 'name' => 'French', 'direction' => 'ltr'),
index 4e1f1dbb1d71ccee1d9407c591ae0c135de637c2..90ee3c992855e06fba3e981779124335f69fec40 100644 (file)
@@ -625,3 +625,75 @@ function mail_notify_attn($user, $notice)
     common_init_locale();
     mail_to_user($user, $subject, $body);
 }
+
+/**
+ * Send a mail message to notify a user that her Twitter bridge link
+ * has stopped working, and therefore has been removed.  This can
+ * happen when the user changes her Twitter password, or otherwise
+ * revokes access.
+ *
+ * @param User $user   user whose Twitter bridge link has been removed
+ *
+ * @return boolean success flag
+ */
+
+function mail_twitter_bridge_removed($user)
+{
+    common_init_locale($user->language);
+
+    $profile = $user->getProfile();
+
+    $subject = sprintf(_('Your Twitter bridge has been disabled.'));
+
+    $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that your " .
+        'link to Twitter has been disabled. Your Twitter credentials ' .
+        'have either changed (did you recently change your Twitter ' .
+        'password?) or you have otherwise revoked our access to your ' .
+        "Twitter account.\n\n" .
+        'You can re-enable your Twitter bridge by visiting your ' .
+        "Twitter settings page:\n\n\t%2\$s\n\n" .
+        "Regards,\n%3\$s\n"),
+        $profile->getBestName(),
+        common_local_url('twittersettings'),
+        common_config('site', 'name'));
+
+    common_init_locale();
+    return mail_to_user($user, $subject, $body);
+}
+
+/**
+ * Send a mail message to notify a user that her Facebook Application
+ * access has been removed.
+ *
+ * @param User $user   user whose Facebook app link has been removed
+ *
+ * @return boolean success flag
+ */
+
+function mail_facebook_app_removed($user)
+{
+    common_init_locale($user->language);
+
+    $profile = $user->getProfile();
+
+    $site_name = common_config('site', 'name');
+
+    $subject = sprintf(
+        _('Your %s Facebook application access has been disabled.',
+            $site_name));
+
+    $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that we are " .
+        'unable to update your Facebook status from %s, and have disabled ' .
+        'the Facebook application for your account. This may be because ' .
+        'you have removed the Facebook application\'s authorization, or ' .
+        'have deleted your Facebook account.  You can re-enable the ' .
+        'Facebook application and automatic status updating by ' .
+        "re-installing the %1\$s Facebook application.\n\nRegards,\n\n%1\$s"),
+        $site_name);
+
+    common_init_locale();
+    return mail_to_user($user, $subject, $body);
+
+}
+
+
index 01bbf5721a4450426f2b53a0d731979fa76c1b02..f1f6e98c195343d9d36a4becba131b701b30531a 100644 (file)
@@ -31,8 +31,6 @@ if (!defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/personal.php';
-
 define('MESSAGES_PER_PAGE', 20);
 
 /**
@@ -47,11 +45,11 @@ define('MESSAGES_PER_PAGE', 20);
  * @see      OutboxAction
  */
 
-class MailboxAction extends PersonalAction
+class MailboxAction extends CurrentUserDesignAction
 {
     var $page = null;
 
-    function prepare($args) 
+    function prepare($args)
     {
         parent::prepare($args);
 
@@ -265,12 +263,12 @@ class MailboxAction extends PersonalAction
      * Returns either the name (and link) of the API client that posted the notice,
      * or one of other other channels.
      *
-     * @param string $source the source of the message 
+     * @param string $source the source of the message
      *
      * @return void
      */
 
-    function showSource($source) 
+    function showSource($source)
     {
         $source_name = _($source);
         switch ($source) {
@@ -297,4 +295,17 @@ class MailboxAction extends PersonalAction
         return;
     }
 
+    /**
+     * Mailbox actions are read only
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean
+     */
+
+    function isReadOnly($args)
+    {
+         return true;
+    }
+
 }
index 606b5d028e7d73826cdd3d0d4a5f757575af2bbf..4e2a2edd61ff78c085d46e5c7419228e7a83a57a 100644 (file)
@@ -89,7 +89,10 @@ class NoticeForm extends Form
         } else {
             $this->user = common_current_user();
         }
-        
+
+        if (common_config('attachments', 'uploads')) {
+            $this->enctype = 'multipart/form-data';
+        }
     }
 
     /**
@@ -142,13 +145,19 @@ class NoticeForm extends Form
                                               'rows' => 4,
                                               'name' => 'status_textarea'),
                             ($this->content) ? $this->content : '');
-
         $this->out->elementStart('dl', 'form_note');
         $this->out->element('dt', null, _('Available characters'));
         $this->out->element('dd', array('id' => 'notice_text-count'),
                             '140');
         $this->out->elementEnd('dl');
-
+        if (common_config('attachments', 'uploads')) {
+            $this->out->element('label', array('for' => 'notice_data-attach'),_('Attach'));
+            $this->out->element('input', array('id' => 'notice_data-attach',
+                                               'type' => 'file',
+                                               'name' => 'attach',
+                                               'title' => _('Attach a file')));
+            $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
+        }
         if ($this->action) {
             $this->out->hidden('notice_return-to', $this->action, 'returnto');
         }
index 4182d8808f7046520a9453e8ab1c90b6562083dd..44726a17b7b7a3da4116724bc0ed1d4c69b8d35b 100644 (file)
@@ -34,6 +34,7 @@ if (!defined('LACONICA')) {
 
 require_once INSTALLDIR.'/lib/favorform.php';
 require_once INSTALLDIR.'/lib/disfavorform.php';
+require_once INSTALLDIR.'/lib/attachmentlist.php';
 
 /**
  * widget for displaying a list of notices
@@ -49,7 +50,6 @@ require_once INSTALLDIR.'/lib/disfavorform.php';
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://laconi.ca/
  * @see      Notice
- * @see      StreamAction
  * @see      NoticeListItem
  * @see      ProfileNoticeList
  */
@@ -85,7 +85,7 @@ class NoticeList extends Widget
     {
         $this->out->elementStart('div', array('id' =>'notices_primary'));
         $this->out->element('h2', null, _('Notices'));
-        $this->out->elementStart('ul', array('class' => 'notices'));
+        $this->out->elementStart('ol', array('class' => 'notices xoxo'));
 
         $cnt = 0;
 
@@ -100,7 +100,7 @@ class NoticeList extends Widget
             $item->show();
         }
 
-        $this->out->elementEnd('ul');
+        $this->out->elementEnd('ol');
         $this->out->elementEnd('div');
 
         return $cnt;
@@ -197,7 +197,7 @@ class NoticeListItem extends Widget
         $this->out->elementStart('div', 'entry-content');
         $this->showNoticeLink();
         $this->showNoticeSource();
-        $this->showReplyTo();
+        $this->showContext();
         $this->out->elementEnd('div');
     }
 
@@ -366,6 +366,7 @@ class NoticeListItem extends Widget
         $this->out->element('abbr', array('class' => 'published',
                                           'title' => $dt),
                             common_date_string($this->notice->created));
+
         $this->out->elementEnd('a');
         $this->out->elementEnd('dd');
         $this->out->elementEnd('dl');
@@ -421,17 +422,18 @@ class NoticeListItem extends Widget
      * @return void
      */
 
-    function showReplyTo()
+    function showContext()
     {
-        if ($this->notice->reply_to) {
-            $replyurl = common_local_url('shownotice',
-                                         array('notice' => $this->notice->reply_to));
+        // XXX: also show context if there are replies to this notice
+        if (!empty($this->notice->conversation)
+            && $this->notice->conversation != $this->notice->id) {
+            $convurl = common_local_url('conversation',
+                                         array('id' => $this->notice->conversation));
             $this->out->elementStart('dl', 'response');
             $this->out->element('dt', null, _('To'));
             $this->out->elementStart('dd');
-            $this->out->element('a', array('href' => $replyurl,
-                                           'rel' => 'in-reply-to'),
-                                _('in reply to'));
+            $this->out->element('a', array('href' => $convurl.'#notice-'.$this->notice->id),
+                                _('in context'));
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
         }
index 94c2738efdabc3c5b1c60e5da6b3a774c8cd8c63..ca14326861b5aaa92ab614bd71fe67eee7d15707 100644 (file)
@@ -51,17 +51,13 @@ class NoticeSection extends Section
     function showContent()
     {
         $notices = $this->getNotices();
-
         $cnt = 0;
-
-        $this->out->elementStart('ul', 'notices');
-
+        $this->out->elementStart('ol', 'notices xoxo');
         while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
             $this->showNotice($notices);
         }
 
-        $this->out->elementEnd('ul');
-
+        $this->out->elementEnd('ol');
         return ($cnt > NOTICES_PER_SECTION);
     }
 
@@ -100,6 +96,37 @@ class NoticeSection extends Section
 
         $this->out->elementStart('p', 'entry-content');
         $this->out->raw($notice->rendered);
+
+        $notice_link_cfg = common_config('site', 'notice_link');
+        if ('direct' === $notice_link_cfg) {
+            $this->out->text(' (');
+            $this->out->element('a', array('href' => $notice->uri), 'see');
+            $this->out->text(')');
+        } elseif ('attachment' === $notice_link_cfg) {
+            if ($count = $notice->hasAttachments()) {
+            // link to attachment(s) pages
+                if (1 === $count) {
+                    $f2p = File_to_post::staticGet('post_id', $notice->id);
+                    $href = common_local_url('attachment', array('attachment' => $f2p->file_id));
+                    $att_class = 'attachment';
+                } else {
+                    $href = common_local_url('attachments', array('notice' => $notice->id));
+                    $att_class = 'attachments';
+                }
+
+                $clip = theme_path('images/icons/clip.png', 'base');
+                $this->out->elementStart('a', array('class' => $att_class, 'style' => "font-style: italic;", 'href' => $href, 'title' => "# of attachments: $count"));
+                $this->out->raw(" ($count&nbsp");
+                $this->out->element('img', array('style' => 'display: inline', 'align' => 'top', 'width' => 20, 'height' => 20, 'src' => $clip, 'alt' => 'alt'));
+                $this->out->text(')');
+                $this->out->elementEnd('a');
+            } else {
+                $this->out->text(' (');
+                $this->out->element('a', array('href' => $notice->uri), 'see');
+                $this->out->text(')');
+            }
+        }
+
         $this->out->elementEnd('p');
         if (!empty($notice->value)) {
             $this->out->elementStart('p');
index 183164e170a86ae2353d4c3d6266d5a6c1024ebc..f224c6c2213ed3324ad49d3f87567abc4ef988fd 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 40cb847dfa8434e96f6ca0a388aa7069e867b8f0..4f6a9609541ded863edcc62edef1ffcff10f4283 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 3af7a39cfa2a807718fafbdd864e16af90bd7cd7..0b7633284e50016bd798b4f663357de4f3c14228 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
diff --git a/lib/ownerdesignaction.php b/lib/ownerdesignaction.php
new file mode 100644 (file)
index 0000000..785b8a9
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for actions that use the page owner's design
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Action
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Base class for actions that use the page owner's design
+ *
+ * Some pages have a clear "owner" -- like the profile page, subscriptions
+ * pages, etc. This superclass uses that owner's chosen design for the page
+ * design.
+ *
+ * @category Action
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ */
+
+class OwnerDesignAction extends Action {
+
+    /** The user for this page. */
+
+    var $user = null;
+
+    /**
+      * Show the owner's design stylesheet
+      *
+      * @return nothing
+      */
+     function showStylesheets()
+     {
+         parent::showStylesheets();
+
+         $user = common_current_user();
+
+         if (empty($user) || $user->viewdesigns) {
+             $design = $this->getDesign();
+
+             if (!empty($design)) {
+                 $design->showCSS($this);
+             }
+         }
+     }
+
+    /**
+     * A design for this action
+     *
+     * if the user attribute has been set, returns that user's
+     * design.
+     *
+     * @return Design a design object to use
+     */
+
+    function getDesign()
+    {
+        if (empty($this->user)) {
+            return null;
+        }
+
+        return $this->user->getDesign();
+    }
+}
diff --git a/lib/peoplesearchresults.php b/lib/peoplesearchresults.php
deleted file mode 100644 (file)
index d3f8408..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-/**
- * People search results class
- *
- * PHP version 5
- *
- * @category Widget
- * @package  Laconica
- * @author   Evan Prodromou <evan@controlyourself.ca>
- * @author   Robin Millette <millette@controlyourself.ca>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://laconi.ca/
- *
- * 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';
-
-/**
- * People search results class
- *
- * Derivative of ProfileList with specialization for highlighting search terms.
- *
- * @category Widget
- * @package  Laconica
- * @author   Evan Prodromou <evan@controlyourself.ca>
- * @author   Robin Millette <millette@controlyourself.ca>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://laconi.ca/
- *
- * @see PeoplesearchAction
- */
-
-class PeopleSearchResults extends ProfileList
-{
-    var $terms = null;
-    var $pattern = null;
-
-    function __construct($profile, $terms, $action)
-    {
-        parent::__construct($profile, $terms, $action);
-        $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));
-    }
-
-    function isReadOnly($args)
-    {
-        return true;
-    }
-}
-
diff --git a/lib/personal.php b/lib/personal.php
deleted file mode 100644 (file)
index f927323..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-/**
- * Laconica, the distributed open-source microblogging tool
- *
- * User profile page
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Personal
- * @package   Laconica
- * @author    Evan Prodromou <evan@controlyourself.ca>
- * @author    Sarven Capadisli <csarven@controlyourself.ca>
- * @copyright 2008-2009 Control Yourself, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://laconi.ca/
- */
-
-if (!defined('LACONICA')) {
-    exit(1);
-}
-
-/**
- * Base class for user profile page
- *
- * @category Personal
- * @package  Laconica
- * @author   Evan Prodromou <evan@controlyourself.ca>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://laconi.ca/
- */
-
-class PersonalAction extends Action
-{
-
-    var $user = null;
-
-    function isReadOnly($args)
-    {
-         return true;
-    }
-
-    function handle($args)
-    {
-        parent::handle($args);
-    }
-
-}
index 3de541e9aaf721ec8cbac4e65aabdfbc34d727c9..d26c7341750600fb6db0fd95e4bfbc4f85e863c1 100644 (file)
@@ -59,7 +59,7 @@ function ping_broadcast_notice($notice) {
 
             $response = xmlrpc_decode($file);
 
-            if (xmlrpc_is_fault($response)) {
+            if (is_array($response) && xmlrpc_is_fault($response)) {
                 common_log(LOG_WARNING,
                            "XML-RPC error for ping ($notify_url, $notice->id) ".
                            "$response[faultString] ($response[faultCode])");
index a8d47ef542b5a0ea9e2c7c344de0f928c4af9035..e47c9b3855d22c22e65b2707d1830511ee7930f6 100644 (file)
@@ -51,7 +51,7 @@ class PopularNoticeSection extends NoticeSection
         if (common_config('db', 'type') == 'pgsql') {
             $weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))';
             if (!empty($this->out->tag)) {
-                $tag = pg_escape_string($this->tag);
+                $tag = pg_escape_string($this->out->tag);
             }
         } else {
             $weightexpr='sum(exp(-(now() - fave.modified) / %s))';
@@ -68,7 +68,7 @@ class PopularNoticeSection extends NoticeSection
         }
         $qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' .
                 'notice.rendered,notice.url,notice.created,notice.modified,' .
-                'notice.reply_to,notice.is_local,notice.source ' .
+                'notice.reply_to,notice.is_local,notice.source,notice.conversation ' .
                 'ORDER BY weight DESC';
 
         $offset = 0;
index 1f2e309945c2ca3af30ba054c277b95445c7a25c..9e9c79c78a90436c76397427826ecf40f4d41bc2 100644 (file)
@@ -47,18 +47,18 @@ require_once INSTALLDIR.'/lib/groupminilist.php';
  * @link     http://laconi.ca/
  */
 
-class ProfileAction extends Action
+class ProfileAction extends OwnerDesignAction
 {
-    var $user = null;
-    var $page = null;
+    var $page    = null;
     var $profile = null;
+    var $tag     = null;
 
     function prepare($args)
     {
         parent::prepare($args);
 
         $nickname_arg = $this->arg('nickname');
-        $nickname = common_canonical_nickname($nickname_arg);
+        $nickname     = common_canonical_nickname($nickname_arg);
 
         // Permanent redirect on non-canonical nickname
 
@@ -85,10 +85,9 @@ class ProfileAction extends Action
             return false;
         }
 
+        $this->tag = $this->trimmed('tag');
         $this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
-
         common_set_returnto($this->selfUrl());
-
         return true;
     }
 
@@ -109,8 +108,10 @@ class ProfileAction extends Action
 
         $this->element('h2', null, _('Subscriptions'));
 
-        if ($profile) {
-            $pml = new ProfileMiniList($profile, $this->user, $this);
+        $cnt = 0;
+
+        if (!empty($profile)) {
+            $pml = new ProfileMiniList($profile, $this);
             $cnt = $pml->show();
             if ($cnt == 0) {
                 $this->element('p', null, _('(None)'));
@@ -138,8 +139,10 @@ class ProfileAction extends Action
 
         $this->element('h2', null, _('Subscribers'));
 
-        if ($profile) {
-            $pml = new ProfileMiniList($profile, $this->user, $this);
+        $cnt = 0;
+
+        if (!empty($profile)) {
+            $pml = new ProfileMiniList($profile, $this);
             $cnt = $pml->show();
             if ($cnt == 0) {
                 $this->element('p', null, _('(None)'));
@@ -160,18 +163,9 @@ class ProfileAction extends Action
 
     function showStatistics()
     {
-        // XXX: WORM cache this
-        $subs = new Subscription();
-        $subs->subscriber = $this->profile->id;
-        $subs_count = (int) $subs->count() - 1;
-
-        $subbed = new Subscription();
-        $subbed->subscribed = $this->profile->id;
-        $subbed_count = (int) $subbed->count() - 1;
-
-        $notices = new Notice();
-        $notices->profile_id = $this->profile->id;
-        $notice_count = (int) $notices->count();
+        $subs_count   = $this->profile->subscriptionCount();
+        $subbed_count = $this->profile->subscriberCount();
+        $notice_count = $this->profile->noticeCount();
 
         $this->elementStart('div', array('id' => 'entity_statistics',
                                          'class' => 'section'));
@@ -196,7 +190,7 @@ class ProfileAction extends Action
                                                              array('nickname' => $this->profile->nickname))),
                        _('Subscriptions'));
         $this->elementEnd('dt');
-        $this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
+        $this->element('dd', null, $subs_count);
         $this->elementEnd('dl');
 
         $this->elementStart('dl', 'entity_subscribers');
@@ -205,12 +199,12 @@ class ProfileAction extends Action
                                                              array('nickname' => $this->profile->nickname))),
                        _('Subscribers'));
         $this->elementEnd('dt');
-        $this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
+        $this->element('dd', 'subscribers', $subbed_count);
         $this->elementEnd('dl');
 
         $this->elementStart('dl', 'entity_notices');
         $this->element('dt', null, _('Notices'));
-        $this->element('dd', null, (is_int($notice_count)) ? $notice_count : '0');
+        $this->element('dd', null, $notice_count);
         $this->elementEnd('dl');
 
         $this->elementEnd('div');
@@ -244,4 +238,5 @@ class ProfileAction extends Action
 
         $this->elementEnd('div');
     }
-}
\ No newline at end of file
+}
+
index a4cc235552a4d24723cbb5abe9934bfa1c9e08a6..774538a4b611de3a892c77c313b14ba2bb4a3088 100644 (file)
@@ -49,25 +49,37 @@ class ProfileList extends Widget
 {
     /** Current profile, profile query. */
     var $profile = null;
-    /** Owner of this list */
-    var $owner = null;
     /** Action object using us. */
     var $action = null;
 
-    function __construct($profile, $owner=null, $action=null)
+    function __construct($profile, $action=null)
     {
         parent::__construct($action);
 
         $this->profile = $profile;
-        $this->owner = $owner;
         $this->action = $action;
     }
 
     function show()
     {
+        $this->startList();
+        $cnt = $this->showProfiles();
+        $this->endList();
+        return $cnt;
+    }
 
+    function startList()
+    {
         $this->out->elementStart('ul', 'profiles');
+    }
+
+    function endList()
+    {
+        $this->out->elementEnd('ul');
+    }
 
+    function showProfiles()
+    {
         $cnt = 0;
 
         while ($this->profile->fetch()) {
@@ -75,24 +87,66 @@ class ProfileList extends Widget
             if($cnt > PROFILES_PER_PAGE) {
                 break;
             }
-            $this->showProfile();
+            $pli = $this->newListItem($this->profile);
+            $pli->show();
         }
 
-        $this->out->elementEnd('ul');
-
         return $cnt;
     }
 
-    function showProfile()
+    function newListItem($profile)
+    {
+        return new ProfileListItem($this->profile, $this->action);
+    }
+}
+
+class ProfileListItem extends Widget
+{
+    /** Current profile. */
+    var $profile = null;
+    /** Action object using us. */
+    var $action = null;
+
+    function __construct($profile, $action)
+    {
+        parent::__construct($action);
+
+        $this->profile = $profile;
+        $this->action  = $action;
+    }
+
+    function show()
+    {
+        $this->startItem();
+        $this->showProfile();
+        $this->showActions();
+        $this->endItem();
+    }
+
+    function startItem()
     {
         $this->out->elementStart('li', array('class' => 'profile',
                                              'id' => 'profile-' . $this->profile->id));
+    }
 
-        $user = common_current_user();
-        $is_own = !is_null($user) && isset($this->owner) && ($user->id === $this->owner->id);
+    function showProfile()
+    {
+        $this->startProfile();
+        $this->showAvatar();
+        $this->showFullName();
+        $this->showLocation();
+        $this->showHomepage();
+        $this->showBio();
+        $this->endProfile();
+    }
 
+    function startProfile()
+    {
         $this->out->elementStart('div', 'entity_profile vcard');
+    }
 
+    function showAvatar()
+    {
         $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE);
         $this->out->elementStart('a', array('href' => $this->profile->profileurl,
                                             'class' => 'url'));
@@ -108,7 +162,10 @@ class ProfileList extends Widget
         $this->out->raw($this->highlight($this->profile->nickname));
         $this->out->elementEnd('span');
         $this->out->elementEnd('a');
+    }
 
+    function showFullName()
+    {
         if (!empty($this->profile->fullname)) {
             $this->out->elementStart('dl', 'entity_fn');
             $this->out->element('dt', null, 'Full name');
@@ -119,6 +176,10 @@ class ProfileList extends Widget
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
         }
+    }
+
+    function showLocation()
+    {
         if (!empty($this->profile->location)) {
             $this->out->elementStart('dl', 'entity_location');
             $this->out->element('dt', null, _('Location'));
@@ -127,6 +188,10 @@ class ProfileList extends Widget
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
         }
+    }
+
+    function showHomepage()
+    {
         if (!empty($this->profile->homepage)) {
             $this->out->elementStart('dl', 'entity_url');
             $this->out->element('dt', null, _('URL'));
@@ -138,6 +203,10 @@ class ProfileList extends Widget
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
         }
+    }
+
+    function showBio()
+    {
         if (!empty($this->profile->bio)) {
             $this->out->elementStart('dl', 'entity_note');
             $this->out->element('dt', null, _('Note'));
@@ -146,94 +215,64 @@ class ProfileList extends Widget
             $this->out->elementEnd('dd');
             $this->out->elementEnd('dl');
         }
+    }
 
-        # 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);
-
-            $this->out->elementStart('dl', 'entity_tags');
-            $this->out->elementStart('dt');
-            if ($is_own) {
-                $this->out->element('a', array('href' => common_local_url('tagother',
-                                                                          array('id' => $this->profile->id))),
-                                    _('Tags'));
-            } else {
-                $this->out->text(_('Tags'));
-            }
-            $this->out->elementEnd('dt');
-            $this->out->elementStart('dd');
-            if ($tags) {
-                $this->out->elementStart('ul', 'tags xoxo');
-                foreach ($tags as $tag) {
-                    $this->out->elementStart('li');
-                    $this->out->element('span', 'mark_hash', '#');
-                    $this->out->element('a', array('rel' => 'tag',
-                                                   'href' => common_local_url($this->action->trimmed('action'),
-                                                                              array('nickname' => $this->owner->nickname,
-                                                                                    'tag' => $tag))),
-                                        $tag);
-                    $this->out->elementEnd('li');
-                }
-                $this->out->elementEnd('ul');
-            } else {
-                $this->out->text(_('(none)'));
-            }
-            $this->out->elementEnd('dd');
-            $this->out->elementEnd('dl');
-        }
-
-        if ($is_own) {
-            $this->showOwnerControls($this->profile);
-        }
-
+    function endProfile()
+    {
         $this->out->elementEnd('div');
+    }
 
-        $this->out->elementStart('div', 'entity_actions');
+    function showActions()
+    {
+        $this->startActions();
+        $this->showSubscribeButton();
+        $this->endActions();
+    }
 
+    function startActions()
+    {
+        $this->out->elementStart('div', 'entity_actions');
         $this->out->elementStart('ul');
+    }
 
+    function showSubscribeButton()
+    {
         // Is this a logged-in user, looking at someone else's
         // profile?
 
+        $user = common_current_user();
+
         if (!empty($user) && $this->profile->id != $user->id) {
             $this->out->elementStart('li', 'entity_subscribe');
             if ($user->isSubscribed($this->profile)) {
                 $usf = new UnsubscribeForm($this->out, $this->profile);
                 $usf->show();
             } else {
-                $sf = new SubscribeForm($this->out, $this->profile);
-                $sf->show();
-            }
-            $this->out->elementEnd('li');
-            $this->out->elementStart('li', 'entity_block');
-            if ($user->id == $this->owner->id) {
-                $this->showBlockForm();
+                // Is it a local user? can't remote sub from a list
+                // XXX: make that possible!
+                $other = User::staticGet('id', $this->profile->id);
+                if (!empty($other)) {
+                    $sf = new SubscribeForm($this->out, $this->profile);
+                    $sf->show();
+                }
             }
             $this->out->elementEnd('li');
         }
+    }
 
+    function endActions()
+    {
         $this->out->elementEnd('ul');
-
         $this->out->elementEnd('div');
-
-        $this->out->elementEnd('li');
     }
 
-    /* Override this in subclasses. */
-
-    function showOwnerControls($profile)
+    function endItem()
     {
-        return;
+        $this->out->elementEnd('li');
     }
 
     function highlight($text)
     {
         return htmlspecialchars($text);
     }
-
-    function showBlockForm()
-    {
-    }
 }
index 57496d0e97d77ee236a74d03adead1ad5860bdd8..357b4a2db4930c8979e3ffc07fba124fe28ee03d 100644 (file)
@@ -47,26 +47,38 @@ define('PROFILES_PER_MINILIST', 27);
 
 class ProfileMiniList extends ProfileList
 {
-    function show()
+
+    function startList()
     {
         $this->out->elementStart('ul', 'entities users xoxo');
+    }
+
+    function newListItem($profile)
+    {
+        return new ProfileMiniListItem($profile, $this->action);
+    }
 
+    function showProfiles()
+    {
         $cnt = 0;
 
         while ($this->profile->fetch()) {
             $cnt++;
-            if($cnt > PROFILES_PER_MINILIST) {
+            if ($cnt > PROFILES_PER_MINILIST) {
                 break;
             }
-            $this->showProfile();
+            $pli = $this->newListItem($this->profile);
+            $pli->show();
         }
 
-        $this->out->elementEnd('ul');
-
         return $cnt;
     }
 
-    function showProfile()
+}
+
+class ProfileMiniListItem extends ProfileListItem
+{
+    function show()
     {
         $this->out->elementStart('li', 'vcard');
         $this->out->elementStart('a', array('title' => $this->profile->getBestName(),
index 8ed290e03aa3c6195567da6c50401f8b12924cdf..9ff243fb53fab479a7ebd9142032409cbab4366f 100644 (file)
@@ -94,8 +94,8 @@ class ProfileSection extends Section
                                     $profile->fullname :
                                     $profile->nickname));
         $this->out->element('span', 'fn nickname', $profile->nickname);
-        $this->out->elementEnd('span');
         $this->out->elementEnd('a');
+        $this->out->elementEnd('span');
         $this->out->elementEnd('td');
         if ($profile->value) {
             $this->out->element('td', 'value', $profile->value);
index fde650d9ed782fd676b7ded4217ca9d4d8788b8d..c2ff10f32f07f4858e933b67fba8d8987c1b9e18 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
 
+define('CLAIM_TIMEOUT', 1200);
+define('QUEUE_HANDLER_MISS_IDLE', 10);
+define('QUEUE_HANDLER_HIT_IDLE', 0);
+
 class QueueHandler extends Daemon
 {
-
     var $_id = 'generic';
 
-    function QueueHandler($id=null)
+    function __construct($id=null, $daemonize=true)
     {
+        parent::__construct($daemonize);
+
         if ($id) {
             $this->set_id($id);
         }
     }
 
+    function timeout()
+    {
+        return 60;
+    }
+
     function class_name()
     {
         return ucfirst($this->transport()) . 'Handler';
@@ -80,41 +88,16 @@ class QueueHandler extends Daemon
         if (!$this->start()) {
             return false;
         }
-        $transport = $this->transport();
-        $this->log(LOG_INFO, 'checking for queued notices for "' . $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);
+
+        $this->log(LOG_INFO, 'checking for queued notices');
+
+        $queue   = $this->transport();
+        $timeout = $this->timeout();
+
+        $qm = QueueManager::get();
+
+        $qm->service($queue, $this);
+
         if (!$this->finish()) {
             return false;
         }
@@ -123,23 +106,19 @@ class QueueHandler extends Daemon
 
     function idle($timeout=0)
     {
-        if ($timeout>0) {
+        if ($timeout > 0) {
             sleep($timeout);
         }
     }
 
-    function clear_old_claims()
+    function log($level, $msg)
     {
-        $qi = new Queue_item();
-        $qi->transport = $this->transport();
-        $qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
-        $qi->update(DB_DATAOBJECT_WHEREADD_ONLY);
-        $qi->free();
-        unset($qi);
+        common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
     }
 
-    function log($level, $msg)
+    function getSockets()
     {
-        common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
+        return array();
     }
 }
+
diff --git a/lib/queuemanager.php b/lib/queuemanager.php
new file mode 100644 (file)
index 0000000..582c247
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Abstract class for queue managers
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QueueManager
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+class QueueManager
+{
+    static $qm = null;
+
+    static function get()
+    {
+        if (empty(self::$qm)) {
+
+            if (Event::handle('StartNewQueueManager', array(&self::$qm))) {
+
+                $enabled = common_config('queue', 'enabled');
+                $type = common_config('queue', 'subsystem');
+
+                if (!$enabled) {
+                    // does everything immediately
+                    self::$qm = new UnQueueManager();
+                } else {
+                    switch ($type) {
+                     case 'db':
+                        self::$qm = new DBQueueManager();
+                        break;
+                     case 'stomp':
+                        self::$qm = new StompQueueManager();
+                        break;
+                     default:
+                        throw new ServerException("No queue manager class for type '$type'");
+                    }
+                }
+            }
+        }
+
+        return self::$qm;
+    }
+
+    function enqueue($object, $queue)
+    {
+        throw ServerException("Unimplemented function 'enqueue' called");
+    }
+
+    function service($queue, $handler)
+    {
+        throw ServerException("Unimplemented function 'service' called");
+    }
+}
index 748966567f9ab86717e6a43ff1ae9277b29924e4..75e72f932295d047bfd0875aab0a91bd5480855a 100644 (file)
@@ -101,7 +101,8 @@ class Router
         $main = array('login', 'logout', 'register', 'subscribe',
                       'unsubscribe', 'confirmaddress', 'recoverpassword',
                       'invite', 'favor', 'disfavor', 'sup',
-                      'block', 'unblock', 'subedit');
+                      'block', 'unblock', 'subedit',
+                      'groupblock', 'groupunblock');
 
         foreach ($main as $a) {
             $m->connect('main/'.$a, array('action' => $a));
@@ -131,7 +132,7 @@ class Router
         // settings
 
         foreach (array('profile', 'avatar', 'password', 'openid', 'im',
-                       'email', 'sms', 'twitter', 'other') as $s) {
+                       'email', 'sms', 'twitter', 'userdesign', 'other') as $s) {
             $m->connect('settings/'.$s, array('action' => $s.'settings'));
         }
 
@@ -151,12 +152,27 @@ class Router
         $m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
                     array('q' => '.+'));
 
-        // notice
+        $m->connect('attachment/:attachment',
+                    array('action' => 'attachment'),
+                    array('attachment' => '[0-9]+'));
+
+        $m->connect('attachment/:attachment/ajax',
+                    array('action' => 'attachment_ajax'),
+                    array('attachment' => '[0-9]+'));
+
+        $m->connect('attachment/:attachment/thumbnail',
+                    array('action' => 'attachment_thumbnail'),
+                    array('attachment' => '[0-9]+'));
 
         $m->connect('notice/new', array('action' => 'newnotice'));
         $m->connect('notice/new?replyto=:replyto',
                     array('action' => 'newnotice'),
                     array('replyto' => '[A-Za-z0-9_-]+'));
+
+        $m->connect('notice/:notice/file',
+            array('action' => 'file'),
+            array('notice' => '[0-9]+'));
+
         $m->connect('notice/:notice',
                     array('action' => 'shownotice'),
                     array('notice' => '[0-9]+'));
@@ -165,6 +181,12 @@ class Router
                     array('action' => 'deletenotice'),
                     array('notice' => '[0-9]+'));
 
+        // conversation
+
+        $m->connect('conversation/:id',
+                    array('action' => 'conversation'),
+                    array('id' => '[0-9]+'));
+
         $m->connect('message/new', array('action' => 'newmessage'));
         $m->connect('message/new?to=:to', array('action' => 'newmessage'), array('to' => '[A-Za-z0-9_-]+'));
         $m->connect('message/:message',
@@ -205,12 +227,20 @@ class Router
                         array('nickname' => '[a-zA-Z0-9]+'));
         }
 
-        foreach (array('members', 'logo', 'rss') as $n) {
+        foreach (array('members', 'logo', 'rss', 'designsettings') as $n) {
             $m->connect('group/:nickname/'.$n,
                         array('action' => 'group'.$n),
                         array('nickname' => '[a-zA-Z0-9]+'));
         }
 
+        $m->connect('group/:nickname/blocked',
+                    array('action' => 'blockedfromgroup'),
+                    array('nickname' => '[a-zA-Z0-9]+'));
+
+        $m->connect('group/:nickname/makeadmin',
+                    array('action' => 'makeadmin'),
+                    array('nickname' => '[a-zA-Z0-9]+'));
+
         $m->connect('group/:id/id',
                     array('action' => 'groupbyid'),
                     array('id' => '[0-9]+'));
@@ -231,7 +261,7 @@ class Router
         $m->connect('api/statuses/:method',
                     array('action' => 'api',
                           'apiaction' => 'statuses'),
-                    array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|friends|followers|featured)(\.(atom|rss|xml|json))?'));
+                    array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
 
         $m->connect('api/statuses/:method/:argument',
                     array('action' => 'api',
@@ -287,7 +317,7 @@ class Router
         $m->connect('api/friendships/:method',
                     array('action' => 'api',
                           'apiaction' => 'friendships'),
-                    array('method' => 'exists(\.(xml|json))'));
+                    array('method' => '(show|exists)(\.(xml|json))'));
 
         // Social graph
 
@@ -325,7 +355,8 @@ class Router
 
         $m->connect('api/favorites/:method/:argument',
                     array('action' => 'api',
-                          'apiaction' => 'favorites'));
+                          'apiaction' => 'favorites',
+                          array('method' => '(create|destroy)')));
 
         $m->connect('api/favorites/:argument',
                     array('action' => 'api',
@@ -406,6 +437,16 @@ class Router
                     array('size' => '(original|96|48|24)',
                           'nickname' => '[a-zA-Z0-9]{1,64}'));
 
+        $m->connect(':nickname/tag/:tag/rss',
+            array('action' => 'userrss'),
+            array('nickname' => '[a-zA-Z0-9]{1,64}'),
+            array('tag' => '[a-zA-Z0-9]+'));
+
+        $m->connect(':nickname/tag/:tag',
+                    array('action' => 'showstream'),
+                    array('nickname' => '[a-zA-Z0-9]{1,64}'),
+                    array('tag' => '[a-zA-Z0-9]+'));
+
         $m->connect(':nickname',
                     array('action' => 'showstream'),
                     array('nickname' => '[a-zA-Z0-9]{1,64}'));
index ddba862dcf9b22113d74787215f3a15f094e6b5a..fe3fd6f4a289871a601d286814690b2f16ba2844 100644 (file)
@@ -97,7 +97,11 @@ class Rss10Action extends Action
         // Parent handling, including cache check
         parent::handle($args);
         // Get the list of notices
-        $this->notices = $this->getNotices($this->limit);
+        if (empty($this->tag)) {
+            $this->notices = $this->getNotices($this->limit);
+        } else {
+            $this->notices = $this->getTaggedNotices($this->tag, $this->limit);
+        }
         $this->showRss();
     }
 
@@ -166,7 +170,7 @@ class Rss10Action extends Action
         $this->elementStart('rdf:Seq');
 
         foreach ($this->notices as $notice) {
-            $this->element('sioct:MicroblogPost', array('rdf:resource' => $notice->uri));
+            $this->element('rdf:li', array('rdf:resource' => $notice->uri));
         }
 
         $this->elementEnd('rdf:Seq');
@@ -193,16 +197,32 @@ class Rss10Action extends Action
         $profile = Profile::staticGet($notice->profile_id);
         $nurl = common_local_url('shownotice', array('notice' => $notice->id));
         $creator_uri = common_profile_uri($profile);
-        $this->elementStart('item', array('rdf:about' => $notice->uri));
+        $this->elementStart('item', array('rdf:about' => $notice->uri,
+                            'rdf:type' => 'http://rdfs.org/sioc/types#MicroblogPost'));
         $title = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
         $this->element('title', null, $title);
         $this->element('link', null, $nurl);
         $this->element('description', null, $profile->nickname."'s status on ".common_exact_date($notice->created));
+        if ($notice->rendered) {
+            $this->element('content:encoded', null, common_xml_safe_str($notice->rendered));
+        }
         $this->element('dc:date', null, common_date_w3dtf($notice->created));
         $this->element('dc:creator', null, ($profile->fullname) ? $profile->fullname : $profile->nickname);
-        $this->element('sioc:has_creator', array('rdf:resource' => $creator_uri));
+        $this->element('foaf:maker', array('rdf:resource' => $creator_uri));
+        $this->element('sioc:has_creator', array('rdf:resource' => $creator_uri.'#acct'));
         $this->element('laconica:postIcon', array('rdf:resource' => $profile->avatarUrl()));
         $this->element('cc:licence', array('rdf:resource' => common_config('license', 'url')));
+        if ($notice->reply_to) {
+            $replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
+            $this->element('sioc:reply_of', array('rdf:resource' => $replyurl));
+        }
+        $attachments = $notice->attachments();
+        if($attachments){
+            foreach($attachments as $attachment){
+                $this->element('enc:enclosure', array('rdf:resource'=>$attachment->url,'enc:type'=>$attachment->mimetype,'enc:length'=>$attachment->size), null);
+            }
+        }
+
         $this->elementEnd('item');
         $this->creators[$creator_uri] = $profile;
     }
@@ -212,15 +232,15 @@ class Rss10Action extends Action
         foreach ($this->creators as $uri => $profile) {
             $id = $profile->id;
             $nickname = $profile->nickname;
-            $this->elementStart('sioc:User', array('rdf:about' => $uri));
+            $this->elementStart('foaf:Agent', array('rdf:about' => $uri));
             $this->element('foaf:nick', null, $nickname);
             if ($profile->fullname) {
                 $this->element('foaf:name', null, $profile->fullname);
             }
-            $this->element('sioc:id', null, $id);
+            $this->element('foaf:holdsAccount', array('rdf:resource' => $uri.'#acct'));
             $avatar = $profile->avatarUrl();
-            $this->element('sioc:avatar', array('rdf:resource' => $avatar));
-            $this->elementEnd('sioc:User');
+            $this->element('foaf:depiction', array('rdf:resource' => $avatar));
+            $this->elementEnd('foaf:Agent');
         }
     }
 
@@ -235,9 +255,11 @@ class Rss10Action extends Action
                                               'xmlns:dc' =>
                                               'http://purl.org/dc/elements/1.1/',
                                               'xmlns:cc' =>
-                                              'http://web.resource.org/cc/',
+                                              'http://creativecommons.org/ns#',
                                               'xmlns:content' =>
                                               'http://purl.org/rss/1.0/modules/content/',
+                                              'xmlns:enc' =>
+                                              'http://purl.oclc.org/net/rss_2.0/enc#',
                                               'xmlns:foaf' =>
                                               'http://xmlns.com/foaf/0.1/',
                                               'xmlns:sioc' =>
@@ -249,10 +271,10 @@ class Rss10Action extends Action
                                               'xmlns' => 'http://purl.org/rss/1.0/'));
         $this->elementStart('sioc:Site', array('rdf:about' => common_root_url()));
         $this->element('sioc:name', null, common_config('site', 'name'));
-        $this->elementStart('sioc:container_of');
+        $this->elementStart('sioc:space_of');
         $this->element('sioc:Container', array('rdf:about' =>
                                                $channel['url']));
-        $this->elementEnd('sioc:container_of');
+        $this->elementEnd('sioc:space_of');
         $this->elementEnd('sioc:Site');
     }
 
index 7b9dbb6182543e9e14969f7564c336cddc867350..772f41883b9dde17f5b5e59f39c276628fe189ad 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -118,12 +118,20 @@ class MySQLSearch extends SearchEngine
             }
             return true;
         } else if ('identica_notices' === $this->table) {
-             $this->target->whereAdd('MATCH(content) ' .
-                                     'AGAINST (\''.addslashes($q).'\' IN BOOLEAN MODE)');
+
+            // Don't show imported notices
+            $this->target->whereAdd('notice.is_local != ' . NOTICE_GATEWAY);
+
             if (strtolower($q) != $q) {
+                $this->target->whereAdd("( MATCH(content) AGAINST ('" . addslashes($q) .
+                    "' IN BOOLEAN MODE)) OR ( MATCH(content) " .
+                    "AGAINST ('"  . addslashes(strtolower($q)) .
+                    "' IN BOOLEAN MODE))");
+            } else {
                 $this->target->whereAdd('MATCH(content) ' .
-                                        'AGAINST (\''.addslashes(strtolower($q)).'\' IN BOOLEAN MODE)', 'OR');
+                                         'AGAINST (\''.addslashes($q).'\' IN BOOLEAN MODE)');
             }
+
             return true;
         } else {
             throw new ServerException('Unknown table: ' . $this->table);
@@ -131,6 +139,28 @@ class MySQLSearch extends SearchEngine
     }
 }
 
+class MySQLLikeSearch extends SearchEngine
+{
+    function query($q)
+    {
+        if ('identica_people' === $this->table) {
+            $qry = sprintf('(nickname LIKE "%%%1$s%%" OR '.
+                           ' fullname LIKE "%%%1$s%%" OR '.
+                           ' location LIKE "%%%1$s%%" OR '.
+                           ' bio      LIKE "%%%1$s%%" OR '.
+                           ' homepage LIKE "%%%1$s%%")', addslashes($q));
+        } else if ('identica_notices' === $this->table) {
+            $qry = sprintf('content LIKE "%%%1$s%%"', addslashes($q));
+        } else {
+            throw new ServerException('Unknown table: ' . $this->table);
+        }
+
+        $this->target->whereAdd($qry);
+
+        return true;
+    }
+}
+
 class PGSearch extends SearchEngine
 {
     function query($q)
@@ -138,6 +168,9 @@ class PGSearch extends SearchEngine
         if ('identica_people' === $this->table) {
             return $this->target->whereAdd('textsearch @@ plainto_tsquery(\''.addslashes($q).'\')');
         } else if ('identica_notices' === $this->table) {
+
+            // XXX: We need to filter out gateway notices (notice.is_local = -2) --Zach
+
             return $this->target->whereAdd('to_tsvector(\'english\', content) @@ plainto_tsquery(\''.addslashes($q).'\')');
         } else {
             throw new ServerException('Unknown table: ' . $this->table);
index e74450e11f3e06dee57ce04754d81c7ac02a6bd6..34fe9373f43832cc793a59484a62f7f007b05620 100644 (file)
@@ -12,7 +12,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -148,10 +148,10 @@ You can also try your search on other engines:
 * [Tweet scan](http://www.tweetscan.com/indexi.php?s=%s)
 * [Google](http://www.google.com/search?q=site%%3A%%%%site.server%%%%+%s)
 * [Yahoo](http://search.yahoo.com/search?p=site%%3A%%%%site.server%%%%+%s)
-
+* [Collecta](http://collecta.com/#q=%s)
 
 E_O_T
-), $qe, $qe, $qe, $qe);
+), $qe, $qe, $qe, $qe, $qe);
         $this->elementStart('dl', array('id' => 'help_search', 'class' => 'help'));
         $this->element('dt', null, _('Search help'));
         $this->elementStart('dd', 'instructions');
index 595dcf1470a438fa4c94151cfaf057a730f9b381..db735216685848daf9b436b8104a47c06e811dfe 100644 (file)
@@ -13,7 +13,7 @@
  * @link     http://laconi.ca/
  *
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index db20c580436b46e0ae4bc361d65013d2234d43d2..17d3a2f64dcb1853190ee0064b6a224d01342be0 100644 (file)
@@ -43,7 +43,7 @@ if (!defined('LACONICA')) {
  * @see      Widget
  */
 
-class SettingsAction extends Action
+class SettingsAction extends CurrentUserDesignAction
 {
     /**
      * A message for the user.
diff --git a/lib/snapshot.php b/lib/snapshot.php
new file mode 100644 (file)
index 0000000..4b05b50
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * A snapshot of site stats that can report itself to headquarters
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Stats
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * A snapshot of site stats that can report itself to headquarters
+ *
+ * This class will collect statistics on the site and report them to
+ * a statistics server of the admin's choice. (Default is the big one
+ * at laconi.ca.)
+ *
+ * It can either be called from a cron job, or run occasionally by the
+ * Web site.
+ *
+ * @category Stats
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ */
+
+class Snapshot
+{
+    var $stats = null;
+
+    /**
+     * Constructor for a snapshot
+     */
+
+    function __construct()
+    {
+    }
+
+    /**
+     * Static function for reporting statistics
+     *
+     * This function checks whether it should report statistics, based on
+     * the current configuation settings. If it should, it creates a new
+     * Snapshot object, takes a snapshot, and reports it to headquarters.
+     *
+     * @return void
+     */
+
+    static function check()
+    {
+        switch (common_config('snapshot', 'run')) {
+        case 'web':
+            // skip if we're not running on the Web.
+            if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) {
+                break;
+            }
+            // Run once every frequency hits
+            // XXX: do frequency by time (once a week, etc.) rather than
+            // hits
+            if (rand() % common_config('snapshot', 'frequency') == 0) {
+                $snapshot = new Snapshot();
+                $snapshot->take();
+                $snapshot->report();
+            }
+            break;
+        case 'cron':
+            // skip if we're running on the Web
+            if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+                break;
+            }
+            common_log(LOG_INFO, 'Running snapshot from cron job');
+            // We're running from the command line; assume
+
+            $snapshot = new Snapshot();
+            $snapshot->take();
+            common_log(LOG_INFO, count($snapshot->stats) . " statistics being uploaded.");
+            $snapshot->report();
+
+            break;
+        case 'never':
+            break;
+        default:
+            common_log(LOG_WARNING, "Unrecognized value for snapshot run config.");
+        }
+    }
+
+    /**
+     * Take a snapshot of the server
+     *
+     * Builds an array of statistical and configuration data based
+     * on the local database and config files. We avoid grabbing any
+     * information that could be personal or private.
+     *
+     * @return void
+     */
+
+    function take()
+    {
+        $this->stats = array();
+
+        // Some basic identification stuff
+
+        $this->stats['version']    = LACONICA_VERSION;
+        $this->stats['phpversion'] = phpversion();
+        $this->stats['name']       = common_config('site', 'name');
+        $this->stats['root']       = common_root_url();
+
+        // non-identifying stats on various tables. Primary
+        // interest is size and rate of activity of service.
+
+        $tables = array('user',
+                        'notice',
+                        'subscription',
+                        'remote_profile',
+                        'user_group');
+
+        foreach ($tables as $table) {
+            $this->tableStats($table);
+        }
+
+        // stats on some important config options
+
+        $this->stats['theme']     = common_config('site', 'theme');
+        $this->stats['dbtype']    = common_config('db', 'type');
+        $this->stats['xmpp']      = common_config('xmpp', 'enabled');
+        $this->stats['inboxes']   = common_config('inboxes', 'enabled');
+        $this->stats['queue']     = common_config('queue', 'enabled');
+        $this->stats['license']   = common_config('license', 'url');
+        $this->stats['fancy']     = common_config('site', 'fancy');
+        $this->stats['private']   = common_config('site', 'private');
+        $this->stats['closed']    = common_config('site', 'closed');
+        $this->stats['memcached'] = common_config('memcached', 'enabled');
+        $this->stats['language']  = common_config('site', 'language');
+        $this->stats['timezone']  = common_config('site', 'timezone');
+
+    }
+
+    /**
+     * Reports statistics to headquarters
+     *
+     * Posts statistics to a reporting server.
+     *
+     * @return void
+     */
+
+    function report()
+    {
+        // XXX: Use OICU2 and OAuth to make authorized requests
+
+        $postdata = http_build_query($this->stats);
+
+        $opts =
+          array('http' =>
+                array(
+                      'method'  => 'POST',
+                      'header'  => 'Content-type: '.
+                                   'application/x-www-form-urlencoded',
+                      'content' => $postdata,
+                      'user_agent' => 'Laconica/'.LACONICA_VERSION
+                      )
+                );
+
+        $context = stream_context_create($opts);
+
+        $reporturl = common_config('snapshot', 'reporturl');
+
+        $result = @file_get_contents($reporturl, false, $context);
+
+        return $result;
+    }
+
+    /**
+     * Updates statistics for a single table
+     *
+     * Determines the size of a table and its oldest and newest rows.
+     * Goal here is to see how active a site is. Note that it
+     * fills up the instance stats variable.
+     *
+     * @param string $table name of table to check
+     *
+     * @return void
+     */
+
+    function tableStats($table)
+    {
+        $inst = DB_DataObject::factory($table);
+
+        $inst->selectAdd();
+        $inst->selectAdd('count(*) as cnt, '.
+                         'min(created) as first, '.
+                         'max(created) as last');
+
+        if ($inst->find(true)) {
+            $this->stats[$table.'count'] = $inst->cnt;
+            $this->stats[$table.'first'] = $inst->first;
+            $this->stats[$table.'last']  = $inst->last;
+        }
+
+        $inst->free();
+        unset($inst);
+    }
+}
diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php
new file mode 100644 (file)
index 0000000..46baeb5
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Abstract class for queue managers
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QueueManager
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+require_once 'Stomp.php';
+
+class LiberalStomp extends Stomp
+{
+    function getSocket()
+    {
+        return $this->_socket;
+    }
+}
+
+class StompQueueManager
+{
+    var $server = null;
+    var $username = null;
+    var $password = null;
+    var $base = null;
+    var $con = null;
+
+    function __construct()
+    {
+        $this->server   = common_config('queue', 'stomp_server');
+        $this->username = common_config('queue', 'stomp_username');
+        $this->password = common_config('queue', 'stomp_password');
+        $this->base     = common_config('queue', 'queue_basename');
+    }
+
+    function _connect()
+    {
+        if (empty($this->con)) {
+            $this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'...");
+            $this->con = new LiberalStomp($this->server);
+
+            if ($this->con->connect($this->username, $this->password)) {
+                $this->_log(LOG_INFO, "Connected.");
+            } else {
+                $this->_log(LOG_ERR, 'Failed to connect to queue server');
+                throw new ServerException('Failed to connect to queue server');
+            }
+        }
+    }
+
+    function enqueue($object, $queue)
+    {
+        $notice = $object;
+
+        $this->_connect();
+
+        // XXX: serialize and send entire notice
+
+        $result = $this->con->send($this->_queueName($queue),
+                                   $notice->id,                // BODY of the message
+                                   array ('created' => $notice->created));
+
+        if (!$result) {
+            common_log(LOG_ERR, 'Error sending to '.$queue.' queue');
+            return false;
+        }
+
+        common_log(LOG_DEBUG, 'complete remote queueing notice ID = '
+                   . $notice->id . ' for ' . $queue);
+    }
+
+    function service($queue, $handler)
+    {
+        $result = null;
+
+        $this->_connect();
+
+        $this->con->setReadTimeout($handler->timeout());
+
+        $this->con->subscribe($this->_queueName($queue));
+
+        while (true) {
+
+            // Wait for something on one of our sockets
+
+            $stompsock = $this->con->getSocket();
+
+            $handsocks = $handler->getSockets();
+
+            $socks = array_merge(array($stompsock), $handsocks);
+
+            $read = $socks;
+            $write = array();
+            $except = array();
+
+            $ready = stream_select($read, $write, $except, $handler->timeout(), 0);
+
+            if ($ready === false) {
+                $this->_log(LOG_ERR, "Error selecting on sockets");
+            } else if ($ready > 0) {
+                if (in_array($stompsock, $read)) {
+                    $this->_handleNotice($queue, $handler);
+                }
+                $handler->idle(QUEUE_HANDLER_HIT_IDLE);
+            }
+        }
+
+        $this->con->unsubscribe($this->_queueName($queue));
+    }
+
+    function _handleNotice($queue, $handler)
+    {
+        $frame = $this->con->readFrame();
+
+        if (!empty($frame)) {
+            $notice = Notice::staticGet('id', $frame->body);
+
+            if (empty($notice)) {
+                $this->_log(LOG_WARNING, 'Got ID '. $frame->body .' for non-existent notice in queue '. $queue);
+                $this->con->ack($frame);
+            } else {
+                if ($handler->handle_notice($notice)) {
+                    $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
+                    $this->con->ack($frame);
+                } else {
+                    $this->_log(LOG_WARNING, 'Failed handling notice '. $notice->id .' posted at ' . $frame->headers['created']  . ' in queue '. $queue);
+                    // FIXME we probably shouldn't have to do
+                    // this kind of queue management ourselves
+                    $this->con->ack($frame);
+                    $this->enqueue($notice, $queue);
+                }
+                unset($notice);
+            }
+
+            unset($frame);
+        }
+    }
+
+    function _queueName($queue)
+    {
+        return common_config('queue', 'queue_basename') . $queue;
+    }
+
+    function _log($level, $msg)
+    {
+        common_log($level, 'StompQueueManager: '.$msg);
+    }
+}
diff --git a/lib/stream.php b/lib/stream.php
deleted file mode 100644 (file)
index 0cb9e0b..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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 show_notice_list($notice)
-    {
-        $nl = new NoticeList($notice);
-        return $nl->show();
-    }
-}
index 4a9b36ae8fa1a198c7d2cb7d2be8f8732c4b62cb..52099192324ada24ea0f5c0c10f7ba199612dd37 100644 (file)
@@ -100,7 +100,7 @@ class SubGroupNav extends Widget
                                          $this->user->nickname),
                                  $action == 'usergroups',
                                  'nav_usergroups');
-            if (!is_null($cur) && $this->user->id === $cur->id) {
+            if (common_config('invite', 'enabled') && !is_null($cur) && $this->user->id === $cur->id) {
                 $this->out->menuItem(common_local_url('invite'),
                                      _('Invite'),
                                      sprintf(_('Invite friends and colleagues to join you on %s'),
index 0e7b9ded522c07e55811fc313ad084e046221e5c..e76023752725344128009f3dc2af602c9d3d5b93 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -44,7 +44,6 @@ function subs_subscribe_user($user, $other_nickname)
 
 function subs_subscribe_to($user, $other)
 {
-
     if ($user->isSubscribed($other)) {
         return _('Already subscribed!.');
     }
@@ -60,12 +59,16 @@ function subs_subscribe_to($user, $other)
 
     subs_notify($other, $user);
 
-        $cache = common_memcache();
+    $cache = common_memcache();
 
     if ($cache) {
         $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
        }
 
+    $profile = $user->getProfile();
+
+    $profile->blowSubscriptionsCount();
+    $other->blowSubscribersCount();
 
     if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
         if (!$other->subscribeTo($user)) {
@@ -117,7 +120,6 @@ function subs_unsubscribe_user($user, $other_nickname)
 
 function subs_unsubscribe_to($user, $other)
 {
-
     if (!$user->isSubscribed($other))
         return _('Not subscribed!.');
 
@@ -139,6 +141,11 @@ function subs_unsubscribe_to($user, $other)
         $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
        }
 
+    $profile = $user->getProfile();
+
+    $profile->blowSubscriptionsCount();
+    $other->blowSubscribersCount();
+
     return true;
 }
 
diff --git a/lib/subscriptionlist.php b/lib/subscriptionlist.php
new file mode 100644 (file)
index 0000000..23da64c
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Widget to show a list of profiles
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Public
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @copyright 2008-2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/profilelist.php';
+
+/**
+ * Widget to show a list of subscriptions
+ *
+ * @category Public
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class SubscriptionList extends ProfileList
+{
+    /** Owner of this list */
+    var $owner = null;
+
+    function __construct($profile, $owner=null, $action=null)
+    {
+        parent::__construct($profile, $action);
+
+        $this->owner = $owner;
+    }
+
+    function newListItem($profile)
+    {
+        return new SubscriptionListItem($profile, $this->owner, $this->action);
+    }
+}
+
+class SubscriptionListItem extends ProfileListItem
+{
+    /** Owner of this list */
+    var $owner = null;
+
+    function __construct($profile, $owner, $action)
+    {
+        parent::__construct($profile, $action);
+
+        $this->owner = $owner;
+    }
+
+    function showProfile()
+    {
+        $this->startProfile();
+        $this->showAvatar();
+        $this->showFullName();
+        $this->showLocation();
+        $this->showHomepage();
+        $this->showBio();
+        // Relevant portion!
+        $this->showTags();
+        $this->endProfile();
+    }
+
+    function isOwn()
+    {
+        $user = common_current_user();
+        return (!empty($user) && ($this->owner->id == $user->id));
+    }
+
+    function showTags()
+    {
+        $tags = Profile_tag::getTags($this->owner->id, $this->profile->id);
+
+        $this->out->elementStart('dl', 'entity_tags');
+        $this->out->elementStart('dt');
+        if ($this->isOwn()) {
+            $this->out->element('a', array('href' => common_local_url('tagother',
+                                                                      array('id' => $this->profile->id))),
+                                _('Tags'));
+        } else {
+            $this->out->text(_('Tags'));
+        }
+        $this->out->elementEnd('dt');
+        $this->out->elementStart('dd');
+        if ($tags) {
+            $this->out->elementStart('ul', 'tags xoxo');
+            foreach ($tags as $tag) {
+                $this->out->elementStart('li');
+                $this->out->element('span', 'mark_hash', '#');
+                $this->out->element('a', array('rel' => 'tag',
+                                               'href' => common_local_url($this->action->trimmed('action'),
+                                                                          array('nickname' => $this->owner->nickname,
+                                                                                'tag' => $tag))),
+                                    $tag);
+                $this->out->elementEnd('li');
+            }
+            $this->out->elementEnd('ul');
+        } else {
+            $this->out->text(_('(none)'));
+        }
+        $this->out->elementEnd('dd');
+        $this->out->elementEnd('dl');
+    }
+}
index ff2aca6d6ec06eae34895ac3691fdb240aebcd80..62f7d8961282659f7c12a4b0db2b57840cfc74da 100644 (file)
@@ -114,7 +114,11 @@ class TagCloudSection extends Section
 
     function tagUrl($tag)
     {
-        return common_local_url('tag', array('tag' => $tag));
+        if ('showstream' === $this->out->trimmed('action')) {
+            return common_local_url('showstream', array('nickname' => $this->out->profile->nickname, 'tag' => $tag));
+        } else {
+            return common_local_url('tag', array('tag' => $tag));
+        }
     }
 
     function divId()
index 95030affed66a424de6f529c4bcdcf92990f3cfd..2fe6ab69b7799cc19fd683a5e6c27adbfb2f96ad 100644 (file)
@@ -43,10 +43,14 @@ if (!defined('LACONICA')) {
 
 function theme_file($relative, $theme=null)
 {
-    if (!$theme) {
+    if (empty($theme)) {
         $theme = common_config('site', 'theme');
     }
-    return INSTALLDIR.'/theme/'.$theme.'/'.$relative;
+    $dir = common_config('theme', 'dir');
+    if (empty($dir)) {
+        $dir = INSTALLDIR.'/theme';
+    }
+    return $dir.'/'.$theme.'/'.$relative;
 }
 
 /**
@@ -60,13 +64,31 @@ function theme_file($relative, $theme=null)
 
 function theme_path($relative, $theme=null)
 {
-    if (!$theme) {
+    if (empty($theme)) {
         $theme = common_config('site', 'theme');
     }
+
+    $path = common_config('theme', 'path');
+
+    if (empty($path)) {
+        $path = common_config('site', 'path') . '/theme/';
+    }
+
+    if ($path[strlen($path)-1] != '/') {
+        $path .= '/';
+    }
+
+    if ($path[0] != '/') {
+        $path = '/'.$path;
+    }
+
     $server = common_config('theme', 'server');
-    if ($server) {
-        return 'http://'.$server.'/'.$theme.'/'.$relative;
-    } else {
-        return common_path('theme/'.$theme.'/'.$relative);
+
+    if (empty($server)) {
+        $server = common_config('site', 'server');
     }
-}
\ No newline at end of file
+
+    // XXX: protocol
+
+    return 'http://'.$server.$path.$theme.'/'.$relative;
+}
index ccc6c93cae00730100cdd984b918ba571891be10..47af32e61f3221d72de65136e9b43805a31af504 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -346,7 +346,7 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
 function is_twitter_bound($notice, $flink) {
 
     // Check to see if notice should go to Twitter
-    if ($flink->noticesync & FOREIGN_NOTICE_SEND) {
+    if (!empty($flink) && ($flink->noticesync & 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) ||
@@ -360,14 +360,11 @@ function is_twitter_bound($notice, $flink) {
 
 function broadcast_twitter($notice)
 {
-    $success = true;
 
     $flink = Foreign_link::getByUserID($notice->profile_id,
         TWITTER_SERVICE);
 
-    // XXX: Not sure WHERE to check whether a notice should go to
-    // Twitter. Should we even put in the queue if it shouldn't? --Zach
-    if (!is_null($flink) && is_twitter_bound($notice, $flink)) {
+    if (is_twitter_bound($notice, $flink)) {
 
         $fuser = $flink->getForeignUser();
         $twitter_user = $fuser->nickname;
@@ -401,33 +398,99 @@ function broadcast_twitter($notice)
         curl_setopt_array($ch, $options);
         $data = curl_exec($ch);
         $errmsg = curl_error($ch);
+        $errno = curl_errno($ch);
 
-        if ($errmsg) {
-            common_debug("cURL error: $errmsg - " .
+        if (!empty($errmsg)) {
+            common_debug("cURL error ($errno): $errmsg - " .
                 "trying to send notice for $twitter_user.",
                          __FILE__);
-            $success = false;
+
+            $user = $flink->getUser();
+
+            if ($errmsg == 'The requested URL returned error: 401') {
+                common_debug(sprintf('User %s (user id: %s) ' .
+                    'has bad Twitter credentials!',
+                    $user->nickname, $user->id));
+
+                    // Bad credentials we need to delete the foreign_link
+                    // to Twitter and inform the user.
+
+                    remove_twitter_link($flink);
+
+                    return true;
+
+            } else {
+
+                // Some other error happened, so we should try to
+                // send again later
+
+                return false;
+            }
+
         }
 
         curl_close($ch);
 
-        if (!$data) {
+        if (empty($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);
+            // XXX: Not sure this represents a failure to send, but it
+            // probably does
 
-        if (!$status->id) {
-            common_debug("Unexpected data returned by Twitter " .
-                " API trying to send update for $twitter_user",
-                         __FILE__);
-            $success = false;
+            return false;
+
+        } else {
+
+            // Twitter should return a status
+            $status = json_decode($data);
+
+            if (empty($status)) {
+                common_debug("Unexpected data returned by Twitter " .
+                    " API trying to send update for $twitter_user",
+                        __FILE__);
+
+                // XXX: Again, this could represent a failure posting
+                // or the Twitter API might just be behaving flakey.
+                // We're treating it as a failure to post.
+
+                return false;
+            }
         }
     }
 
-    return $success;
+    return true;
+}
+
+function remove_twitter_link($flink)
+{
+    $user = $flink->getUser();
+
+    common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
+        "user $user->nickname (user id: $user->id).");
+
+    $result = $flink->delete();
+
+    if (empty($result)) {
+        common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
+            "Foreign_link for $user->nickname (user id: $user->id)!");
+        common_log_db_error($flink, 'DELETE', __FILE__);
+    }
+
+    // Notify the user that her Twitter bridge is down
+
+    $result = mail_twitter_bridge_removed($user);
+
+    if (!$result) {
+
+        $msg = 'Unable to send email to notify ' .
+            "$user->nickname (user id: $user->id) " .
+            'that their Twitter bridge link was ' .
+            'removed!';
+
+        common_log(LOG_WARNING, $msg);
+    }
+
 }
+
index ca8b03cdcd79f6230df08db5bce0495932cd8773..f48513e67f7d243140cdba2c19883dadc10cb2a0 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -17,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 class TwitterapiAction extends Action
 {
@@ -87,7 +89,7 @@ class TwitterapiAction extends Action
 
         $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
         $twitter_user['protected'] = false; # not supported by Laconica yet
-        $twitter_user['followers_count'] = $this->count_subscriptions($profile);
+        $twitter_user['followers_count'] = $profile->subscriberCount();
 
         // To be supported soon...
         $twitter_user['profile_background_color'] = '';
@@ -96,17 +98,11 @@ class TwitterapiAction extends Action
         $twitter_user['profile_sidebar_fill_color'] = '';
         $twitter_user['profile_sidebar_border_color'] = '';
 
-        $subbed = DB_DataObject::factory('subscription');
-        $subbed->subscriber = $profile->id;
-        $subbed_count = (int) $subbed->count() - 1;
-        $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
+        $twitter_user['friends_count'] = $profile->subscriptionCount();
 
         $twitter_user['created_at'] = $this->date_twitter($profile->created);
 
-        $faves = DB_DataObject::factory('fave');
-        $faves->user_id = $user->id;
-        $faves_count = (int) $faves->count();
-        $twitter_user['favourites_count'] = $faves_count; // British spelling!
+        $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
 
         // Need to pull up the user for some of this
         $user = User::staticGet($profile->id);
@@ -127,11 +123,7 @@ class TwitterapiAction extends Action
         $twitter_user['profile_background_image_url'] = '';
         $twitter_user['profile_background_tile'] = false;
 
-        $notices = DB_DataObject::factory('notice');
-        $notices->profile_id = $profile->id;
-        $notice_count = (int) $notices->count();
-
-        $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
+        $twitter_user['statuses_count'] = $profile->noticeCount();
 
         // Is the requesting user following this user?
         $twitter_user['following'] = false;
@@ -205,7 +197,6 @@ class TwitterapiAction extends Action
 
     function twitter_rss_entry_array($notice)
     {
-
         $profile = $notice->getProfile();
         $entry = array();
 
@@ -222,6 +213,19 @@ class TwitterapiAction extends Action
         $entry['updated'] = $entry['published'];
         $entry['author'] = $profile->getBestName();
 
+        # Enclosure
+        $attachments = $notice->attachments();
+        if($attachments){
+            $entry['enclosures']=array();
+            foreach($attachments as $attachment){
+                $enclosure=array();
+                $enclosure['url']=$attachment->url;
+                $enclosure['mimetype']=$attachment->mimetype;
+                $enclosure['size']=$attachment->size;
+                $entry['enclosures'][]=$enclosure;
+            }
+        }
+
         # RSS Item specific
         $entry['description'] = $entry['content'];
         $entry['pubDate'] = common_date_rfc2822($notice->created);
@@ -276,6 +280,67 @@ class TwitterapiAction extends Action
         return $twitter_dm;
     }
 
+    function twitter_relationship_array($source, $target)
+    {
+        $relationship = array();
+
+        $relationship['source'] =
+            $this->relationship_details_array($source, $target);
+        $relationship['target'] =
+            $this->relationship_details_array($target, $source);
+
+        return array('relationship' => $relationship);
+    }
+
+    function relationship_details_array($source, $target)
+    {
+        $details = array();
+
+        $details['screen_name'] = $source->nickname;
+        $details['followed_by'] = $target->isSubscribed($source);
+        $details['following'] = $source->isSubscribed($target);
+
+        $notifications = false;
+
+        if ($source->isSubscribed($target)) {
+
+            $sub = Subscription::pkeyGet(array('subscriber' =>
+                $source->id, 'subscribed' => $target->id));
+
+            if (!empty($sub)) {
+                $notifications = ($sub->jabber || $sub->sms);
+            }
+        }
+
+        $details['notifications_enabled'] = $notifications;
+        $details['blocking'] = $source->hasBlocked($target);
+        $details['id'] = $source->id;
+
+        return $details;
+    }
+
+    function show_twitter_xml_relationship($relationship)
+    {
+        $this->elementStart('relationship');
+
+        foreach($relationship as $element => $value) {
+            if ($element == 'source' || $element == 'target') {
+                $this->elementStart($element);
+                $this->show_xml_relationship_details($value);
+                $this->elementEnd($element);
+            }
+        }
+
+        $this->elementEnd('relationship');
+    }
+
+    function show_xml_relationship_details($details)
+    {
+        foreach($details as $element => $value) {
+            $this->element($element, null, $value);
+        }
+    }
+
     function show_twitter_xml_status($twitter_status)
     {
         $this->elementStart('status');
@@ -315,6 +380,13 @@ class TwitterapiAction extends Action
         $this->element('pubDate', null, $entry['pubDate']);
         $this->element('guid', null, $entry['guid']);
         $this->element('link', null, $entry['link']);
+
+        # RSS only supports 1 enclosure per item
+        if($entry['enclosures']){
+            $enclosure = $entry['enclosures'][0];
+            $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
+        }
+
         $this->elementEnd('item');
     }
 
@@ -521,11 +593,11 @@ class TwitterapiAction extends Action
     function init_document($type='xml')
     {
         switch ($type) {
-         case 'xml':
+        case 'xml':
             header('Content-Type: application/xml; charset=utf-8');
             $this->startXML();
             break;
-         case 'json':
+        case 'json':
             header('Content-Type: application/json; charset=utf-8');
 
             // Check for JSONP callback
@@ -534,16 +606,16 @@ class TwitterapiAction extends Action
                 print $callback . '(';
             }
             break;
-         case 'rss':
+        case 'rss':
             header("Content-Type: application/rss+xml; charset=utf-8");
             $this->init_twitter_rss();
             break;
-         case 'atom':
+        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.'));
+        default:
+            $this->clientError(_('Not a supported data format.'));
             break;
         }
 
@@ -553,10 +625,10 @@ class TwitterapiAction extends Action
     function end_document($type='xml')
     {
         switch ($type) {
-         case 'xml':
+        case 'xml':
             $this->endXML();
             break;
-         case 'json':
+        case 'json':
 
             // Check for JSONP callback
             $callback = $this->arg('callback');
@@ -564,20 +636,20 @@ class TwitterapiAction extends Action
                 print ')';
             }
             break;
-         case 'rss':
+        case 'rss':
             $this->end_twitter_rss();
             break;
-         case 'atom':
+        case 'atom':
             $this->end_twitter_rss();
             break;
-         default:
-            $this->client_error(_('Not a supported data format.'));
+        default:
+            $this->clientError(_('Not a supported data format.'));
             break;
         }
         return;
     }
 
-    function client_error($msg, $code = 400, $content_type = 'json')
+    function clientError($msg, $code = 400, $content_type = 'json')
     {
 
         static $status = array(400 => 'Bad Request',
@@ -657,14 +729,14 @@ class TwitterapiAction extends Action
     {
         $profile_array = $this->twitter_user_array($profile, true);
         switch ($content_type) {
-         case 'xml':
+        case 'xml':
             $this->show_twitter_xml_user($profile_array);
             break;
-         case 'json':
+        case 'json':
             $this->show_json_objects($profile_array);
             break;
-         default:
-            $this->client_error(_('Not a supported data format.'));
+        default:
+            $this->clientError(_('Not a supported data format.'));
             return;
         }
         return;
@@ -672,8 +744,8 @@ class TwitterapiAction extends Action
 
     function get_user($id, $apidata=null)
     {
-        if (!$id) {
-            
+        if (empty($id)) {
+
             // Twitter supports these other ways of passing the user ID
             if (is_numeric($this->arg('id'))) {
                 return User::staticGet($this->arg('id'));
@@ -681,7 +753,7 @@ class TwitterapiAction extends Action
                 $nickname = common_canonical_nickname($this->arg('id'));
                 return User::staticGet('nickname', $nickname);
             } else if ($this->arg('user_id')) {
-                // This is to ensure that a non-numeric user_id still 
+                // This is to ensure that a non-numeric user_id still
                 // overrides screen_name even if it doesn't get used
                 if (is_numeric($this->arg('user_id'))) {
                     return User::staticGet('id', $this->arg('user_id'));
@@ -693,7 +765,7 @@ class TwitterapiAction extends Action
                 // Fall back to trying the currently authenticated user
                 return $apidata['user'];
             }
-            
+
         } else if (is_numeric($id)) {
             return User::staticGet($id);
         } else {
@@ -720,13 +792,13 @@ class TwitterapiAction extends Action
     {
         $source_name = _($source);
         switch ($source) {
-         case 'web':
-         case 'xmpp':
-         case 'mail':
-         case 'omb':
-         case 'api':
+        case 'web':
+        case 'xmpp':
+        case 'mail':
+        case 'omb':
+        case 'api':
             break;
-         default:
+        default:
             $ns = Notice_source::staticGet($source);
             if ($ns) {
                 $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
@@ -736,4 +808,49 @@ class TwitterapiAction extends Action
         return $source_name;
     }
 
+    /**
+     * Returns query argument or default value if not found. Certain
+     * parameters used throughout the API are lightly scrubbed and
+     * bounds checked.  This overrides Action::arg().
+     *
+     * @param string $key requested argument
+     * @param string $def default value to return if $key is not provided
+     *
+     * @return var $var
+     */
+    function arg($key, $def=null)
+    {
+
+        // XXX: Do even more input validation/scrubbing?
+
+        if (array_key_exists($key, $this->args)) {
+            switch($key) {
+            case 'page':
+                $page = (int)$this->args['page'];
+                return ($page < 1) ? 1 : $page;
+            case 'count':
+                $count = (int)$this->args['count'];
+                if ($count < 1) {
+                    return 20;
+                } elseif ($count > 200) {
+                    return 200;
+                } else {
+                    return $count;
+                }
+            case 'since_id':
+                $since_id = (int)$this->args['since_id'];
+                return ($since_id < 1) ? 0 : $since_id;
+            case 'max_id':
+                $max_id = (int)$this->args['max_id'];
+                return ($max_id < 1) ? 0 : $max_id;
+            case 'since':
+                return strtotime($this->args['since']);
+            default:
+                return parent::arg($key, $def);
+            }
+        } else {
+            return $def;
+        }
+    }
+
 }
diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php
new file mode 100644 (file)
index 0000000..5154610
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * A queue manager interface for just doing things immediately
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  QueueManager
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+class UnQueueManager
+{
+    function enqueue($object, $queue)
+    {
+        $notice = $object;
+
+        switch ($queue)
+        {
+         case 'omb':
+            if ($this->_isLocal($notice)) {
+                require_once(INSTALLDIR.'/lib/omb.php');
+                omb_broadcast_remote_subscribers($notice);
+            }
+            break;
+         case 'public':
+            if ($this->_isLocal($notice)) {
+                require_once(INSTALLDIR.'/lib/jabber.php');
+                jabber_public_notice($notice);
+            }
+            break;
+         case 'twitter':
+            if ($this->_isLocal($notice)) {
+                broadcast_twitter($notice);
+            }
+            break;
+         case 'facebook':
+            if ($this->_isLocal($notice)) {
+                require_once INSTALLDIR . '/lib/facebookutil.php';
+                return facebookBroadcastNotice($notice);
+            }
+            break;
+         case 'ping':
+            if ($this->_isLocal($notice)) {
+                require_once INSTALLDIR . '/lib/ping.php';
+                return ping_broadcast_notice($notice);
+            }
+         case 'sms':
+            require_once(INSTALLDIR.'/lib/mail.php');
+            mail_broadcast_notice_sms($notice);
+            break;
+         case 'jabber':
+            require_once(INSTALLDIR.'/lib/jabber.php');
+            jabber_broadcast_notice($notice);
+            break;
+         default:
+            throw ServerException("UnQueueManager: Unknown queue: $type");
+        }
+    }
+
+    function _isLocal($notice)
+    {
+        return ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
+                $notice->is_local == NOTICE_LOCAL_NONPUBLIC);
+    }
+}
\ No newline at end of file
index 22308f432c79324f0aa71586c7ce86ee891d2de8..9e8ec41d2592a29f46f0aa62195d24ef5ea13fcb 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -114,7 +114,7 @@ function common_check_user($nickname, $password)
         return false;
     }
     $user = User::staticGet('nickname', $nickname);
-    if (is_null($user)) {
+    if (is_null($user) || $user === false) {
         return false;
     } else {
         if (0 == strcmp(common_munge_password($password, $user->id),
@@ -139,8 +139,22 @@ function common_have_session()
 
 function common_ensure_session()
 {
+    $c = null;
+    if (array_key_exists(session_name, $_COOKIE)) {
+        $c = $_COOKIE[session_name()];
+    }
     if (!common_have_session()) {
+        if (common_config('sessions', 'handle')) {
+            Session::setSaveHandler();
+        }
         @session_start();
+        if (!isset($_SESSION['started'])) {
+            $_SESSION['started'] = time();
+            if (!empty($c)) {
+                common_log(LOG_WARNING, 'Session cookie "' . $_COOKIE[session_name()] . '" ' .
+                           ' is set but started value is null');
+            }
+        }
     }
 }
 
@@ -395,7 +409,7 @@ function common_render_text($text)
     return $r;
 }
 
-function common_replace_urls_callback($text, $callback) {
+function common_replace_urls_callback($text, $callback, $notice_id = null) {
     // Start off with a regex
     $regex = '#'.
     '(?:'.
@@ -466,7 +480,11 @@ function common_replace_urls_callback($text, $callback) {
         $url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
 
         // Call user specified func
-        $modified_url = call_user_func($callback, $url);
+        if (empty($notice_id)) {
+            $modified_url = call_user_func($callback, $url);
+        } else {
+            $modified_url = call_user_func($callback, array($url, $notice_id));
+        }
 
         // Replace it!
         $start = mb_strpos($text, $url, $offset);
@@ -481,107 +499,79 @@ function common_linkify($url) {
     // It comes in special'd, so we unspecial it before passing to the stringifying
     // functions
     $url = htmlspecialchars_decode($url);
-    $display = $url;
-    $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
 
-    $attrs = array('href' => $url, 'rel' => 'external');
+    $canon = File_redirection::_canonUrl($url);
 
-    if ($longurl = common_longurl($url)) {
-        $attrs['title'] = $longurl;
+    $longurl_data = File_redirection::where($url);
+    if (is_array($longurl_data)) {
+        $longurl = $longurl_data['url'];
+    } elseif (is_string($longurl_data)) {
+        $longurl = $longurl_data;
+    } else {
+        throw new ServerException("Can't linkify url '$url'");
     }
 
-    return XMLStringer::estring('a', $attrs, $display);
-}
+    $attrs = array('href' => $canon, 'rel' => 'external');
 
-function common_longurl($short_url)
-{
-    $long_url = common_shorten_link($short_url, true);
-    if ($long_url === $short_url) return false;
-    return $long_url;
-}
+    $is_attachment = false;
+    $attachment_id = null;
+    $has_thumb = false;
 
-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] = common_replace_urls_callback($text, 'common_shorten_link');;
-}
-
-function common_shorten_link($url, $reverse = false)
-{
+    // Check to see whether there's a filename associated with this URL.
+    // If there is, it's an upload and qualifies as an attachment
 
-    static $url_cache = array();
-    if ($reverse) return isset($url_cache[$url]) ? $url_cache[$url] : $url;
+    $localfile = File::staticGet('url', $longurl);
 
-    $user = common_current_user();
-    if (!isset($user)) {
-      // common current user does not find a user when called from the XMPP daemon
-      // therefore we'll set one here fix, so that XMPP given URLs may be shortened
-      $user->urlshorteningservice = 'ur1.ca';
+    if (!empty($localfile)) {
+        if (isset($localfile->filename)) {
+            $is_attachment = true;
+            $attachment_id = $localfile->id;
+        }
     }
-    $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;
+    // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
+    // where ID is the id of the attachment for the given URL.
+    //
+    // we need a better test telling what can be shown as an attachment
+    // we're currently picking up oembeds only.
+    // I think the best option is another file_view table in the db
+    // and associated dbobject.
 
-     case '2tu.us':
-        $short_url_service = new TightUrl;
-        $short_url = $short_url_service->shorten($url);
-        break;
+    $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
+    $file = new File;
+    $file->query($query);
+    $file->fetch();
 
-     case 'ptiturl.com':
-        $short_url_service = new PtitUrl;
-        $short_url = $short_url_service->shorten($url);
-        break;
+    if (!empty($file->file_id)) {
+        $is_attachment = true;
+        $attachment_id = $file->file_id;
 
-     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;
+        $query = "select file_thumbnail.file_id as file_id from file join file_thumbnail on file.id = file_thumbnail.file_id where file.url='$longurl'";
+        $file2 = new File;
+        $file2->query($query);
+        $file2->fetch();
 
-     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;
+        if (!empty($file2)) {
+            $has_thumb = true;
+        }
     }
 
-    curl_close($curlh);
-
-    if ($short_url) {
-        $url_cache[(string)$short_url] = $url;
-        return (string)$short_url;
+    // Add clippy
+    if ($is_attachment) {
+        $attrs['class'] = 'attachment';
+        if ($has_thumb) {
+            $attrs['class'] = 'attachment thumbnail';
+        }
+        $attrs['id'] = "attachment-{$attachment_id}";
     }
-    return $url;
+
+    return XMLStringer::estring('a', $attrs, $url);
+}
+
+function common_shorten_links($text)
+{
+    if (mb_strlen($text) <= 140) return $text;
+    return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
 }
 
 function common_xml_safe_str($str)
@@ -644,7 +634,7 @@ function common_at_link($sender_id, $nickname)
 function common_group_link($sender_id, $nickname)
 {
     $sender = Profile::staticGet($sender_id);
-    $group = User_group::staticGet('nickname', common_canonical_nickname($nickname));
+    $group = User_group::getForNickname($nickname);
     if ($group && $sender->isMember($group)) {
         $attrs = array('href' => $group->permalink(),
                        'class' => 'url');
@@ -843,7 +833,12 @@ function common_date_iso8601($dt)
 
 function common_sql_now()
 {
-    return strftime('%Y-%m-%d %H:%M:%S', time());
+    return common_sql_date(time());
+}
+
+function common_sql_date($datetime)
+{
+    return strftime('%Y-%m-%d %H:%M:%S', $datetime);
 }
 
 function common_redirect($url, $code=307)
@@ -867,101 +862,43 @@ function common_redirect($url, $code=307)
 
 function common_broadcast_notice($notice, $remote=false)
 {
-    if (common_config('queue', 'enabled')) {
-        // Do it later!
-        return common_enqueue_notice($notice);
-    } else {
-        return common_real_broadcast($notice, $remote);
-    }
+    return common_enqueue_notice($notice);
 }
 
 // Stick the notice on the queue
 
 function common_enqueue_notice($notice)
 {
-    $transports = array('twitter', 'facebook', 'ping');
+    static $localTransports = array('omb',
+                                    'twitter',
+                                    'facebook',
+                                    'ping');
+    static $allTransports = array('sms');
 
-    // If inboxes are enabled, wait till inboxes are filled
-    // before doing inbox-dependent broadcasts
+    $transports = $allTransports;
 
-    $transports = array_merge($transports, common_post_inbox_transports());
+    $xmpp = common_config('xmpp', 'enabled');
 
-    foreach ($transports as $transport) {
-        common_enqueue_notice_transport($notice, $transport);
+    if ($xmpp) {
+        $transports[] = 'jabber';
     }
 
-    return $result;
-}
-
-function common_post_inbox_transports()
-{
-    $transports = array('omb', 'sms');
-
-    if (common_config('xmpp', 'enabled')) {
-        $transports = array_merge($transports, array('jabber', 'public'));
+    if ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
+        $notice->is_local == NOTICE_LOCAL_NONPUBLIC) {
+        $transports = array_merge($transports, $localTransports);
+        if ($xmpp) {
+            $transports[] = 'public';
+        }
     }
 
-    return $transports;
-}
+    $qm = QueueManager::get();
 
-function common_enqueue_notice_transport($notice, $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);
-        throw new ServerException('DB error inserting queue item: ' . $last_error->message);
+    foreach ($transports as $transport)
+    {
+        $qm->enqueue($notice, $transport);
     }
-    common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
-    return true;
-}
 
-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);
-        }
-    }
-    if ($success) {
-        $success = broadcast_twitter($notice);
-        if (!$success) {
-            common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id);
-        }
-    }
-
-    // XXX: Do a real-time FB broadcast here?
-
-    // XXX: broadcast notices to other IM
-    return $success;
+    return true;
 }
 
 function common_broadcast_profile($profile)
@@ -1041,20 +978,26 @@ function common_ensure_syslog()
 {
     static $initialized = false;
     if (!$initialized) {
-        openlog(common_config('syslog', 'appname'), 0, LOG_USER);
+        openlog(common_config('syslog', 'appname'), 0,
+            common_config('syslog', 'facility'));
         $initialized = true;
     }
 }
 
+function common_log_line($priority, $msg)
+{
+    static $syslog_priorities = array('LOG_EMERG', 'LOG_ALERT', 'LOG_CRIT', 'LOG_ERR',
+                                      'LOG_WARNING', 'LOG_NOTICE', 'LOG_INFO', 'LOG_DEBUG');
+    return date('Y-m-d H:i:s') . ' ' . $syslog_priorities[$priority] . ': ' . $msg . "\n";
+}
+
 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";
+            $output = common_log_line($priority, $msg);
             fwrite($log, $output);
             fclose($log);
         }
@@ -1085,6 +1028,9 @@ function common_log_objstring(&$object)
     if (is_null($object)) {
         return "null";
     }
+    if (!($object instanceof DB_DataObject)) {
+        return "(unknown)";
+    }
     $arr = $object->toArray();
     $fields = array();
     foreach ($arr as $k => $v) {
@@ -1124,7 +1070,7 @@ function common_accept_to_prefs($accept, $def = '*/*')
 
     foreach($parts as $part) {
         // FIXME: doesn't deal with params like 'text/html; level=1'
-        @list($value, $qpart) = explode(';', $part);
+        @list($value, $qpart) = explode(';', trim($part));
         $match = array();
         if(!isset($qpart)) {
             $prefs[$value] = 1;
@@ -1285,18 +1231,39 @@ function common_canonical_sms($sms)
 function common_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
 {
     switch ($errno) {
+
+     case E_ERROR:
+     case E_COMPILE_ERROR:
+     case E_CORE_ERROR:
      case E_USER_ERROR:
-        common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline)");
-        exit(1);
+     case E_PARSE:
+     case E_RECOVERABLE_ERROR:
+        common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline) [ABORT]");
+        die();
         break;
 
+     case E_WARNING:
+     case E_COMPILE_WARNING:
+     case E_CORE_WARNING:
      case E_USER_WARNING:
         common_log(LOG_WARNING, "[$errno] $errstr ($errfile:$errline)");
         break;
 
+     case E_NOTICE:
      case E_USER_NOTICE:
         common_log(LOG_NOTICE, "[$errno] $errstr ($errfile:$errline)");
         break;
+
+     case E_STRICT:
+     case E_DEPRECATED:
+     case E_USER_DEPRECATED:
+        // XXX: config variable to log this stuff, too
+        break;
+
+     default:
+        common_log(LOG_ERR, "[$errno] $errstr ($errfile:$errline) [UNKNOWN LEVEL, die()'ing]");
+        die();
+        break;
     }
 
     // FIXME: show error page if we're on the Web
@@ -1315,7 +1282,13 @@ function common_session_token()
 
 function common_cache_key($extra)
 {
-    return 'laconica:' . common_keyize(common_config('site', 'name')) . ':' . $extra;
+    $base_key = common_config('memcached', 'base');
+
+    if (empty($base_key)) {
+        $base_key = common_keyize(common_config('site', 'name'));
+    }
+
+    return 'laconica:' . $base_key . ':' . $extra;
 }
 
 function common_keyize($str)
@@ -1364,3 +1337,92 @@ function common_database_tablename($tablename)
   //table prefixes could be added here later
   return $tablename;
 }
+
+function common_shorten_url($long_url)
+{
+    $user = common_current_user();
+    if (empty($user)) {
+        // common current user does not find a user when called from the XMPP daemon
+        // therefore we'll set one here fix, so that XMPP given URLs may be shortened
+        $svc = 'ur1.ca';
+    } else {
+        $svc = $user->urlshorteningservice;
+    }
+
+    $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($svc) {
+     case 'ur1.ca':
+        require_once INSTALLDIR.'/lib/Shorturl_api.php';
+        $short_url_service = new LilUrl;
+        $short_url = $short_url_service->shorten($long_url);
+        break;
+
+     case '2tu.us':
+        $short_url_service = new TightUrl;
+        require_once INSTALLDIR.'/lib/Shorturl_api.php';
+        $short_url = $short_url_service->shorten($long_url);
+        break;
+
+     case 'ptiturl.com':
+        require_once INSTALLDIR.'/lib/Shorturl_api.php';
+        $short_url_service = new PtitUrl;
+        $short_url = $short_url_service->shorten($long_url);
+        break;
+
+     case 'bit.ly':
+        curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_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($long_url));
+        $short_url = curl_exec($curlh);
+        break;
+     case 'snipr.com':
+        curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url));
+        $short_url = curl_exec($curlh);
+        break;
+     case 'metamark.net':
+        curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url));
+        $short_url = curl_exec($curlh);
+        break;
+     case 'tinyurl.com':
+        curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url));
+        $short_url = curl_exec($curlh);
+        break;
+     default:
+        $short_url = false;
+    }
+
+    curl_close($curlh);
+
+    return $short_url;
+}
+
+function common_client_ip()
+{
+    if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) {
+        return null;
+    }
+
+    if ($_SERVER['HTTP_X_FORWARDED_FOR']) {
+        if ($_SERVER['HTTP_CLIENT_IP']) {
+            $proxy = $_SERVER['HTTP_CLIENT_IP'];
+        } else {
+            $proxy = $_SERVER['REMOTE_ADDR'];
+        }
+        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
+    } else {
+        if ($_SERVER['HTTP_CLIENT_IP']) {
+            $ip = $_SERVER['HTTP_CLIENT_IP'];
+        } else {
+            $ip = $_SERVER['REMOTE_ADDR'];
+        }
+    }
+
+    return array($ip, $proxy);
+}
diff --git a/lib/webcolor.php b/lib/webcolor.php
new file mode 100644 (file)
index 0000000..f3ca6e9
--- /dev/null
@@ -0,0 +1,192 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Base class for deleting things
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Personal
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+     exit(1);
+}
+
+class WebColor {
+
+    // XXX: Maybe make getters and setters for r,g,b values and tuples,
+    // e.g.: to support this kinda CSS representation: rgb(255,0,0)
+    // http://www.w3.org/TR/CSS21/syndata.html#color-units
+
+    var $red   = 0;
+    var $green = 0;
+    var $blue  = 0;
+
+    /**
+     * Constructor
+     *
+     * @return nothing
+     */
+
+    function __construct($color = null)
+    {
+        if (isset($color)) {
+            $this->parseColor($color);
+        }
+    }
+
+    /**
+     * Parses input to and tries to determine whether the color
+     * is being specified via an integer or hex tuple and sets
+     * the RGB instance variables accordingly.
+     *
+     * XXX: Maybe support (r,g,b) style, and array?
+     *
+     * @param mixed $color
+     *
+     * @return nothing
+     */
+
+    function parseColor($color) {
+
+        if (is_numeric($color)) {
+            $this->setIntColor($color);
+        } else {
+
+            // XXX named colors
+
+            // XXX: probably should do even more validation
+
+            if (preg_match('/(#([0-9A-Fa-f]{3,6})\b)/u', $color) > 0) {
+                $this->setHexColor($color);
+            } else {
+                $errmsg = _('%s is not a valid color!');
+                throw new WebColorException(sprintf($errmsg, $color));
+            }
+        }
+    }
+
+    /**
+     * @param string $name
+     *
+     * @return nothing
+     */
+
+    function setNamedColor($name)
+    {
+        // XXX Implement this
+    }
+
+
+    /**
+     * Sets the RGB color values from a a hex tuple
+     *
+     * @param string $hexcolor
+     *
+     * @return nothing
+     */
+
+    function setHexColor($hexcolor) {
+
+        if ($hexcolor[0] == '#') {
+            $hexcolor = substr($hexcolor, 1);
+        }
+
+        if (strlen($hexcolor) == 6) {
+            list($r, $g, $b) = array($hexcolor[0].$hexcolor[1],
+                                     $hexcolor[2].$hexcolor[3],
+                                     $hexcolor[4].$hexcolor[5]);
+        } elseif (strlen($hexcolor) == 3) {
+            list($r, $g, $b) = array($hexcolor[0].$hexcolor[0],
+                                     $hexcolor[1].$hexcolor[1],
+                                     $hexcolor[2].$hexcolor[2]);
+        } else {
+            $errmsg = _('%s is not a valid color! Use 3 or 6 hex chars.');
+            throw new WebColorException(sprintf($errmsg, $hexcolor));
+        }
+
+        $this->red   = hexdec($r);
+        $this->green = hexdec($g);
+        $this->blue  = hexdec($b);
+
+    }
+
+    /**
+     * Sets the RGB color values from a 24-bit integer
+     *
+     * @param int $intcolor
+     *
+     * @return nothing
+     */
+
+    function setIntColor($intcolor)
+    {
+        // We could do 32 bit and have an alpha channel because
+        // Sarven wants one real bad, but nah.
+
+        $this->red   = $intcolor >> 16;
+        $this->green = $intcolor >> 8 & 0xFF;
+        $this->blue  = $intcolor & 0xFF;
+
+    }
+
+    /**
+     * Returns a hex tuple of the RGB color useful for output in HTML
+     *
+     * @return string
+     */
+
+    function hexValue() {
+
+        $hexcolor  = (strlen(dechex($this->red)) < 2 ? '0' : '' ) .
+            dechex($this->red);
+        $hexcolor .= (strlen(dechex($this->green)) < 2 ? '0' : '') .
+            dechex($this->green);
+        $hexcolor .= (strlen(dechex($this->blue)) < 2 ? '0' : '') .
+            dechex($this->blue);
+
+        return strtoupper($hexcolor);
+
+    }
+
+    /**
+     * Returns a 24-bit packed integer representation of the RGB color
+     * for convenient storage in the DB
+     *
+     * XXX: probably could just use hexdec() instead
+     *
+     * @return int
+     */
+
+    function intValue()
+    {
+        $intcolor = 256 * 256 * $this->red + 256 * $this->green + $this->blue;
+        return $intcolor;
+    }
+
+}
+
+class WebColorException extends Exception
+{
+}
+
+?>
\ No newline at end of file
index 91015fd4503f4875d489807a8b7bdf5642c561ee..77d476c30e12217d1d3e8fe7edd54dc3be406f37 100644 (file)
@@ -1,7 +1,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
@@ -21,8 +21,10 @@ if (!defined('LACONICA')) { exit(1); }
 
 require_once(INSTALLDIR.'/lib/queuehandler.php');
 
+define('PING_INTERVAL', 120);
+
 /**
- * Common superclass for all XMPP-using queue handlers. They all need to 
+ * 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.
@@ -30,12 +32,14 @@ require_once(INSTALLDIR.'/lib/queuehandler.php');
 
 class XmppQueueHandler extends QueueHandler
 {
-    
+    var $pingid = 0;
+    var $lastping = null;
+
     function start()
     {
         # Low priority; we don't want to receive messages
         $this->log(LOG_INFO, "INITIALIZE");
-        $this->conn = jabber_connect($this->_id);
+        $this->conn = jabber_connect($this->_id.$this->transport());
         if ($this->conn) {
             $this->conn->addEventHandler('message', 'forward_message', $this);
             $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
@@ -44,7 +48,12 @@ class XmppQueueHandler extends QueueHandler
         }
         return !is_null($this->conn);
     }
-    
+
+    function timeout()
+    {
+        return 10;
+    }
+
     function handle_reconnect(&$pl)
     {
         $this->conn->processUntil('session_start');
@@ -56,14 +65,36 @@ class XmppQueueHandler extends QueueHandler
         # Process the queue for as long as needed
         try {
             if ($this->conn) {
+                $this->log(LOG_DEBUG, "Servicing the XMPP queue.");
                 $this->conn->processTime($timeout);
+                $now = time();
+                if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) {
+                    $this->sendPing();
+                    $this->lastping = $now;
+                }
             }
         } catch (XMPPHP_Exception $e) {
             $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
             die($e->getMessage());
         }
     }
-    
+
+    function sendPing()
+    {
+        $jid = jabber_daemon_address().'/'.$this->_id.$this->transport();
+        $server = common_config('xmpp', 'server');
+
+        if (!isset($this->pingid)) {
+            $this->pingid = 0;
+        } else {
+            $this->pingid++;
+        }
+
+        $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
+
+               $this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
+    }
+
     function forward_message(&$pl)
     {
         if ($pl['type'] != 'chat') {
@@ -92,7 +123,12 @@ class XmppQueueHandler extends QueueHandler
         if (common_config('xmpp', 'listener')) {
             return common_config('xmpp', 'listener');
         } else {
-            return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener';
+            return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon';
         }
     }
+
+    function getSockets()
+    {
+        return array($this->conn->getSocket());
+    }
 }
diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php
new file mode 100644 (file)
index 0000000..6eb09c3
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Menu for login group of actions
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Menu
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/widget.php';
+
+/**
+ * Menu for login group of actions
+ *
+ * @category Output
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ * @see      Widget
+ */
+
+class FBCLoginGroupNav extends Widget
+{
+    var $action = null;
+
+    /**
+     * Construction
+     *
+     * @param Action $action current action, used for output
+     */
+
+    function __construct($action=null)
+    {
+        parent::__construct($action);
+        $this->action = $action;
+    }
+
+    /**
+     * Show the menu
+     *
+     * @return void
+     */
+
+    function show()
+    {
+        $this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
+        $this->action->element('dt', null, _('Local views'));
+        $this->action->elementStart('dd');
+
+        // action => array('prompt', 'title')
+        $menu = array();
+
+        $menu['login'] = array(_('Login'),
+                         _('Login with a username and password'));
+
+        if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
+            $menu['register'] = array(_('Register'),
+                                _('Sign up for a new account'));
+        }
+
+        $menu['openidlogin'] = array(_('OpenID'),
+                               _('Login or register with OpenID'));
+
+        $menu['FBConnectLogin'] = array(_('Facebook'),
+                               _('Login or register using Facebook'));
+
+        $action_name = $this->action->trimmed('action');
+        $this->action->elementStart('ul', array('class' => 'nav'));
+
+        foreach ($menu as $menuaction => $menudesc) {
+            $this->action->menuItem(common_local_url($menuaction),
+                                    $menudesc[0],
+                                    $menudesc[1],
+                                    $action_name === $menuaction);
+        }
+
+        $this->action->elementEnd('ul');
+
+        $this->action->elementEnd('dd');
+        $this->action->elementEnd('dl');
+    }
+}
diff --git a/plugins/FBConnect/FBCSettingsNav.php b/plugins/FBConnect/FBCSettingsNav.php
new file mode 100644 (file)
index 0000000..8b84118
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Menu for login group of actions
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Menu
+ * @package   Laconica
+ * @author    Evan Prodromou <evan@controlyourself.ca>
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/widget.php';
+
+/**
+ * A widget for showing the connect group local nav menu
+ *
+ * @category Output
+ * @package  Laconica
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ *
+ * @see      Widget
+ */
+
+class FBCSettingsNav extends Widget
+{
+    var $action = null;
+
+    /**
+     * Construction
+     *
+     * @param Action $action current action, used for output
+     */
+
+    function __construct($action=null)
+    {
+        parent::__construct($action);
+        $this->action = $action;
+    }
+
+    /**
+     * Show the menu
+     *
+     * @return void
+     */
+
+    function show()
+    {
+
+        $this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
+        $this->action->element('dt', null, _('Local views'));
+        $this->action->elementStart('dd');
+
+        # action => array('prompt', 'title')
+        $menu =
+          array('imsettings' =>
+                array(_('IM'),
+                      _('Updates by instant messenger (IM)')),
+                'smssettings' =>
+                array(_('SMS'),
+                      _('Updates by SMS')),
+                'twittersettings' =>
+                array(_('Twitter'),
+                      _('Twitter integration options')),
+                'FBConnectSettings' =>
+                array(_('Facebook'),
+                      _('Facebook Connect settings')));
+
+        $action_name = $this->action->trimmed('action');
+        $this->action->elementStart('ul', array('class' => 'nav'));
+
+        foreach ($menu as $menuaction => $menudesc) {
+            if ($menuaction == 'imsettings' &&
+                !common_config('xmpp', 'enabled')) {
+                continue;
+            }
+            $this->action->menuItem(common_local_url($menuaction),
+                                   $menudesc[0],
+                                   $menudesc[1],
+                                   $action_name === $menuaction);
+        }
+
+        $this->action->elementEnd('ul');
+
+        $this->action->elementEnd('dd');
+        $this->action->elementEnd('dl');
+    }
+}
diff --git a/plugins/FBConnect/FBC_XDReceiver.php b/plugins/FBConnect/FBC_XDReceiver.php
new file mode 100644 (file)
index 0000000..57c98b4
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/*
+ * Generates the cross domain communication channel file
+ * (xd_receiver.html). By generating it we can add some caching
+ * instructions.
+ *
+ * See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
+ */
+class FBC_XDReceiverAction extends Action
+{
+
+    /**
+     * Do we need to write to the database?
+     *
+     * @return boolean true
+     */
+
+    function isReadonly()
+    {
+        return true;
+    }
+
+    /**
+     * Handle a request
+     *
+     * @param array $args Arguments from $_REQUEST
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        // Parent handling, including cache check
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function showPage()
+    {
+        // cache the xd_receiver
+        header('Cache-Control: max-age=225065900');
+        header('Expires:');
+        header('Pragma:');
+
+        $this->startXML('html',
+                        '-//W3C//DTD XHTML 1.0 Strict//EN',
+                        'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+
+        $language = $this->getLanguage();
+
+        $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
+                                          'xml:lang' => $language,
+                                          'lang' => $language));
+        $this->elementStart('head');
+        $this->element('title', null, 'cross domain receiver page');
+        $this->element('script',
+            array('src' =>
+                'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js',
+                'type' => 'text/javascript'), '');
+        $this->elementEnd('head');
+        $this->elementStart('body');
+        $this->elementEnd('body');
+
+        $this->elementEnd('html');
+    }
+
+}
+
diff --git a/plugins/FBConnect/FBConnectAuth.php b/plugins/FBConnect/FBConnectAuth.php
new file mode 100644 (file)
index 0000000..3cf9fef
--- /dev/null
@@ -0,0 +1,454 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to enable Facebook Connect
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
+
+class FBConnectauthAction extends Action
+{
+
+    var $fbuid      = null;
+    var $fb_fields  = null;
+
+    function prepare($args) {
+        parent::prepare($args);
+
+        try {
+
+            $this->fbuid = getFacebook()->get_loggedin_user();
+
+            if ($this->fbuid > 0) {
+                $this->fb_fields = $this->getFacebookFields($this->fbuid,
+                    array('first_name', 'last_name', 'name'));
+            } else {
+                common_debug("No Facebook User found.");
+            }
+
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, 'Problem getting Facebook uid: ' .
+                $e->getMessage());
+        }
+
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (common_is_real_login()) {
+
+            // User is already logged in.  Does she already have a linked Facebook acct?
+            $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
+
+            if (!empty($flink)) {
+
+                // User already has a linked Facebook account and shouldn't be here
+                common_debug('There is already a local user (' . $flink->user_id .
+                    ') linked with this Facebook (' . $this->fbuid . ').');
+
+                // We don't want these cookies
+                getFacebook()->clear_cookie_state();
+
+                $this->clientError(_('There is already a local user linked with this Facebook.'));
+
+            } else {
+
+                // User came from the Facebook connect settings tab, and
+                // probably just wants to link/relink their Facebook account
+                $this->connectUser();
+            }
+
+        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->showForm(_('There was a problem with your session token. Try again, please.'));
+                return;
+            }
+            if ($this->arg('create')) {
+                if (!$this->boolean('license')) {
+                    $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
+                                    $this->trimmed('newname'));
+                    return;
+                }
+                $this->createNewUser();
+            } else if ($this->arg('connect')) {
+                $this->connectNewUser();
+            } else {
+                common_debug(print_r($this->args, true), __FILE__);
+                $this->showForm(_('Something weird happened.'),
+                                $this->trimmed('newname'));
+            }
+        } else {
+            $this->tryLogin();
+        }
+    }
+
+    function showPageNotice()
+    {
+        if ($this->error) {
+            $this->element('div', array('class' => 'error'), $this->error);
+        } else {
+            $this->element('div', 'instructions',
+                           sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
+        }
+    }
+
+    function title()
+    {
+        return _('Facebook Account Setup');
+    }
+
+    function showForm($error=null, $username=null)
+    {
+        $this->error = $error;
+        $this->username = $username;
+
+        $this->showPage();
+    }
+
+    function showPage()
+    {
+        parent::showPage();
+    }
+
+    function showContent()
+    {
+        if (!empty($this->message_text)) {
+            $this->element('p', null, $this->message);
+            return;
+        }
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_facebook_connect',
+                                          'class' => 'form_settings',
+                                          'action' => common_local_url('FBConnectAuth')));
+        $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
+        $this->element('legend', null, _('Connection options'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->element('input', array('type' => 'checkbox',
+                                      'id' => 'license',
+                                      'class' => 'checkbox',
+                                      'name' => 'license',
+                                      'value' => 'true'));
+        $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
+        $this->text(_('My text and files are available under '));
+        $this->element('a', array('href' => common_config('license', 'url')),
+                       common_config('license', 'title'));
+        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
+        $this->elementEnd('label');
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+        $this->element('legend', null,
+                       _('Create new account'));
+        $this->element('p', null,
+                       _('Create a new user with this nickname.'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('newname', _('New nickname'),
+                     ($this->username) ? $this->username : '',
+                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('create', _('Create'));
+        $this->elementEnd('fieldset');
+
+        $this->elementStart('fieldset');
+        $this->element('legend', null,
+                       _('Connect existing account'));
+        $this->element('p', null,
+                       _('If you already have an account, login with your username and password to connect it to your Facebook.'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('nickname', _('Existing nickname'));
+        $this->elementEnd('li');
+        $this->elementStart('li');
+        $this->password('password', _('Password'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('connect', _('Connect'));
+        $this->elementEnd('fieldset');
+
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+
+    function message($msg)
+    {
+        $this->message_text = $msg;
+        $this->showPage();
+    }
+
+    function createNewUser()
+    {
+
+        if (common_config('site', 'closed')) {
+            $this->clientError(_('Registration not allowed.'));
+            return;
+        }
+
+        $invite = null;
+
+        if (common_config('site', 'inviteonly')) {
+            $code = $_SESSION['invitecode'];
+            if (empty($code)) {
+                $this->clientError(_('Registration not allowed.'));
+                return;
+            }
+
+            $invite = Invitation::staticGet($code);
+
+            if (empty($invite)) {
+                $this->clientError(_('Not a valid invitation code.'));
+                return;
+            }
+        }
+
+        $nickname = $this->trimmed('newname');
+
+        if (!Validate::string($nickname, array('min_length' => 1,
+                                               'max_length' => 64,
+                                               'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
+            $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+            return;
+        }
+
+        if (!User::allowed_nickname($nickname)) {
+            $this->showForm(_('Nickname not allowed.'));
+            return;
+        }
+
+        if (User::staticGet('nickname', $nickname)) {
+            $this->showForm(_('Nickname already in use. Try another one.'));
+            return;
+        }
+
+        $fullname = trim($this->fb_fields['firstname'] .
+            ' ' . $this->fb_fields['lastname']);
+
+        $args = array('nickname' => $nickname, 'fullname' => $fullname);
+
+        if (!empty($invite)) {
+            $args['code'] = $invite->code;
+        }
+
+        $user = User::register($args);
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_set_user($user);
+        common_real_login(true);
+
+        common_debug("Registered new user $user->id from Facebook user $this->fbuid");
+
+        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
+                        303);
+    }
+
+    function connectNewUser()
+    {
+        $nickname = $this->trimmed('nickname');
+        $password = $this->trimmed('password');
+
+        if (!common_check_user($nickname, $password)) {
+            $this->showForm(_('Invalid username or password.'));
+            return;
+        }
+
+        $user = User::staticGet('nickname', $nickname);
+
+        if ($user) {
+            common_debug("Legit user to connect to Facebook: $nickname");
+        }
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_debug("Connected Facebook user $this->fbuid to local user $user->id");
+
+        common_set_user($user);
+        common_real_login(true);
+
+        $this->goHome($user->nickname);
+    }
+
+    function connectUser()
+    {
+        $user = common_current_user();
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_debug("Connected Facebook user $this->fbuid to local user $user->id");
+
+        // Return to Facebook connection settings tab
+        common_redirect(common_local_url('FBConnectSettings'), 303);
+    }
+
+    function tryLogin()
+    {
+        common_debug("Trying Facebook Login...");
+
+        $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
+
+        if ($flink) {
+            $user = $flink->getUser();
+
+            if (!empty($user)) {
+
+                common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
+
+                common_set_user($user);
+                common_real_login(true);
+                $this->goHome($user->nickname);
+            }
+
+        } else {
+
+            common_debug("No flink found for fbuid: $this->fbuid");
+
+            $this->showForm(null, $this->bestNewNickname());
+        }
+    }
+
+    function goHome($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, 303);
+    }
+
+    function flinkUser($user_id, $fbuid)
+    {
+        $flink = new Foreign_link();
+        $flink->user_id = $user_id;
+        $flink->foreign_id = $fbuid;
+        $flink->service = FACEBOOK_CONNECT_SERVICE;
+        $flink->created = common_sql_now();
+
+        $flink_id = $flink->insert();
+
+        return $flink_id;
+    }
+
+    function bestNewNickname()
+    {
+        if (!empty($this->fb_fields['name'])) {
+            $nickname = $this->nicknamize($this->fb_fields['name']);
+            if ($this->isNewNickname($nickname)) {
+                return $nickname;
+            }
+        }
+
+        // Try the full name
+
+        $fullname = trim($this->fb_fields['firstname'] .
+            ' ' . $this->fb_fields['lastname']);
+
+        if (!empty($fullname)) {
+            $fullname = $this->nicknamize($fullname);
+            if ($this->isNewNickname($fullname)) {
+                return $fullname;
+            }
+        }
+
+        return null;
+    }
+
+     // Given a string, try to make it work as a nickname
+
+     function nicknamize($str)
+     {
+         $str = preg_replace('/\W/', '', $str);
+         return strtolower($str);
+     }
+
+    function isNewNickname($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;
+    }
+
+    // XXX: Consider moving this to lib/facebookutil.php
+    function getFacebookFields($fb_uid, $fields) {
+        try {
+
+            $facebook = getFacebook();
+
+            $infos = $facebook->api_client->users_getInfo($fb_uid, $fields);
+
+            if (empty($infos)) {
+                return null;
+            }
+            return reset($infos);
+
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, "Facebook client failure when requesting " .
+                join(",", $fields) . " on uid " . $fb_uid .
+                    " : ". $e->getMessage());
+            return null;
+        }
+    }
+
+}
diff --git a/plugins/FBConnect/FBConnectLogin.php b/plugins/FBConnect/FBConnectLogin.php
new file mode 100644 (file)
index 0000000..205086c
--- /dev/null
@@ -0,0 +1,67 @@
+<?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 . '/plugins/FBConnect/FBConnectPlugin.php';
+
+class FBConnectLoginAction extends Action
+{
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (common_is_real_login()) {
+            $this->clientError(_('Already logged in.'));
+        }
+
+        $this->showPage();
+    }
+
+    function getInstructions()
+    {
+        return _('Login with your Facebook Account');
+    }
+
+    function showPageNotice()
+    {
+        $instr = $this->getInstructions();
+        $output = common_markup_to_html($instr);
+        $this->elementStart('div', 'instructions');
+        $this->raw($output);
+        $this->elementEnd('div');
+    }
+
+    function title()
+    {
+        return _('Facebook Login');
+    }
+
+    function showContent() {
+
+        $this->elementStart('fieldset');
+        $this->element('fb:login-button', array('onlogin' => 'goto_login()',
+            'length' => 'long'));
+
+        $this->elementEnd('fieldset');
+    }
+
+}
diff --git a/plugins/FBConnect/FBConnectPlugin.css b/plugins/FBConnect/FBConnectPlugin.css
new file mode 100644 (file)
index 0000000..e526754
--- /dev/null
@@ -0,0 +1,36 @@
+/** Styles for Facebook logo and Facebook user profile avatar.
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+#site_nav_global_primary #nav_fb {
+position:relative;
+margin-left:18px;
+}
+
+#nav_fb #fbc_profile-pic {
+position:absolute;
+top:-3px;
+left:-18px;
+display:inline;
+border:1px solid #3B5998;
+padding:1px;
+}
+
+#nav_fb #fb_favicon {
+position:absolute;
+top:-13px;
+left:-25px;
+display:inline;
+}
+
+#settings_facebook_connect_options legend {
+display:none;
+}
+#form_settings_facebook_connect fieldset fieldset legend {
+display:block;
+}
diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php
new file mode 100644 (file)
index 0000000..65870a1
--- /dev/null
@@ -0,0 +1,395 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Plugin to enable Facebook Connect
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+define("FACEBOOK_CONNECT_SERVICE", 3);
+
+require_once INSTALLDIR . '/lib/facebookutil.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
+
+/**
+ * Plugin to enable Facebook Connect
+ *
+ * @category Plugin
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class FBConnectPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    // Hook in new actions
+    function onRouterInitialized(&$m) {
+        $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
+        $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
+        $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
+        $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
+     }
+
+    // Add in xmlns:fb
+    function onStartShowHTML($action)
+    {
+
+        if ($this->reqFbScripts($action)) {
+
+            // XXX: Horrible hack to make Safari, FF2, and Chrome work with
+            // Facebook Connect. These browser cannot use Facebook's
+            // DOM parsing routines unless the mime type of the page is
+            // text/html even though Facebook Connect uses XHTML.  This is
+            // A bug in Facebook Connect, and this is a temporary solution
+            // until they fix their JavaScript libs.
+            header('Content-Type: text/html');
+
+            $action->extraHeaders();
+
+            $action->startXML('html',
+                '-//W3C//DTD XHTML 1.0 Strict//EN',
+                'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+
+            $language = $action->getLanguage();
+
+            $action->elementStart('html',
+                array('xmlns'  => 'http://www.w3.org/1999/xhtml',
+                      'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
+                      'xml:lang' => $language,
+                      'lang'     => $language));
+
+            return false;
+
+        } else {
+
+            return true;
+        }
+    }
+
+    // Note: this script needs to appear in the <body>
+
+    function onStartShowHeader($action)
+    {
+        if ($this->reqFbScripts($action)) {
+
+            $apikey = common_config('facebook', 'apikey');
+            $plugin_path = common_path('plugins/FBConnect');
+
+            $login_url = common_local_url('FBConnectAuth');
+            $logout_url = common_local_url('logout');
+
+            // XXX: Facebook says we don't need this FB_RequireFeatures(),
+            // but we actually do, for IE and Safari. Gar.
+
+            $html = sprintf('<script type="text/javascript">
+                                window.onload = function () {
+                                    FB_RequireFeatures(
+                                        ["XFBML"],
+                                            function() {
+                                                FB.Facebook.init("%s", "../xd_receiver.html");
+                                            }
+                                        ); }
+
+                                function goto_login() {
+                                    window.location = "%s";
+                                }
+
+                                function goto_logout() {
+                                    window.location = "%s";
+                                }
+                              </script>', $apikey,
+                                  $login_url, $logout_url);
+
+            $action->raw($html);
+        }
+
+    }
+
+    // Note: this script needs to appear as close as possible to </body>
+
+    function onEndShowFooter($action)
+    {
+        if ($this->reqFbScripts($action)) {
+
+            $action->element('script',
+                array('type' => 'text/javascript',
+                      'src'  => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
+                      '');
+        }
+    }
+
+    function onEndShowLaconicaStyles($action)
+    {
+
+        if ($this->reqFbScripts($action)) {
+
+            $action->element('link', array('rel' => 'stylesheet',
+                'type' => 'text/css',
+                'href' => common_path('plugins/FBConnect/FBConnectPlugin.css')));
+        }
+    }
+
+    /**
+     * Does the Action we're plugged into require the FB Scripts?  We only
+     * want to output FB namespace, scripts, CSS, etc. on the pages that
+     * really need them.
+     *
+     * @param Action the action in question
+     *
+     * @return boolean true
+     */
+
+    function reqFbScripts($action) {
+
+        // If you're logged in w/FB Connect, you always need the FB stuff
+
+        $fbuid = $this->loggedIn();
+
+        if (!empty($fbuid)) {
+            return true;
+        }
+
+        // List of actions that require FB stuff
+
+        $needy = array('FBConnectLoginAction',
+                       'FBConnectauthAction',
+                       'FBConnectSettingsAction');
+
+        if (in_array(get_class($action), $needy)) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Is the user currently logged in with FB Connect?
+     *
+     * @return mixed $fbuid the Facebook ID of the logged in user, or null
+     */
+
+    function loggedIn()
+    {
+        $user = common_current_user();
+
+        if (!empty($user)) {
+
+            $flink = Foreign_link::getByUserId($user->id,
+                FACEBOOK_CONNECT_SERVICE);
+            $fbuid = 0;
+
+            if (!empty($flink)) {
+
+                try {
+
+                    $facebook = getFacebook();
+                    $fbuid    = getFacebook()->get_loggedin_user();
+
+                } catch (Exception $e) {
+                    common_log(LOG_WARNING,
+                        'Problem getting Facebook client: ' .
+                            $e->getMessage());
+                }
+
+                if ($fbuid > 0) {
+                    return $fbuid;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    function onStartPrimaryNav($action)
+    {
+
+        $user = common_current_user();
+
+        if (!empty($user)) {
+
+            $fbuid = $this->loggedIn();
+
+            if (!empty($fbuid)) {
+
+                /* Default FB silhouette pic for FB users who haven't
+                   uploaded a profile pic yet. */
+
+                $silhouetteUrl =
+                    'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
+
+                $url = $this->getProfilePicURL($fbuid);
+
+                $action->elementStart('li', array('id' => 'nav_fb'));
+
+                $action->element('img', array('id' => 'fbc_profile-pic',
+                    'src' => (!empty($url)) ? $url : $silhouetteUrl,
+                    'alt' => 'Facebook Connect User',
+                    'width' => '16'), '');
+
+                $iconurl =  common_path('plugins/FBConnect/fbfavicon.ico');
+                $action->element('img', array('id' => 'fb_favicon',
+                    'src' => $iconurl));
+
+                $action->elementEnd('li');
+
+            }
+
+            $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
+                _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
+            $action->menuItem(common_local_url('profilesettings'),
+                _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
+            if (common_config('xmpp', 'enabled')) {
+                $action->menuItem(common_local_url('imsettings'),
+                    _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
+            } else {
+             $action->menuItem(common_local_url('smssettings'),
+                 _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
+            }
+            if (common_config('invite', 'enabled')) {
+                $action->menuItem(common_local_url('invite'),
+                    _('Invite'),
+                    sprintf(_('Invite friends and colleagues to join you on %s'),
+                    common_config('site', 'name')),
+                    false, 'nav_invitecontact');
+            }
+
+            // Need to override the Logout link to make it do FB stuff
+            if (!empty($fbuid)) {
+
+                $logout_url = common_local_url('logout');
+                $title =  _('Logout from the site');
+                $text = _('Logout');
+
+                $html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' .
+                    'onclick="FB.Connect.logout(function() { goto_logout() })">%s</a></li>',
+                    $logout_url, $title, $text);
+
+                $action->raw($html);
+
+             } else {
+                 $action->menuItem(common_local_url('logout'),
+                     _('Logout'), _('Logout from the site'), false, 'nav_logout');
+             }
+         }
+         else {
+             if (!common_config('site', 'closed')) {
+                 $action->menuItem(common_local_url('register'),
+                     _('Register'), _('Create an account'), false, 'nav_register');
+             }
+             $action->menuItem(common_local_url('login'),
+                 _('Login'), _('Login to the site'), false, 'nav_login');
+         }
+
+         $action->menuItem(common_local_url('doc', array('title' => 'help')),
+             _('Help'), _('Help me!'), false, 'nav_help');
+         $action->menuItem(common_local_url('peoplesearch'),
+             _('Search'), _('Search for people or text'), false, 'nav_search');
+
+        return false;
+    }
+
+    function onStartShowLocalNavBlock($action)
+    {
+        $action_name   = get_class($action);
+
+        $login_actions = array('LoginAction', 'RegisterAction',
+            'OpenidloginAction', 'FBConnectLoginAction');
+
+        if (in_array($action_name, $login_actions)) {
+            $nav = new FBCLoginGroupNav($action);
+            $nav->show();
+            return false;
+        }
+
+        $connect_actions = array('SmssettingsAction', 'ImsettingsAction',
+            'TwittersettingsAction', 'FBConnectSettingsAction');
+
+        if (in_array($action_name, $connect_actions)) {
+            $nav = new FBCSettingsNav($action);
+            $nav->show();
+            return false;
+        }
+
+        return true;
+    }
+
+    function onStartLogout($action)
+    {
+        $action->logout();
+        $fbuid = $this->loggedIn();
+
+        if (!empty($fbuid)) {
+            try {
+                $facebook = getFacebook();
+                $facebook->expire_session();
+            } catch (Exception $e) {
+                common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
+                    $e->getMessage());
+            }
+        }
+
+        return true;
+    }
+
+    function getProfilePicURL($fbuid)
+    {
+
+        $facebook = getFacebook();
+        $url      = null;
+
+        try {
+
+            $fqry = 'SELECT pic_square FROM user WHERE uid = %s';
+
+            $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
+
+            if (!empty($result)) {
+                $url = $result[0]['pic_square'];
+            }
+
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, "Facebook client failure requesting profile pic!");
+        }
+
+       return $url;
+
+    }
+
+}
diff --git a/plugins/FBConnect/FBConnectSettings.php b/plugins/FBConnect/FBConnectSettings.php
new file mode 100644 (file)
index 0000000..034eceb
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Facebook Connect settings
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Settings
+ * @package   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/connectsettingsaction.php';
+
+/**
+ * Facebook Connect settings action
+ *
+ * @category Settings
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+class FBConnectSettingsAction extends ConnectSettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Facebook Connect Settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Manage how your account connects to Facebook');
+    }
+
+    /**
+     * Content area of the page
+     *
+     * Shows a form for uploading an avatar.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_facebook',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('FBConnectSettings')));
+
+        if (!$flink) {
+
+            $this->element('p', 'instructions',
+                _('There is no Facebook user connected to this account.'));
+
+            $this->element('fb:login-button', array('onlogin' => 'goto_login()',
+                'length' => 'long'));
+
+        } else {
+
+            $this->element('p', 'form_note',
+                           _('Connected Facebook user'));
+
+            $this->elementStart('p', array('class' => 'facebook-user-display'));
+            $this->elementStart('fb:profile-pic',
+                array('uid' => $flink->foreign_id,
+                      'size' => 'small',
+                      'linked' => 'true',
+                      'facebook-logo' => 'true'));
+            $this->elementEnd('fb:profile-pic');
+
+            $this->elementStart('fb:name', array('uid' => $flink->foreign_id,
+                                                 'useyou' => 'false'));
+            $this->elementEnd('fb:name');
+            $this->elementEnd('p');
+
+            $this->hidden('token', common_session_token());
+
+            $this->elementStart('fieldset');
+
+            $this->element('legend', null, _('Disconnect my account from Facebook'));
+
+            if (!$user->password) {
+
+                $this->elementStart('p', array('class' => 'form_guide'));
+                $this->text(_('Disconnecting your Faceboook ' .
+                              'would make it impossible to log in! Please '));
+                $this->element('a',
+                    array('href' => common_local_url('passwordsettings')),
+                        _('set a password'));
+
+                $this->text(_(' first.'));
+                $this->elementEnd('p');
+            } else {
+
+                $note = 'Keep your %s account but disconnect from Facebook. ' .
+                    'You\'ll use your %s password to log in.';
+
+                $site = common_config('site', 'name');
+
+                $this->element('p', 'instructions',
+                    sprintf($note, $site, $site));
+
+                $this->submit('disconnect', _('Disconnect'));
+            }
+
+            $this->elementEnd('fieldset');
+        }
+
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Handle post
+     *
+     * Disconnects the current Facebook user from the current user's account
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('disconnect')) {
+
+            $user = common_current_user();
+
+            $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
+            $result = $flink->delete();
+
+            if ($result === false) {
+                common_log_db_error($user, 'DELETE', __FILE__);
+                $this->serverError(_('Couldn\'t delete link to Facebook.'));
+                return;
+            }
+
+            try {
+
+                // Clear FB Connect cookies out
+                $facebook = getFacebook();
+                $facebook->clear_cookie_state();
+
+            } catch (Exception $e) {
+                common_log(LOG_WARNING,
+                    'Couldn\'t clear Facebook cookies: ' .
+                        $e->getMessage());
+            }
+
+            $this->showForm(_('You have disconnected from Facebook.'), true);
+
+        } else {
+            $this->showForm(_('Not sure what you\'re trying to do.'));
+            return;
+        }
+
+    }
+
+}
diff --git a/plugins/FBConnect/fbfavicon.ico b/plugins/FBConnect/fbfavicon.ico
new file mode 100644 (file)
index 0000000..c57c034
Binary files /dev/null and b/plugins/FBConnect/fbfavicon.ico differ
diff --git a/scripts/allsites.php b/scripts/allsites.php
new file mode 100755 (executable)
index 0000000..d6768c2
--- /dev/null
@@ -0,0 +1,40 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<ENDOFHELP
+allsites.php - list all sites configured for multi-site use
+
+returns the nickname of each site configured for multi-site use
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+$sn = new Status_network();
+
+if ($sn->find()) {
+    while ($sn->fetch()) {
+        print "$sn->nickname\n";
+    }
+}
\ No newline at end of file
diff --git a/scripts/commandline.inc b/scripts/commandline.inc
new file mode 100644 (file)
index 0000000..3b6ef60
--- /dev/null
@@ -0,0 +1,174 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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/>.
+ */
+
+// -*- mode: php -*-
+
+# 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('LACONICA', true);
+
+// Set various flags so we don't time out on long-running processes
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+// Add extlib to our path so we can get Console_Getopt
+
+$_extra_path = array(INSTALLDIR.'/extlib/');
+
+set_include_path(implode(PATH_SEPARATOR, $_extra_path) . PATH_SEPARATOR . get_include_path());
+
+require_once 'Console/Getopt.php';
+
+// Note: $shortoptions and $longoptions should be pre-defined!
+
+$_default_shortoptions = 'qvhc:s:p:';
+
+$_default_longoptions = array('quiet', 'verbose', 'help', 'conf=', 'server=', 'path=');
+
+if (isset($shortoptions)) {
+    $shortoptions .= $_default_shortoptions;
+} else {
+    $shortoptions = $_default_shortoptions;
+}
+
+if (isset($longoptions)) {
+    $longoptions = array_merge($longoptions, $_default_longoptions);
+} else {
+    $longoptions = $_default_longoptions;
+}
+
+$parser = new Console_Getopt();
+
+$result = $parser->getopt($argv, $shortoptions, $longoptions);
+
+if (PEAR::isError($result)) {
+    print $result->getMessage()."\n";
+    exit(1);
+} else {
+    list($options, $args) = $result;
+}
+
+function show_help()
+{
+    global $helptext;
+
+    $_default_help_text = <<<END_OF_DEFAULT
+      General options:
+
+    -q --quiet           Quiet (little output)
+    -v --verbose         Verbose (lots of output)
+    -c --conf=<filename> Use <filename> as config file
+    -s --server=<name>   Use <name> as server name
+    -p --path=<path>     Use <path> as path name
+    -h --help            Show this message and quit.
+
+END_OF_DEFAULT;
+    if (isset($helptext)) {
+        print $helptext;
+    }
+    print $_default_help_text;
+    exit(0);
+}
+
+foreach ($options as $option) {
+
+    switch ($option[0]) {
+     case '--server':
+     case 's':
+        $server = $option[1];
+        break;
+
+     case '--path':
+     case 'p':
+        $path = $option[1];
+        break;
+
+     case '--conf':
+     case 'c':
+        $conffile = $option[1];
+        break;
+
+     case '--help':
+     case 'h':
+        show_help();
+    }
+}
+
+require_once INSTALLDIR . '/lib/common.php';
+
+set_error_handler('common_error_handler');
+
+function _make_matches($opt, $alt)
+{
+    $matches = array();
+
+    if (strlen($opt) > 1 && 0 != strncmp($opt, '--', 2)) {
+        $matches[] = '--'.$opt;
+    } else {
+        $matches[] = $opt;
+    }
+
+    if (!empty($alt)) {
+        if (strlen($alt) > 1 && 0 != strncmp($alt, '--', 2)) {
+            $matches[] = '--'.$alt;
+        } else {
+            $matches[] = $alt;
+        }
+    }
+
+    return $matches;
+}
+
+function have_option($opt, $alt=null)
+{
+    global $options;
+
+    $matches = _make_matches($opt, $alt);
+
+    foreach ($options as $option) {
+        if (in_array($option[0], $matches)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+function get_option_value($opt, $alt=null)
+{
+    global $options;
+
+    $matches = _make_matches($opt, $alt);
+
+    foreach ($options as $option) {
+        if (in_array($option[0], $matches)) {
+            return $option[1];
+        }
+    }
+
+    return null;
+}
index b18eaa2cd3fa3d783c911997efcfe920ccb36c1e..90e1ec63c0d374411af49e07c3d6fc3d8c430f13 100644 (file)
  * 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');
+$helptext = <<<ENDOFHELP
+USAGE: decache.php <table> <id> [<column>]
+Clears the cache for the object in table <table> with id <id>
+If <column> is specified, use that instead of 'id'
+ENDOFHELP;
 
-if ($argc < 3 || $argc > 4) {
-    print "USAGE: decache.php <table> <id> [<column>]\n";
-    print "Clears the cache for the object in table <table> with id <id>.\n\n";
-    print "If <column> is specified, use that instead of 'id'\n";
-    exit(1);
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (count($args) < 2 || count($args) > 3) {
+    show_help();
 }
 
-$table = $argv[1];
-$id = $argv[2];
-if ($argc > 3) {
-    $column = $argv[3];
+$table = $args[0];
+$id = $args[1];
+if (count($args) > 2) {
+    $column = $args[2];
 } else {
-    $colum = 'id';
+    $column = 'id';
 }
 
 $object = Memcached_DataObject::staticGet($table, $column, $id);
diff --git a/scripts/delete_status_network.sh b/scripts/delete_status_network.sh
new file mode 100755 (executable)
index 0000000..3218738
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+source /etc/laconica/setup.cfg
+
+export nickname=$1
+
+export database=$nickname$DBBASE
+
+# Create the db
+
+mysqladmin -h $DBHOST -u $ADMIN --password=$ADMINPASS -f drop $database
+
+mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS
+
+delete from status_network where nickname = '$nickname';
+
+ENDOFCOMMANDS
+
+for top in $AVATARBASE $FILEBASE $BACKGROUNDBASE; do
+    rm -Rf $top/$nickname
+done
index 40f60da5d80996fc19901c61546fc91be72e57ba..05e1d9366bed3a65cea394f1e0118bfc8ea2adf6 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_ENJIT_HELP
+Daemon script for watching new notices and posting to enjit.
+
+    -i --id           Identity (default none)
+
+END_OF_ENJIT_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+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';
@@ -60,7 +63,6 @@ class EnjitQueueHandler extends QueueHandler
                     return "skipped";
                 }
 
-
                 #
                 # Build an Atom message from the notice
                 #
@@ -93,8 +95,8 @@ class EnjitQueueHandler extends QueueHandler
         $ch   = curl_init();
 
         curl_setopt($ch, CURLOPT_URL, $url);
-                curl_setopt($ch, CURLOPT_HEADER, 1); 
+
+                curl_setopt($ch, CURLOPT_HEADER, 1);
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
         curl_setopt($ch, CURLOPT_POST, 1) ;
         curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
@@ -103,7 +105,7 @@ class EnjitQueueHandler extends QueueHandler
                 #
         # curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
         # curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
-                # curl_setopt($ch, CURLOPT_VERBOSE, 1); 
+                # curl_setopt($ch, CURLOPT_VERBOSE, 1);
 
         $result = curl_exec($ch);
 
@@ -115,13 +117,18 @@ class EnjitQueueHandler extends QueueHandler
 
                 return $code;
     }
-    
 
 }
 
-mb_internal_encoding('UTF-8');
-
-$id = ($argc > 1) ? $argv[1] : null;
+if (have_option('-i')) {
+    $id = get_option_value('-i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
 $handler = new EnjitQueueHandler($id);
 
index c6859cb219121f10b4885b277f94702ed7005a9b..05a35577fea71b87f34a35f3746b6a0c68e9c8d7 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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/facebookutil.php');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_FACEBOOK_HELP
+Daemon script for pushing new notices to Facebook.
+
+    -i --id           Identity (default none)
+
+END_OF_FACEBOOK_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
 
-set_error_handler('common_error_handler');
+require_once INSTALLDIR . '/lib/facebookutil.php';
+require_once INSTALLDIR . '/lib/queuehandler.php';
 
 class FacebookQueueHandler extends QueueHandler
 {
-    
     function transport()
     {
         return 'facebook';
     }
-    
+
     function start()
     {
         $this->log(LOG_INFO, "INITIALIZE");
@@ -51,20 +52,22 @@ class FacebookQueueHandler extends QueueHandler
     {
         return facebookBroadcastNotice($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;
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
 $handler = new FacebookQueueHandler($id);
 
diff --git a/scripts/fixup_conversations.php b/scripts/fixup_conversations.php
new file mode 100755 (executable)
index 0000000..0be0b4b
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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', realpath(dirname(__FILE__) . '/..'));
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+common_log(LOG_INFO, 'Fixing up conversations.');
+
+$nid = new Notice();
+$nid->query('select id, reply_to from notice where conversation is null');
+
+while ($nid->fetch()) {
+
+    $cid = null;
+    
+    $notice = new Notice();
+    
+    if (empty($nid->reply_to)) {
+        $cid = $nid->id;
+    } else {
+        $reply = Notice::staticGet('id', $notice->reply_to);
+
+        if (empty($reply)) {
+            common_log(LOG_WARNING, "Replied-to notice $notice->reply_to not found.");
+            $notice->conversation = $notice->id;
+        } else if (empty($reply->conversation)) {
+            common_log(LOG_WARNING, "Replied-to notice $reply->id has no conversation ID.");
+            $notice->conversation = $notice->id;
+        } else {
+            $notice->conversation = $reply->conversation;
+        }
+       
+       unset($reply);
+       $reply = null;
+    }
+
+    print "$notice->conversation";
+
+    $result = $notice->update($orig);
+
+    if (!$result) {
+        common_log_db_error($notice, 'UPDATE', __FILE__);
+        continue;
+    }
+
+    $notice = null;
+    $orig = null;
+    unset($notice);
+    unset($orig);
+    
+    print ".\n";
+}
index 6f65c78a1fe8f8971c3ed5ad0d90b04cd5f2ed63..bd38e3105575a2720c3f45f080cf1197d5edcf72 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index a5c8a0a5acb5e0dcf9afacb932aacd4d8151c5a1..3e55edef17257fbf4233f858947fbef33643a25c 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index c27185546e7004c150f0290274621dcbf48311d1..3e7eb7acb872bc425ecad7020636d347d69d0e54 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 6010e21d1121e418be8b0f7e3395d4b1191d7677..9d8cfda08c29c572283dc153f8ce7e51884e1440 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
index 1693760914e32e96c6227e4715fd3c669728aead..8c9a9127fdd772f25658020151063848101758bf 100644 (file)
  */
 
 # 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');
-require_once('DB.php');
+$helptext = <<<ENDOFHELP
+fixup_utf8.php <maxdate> <maxid> <minid>
+
+Fixup records in a database that stored the data incorrectly (pre-0.7.4 for Laconica).
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+require_once 'DB.php';
 
 class UTF8FixerUpper
 {
@@ -356,9 +353,9 @@ class UTF8FixerUpper
     }
 }
 
-$max_date = ($argc > 1) ? $argv[1] : null;
-$max_id = ($argc > 2) ? $argv[2] : null;
-$min_id = ($argc > 3) ? $argv[3] : null;
+$max_date = (count($args) > 0) ? $args[0] : null;
+$max_id = (count($args) > 1) ? $args[1] : null;
+$min_id = (count($args) > 2) ? $args[2] : null;
 
 $fixer = new UTF8FixerUpper(array('max_date' => $max_date,
                                   'max_notice' => $max_id,
index 4f5704249319b018beee7be4dcdc613fe0997621..9927cc6d95b07a9f4a831b204dde44a6ce5b088b 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$helptext = <<<ENDOFHELP
+getpiddir.php - print out the currently configured PID directory
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
 
-echo common_config('daemon','piddir');
+echo common_config('daemon', 'piddir');
index 0996ba9f415da150bc3b076c1c44b929d76d762a..97c230784f73e5be9897bfc536583a6cdd97c900 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * daemon names.
  */
 
-# 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');
+$helptext = <<<ENDOFHELP
+getvaliddaemons.php - print out the currently configured PID directory
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
 
 if(common_config('xmpp','enabled')) {
     echo "xmppdaemon.php jabberqueuehandler.php publicqueuehandler.php ";
     echo "xmppconfirmhandler.php ";
 }
+if(common_config('twitterbridge','enabled')) {
+    echo "twitterstatusfetcher.php ";
+}
 echo "ombqueuehandler.php ";
 echo "twitterqueuehandler.php ";
 echo "facebookqueuehandler.php ";
index 7d14f0efe6258fd85e8fd5ac591148ebd0b6a7d3..4883fea2015be1e1432fdb4f8910c3100069f43c 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
 
 # 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__) . '/..'));
 
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
+$helptext = <<<ENDOFHELP
+inbox_users.php <idfile>
 
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
+Update users to use inbox table. Listed in an ID file, default 'ids.txt'.
 
-require_once(INSTALLDIR . '/lib/common.php');
+ENDOFHELP;
 
-$id_file = ($argc > 1) ? $argv[1] : 'ids.txt';
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+$id_file = (count($args) > 1) ? $args[0] : '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 ' .
@@ -80,30 +76,30 @@ foreach ($ids as $id) {
                                                        'AND notice.created >= subscription.created ' .
                                                        'AND NOT EXISTS (SELECT user_id, notice_id ' .
                                                        'FROM notice_inbox ' .
-                                                       'WHERE user_id = ' . $user->id . ' ' . 
+                                                       'WHERE user_id = ' . $user->id . ' ' .
                                                        'AND notice_id = notice.id) ' .
                                                        'ORDER BY notice.created DESC ' .
                                                        'LIMIT 0, 1000');
-       
+
        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));
                $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id . ';last'));
index 8b6e974c0aa7af3a87cca507ccfbf10f4d6b1920..5b581629d33fc79a58844ab236f46a73944e3263 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_JABBER_HELP
+Daemon script for pushing new notices to Jabber users.
+
+    -i --id           Identity (default none)
 
-set_error_handler('common_error_handler');
+END_OF_JABBER_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/common.php';
+require_once INSTALLDIR . '/lib/jabber.php';
+require_once INSTALLDIR . '/lib/xmppqueuehandler.php';
 
 class JabberQueueHandler extends XmppQueueHandler
 {
-
     var $conn = null;
 
     function transport()
@@ -61,13 +63,16 @@ if (common_config('xmpp','enabled')==false) {
     exit();
 }
 
-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');
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
-$handler = new JabberQueueHandler($resource);
+$handler = new JabberQueueHandler($id);
 
 $handler->runOnce();
index 9dd647bf46c22841ea8b6a7fe2dd0d68c63ede12..11ddf06b7502734b3c3b239433e764fc31aca78a 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$helptext = <<<END_OF_HELP
+Script for converting mail messages into notices. Takes message body
+as STDIN.
+
+END_OF_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
 require_once(INSTALLDIR . '/lib/mail.php');
 require_once('Mail/mimeDecode.php');
 
@@ -36,18 +36,17 @@ require_once('Mail/mimeDecode.php');
 
 class MailerDaemon
 {
-
     function __construct()
     {
     }
 
     function handle_message($fname='php://stdin')
     {
-        list($from, $to, $msg) = $this->parse_message($fname);
+        list($from, $to, $msg, $attachments) = $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));
+        common_log(LOG_INFO, "Mail from $from to $to with ".count($attachments) .' attachment(s): ' .substr($msg, 0, 20));
         $user = $this->user_from($from);
         if (!$user) {
             $this->error($from, _('Not a registered user.'));
@@ -66,7 +65,47 @@ class MailerDaemon
             return true;
         }
         $msg = $this->cleanup_msg($msg);
-        $err = $this->add_notice($user, $msg);
+        $msg = common_shorten_links($msg);
+        if (mb_strlen($msg) > 140) {
+            $this->error($from,_('That\'s too long. '.
+                'Max notice size is 140 chars.'));
+        }
+        $fileRecords = array();
+        foreach($attachments as $attachment){
+            $mimetype = $this->getUploadedFileType($attachment);
+            $stream  = stream_get_meta_data($attachment);
+            if (!$this->isRespectsQuota($user,filesize($stream['uri']))) {
+                die('error() should trigger an exception before reaching here.');
+            }
+            $filename = $this->saveFile($user, $attachment,$mimetype);
+            
+            fclose($attachment);
+            
+            if (empty($filename)) {
+                $this->error($from,_('Couldn\'t save file.'));
+            }
+
+            $fileRecord = $this->storeFile($filename, $mimetype);
+            $fileRecords[] = $fileRecord;
+            $fileurl = common_local_url('attachment',
+                array('attachment' => $fileRecord->id));
+
+            // not sure this is necessary -- Zach
+            $this->maybeAddRedir($fileRecord->id, $fileurl);
+
+            $short_fileurl = common_shorten_url($fileurl);
+            $msg .= ' ' . $short_fileurl;
+
+            if (mb_strlen($msg) > 140) {
+                $this->deleteFile($filename);
+                $this->error($from,_('Max notice size is 140 chars, including attachment URL.'));
+            }
+
+            // Also, not sure this is necessary -- Zach
+            $this->maybeAddRedir($fileRecord->id, $short_fileurl);
+        }
+
+        $err = $this->add_notice($user, $msg, $fileRecords);
         if (is_string($err)) {
             $this->error($from, $err);
             return false;
@@ -75,6 +114,89 @@ class MailerDaemon
         }
     }
 
+    function saveFile($user, $attachment, $mimetype) {
+
+        $filename = File::filename($user->getProfile(), "email", $mimetype);
+
+        $filepath = File::path($filename);
+
+        $stream  = stream_get_meta_data($attachment);
+        if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) {
+            return $filename;
+        } else {   
+            $this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath));
+        }
+    }
+
+    function storeFile($filename, $mimetype) {
+
+        $file = new File;
+        $file->filename = $filename;
+
+        $file->url = File::url($filename);
+
+        $filepath = File::path($filename);
+
+        $file->size = filesize($filepath);
+        $file->date = time();
+        $file->mimetype = $mimetype;
+
+        $file_id = $file->insert();
+
+        if (!$file_id) {
+            common_log_db_error($file, "INSERT", __FILE__);
+            $this->error(null,_('There was a database error while saving your file. Please try again.'));
+        }
+
+        return $file;
+    }
+
+    function maybeAddRedir($file_id, $url)
+    {   
+        $file_redir = File_redirection::staticGet('url', $url);
+
+        if (empty($file_redir)) {
+            $file_redir = new File_redirection;
+            $file_redir->url = $url;
+            $file_redir->file_id = $file_id;
+
+            $result = $file_redir->insert();
+
+            if (!$result) {
+                common_log_db_error($file_redir, "INSERT", __FILE__);
+                $this->error(null,_('There was a database error while saving your file. Please try again.'));
+            }
+        }
+    }
+
+    function getUploadedFileType($fileHandle) {
+        require_once 'MIME/Type.php';
+
+        $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+        $cmd = common_config('attachments', 'filecommand');
+
+        $stream  = stream_get_meta_data($fileHandle);
+        $filetype = MIME_Type::autoDetect($stream['uri']);
+        if (in_array($filetype, common_config('attachments', 'supported'))) {
+            return $filetype;
+        }
+        $media = MIME_Type::getMedia($filetype);
+        if ('application' !== $media) {
+            $hint = sprintf(_(' Try using another %s format.'), $media);
+        } else {
+            $hint = '';
+        }
+        $this->error(null,sprintf(
+            _('%s is not a supported filetype on this server.'), $filetype) . $hint);
+    }
+
+    function isRespectsQuota($user,$fileSize) {
+        $file = new File;
+        $ret = $file->isRespectsQuota($user,$fileSize);
+        if (true === $ret) return true;
+        $this->error(null,$ret);
+    }
+
     function error($from, $msg)
     {
         file_put_contents("php://stderr", $msg . "\n");
@@ -134,19 +256,30 @@ class MailerDaemon
         common_log($level, 'MailDaemon: '.$msg);
     }
 
-    function add_notice($user, $msg)
+    function add_notice($user, $msg, $fileRecords)
     {
         $notice = Notice::saveNew($user->id, $msg, 'mail');
         if (is_string($notice)) {
             $this->log(LOG_ERR, $notice);
             return $notice;
         }
+        foreach($fileRecords as $fileRecord){
+            $this->attachFile($notice, $fileRecord);
+        }
         common_broadcast_notice($notice);
         $this->log(LOG_INFO,
                    'Added notice ' . $notice->id . ' from user ' . $user->nickname);
         return true;
     }
 
+    function attachFile($notice, $filerec)
+    {   
+        File_to_post::processNew($filerec->id, $notice->id);
+
+        $this->maybeAddRedir($filerec->id,
+            common_local_url('file', array('notice' => $notice->id)));
+    }
+
     function parse_message($fname)
     {
         $contents = file_get_contents($fname);
@@ -164,12 +297,19 @@ class MailerDaemon
 
         $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
 
+        $attachments = array();
+
         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 ($part->body) {
+                       $attachment = tmpfile();
+                       fwrite($attachment, $part->body);
+                        $attachments[] = $attachment;
+                    }
                 }
             }
         } else if ($type == 'text/plain') {
@@ -177,8 +317,7 @@ class MailerDaemon
         } else {
             $this->unsupported_type($type);
         }
-
-        return array($from, $to, $msg);
+        return array($from, $to, $msg, $attachments);
     }
 
     function unsupported_type($type)
index cdcea51dc737d1e6eaa9ab884aaa2779e9c50ae6..1587192b6fb5b9c1532e6af18411fa76dbc44ba4 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_OMB_HELP
+Daemon script for pushing new notices to OpenMicroBlogging subscribers.
+
+    -i --id           Identity (default none)
+
+END_OF_OMB_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+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");
@@ -56,7 +60,7 @@ class OmbQueueHandler extends QueueHandler
             return omb_broadcast_remote_subscribers($notice);
         }
     }
-    
+
     function finish()
     {
     }
@@ -68,12 +72,15 @@ class OmbQueueHandler extends QueueHandler
     }
 }
 
-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;
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
 $handler = new OmbQueueHandler($id);
 
index ada6ecdba2070f20ad9a865553fdca2543df15ec..23678ea4b5278f16e6dd0f69c93e0b340931fe56 100644 (file)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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/ping.php');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_PING_HELP
+Daemon script for pushing new notices to ping servers.
+
+    -i --id           Identity (default none)
 
-set_error_handler('common_error_handler');
+END_OF_PING_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/ping.php';
+require_once INSTALLDIR . '/lib/queuehandler.php';
 
 class PingQueueHandler extends QueueHandler {
 
@@ -52,12 +54,15 @@ class PingQueueHandler extends QueueHandler {
        }
 }
 
-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;
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
 $handler = new PingQueueHandler($id);
 
index b0fa22d438e6a66c0c3931e33965a739e4afe087..701d50e0189f737ed36b41d4f90bcbcf2c23e5dc 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_PUBLIC_HELP
+Daemon script for pushing new notices to public XMPP subscribers.
+
+    -i --id           Identity (default none)
+
+END_OF_PUBLIC_HELP;
 
-set_error_handler('common_error_handler');
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/jabber.php';
+require_once INSTALLDIR . '/lib/xmppqueuehandler.php';
 
 class PublicQueueHandler extends XmppQueueHandler
 {
-    
+
     function transport()
     {
         return 'public';
     }
-    
+
     function handle_notice($notice)
     {
         try {
@@ -59,13 +61,16 @@ if (common_config('xmpp','enabled')==false) {
     exit();
 }
 
-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');
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
-$handler = new PublicQueueHandler($resource);
+$handler = new PublicQueueHandler($id);
 
 $handler->runOnce();
diff --git a/scripts/reportsnapshot.php b/scripts/reportsnapshot.php
new file mode 100644 (file)
index 0000000..c644b55
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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', realpath(dirname(__FILE__) . '/..'));
+
+$helptext = <<<END_OF_SNAPSHOT_HELP
+Batch script for sending snapshot information about this installation to devs.
+
+END_OF_SNAPSHOT_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+Snapshot::check();
index d694eed095b4c2ffcfbae68dc728d9fadf6acb28..b70689f030a7a67f8ec06315a3de41a9a88eb9e5 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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);
-}
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
 
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
+$helptext = <<<END_OF_PASSWORD_HELP
+setpassword.php <username> <password>
 
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
+Sets the password of user with name <username> to <password>
 
-require_once(INSTALLDIR . '/lib/common.php');
+END_OF_PASSWORD_HELP;
 
-if ($argc != 3) {
-    print "USAGE: setpassword.php <username> <password>\n";
-    print "Sets the password of user with name <username> to <password>\n";
-    exit(1);
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+if (count($args) < 2) {
+    show_help();
 }
 
-$nickname = $argv[1];
-$password = $argv[2];
+$nickname = $args[0];
+$password = $args[1];
 
 if (mb_strlen($password) < 6) {
     print "Password must be 6 characters or more.\n";
diff --git a/scripts/setup.cfg.sample b/scripts/setup.cfg.sample
new file mode 100644 (file)
index 0000000..8d03b06
--- /dev/null
@@ -0,0 +1,14 @@
+# CONFIGURATION FILE for setup_status_network.sh
+
+export DBHOST=localhost
+export DBHOSTNAME=masterdb.example.net
+export DBBASE=_example_net
+export USERBASE=_example_net
+export ADMIN=root
+export ADMINPASS=yourpassword
+export SITEDB=example_net_site
+export AVATARBASE=/var/www/avatar.example.net
+export BACKGROUNDBASE=/var/www/background.example.net
+export FILEBASE=/var/www/file.example.net
+export PWDGEN="pwgen 20"
+
diff --git a/scripts/setup_status_network.sh b/scripts/setup_status_network.sh
new file mode 100755 (executable)
index 0000000..1744064
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+source /etc/laconica/setup.cfg
+
+export nickname=$1
+export sitename=$2
+
+export password=`$PWDGEN`
+export database=$nickname$DBBASE
+export username=$nickname$USERBASE
+
+# Create the db
+
+mysqladmin -h $DBHOST -u $ADMIN --password=$ADMINPASS create $database
+
+for f in laconica.sql innodb.sql sms_carrier.sql foreign_services.sql notice_source.sql; do
+    mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $database < ../db/$f;
+done
+
+mysql -h $DBHOST -u $ADMIN --password=$ADMINPASS $SITEDB << ENDOFCOMMANDS
+
+GRANT INSERT,SELECT,UPDATE,DELETE ON $database.* TO '$username'@'localhost' IDENTIFIED BY '$password';
+GRANT INSERT,SELECT,UPDATE,DELETE ON $database.* TO '$username'@'%' IDENTIFIED BY '$password';
+INSERT INTO status_network (nickname, dbhost, dbuser, dbpass, dbname, sitename, created)
+VALUES ('$nickname', '$DBHOSTNAME', '$username', '$password', '$database', '$sitename', now());
+
+ENDOFCOMMANDS
+
+for top in $AVATARBASE $FILEBASE $BACKGROUNDBASE; do
+    mkdir $top/$nickname
+    chmod a+w $top/$nickname
+done
diff --git a/scripts/showcache.php b/scripts/showcache.php
new file mode 100644 (file)
index 0000000..7a88fdb
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, 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', realpath(dirname(__FILE__) . '/..'));
+
+$shortoptions = "t:c:v:k:";
+
+$helptext = <<<ENDOFHELP
+USAGE: showcache.php <args>
+shows the cached object based on the args
+
+  -t table     Table to look up
+  -c column    Column to look up, default "id"
+  -v value     Value to look up
+  -k key       Key to look up; other args are ignored
+
+ENDOFHELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+$karg = get_option_value('k');
+
+if (!empty($karg)) {
+    $k = common_cache_key($karg);
+} else {
+    $table = get_option_value('t');
+    if (empty($table)) {
+        die("No table or key specified\n");
+    }
+    $column = get_option_value('c');
+    if (empty($column)) {
+        $column = 'id';
+    }
+    $value = get_option_value('v');
+
+    $k = Memcached_DataObject::cacheKey($table, $column, $value);
+}
+
+print "Checking key '$k'...\n";
+
+$c = common_memcache();
+
+if (empty($c)) {
+    die("Can't initialize cache object!\n");
+}
+
+$obj = $c->get($k);
+
+if (empty($obj)) {
+    print "Empty.\n";
+} else {
+    var_dump($obj);
+    print "\n";
+}
index 39eb859bbad1f10e678d604cb87f08b42f6c5749..88ca2ba7aac4f56d60c7109a962e1a4b56a9ccc1 100755 (executable)
@@ -1,10 +1,37 @@
+#!/usr/bin/env php
 <?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
 
-require_once(INSTALLDIR . '/lib/common.php');
-require_once(INSTALLDIR . '/lib/util.php');
+$shortoptions = 'f:d:u:';
+
+$helptext = <<<END_OF_SITEMAP_HELP
+Script for creating sitemaps files per http://sitemaps.org/
+
+    -f <indexfile>   Use <indexfile> as output file
+    -d <outputdir>   Use <outputdir> for new sitemaps
+    -u <outputurl>   Use <outputurl> as root for URLs
+
+END_OF_SITEMAP_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
 
 $output_paths = parse_args();
 
@@ -13,11 +40,11 @@ notices_map();
 user_map();
 index_map();
 
-# ------------------------------------------------------------------------------
-# Main functions: get data out and turn them into sitemaps
-# ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+// Main functions: get data out and turn them into sitemaps
+// ------------------------------------------------------------------------------
 
-# Generate index sitemap of all other sitemaps.
+// Generate index sitemap of all other sitemaps.
 function index_map()
 {
     global $output_paths;
@@ -26,7 +53,7 @@ function index_map()
 
     foreach (glob("$output_dir*.xml") as $file_name) {
 
-        # Just the file name please.
+        // Just the file name please.
         $file_name = preg_replace("|$output_dir|", '', $file_name);
 
         $index_urls .= sitemap(
@@ -40,7 +67,7 @@ function index_map()
     write_file($output_paths['index_file'], sitemapindex($index_urls));
 }
 
-# Generate sitemap of standard site elements.
+// Generate sitemap of standard site elements.
 function standard_map()
 {
     global $output_paths;
@@ -61,7 +88,7 @@ function standard_map()
                                     )
                               );
 
-    $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog', 
+    $docs = array('about', 'faq', 'contact', 'im', 'openid', 'openmublog',
         'privacy', 'source', 'badge');
 
     foreach($docs as $title) {
@@ -79,7 +106,7 @@ function standard_map()
     write_file($urlset_path, urlset($standard_map_urls));
 }
 
-# Generate sitemaps of all notices.
+// Generate sitemaps of all notices.
 function notices_map()
 {
     global $output_paths;
@@ -93,14 +120,14 @@ function notices_map()
 
     while ($notices->fetch()) {
 
-        # Maximum 50,000 URLs per sitemap file.
+        // Maximum 50,000 URLs per sitemap file.
         if ($notice_count == 50000) {
             $notice_count = 0;
             $map_count++;
         }
 
-        # remote notices have an URL
-        
+        // 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)),
@@ -114,11 +141,11 @@ function notices_map()
         }
     }
 
-    # Make full sitemaps from the lists and save them.
+    // Make full sitemaps from the lists and save them.
     array_to_map($notice_list, 'notice');
 }
 
-# Generate sitemaps of all users.
+// Generate sitemaps of all users.
 function user_map()
 {
     global $output_paths;
@@ -132,7 +159,7 @@ function user_map()
 
     while ($users->fetch()) {
 
-        # Maximum 50,000 URLs per sitemap file.
+        // Maximum 50,000 URLs per sitemap file.
         if ($user_count == 50000) {
             $user_count = 0;
             $map_count++;
@@ -140,7 +167,7 @@ function user_map()
 
         $user_args = array('nickname' => $users->nickname);
 
-        # Define parameters for generating <url></url> elements.
+        // Define parameters for generating <url></url> elements.
         $user = array(
                       'url'        => common_local_url('showstream', $user_args),
                       'changefreq' => 'daily',
@@ -183,8 +210,8 @@ function user_map()
                       'priority'   => '0.5',
                       );
 
-        # Construct a <url></url> element for each user facet and add it
-        # to our existing list of those.
+        // 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);
@@ -196,9 +223,9 @@ function user_map()
         $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')).
+    // 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');
@@ -208,14 +235,14 @@ function user_map()
     array_to_map($foaf_list, 'foaf');
 }
 
-# ------------------------------------------------------------------------------
-# XML generation functions
-# ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+// XML generation functions
+// ------------------------------------------------------------------------------
 
-# Generate a <url></url> element.
+// Generate a <url></url> element.
 function url($url_args)
 {
-    $url        = preg_replace('/&/', '&amp;', $url_args['url']); # escape ampersands for XML
+    $url        = preg_replace('/&/', '&amp;', $url_args['url']); // escape ampersands for XML
     $lastmod    = $url_args['lastmod'];
     $changefreq = $url_args['changefreq'];
     $priority   = $url_args['priority'];
@@ -246,7 +273,7 @@ function url($url_args)
 
 function sitemap($sitemap_args)
 {
-    $url        = preg_replace('/&/', '&amp;', $sitemap_args['url']); # escape ampersands for XML
+    $url        = preg_replace('/&/', '&amp;', $sitemap_args['url']); // escape ampersands for XML
     $lastmod    = $sitemap_args['lastmod'];
 
     if (is_null($url)) {
@@ -265,7 +292,7 @@ function sitemap($sitemap_args)
     return $sitemap_out;
 }
 
-# Generate a <urlset></urlset> element.
+// Generate a <urlset></urlset> element.
 function urlset($urlset_text)
 {
     $urlset = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
@@ -276,7 +303,7 @@ function urlset($urlset_text)
     return $urlset;
 }
 
-# Generate a <urlset></urlset> element.
+// Generate a <urlset></urlset> element.
 function sitemapindex($sitemapindex_text)
 {
     $sitemapindex = '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
@@ -287,49 +314,31 @@ function sitemapindex($sitemapindex_text)
     return $sitemapindex;
 }
 
-# Generate a sitemap from an array containing <url></url> elements and write it to a file.
+// 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.
+        // $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
-# ------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------
+// Internal functions
+// ------------------------------------------------------------------------------
 
-# Parse command line arguments.
+// 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];
+    $index_file = get_option_value('f');
+    $output_dir = get_option_value('d');
+    $output_url = get_option_value('u');
 
     if (file_exists($output_dir)) {
         if (is_writable($output_dir) === false) {
@@ -348,7 +357,7 @@ function parse_args()
     return $paths;
 }
 
-# Ensure paths end with a "/".
+// Ensure paths end with a "/".
 function trailing_slash($path)
 {
     if (preg_match('/\/$/', $path) == 0) {
@@ -358,7 +367,7 @@ function trailing_slash($path)
     return $path;
 }
 
-# Write data to disk.
+// Write data to disk.
 function write_file($path, $data)
 {
     if (is_null($path)) {
@@ -376,7 +385,7 @@ function write_file($path, $data)
     }
 }
 
-# Display an error message and exit.
+// Display an error message and exit.
 function error ($error_msg)
 {
     if (is_null($error_msg)) {
index 38f2f11febb4e6f812da9eb8f65d7a75348c481e..94b846d987bc22f3a6fdb7016c58ef7b73f5c24b 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_SMS_HELP
+Daemon script for pushing new notices to local subscribers using SMS.
+
+    -i --id           Identity (default none)
+
+END_OF_SMS_HELP;
 
-set_error_handler('common_error_handler');
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/mail.php';
+require_once INSTALLDIR . '/lib/queuehandler.php';
 
 class SmsQueueHandler extends QueueHandler
 {
-    
     function transport()
     {
         return 'sms';
@@ -51,18 +52,21 @@ class SmsQueueHandler extends QueueHandler
     {
         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;
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
 $handler = new SmsQueueHandler($id);
 
index 9759adbd07b83f6798e934ed438f5107a6e22a2a..c16af3c4be17eda218dd5525c3a3fec8fca2414d 100755 (executable)
@@ -2,7 +2,7 @@
 
 # Laconica - a distributed open-source microblogging tool
 
-# Copyright (C) 2008, Controlez-Vous, Inc.
+# Copyright (C) 2008, 2009, Control Yourself, 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
index 3311b2ed140b175a3286c9a12103388eb3af365b..fe7c16bea1d436e2a1c403eb75710b0eee80152c 100755 (executable)
@@ -2,7 +2,7 @@
 
 # Laconica - a distributed open-source microblogging tool
 
-# Copyright (C) 2008, Controlez-Vous, Inc.
+# Copyright (C) 2008, 2009, Control Yourself, 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
index 3869e95c4c3cf5ae2735f54dbff600349af6f7d8..9ead20acd6c0230f0b42af6a24f6cd670404c297 100755 (executable)
@@ -2,7 +2,7 @@
 
 # Laconica - a distributed open-source microblogging tool
 
-# Copyright (C) 2008, Controlez-Vous, Inc.
+# Copyright (C) 2008, 2009, Control Yourself, 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
 # This program tries to start the daemons for Laconica.
 # Note that the 'maildaemon' needs to run as a mail filter.
 
+ARGSG=
+ARGSD=
+
+if [ $# -gt 0 ]; then
+    ARGSG="$ARGSG -s$1"
+    ID=`echo $1 | sed s/\\\\./_/g`
+    ARGSD="$ARGSD -s$1 -i$ID"
+fi
+
+if [ $# -gt 1 ]; then
+    ARGSD="$ARGSD -p$2"
+    ARGSG="$ARGSG -p$2"
+fi
+
 DIR=`dirname $0`
-DAEMONS=`php $DIR/getvaliddaemons.php`
+DAEMONS=`php $DIR/getvaliddaemons.php $ARGSG`
 
 for f in $DAEMONS; do
 
-         echo -n "Starting $f...";
-        php $DIR/$f
-        echo "DONE."
+         printf "Starting $f...";
+        php $DIR/$f $ARGSD
+        printf "DONE.\n"
+
 done
index 2134b4ab00f2af90e95750a5a24fb9153d97a917..60ffd83ad1e45d8991aa4115a3e8c305acba5ddb 100755 (executable)
@@ -2,7 +2,7 @@
 
 # Laconica - a distributed open-source microblogging tool
 
-# Copyright (C) 2008, Controlez-Vous, Inc.
+# Copyright (C) 2008, 2009, Control Yourself, 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
@@ -24,7 +24,8 @@ SDIR=`dirname $0`
 DIR=`php $SDIR/getpiddir.php`
 
 for f in jabberhandler ombhandler publichandler smshandler pinghandler \
-        xmppconfirmhandler xmppdaemon twitterhandler facebookhandler; do
+        xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
+        twitterstatusfetcher; do
 
        FILES="$DIR/$f.*.pid"
        for ff in "$FILES" ; do
index bd08ba58d6a9bcba0e1bcfd3af3941f4aaa461d5..fe53ff44d634fa92895dd00c34ccc804fa245295 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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);
 
 // Uncomment this to get useful console output
-//define('SCRIPT_DEBUG', true);
 
-require_once(INSTALLDIR . '/lib/common.php');
+$helptext = <<<END_OF_TWITTER_HELP
+Batch script for synching local friends with Twitter friends.
+
+END_OF_TWITTER_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
 
 // Make a lockfile
 $lockfilename = lockFilename();
index 0d2eaeaf09263e0d3bed6f50110855b38c752e26..b2135d6825ee2f52eccbe481a8c56cbfa3958861 100644 (file)
  * 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);
-}
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
 
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
+$shortoptions = 'u::';
+$longoptions = array('start-user-id::');
 
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
+$helptext = <<<END_OF_TRIM_HELP
+Batch script for trimming notice inboxes to a reasonable size.
+
+    -u <id>
+    --start-user-id=<id>   User ID to start after. Default is all.
 
-require_once(INSTALLDIR . '/lib/common.php');
+END_OF_TRIM_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+$id = null;
+
+if (have_option('u')) {
+    $id = get_option_value('u');
+} else if (have_option('--start-user-id')) {
+    $id = get_option_value('--start-user-id');
+} else {
+    $id = null;
+}
 
 $user = new User();
-if ($argc > 1) {
-    $user->whereAdd('id > ' . $argv[1]);
+
+if (!empty($id)) {
+    $user->whereAdd('id > ' . $id);
 }
+
 $cnt = $user->find();
 
 while ($user->fetch()) {
@@ -74,10 +85,10 @@ while ($user->fetch()) {
     $delay = 3.0 * ($finish - $start);
 
     print "Delaying $delay seconds...";
-    
+
     // Wait to let slaves catch up
 
     usleep($delay * 1000000);
-    
+
     print "DONE.\n";
 }
index 7da4f1e20aa1fc2f0e6fe8fc3d1ef13a12ec5792..00e735d983610b99bf689c8a8bc740cbf33af1ae 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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/twitter.php');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_ENJIT_HELP
+Daemon script for pushing new notices to Twitter.
+
+    -i --id           Identity (default none)
+
+END_OF_ENJIT_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
 
-set_error_handler('common_error_handler');
+require_once INSTALLDIR . '/lib/twitter.php';
+require_once INSTALLDIR . '/lib/queuehandler.php';
 
 class TwitterQueueHandler extends QueueHandler
 {
-    
     function transport()
     {
         return 'twitter';
     }
-    
+
     function start()
     {
         $this->log(LOG_INFO, "INITIALIZE");
@@ -51,20 +52,22 @@ class TwitterQueueHandler extends QueueHandler
     {
         return broadcast_twitter($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;
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
 $handler = new TwitterQueueHandler($id);
 
diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php
new file mode 100755 (executable)
index 0000000..8b10bfb
--- /dev/null
@@ -0,0 +1,645 @@
+#!/usr/bin/env php
+<?php
+/**
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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', realpath(dirname(__FILE__) . '/..'));
+
+// Tune number of processes and how often to poll Twitter
+// XXX: Should these things be in config.php?
+define('MAXCHILDREN', 2);
+define('POLL_INTERVAL', 60); // in seconds
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_TRIM_HELP
+Batch script for retrieving Twitter messages from foreign service.
+
+  -i --id      Identity (default 'generic')
+    
+END_OF_TRIM_HELP;
+
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/common.php';
+require_once INSTALLDIR . '/lib/daemon.php';
+
+/**
+ * Fetcher for statuses from Twitter
+ *
+ * Fetches statuses from Twitter and inserts them as notices in local
+ * system.
+ *
+ * @category Twitter
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @author   Evan Prodromou <evan@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ */
+
+// NOTE: an Avatar path MUST be set in config.php for this
+// script to work: e.g.: $config['avatar']['path'] = '/laconica/avatar';
+
+class TwitterStatusFetcher extends Daemon
+{
+    private $_children = array();
+
+    /**
+     * Name of this daemon
+     *
+     * @return string Name of the daemon.
+     */
+
+    function name()
+    {
+        return ('twitterstatusfetcher.'.$this->_id);
+    }
+
+    /**
+     * Run the daemon
+     *
+     * @return void
+     */
+
+    function run()
+    {
+        do {
+
+            $flinks = $this->refreshFlinks();
+
+            foreach ($flinks as $f) {
+
+                // We have to disconnect from the DB before forking so
+                // each sub-process will open its own connection and
+                // avoid stomping on the others
+
+                $conn = &$f->getDatabaseConnection();
+                $conn->disconnect();
+
+                $pid = pcntl_fork();
+
+                if ($pid == -1) {
+                    die ("Couldn't fork!");
+                }
+
+                if ($pid) {
+
+                    // Parent
+                    if (defined('SCRIPT_DEBUG')) {
+                        common_debug("Parent: forked new status ".
+                                     " fetcher process " . $pid);
+                    }
+
+                    $this->_children[] = $pid;
+
+                } else {
+
+                    // Child
+                    $this->getTimeline($f);
+                    exit();
+                }
+
+                // Remove child from ps list as it finishes
+                while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) {
+
+                    if (defined('SCRIPT_DEBUG')) {
+                        common_debug("Child $c finished.");
+                    }
+
+                    $this->removePs($this->_children, $c);
+                }
+
+                // Wait! We have too many damn kids.
+                if (sizeof($this->_children) > MAXCHILDREN) {
+
+                    if (defined('SCRIPT_DEBUG')) {
+                        common_debug('Too many children. Waiting...');
+                    }
+
+                    if (($c = pcntl_wait($status, WUNTRACED)) > 0) {
+
+                        if (defined('SCRIPT_DEBUG')) {
+                            common_debug("Finished waiting for $c");
+                        }
+
+                        $this->removePs($this->_children, $c);
+                    }
+                }
+            }
+
+            // Remove all children from the process list before restarting
+            while (($c = pcntl_wait($status, WUNTRACED)) > 0) {
+
+                if (defined('SCRIPT_DEBUG')) {
+                    common_debug("Child $c finished.");
+                }
+
+                $this->removePs($this->_children, $c);
+            }
+
+            // Rest for a bit before we fetch more statuses
+
+            if (defined('SCRIPT_DEBUG')) {
+                common_debug('Waiting ' . POLL_INTERVAL .
+                    ' secs before hitting Twitter again.');
+            }
+
+            if (POLL_INTERVAL > 0) {
+                sleep(POLL_INTERVAL);
+            }
+
+        } while (true);
+    }
+
+    /**
+     * Refresh the foreign links for this user
+     *
+     * @return void
+     */
+
+    function refreshFlinks()
+    {
+        $flink = new Foreign_link();
+
+        $flink->service = 1; // Twitter
+
+        $flink->orderBy('last_noticesync');
+
+        $cnt = $flink->find();
+
+        if (defined('SCRIPT_DEBUG')) {
+            common_debug('Updating Twitter friends subscriptions' .
+                " for $cnt users.");
+        }
+
+        $flinks = array();
+
+        while ($flink->fetch()) {
+
+            if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
+                FOREIGN_NOTICE_RECV) {
+                $flinks[] = clone($flink);
+            }
+        }
+
+        $flink->free();
+        unset($flink);
+
+        return $flinks;
+    }
+
+    /**
+     * Unknown
+     *
+     * @param array  &$plist unknown.
+     * @param string $ps     unknown.
+     *
+     * @return unknown
+     * @todo document
+     */
+
+    function removePs(&$plist, $ps)
+    {
+        for ($i = 0; $i < sizeof($plist); $i++) {
+            if ($plist[$i] == $ps) {
+                unset($plist[$i]);
+                $plist = array_values($plist);
+                break;
+            }
+        }
+    }
+
+    function getTimeline($flink)
+    {
+        if (empty($flink)) {
+            common_log(LOG_WARNING,
+                "Can't retrieve Foreign_link for foreign ID $fid");
+            return;
+        }
+
+        $fuser = $flink->getForeignUser();
+
+        if (empty($fuser)) {
+            common_log(LOG_WARNING, "Unmatched user for ID " .
+                $flink->user_id);
+            return;
+        }
+
+        if (defined('SCRIPT_DEBUG')) {
+            common_debug('Trying to get timeline for Twitter user ' .
+                "$fuser->nickname ($flink->foreign_id).");
+        }
+
+        // XXX: Biggest remaining issue - How do we know at which status
+        // to start importing?  How many statuses?  Right now I'm going
+        // with the default last 20.
+
+        $url = 'http://twitter.com/statuses/friends_timeline.json';
+
+        $timeline_json = get_twitter_data($url, $fuser->nickname,
+            $flink->credentials);
+
+        $timeline = json_decode($timeline_json);
+
+        if (empty($timeline)) {
+            common_log(LOG_WARNING, "Empty timeline.");
+            return;
+        }
+
+        // Reverse to preserve order
+        foreach (array_reverse($timeline) as $status) {
+
+            // Hacktastic: filter out stuff coming from this Laconica
+            $source = mb_strtolower(common_config('integration', 'source'));
+
+            if (preg_match("/$source/", mb_strtolower($status->source))) {
+                if (defined('SCRIPT_DEBUG')) {
+                    common_debug('Skipping import of status ' . $status->id .
+                        ' with source ' . $source);
+                }
+                continue;
+            }
+
+            $this->saveStatus($status, $flink);
+        }
+
+        // Okay, record the time we synced with Twitter for posterity
+        $flink->last_noticesync = common_sql_now();
+        $flink->update();
+    }
+
+    function saveStatus($status, $flink)
+    {
+        $id = $this->ensureProfile($status->user);
+        $profile = Profile::staticGet($id);
+
+        if (!$profile) {
+            common_log(LOG_ERR,
+                'Problem saving notice. No associated Profile.');
+            return null;
+        }
+
+        // XXX: change of screen name?
+
+        $uri = 'http://twitter.com/' . $status->user->screen_name .
+            '/status/' . $status->id;
+
+        $notice = Notice::staticGet('uri', $uri);
+
+        // check to see if we've already imported the status
+
+        if (!$notice) {
+
+            $notice = new Notice();
+
+            $notice->profile_id = $id;
+            $notice->uri        = $uri;
+            $notice->created    = strftime('%Y-%m-%d %H:%M:%S',
+                                           strtotime($status->created_at));
+            $notice->content    = common_shorten_links($status->text); // XXX
+            $notice->rendered   = common_render_content($notice->content, $notice);
+            $notice->source     = 'twitter';
+            $notice->reply_to   = null; // XXX lookup reply
+            $notice->is_local   = NOTICE_GATEWAY;
+
+            if (Event::handle('StartNoticeSave', array(&$notice))) {
+                $id = $notice->insert();
+                Event::handle('EndNoticeSave', array($notice));
+            }
+        }
+
+        if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id,
+                                         'user_id' => $flink->user_id))) {
+            // Add to inbox
+            $inbox = new Notice_inbox();
+
+            $inbox->user_id   = $flink->user_id;
+            $inbox->notice_id = $notice->id;
+            $inbox->created   = $notice->created;
+            $inbox->source    = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source
+
+            $inbox->insert();
+        }
+    }
+
+    function ensureProfile($user)
+    {
+        // check to see if there's already a profile for this user
+        $profileurl = 'http://twitter.com/' . $user->screen_name;
+        $profile = Profile::staticGet('profileurl', $profileurl);
+
+        if ($profile) {
+            if (defined('SCRIPT_DEBUG')) {
+                common_debug("Profile for $profile->nickname found.");
+            }
+
+            // Check to see if the user's Avatar has changed
+            $this->checkAvatar($user, $profile);
+
+            return $profile->id;
+
+        } else {
+            if (defined('SCRIPT_DEBUG')) {
+                common_debug('Adding profile and remote profile ' .
+                    "for Twitter user: $profileurl");
+            }
+
+            $profile = new Profile();
+            $profile->query("BEGIN");
+
+            $profile->nickname = $user->screen_name;
+            $profile->fullname = $user->name;
+            $profile->homepage = $user->url;
+            $profile->bio = $user->description;
+            $profile->location = $user->location;
+            $profile->profileurl = $profileurl;
+            $profile->created = common_sql_now();
+
+            $id = $profile->insert();
+
+            if (empty($id)) {
+                common_log_db_error($profile, 'INSERT', __FILE__);
+                $profile->query("ROLLBACK");
+                return false;
+            }
+
+            // check for remote profile
+            $remote_pro = Remote_profile::staticGet('uri', $profileurl);
+
+            if (!$remote_pro) {
+
+                $remote_pro = new Remote_profile();
+
+                $remote_pro->id = $id;
+                $remote_pro->uri = $profileurl;
+                $remote_pro->created = common_sql_now();
+
+                $rid = $remote_pro->insert();
+
+                if (empty($rid)) {
+                    common_log_db_error($profile, 'INSERT', __FILE__);
+                    $profile->query("ROLLBACK");
+                    return false;
+                }
+            }
+
+            $profile->query("COMMIT");
+
+            $this->saveAvatars($user, $id);
+
+            return $id;
+        }
+    }
+
+    function checkAvatar($twitter_user, $profile)
+    {
+        global $config;
+
+        $path_parts = pathinfo($twitter_user->profile_image_url);
+
+        $newname = 'Twitter_' . $twitter_user->id . '_' .
+            $path_parts['basename'];
+
+        $oldname = $profile->getAvatar(48)->filename;
+
+        if ($newname != $oldname) {
+
+            if (defined('SCRIPT_DEBUG')) {
+                common_debug('Avatar for Twitter user ' .
+                    "$profile->nickname has changed.");
+                common_debug("old: $oldname new: $newname");
+            }
+
+            $this->updateAvatars($twitter_user, $profile);
+        }
+
+        if ($this->missingAvatarFile($profile)) {
+
+            if (defined('SCRIPT_DEBUG')) {
+                common_debug('Twitter user ' . $profile->nickname .
+                    ' is missing one or more local avatars.');
+                common_debug("old: $oldname new: $newname");
+            }
+
+            $this->updateAvatars($twitter_user, $profile);
+        }
+
+    }
+
+    function updateAvatars($twitter_user, $profile) {
+
+        global $config;
+
+        $path_parts = pathinfo($twitter_user->profile_image_url);
+
+        $img_root = substr($path_parts['basename'], 0, -11);
+        $ext = $path_parts['extension'];
+        $mediatype = $this->getMediatype($ext);
+
+        foreach (array('mini', 'normal', 'bigger') as $size) {
+            $url = $path_parts['dirname'] . '/' .
+                $img_root . '_' . $size . ".$ext";
+            $filename = 'Twitter_' . $twitter_user->id . '_' .
+                $img_root . "_$size.$ext";
+
+            $this->updateAvatar($profile->id, $size, $mediatype, $filename);
+            $this->fetchAvatar($url, $filename);
+        }
+    }
+
+    function missingAvatarFile($profile) {
+
+        foreach (array(24, 48, 73) as $size) {
+
+            $filename = $profile->getAvatar($size)->filename;
+            $avatarpath = Avatar::path($filename);
+
+            if (file_exists($avatarpath) == FALSE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    function getMediatype($ext)
+    {
+        $mediatype = null;
+
+        switch (strtolower($ext)) {
+        case 'jpg':
+            $mediatype = 'image/jpg';
+            break;
+        case 'gif':
+            $mediatype = 'image/gif';
+            break;
+        default:
+            $mediatype = 'image/png';
+        }
+
+        return $mediatype;
+    }
+
+    function saveAvatars($user, $id)
+    {
+        global $config;
+
+        $path_parts = pathinfo($user->profile_image_url);
+        $ext = $path_parts['extension'];
+        $end = strlen('_normal' . $ext);
+        $img_root = substr($path_parts['basename'], 0, -($end+1));
+        $mediatype = $this->getMediatype($ext);
+
+        foreach (array('mini', 'normal', 'bigger') as $size) {
+            $url = $path_parts['dirname'] . '/' .
+                $img_root . '_' . $size . ".$ext";
+            $filename = 'Twitter_' . $user->id . '_' .
+                $img_root . "_$size.$ext";
+
+            if ($this->fetchAvatar($url, $filename)) {
+                $this->newAvatar($id, $size, $mediatype, $filename);
+            } else {
+                common_log(LOG_WARNING, "Problem fetching Avatar: $url", __FILE__);
+            }
+        }
+    }
+
+    function updateAvatar($profile_id, $size, $mediatype, $filename) {
+
+        if (defined('SCRIPT_DEBUG')) {
+            common_debug("Updating avatar: $size");
+        }
+
+        $profile = Profile::staticGet($profile_id);
+
+        if (empty($profile)) {
+            if (defined('SCRIPT_DEBUG')) {
+                common_debug("Couldn't get profile: $profile_id!");
+            }
+            return;
+        }
+
+        $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
+        $avatar = $profile->getAvatar($sizes[$size]);
+
+        // Delete the avatar, if present
+        if ($avatar) {
+            $avatar->delete();
+        }
+
+        $this->newAvatar($profile->id, $size, $mediatype, $filename);
+    }
+
+    function newAvatar($profile_id, $size, $mediatype, $filename)
+    {
+        global $config;
+
+        $avatar = new Avatar();
+        $avatar->profile_id = $profile_id;
+
+        switch($size) {
+        case 'mini':
+            $avatar->width  = 24;
+            $avatar->height = 24;
+            break;
+        case 'normal':
+            $avatar->width  = 48;
+            $avatar->height = 48;
+            break;
+        default:
+
+            // Note: Twitter's big avatars are a different size than
+            // Laconica's (Laconica's = 96)
+
+            $avatar->width  = 73;
+            $avatar->height = 73;
+        }
+
+        $avatar->original = 0; // we don't have the original
+        $avatar->mediatype = $mediatype;
+        $avatar->filename = $filename;
+        $avatar->url = Avatar::url($filename);
+
+        if (defined('SCRIPT_DEBUG')) {
+            common_debug("new filename: $avatar->url");
+        }
+
+        $avatar->created = common_sql_now();
+
+        $id = $avatar->insert();
+
+        if (empty($id)) {
+            common_log_db_error($avatar, 'INSERT', __FILE__);
+            return null;
+        }
+
+        if (defined('SCRIPT_DEBUG')) {
+            common_debug("Saved new $size avatar for $profile_id.");
+        }
+
+        return $id;
+    }
+
+    function fetchAvatar($url, $filename)
+    {
+        $avatar_dir = INSTALLDIR . '/avatar/';
+
+        $avatarfile = $avatar_dir . $filename;
+
+        $out = fopen($avatarfile, 'wb');
+        if (!$out) {
+            common_log(LOG_WARNING, "Couldn't open file $filename", __FILE__);
+            return false;
+        }
+
+        if (defined('SCRIPT_DEBUG')) {
+            common_debug("Fetching avatar: $url");
+        }
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_FILE, $out);
+        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
+        $result = curl_exec($ch);
+        curl_close($ch);
+
+        fclose($out);
+
+        return $result;
+    }
+}
+
+declare(ticks = 1);
+
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
+
+$fetcher = new TwitterStatusFetcher($id);
+$fetcher->runOnce();
+
index fa0fb64cda575004298d3af4ca67aad316674742..b0b576eb44ff3a2295f479ad62fe3f5f7dc3d1ad 100644 (file)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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', realpath(dirname(__FILE__) . '/..'));
 
-# 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();
-}
+$helptext = <<<ENDOFHELP
+uncache_users.php <idfile>
 
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
+Uncache users listed in an ID file, default 'ids.txt'.
 
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
+ENDOFHELP;
 
-require_once(INSTALLDIR . '/lib/common.php');
+require_once INSTALLDIR.'/scripts/commandline.inc';
 
-$id_file = ($argc > 1) ? $argv[1] : 'ids.txt';
+$id_file = (count($args) > 1) ? $args[0] : 'ids.txt';
 
 common_log(LOG_INFO, 'Updating user inboxes.');
 
 $ids = file($id_file);
 
+$memc = common_memcache();
+
 foreach ($ids as $id) {
-       
+
        $user = User::staticGet('id', $id);
 
        if (!$user) {
@@ -51,9 +46,7 @@ foreach ($ids as $id) {
        }
 
     $user->decache();
-    
-    $memc = common_memcache();
-    
+
     $memc->delete(common_cache_key('user:notices_with_friends:'. $user->id));
     $memc->delete(common_cache_key('user:notices_with_friends:'. $user->id . ';last'));
 }
index 4d7adafea0f44013c0ef5b650c591a4e6b9abec1..2f4ca87208ec7da55fb7dbfa5248c3ba94058e1c 100755 (executable)
@@ -1,66 +1,97 @@
+#!/usr/bin/env php
 <?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, Control Yourself, 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');
+
+// Master Laconica .pot file location (created by update_pot.sh)
+$laconica_pot = INSTALLDIR . '/locale/laconica.po';
 
 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'
-);
+$languages = get_all_languages();
 
 /* 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";
-       }
+
+foreach ($languages as $language) {
+
+    $code = $language['lang'];
+    $file_url = 'http://laconi.ca/pootle/' . $code .
+        '/laconica/LC_MESSAGES/laconica.po';
+    $lcdir = INSTALLDIR . '/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 */
+    $new_file = curl_get_file($file_url);
+
+    if ($new_file === FALSE) {
+        echo "Couldn't retrieve .po file for $code: $file_url\n";
+        continue;
+    }
+
+    // Update if the local .po file is different to the one downloaded, or
+    // if the .mo file is not present.
+    if (sha1($new_file) != $existingSHA1 || !file_exists($mofile)) {
+        echo "Updating ".$code."\n";
+        file_put_contents($pofile, $new_file);
+        system(sprintf('msgmerge -U %s %s', $pofile, $laconica_pot));
+        system(sprintf('msgfmt -f -o %s %s', $mofile, $pofile));
+    } else {
+        echo "Unchanged - ".$code."\n";
+    }
 }
+
 echo "Finished\n";
+
+
+function curl_get_file($url)
+{
+    $c = curl_init();
+    curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
+    curl_setopt($c, CURLOPT_URL, $url);
+    $contents = curl_exec($c);
+    curl_close($c);
+
+    if (!empty($contents)) {
+        return $contents;
+    }
+
+    return FALSE;
+}
index 7f39235fed8464f7e3847990c3976d75e7bd05b8..d6821ddefae03affd7595396f14489f6a74f8571 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_JABBER_HELP
+Daemon script for pushing new confirmations to Jabber users.
+
+    -i --id           Identity (default none)
+
+END_OF_JABBER_HELP;
 
-set_error_handler('common_error_handler');
+require_once INSTALLDIR.'/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/jabber.php';
+require_once INSTALLDIR . '/lib/xmppqueuehandler.php';
 
 define('CLAIM_TIMEOUT', 1200);
 
 class XmppConfirmHandler extends XmppQueueHandler
 {
-
     var $_id = 'confirm';
-    
+
     function class_name()
     {
         return 'XmppConfirmHandler';
     }
-    
+
     function run()
     {
         if (!$this->start()) {
@@ -147,14 +147,17 @@ if (common_config('xmpp','enabled')==false) {
     exit();
 }
 
-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');
+if (have_option('i')) {
+    $id = get_option_value('i');
+} else if (have_option('--id')) {
+    $id = get_option_value('--id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
-$handler = new XmppConfirmHandler($resource);
+$handler = new XmppConfirmHandler($id);
 
 $handler->runOnce();
 
index b79fa1b3ba0145101e167ddafea80fdd67ca3fc8..488b4b514c46a8e997e5f14250380a49ad0fec42 100755 (executable)
@@ -2,7 +2,7 @@
 <?php
 /*
  * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
+ * Copyright (C) 2008, 2009, Control Yourself, 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
  * 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');
+$shortoptions = 'fi::';
+$longoptions = array('id::', 'foreground');
+
+$helptext = <<<END_OF_XMPP_HELP
+Daemon script for receiving new notices from Jabber users.
+
+    -i --id           Identity (default none)
+    -f --foreground   Stay in the foreground (default background)
+
+END_OF_XMPP_HELP;
 
-set_error_handler('common_error_handler');
+require_once INSTALLDIR.'/scripts/commandline.inc';
+
+require_once INSTALLDIR . '/lib/common.php';
+require_once INSTALLDIR . '/lib/jabber.php';
+require_once INSTALLDIR . '/lib/daemon.php';
 
 # 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
@@ -39,9 +43,10 @@ set_error_handler('common_error_handler');
 
 class XMPPDaemon extends Daemon
 {
-
-    function XMPPDaemon($resource=null)
+    function __construct($resource=null, $daemonize=true)
     {
+        parent::__construct($daemonize);
+
         static $attrs = array('server', 'port', 'user', 'password', 'host');
 
         foreach ($attrs as $attr)
@@ -50,17 +55,18 @@ class XMPPDaemon extends Daemon
         }
 
         if ($resource) {
-            $this->resource = $resource;
+            $this->resource = $resource . 'daemon';
         } else {
             $this->resource = common_config('xmpp', 'resource') . 'daemon';
         }
 
-        $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->user}@{$this->server}/{$this->resource}");
+        $this->jid = $this->user.'@'.$this->server.'/'.$this->resource;
+
+        $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->jid}");
     }
 
     function connect()
     {
-
         $connect_to = ($this->host) ? $this->host : $this->server;
 
         $this->log(LOG_INFO, "Connecting to $connect_to on port $this->port");
@@ -71,10 +77,17 @@ class XMPPDaemon extends Daemon
             return false;
         }
 
+        $this->log(LOG_INFO, "Connected");
+
         $this->conn->setReconnectTimeout(600);
 
+        $this->log(LOG_INFO, "Sending initial presence.");
+
         jabber_send_presence("Send me a message to post a notice", 'available',
                              null, 'available', 100);
+
+        $this->log(LOG_INFO, "Done connecting.");
+
         return !$this->conn->isDisconnected();
     }
 
@@ -87,18 +100,44 @@ class XMPPDaemon extends Daemon
     {
         if ($this->connect()) {
 
+            $this->log(LOG_DEBUG, "Initializing stanza handlers.");
+
             $this->conn->addEventHandler('message', 'handle_message', $this);
             $this->conn->addEventHandler('presence', 'handle_presence', $this);
             $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
 
-            $this->conn->process();
+            $this->log(LOG_DEBUG, "Beginning processing loop.");
+
+            while ($this->conn->processTime(60)) {
+                $this->sendPing();
+            }
+        }
+    }
+
+    function sendPing()
+    {
+        if (!isset($this->pingid)) {
+            $this->pingid = 0;
+        } else {
+            $this->pingid++;
         }
+
+        $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
+
+               $this->conn->send("<iq from='{$this->jid}' to='{$this->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
     }
 
     function handle_reconnect(&$pl)
     {
+        $this->log(LOG_DEBUG, "Got reconnection callback.");
         $this->conn->processUntil('session_start');
+        $this->log(LOG_DEBUG, "Sending reconnection presence.");
         $this->conn->presence('Send me a message to post a notice', 'available', null, 'available', 100);
+        unset($pl['xml']);
+        $pl['xml'] = null;
+
+        $pl = null;
+        unset($pl);
     }
 
     function get_user($from)
@@ -109,21 +148,27 @@ class XMPPDaemon extends Daemon
 
     function handle_message(&$pl)
     {
+        $from = jabber_normalize_jid($pl['from']);
+
         if ($pl['type'] != 'chat') {
+            $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from.");
             return;
         }
+
         if (mb_strlen($pl['body']) == 0) {
+            $this->log(LOG_WARNING, "Ignoring message with empty body from $from.");
             return;
         }
 
-        $from = jabber_normalize_jid($pl['from']);
-
         # Forwarded from another daemon (probably a broadcaster) for
         # us to handle
 
         if ($this->is_self($from)) {
+            $this->log(LOG_INFO, "Got forwarded notice from self ($from).");
             $from = $this->get_ofrom($pl);
+            $this->log(LOG_INFO, "Originally sent by $from.");
             if (is_null($from) || $this->is_self($from)) {
+                $this->log(LOG_INFO, "Ignoring notice originally sent by $from.");
                 return;
             }
         }
@@ -138,6 +183,7 @@ class XMPPDaemon extends Daemon
             return;
         }
         if ($this->handle_command($user, $pl['body'])) {
+            $this->log(LOG_INFO, "Command messag by $from handled.");
             return;
         } else if ($this->is_autoreply($pl['body'])) {
             $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from);
@@ -146,17 +192,31 @@ class XMPPDaemon extends Daemon
             $this->log(LOG_INFO, 'Ignoring OTR from ' . $from);
             return;
         } else if ($this->is_direct($pl['body'])) {
+            $this->log(LOG_INFO, 'Got a direct message ' . $from);
+
             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->log(LOG_INFO, 'Direct message from '. $user->nickname . ' to ' . $to);
+
             $this->add_direct($user, $body, $to, $from);
         } else {
+
+            $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
+
             $this->add_notice($user, $pl);
         }
 
         $user->free();
         unset($user);
+
+        unset($pl['xml']);
+        $pl['xml'] = null;
+
+        $pl = null;
+        unset($pl);
     }
 
     function is_self($from)
@@ -259,6 +319,7 @@ class XMPPDaemon extends Daemon
         $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
         if (is_string($notice)) {
             $this->log(LOG_ERR, $notice);
+            $this->from_site($user->jabber, $notice);
             return;
         }
         common_broadcast_notice($notice);
@@ -301,11 +362,23 @@ class XMPPDaemon extends Daemon
             }
             break;
         }
+        unset($pl['xml']);
+        $pl['xml'] = null;
+
+        $pl = null;
+        unset($pl);
     }
 
     function log($level, $msg)
     {
-        common_log($level, 'XMPPDaemon('.$this->resource.'): '.$msg);
+        $text = 'XMPPDaemon('.$this->resource.'): '.$msg;
+        common_log($level, $text);
+        if (!$this->daemonize)
+        {
+            $line = common_log_line($level, $text);
+            echo $line;
+            echo "\n";
+        }
     }
 
     function subscribed($to)
@@ -321,13 +394,16 @@ if (common_config('xmpp','enabled')==false) {
     exit();
 }
 
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
+if (have_option('i', 'id')) {
+    $id = get_option_value('i', 'id');
+} else if (count($args) > 0) {
+    $id = $args[0];
+} else {
+    $id = null;
+}
 
-$resource = ($argc > 1) ? $argv[1] : (common_config('xmpp','resource') . '-listen');
+$foreground = have_option('f', 'foreground');
 
-$daemon = new XMPPDaemon($resource);
+$daemon = new XMPPDaemon($id, !$foreground);
 
 $daemon->runOnce();
index b79adf15c0e08140a2aac575aaa699ab582d380d..8204b9db6c61708aeadb86d7bcf05284a6101188 100644 (file)
@@ -4,68 +4,68 @@
 
 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
+    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
+    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 notice.id = $id AND notice.is_local != -2
+    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
+    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
+    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
+    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
+    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
 }
 
index 8029a5eee17266e4a0cdd2ff0443ce03db8b56ca..3426e71c0b656c99cf5423bce37a2d8e2445f227 100644 (file)
@@ -12,9 +12,9 @@ img { display:block; border:0; }
 a abbr { cursor: pointer; border-bottom:0; }
 table { border-collapse:collapse; }
 ol { list-style-position:inside; }
-html { font-size: 87.5%; background-color:#fff; height:100%; }
+html { font-size: 87.5%; }
 body {
-background-color:#fff;
+background-color:#FFFFFF;
 color:#000;
 font-family:sans-serif;
 font-size:1em;
@@ -77,7 +77,8 @@ margin:0 0 18px 0;
 form label {
 font-weight:bold;
 }
-input.checkbox {
+input.checkbox,
+input.radio {
 position:relative;
 top:2px;
 left:0;
@@ -154,7 +155,8 @@ font-weight:bold;
 #form_invite legend,
 #form_notice_delete legend,
 #form_password_recover legend,
-#form_password_change legend {
+#form_password_change legend,
+.form_entity_block legend {
 display:none;
 }
 
@@ -168,7 +170,8 @@ margin-bottom:0;
 margin-bottom:11px;
 }
 
-.form_settings input.checkbox {
+.form_settings input.checkbox,
+.form_settings input.radio {
 margin-top:3px;
 margin-left:0;
 }
@@ -180,13 +183,19 @@ margin-left:11px;
 float:left;
 width:90%;
 }
-
+.form_settings label.radio {
+margin-top:0;
+margin-right:47px;
+margin-left:11px;
+width:auto;
+}
 
 #form_login p.form_guide,
 #form_register #settings_rememberme p.form_guide,
 #form_openid_login #settings_rememberme p.form_guide,
 #settings_twitter_remove p.form_guide,
-#form_search ul.form_data #q {
+#form_search ul.form_data #q,
+#design_background-image_onoff p.form_guide {
 margin-left:0;
 }
 
@@ -197,10 +206,15 @@ border-radius:4px;
 padding:0 7px;
 }
 
-
+.form_settings input.form_action-default {
+margin-right:11px;
+}
+.form_settings input.form_action-default,
+.form_settings input.form_action-primary {
+padding:0;
+}
 .form_settings input.form_action-secondary {
 margin-left:29px;
-padding:0;
 }
 
 #form_search .submit {
@@ -248,10 +262,10 @@ display:none;
 }
 
 #site_notice {
-position:absolute;
-top:65px;
-right:18px;
-width:250px;
+float:right;
+clear:right;
+margin-top:7px;
+margin-right:18px;
 width:24%;
 }
 #page_notice {
@@ -259,10 +273,9 @@ clear:both;
 margin-bottom:18px;
 }
 
-
 #anon_notice {
 float:left;
-width:43.2%;
+width:42.4%;
 padding:1.1%;
 border-radius:7px;
 -moz-border-radius:7px;
@@ -274,7 +287,6 @@ font-size:1.1em;
 font-weight:bold;
 }
 
-
 #footer {
 float:left;
 width:64%;
@@ -304,7 +316,6 @@ padding:4px 11px;
 border-width:1px;
 border-style:solid;
 border-bottom:0;
-text-shadow: 2px 2px 2px #ddd;
 font-weight:bold;
 }
 #site_nav_local_views .nav {
@@ -385,7 +396,7 @@ margin-bottom:1em;
 }
 
 #content {
-width:64.009%;
+width:63.311%;
 min-height:259px;
 padding:1.795%;
 float:left;
@@ -394,8 +405,11 @@ border-radius:7px;
 -moz-border-radius-topleft:0;
 -webkit-border-radius:7px;
 -webkit-border-top-left-radius:0;
-border-style:solid;
 border-width:1px;
+border-style:solid;
+}
+#shownotice #content {
+min-height:0;
 }
 
 #content_inner {
@@ -408,7 +422,7 @@ float:left;
 width:27.917%;
 min-height:259px;
 float:left;
-margin-left:0.385%;
+margin-left:0.699%;
 padding:1.795%;
 border-radius:7px;
 -moz-border-radius:7px;
@@ -418,7 +432,7 @@ border-style:solid;
 }
 
 #form_notice {
-width:45.664%;
+width:45%;
 float:left;
 position:relative;
 line-height:1;
@@ -440,6 +454,8 @@ width:80.789%;
 height:67px;
 line-height:1.5;
 padding:7px 7px 16px 7px;
+position:relative;
+z-index:2;
 }
 #form_notice label {
 display:block;
@@ -447,8 +463,22 @@ float:left;
 font-size:1.3em;
 margin-bottom:7px;
 }
-#form_notice #notice_submit label {
-display:none;
+#form_notice label[for=notice_data-attach],
+#form_notice #notice_data-attach {
+position:absolute;
+top:25px;
+cursor:pointer;
+}
+#form_notice label[for=notice_data-attach] {
+text-indent:-9999px;
+left:86%;
+width:16px;
+height:16px;
+}
+#form_notice #notice_data-attach {
+left:40.6%;
+padding:0;
+height:16px;
 }
 #form_notice .form_note {
 position:absolute;
@@ -481,13 +511,26 @@ margin-bottom:7px;
 margin-left:18px;
 float:left;
 }
-#form_notice .error {
+#form_notice .error,
+#form_notice .success {
 float:left;
 clear:both;
-width:96.9%;
+width:81.5%;
 margin-bottom:0;
 line-height:1.618;
 }
+#form_notice #notice_data-attach_selected code {
+float:left;
+width:90%;
+display:block;
+font-size:1.1em;
+line-height:1.8;
+overflow:auto;
+}
+#form_notice #notice_data-attach_selected button {
+float:right;
+font-size:0.8em;
+}
 
 /* entity_profile */
 .entity_profile {
@@ -519,7 +562,8 @@ margin-bottom:18px;
 .entity_profile .entity_location,
 .entity_profile .entity_url,
 .entity_profile .entity_note,
-.entity_profile .entity_tags {
+.entity_profile .entity_tags,
+.entity_profile .entity_aliases {
 margin-left:113px;
 margin-bottom:4px;
 }
@@ -545,16 +589,16 @@ font-weight:normal;
 content: ")";
 font-weight:normal;
 }
-
-.entity_profile dt {
-display:none;
-}
+.entity_profile dt,
 .entity_profile h2 {
 display:none;
 }
+.entity_profile .role {
+margin-left:11px;
+font-style:italic;
+}
 /* entity_profile */
 
-
 /*entity_actions*/
 .entity_actions {
 float:right;
@@ -596,10 +640,13 @@ display:block;
 
 .form_user_block input.submit,
 .form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
 .entity_send-a-message a,
 .entity_edit a,
 .form_user_nudge input.submit,
-.entity_nudge p {
+.entity_nudge p,
+.form_make_admin input.submit {
 border:0;
 padding-left:20px;
 }
@@ -636,6 +683,7 @@ margin-bottom:29px;
 clear:both;
 float:left;
 width:100%;
+list-style-position:inside;
 }
 .aside .section h2 {
 text-transform:uppercase;
@@ -659,6 +707,7 @@ list-style-type:none;
 float:left;
 margin-right:7px;
 margin-bottom:7px;
+display:inline;
 }
 .section .entities li .photo {
 margin-right:0;
@@ -678,7 +727,6 @@ margin-bottom:0;
 min-height:60px;
 }
 
-
 .profile .form_group_join legend,
 .profile .form_group_leave legend,
 .profile .form_user_subscribe legend,
@@ -713,13 +761,11 @@ display:inline;
 margin-right:11px;
 }
 
-
 .profile .entity_profile .form_subscription_edit label {
 font-weight:normal;
 margin-right:11px;
 }
 
-
 /* NOTICE */
 .notice,
 .profile {
@@ -730,15 +776,16 @@ clear:both;
 float:left;
 width:100%;
 border-top-width:1px;
-border-top-style:dashed;
+border-top-style:dotted;
 }
 .notices li {
 list-style-type:none;
 }
-.notices li.hover {
-border-radius:4px;
--moz-border-radius:4px;
--webkit-border-radius:4px;
+.notices .notices {
+margin-top:7px;
+margin-left:2%;
+width:98%;
+float:left;
 }
 
 /* NOTICES */
@@ -769,16 +816,14 @@ overflow:hidden;
 font-weight:bold;
 }
 
-.notice .author .photo {
-margin-bottom:0;
-}
-
 .vcard .photo {
 display:inline;
 margin-right:11px;
-margin-bottom:11px;
 float:left;
 }
+#shownotice .vcard .photo {
+margin-bottom:4px;
+}
 .vcard .url {
 text-decoration:none;
 }
@@ -791,6 +836,9 @@ float:left;
 width:100%;
 overflow:hidden;
 }
+.notice .entry-title.ov {
+overflow:visible;
+}
 #shownotice .notice .entry-title {
 font-size:2.2em;
 }
@@ -815,9 +863,10 @@ clear:left;
 float:left;
 font-size:0.95em;
 margin-left:59px;
-width:70%;
+width:60%;
 }
-#showstream .notice div.entry-content {
+#showstream .notice div.entry-content,
+#shownotice .notice div.entry-content {
 margin-left:0;
 }
 
@@ -844,15 +893,12 @@ display:inline-block;
 text-transform:lowercase;
 }
 
-
 .notice-options {
-padding-left:2%;
-float:left;
-width:50%;
 position:relative;
 font-size:0.95em;
-width:12.5%;
+width:90px;
 float:right;
+margin-right:11px;
 }
 
 .notice-options a {
@@ -913,6 +959,97 @@ border:0;
 padding:0;
 }
 
+.notice .attachment {
+position:relative;
+padding-left:16px;
+}
+#attachments .attachment {
+padding-left:0;
+}
+.notice .attachment img {
+position:absolute;
+top:18px;
+left:0;
+z-index:99;
+}
+#shownotice .notice .attachment img {
+position:static;
+}
+
+#attachments {
+clear:both;
+float:left;
+width:100%;
+margin-top:18px;
+}
+#attachments dt {
+font-weight:bold;
+font-size:1.3em;
+margin-bottom:4px;
+}
+
+#attachments ol li {
+margin-bottom:18px;
+list-style-type:decimal;
+float:left;
+clear:both;
+}
+
+#jOverlayContent,
+#jOverlayContent #content,
+#jOverlayContent #content_inner {
+width: auto !important;
+margin-bottom:0;
+}
+#jOverlayContent #content {
+padding:11px;
+min-height:auto;
+}
+#jOverlayContent .external span {
+display:block;
+margin-bottom:11px;
+}
+#jOverlayContent button {
+position:absolute;
+top:0;
+right:0;
+width:29px;
+height:29px;
+text-align:center;
+font-weight:bold;
+padding:0;
+}
+#jOverlayContent h1 {
+max-width:425px;
+}
+#jOverlayContent #content {
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+#jOverlayLoading {
+top:22.5%;
+left:40%;
+}
+#attachment_view img {
+max-width:480px;
+max-height:480px;
+}
+#attachment_view #oembed_info {
+margin-top:11px;
+}
+#attachment_view #oembed_info dt,
+#attachment_view #oembed_info dd {
+float:left;
+}
+#attachment_view #oembed_info dt {
+clear:left;
+margin-right:11px;
+font-weight:bold;
+}
+#attachment_view #oembed_info dt:after {
+content: ":";
+}
 
 #usergroups #new_group {
 float: left;
@@ -925,7 +1062,6 @@ margin-bottom:18px;
 padding-left:20px;
 }
 
-
 #filter_tags {
 margin-bottom:11px;
 float:left;
@@ -971,8 +1107,6 @@ top:3px;
 left:3px;
 }
 
-
-
 .pagination {
 float:left;
 clear:both;
@@ -1018,7 +1152,6 @@ padding-right:30px;
 }
 /* END: NOTICE */
 
-
 .hentry .entry-content p {
 margin-bottom:18px;
 }
@@ -1035,12 +1168,9 @@ margin-bottom:18px;
 margin-left:18px;
 }
 
-
-
-
 /* TOP_POSTERS */
 .section tbody td {
-padding-right:11px;
+padding-right:18px;
 padding-bottom:11px;
 }
 .section .vcard .photo {
@@ -1065,7 +1195,6 @@ margin-right:0;
 display:none;
 }
 
-
 /* tagcloud */
 .tag-cloud {
 list-style-type:none;
@@ -1148,6 +1277,35 @@ clear:both;
 margin-bottom:0;
 }
 
+#form_settings_design #settings_design_background-image img {
+max-width:480px;
+max-height:480px;
+}
+
+#form_settings_design #settings_design_color .form_data,
+#form_settings_design #color-picker {
+float:left;
+}
+#form_settings_design #settings_design_color .form_data {
+width:400px;
+margin-right:28px;
+}
+
+#settings_design_color .form_data li {
+width:33%;
+}
+#settings_design_color .form_data label {
+float:none;
+display:block;
+}
+#settings_design_color .form_data .swatch {
+padding:11px;
+margin-left:0;
+}
+
+.instructions ul {
+list-style-position:inside;
+}
 .instructions p,
 .instructions ul {
 margin-bottom:18px;
index 163b41fb4caa1e96ee857d0371db10dabb003436..e6b1c9ee536ada46cdf3ce77c08b64245271813b 100644 (file)
@@ -1,6 +1,3 @@
-@import url("display.css");
-@import url("../../identica/css/display.css");
-
 * {
 font-size:14px;
 font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
diff --git a/theme/base/css/farbtastic.css b/theme/base/css/farbtastic.css
new file mode 100644 (file)
index 0000000..7efcc73
--- /dev/null
@@ -0,0 +1,32 @@
+.farbtastic {
+  position: relative;
+}
+.farbtastic * {
+  position: absolute;
+  cursor: crosshair;
+}
+.farbtastic, .farbtastic .wheel {
+  width: 195px;
+  height: 195px;
+}
+.farbtastic .color, .farbtastic .overlay {
+  top: 47px;
+  left: 47px;
+  width: 101px;
+  height: 101px;
+}
+.farbtastic .wheel {
+  background: url(../../../js/farbtastic/wheel.png) no-repeat;
+  width: 195px;
+  height: 195px;
+}
+.farbtastic .overlay {
+  background: url(../../../js/farbtastic/mask.png) no-repeat;
+}
+.farbtastic .marker {
+  width: 17px;
+  height: 17px;
+  margin: -8px 0 0 -8px;
+  overflow: hidden; 
+  background: url(../../../js/farbtastic/marker.png) no-repeat;
+}
index 5d8bea8ae3f5c1992228021f287448212c0ad2b5..3e128b84ed607f38df446a4c88f273edc412bcb9 100644 (file)
@@ -1,21 +1,30 @@
 /* IE specific styles */
-legend {
-margin-left:-7px;
-}
-input.checkbox {
+input.checkbox,
+input.radio {
 top:0;
 }
 #form_notice textarea {
 width:78%;
 }
+#form_notice .form_note + label {
+position:absolute;
+top:25px;
+left:83%;
+text-indent:-9999px;
+height:16px;
+width:16px;
+display:block;
+}
 #form_notice #notice_action-submit {
 width:17%;
 max-width:17%;
 }
-#anon_notice {
-max-width:39%;
+#form_notice #notice_data-attach_selected {
+width:78.5%;
+}
+#form_notice #notice_data-attach_selected button {
+padding:0 4px;
 }
-
 .notice-options input.submit {
 font-size:0;
 margin-top:3px;
@@ -30,3 +39,12 @@ margin-right:4px;
 .entity_profile {
 width:64%;
 }
+.notice {
+z-index:1;
+}
+.notice:hover {
+z-index:9999;
+}
+.notice .thumbnail img {
+z-index:9999;
+}
index 76a82c0042d32841784b46fd0bb50d340ddd16d8..dde4d6fc77081906566f6f7ec0cebea44c30dacc 100644 (file)
@@ -5,6 +5,12 @@ margin-left:7px;
 address .fn {
 display:none;
 }
+
+#wrap {
+width:1003px;
+margin:0 auto;
+}
+
 #content {
 width:70%;
 }
@@ -26,5 +32,6 @@ margin-bottom:123px;
 width:20%;
 }
 .notice div.entry-content {
-width:63%;
+width:50%;
+margin-left:30px;
 }
diff --git a/theme/base/default-avatar-mini.png b/theme/base/default-avatar-mini.png
new file mode 100644 (file)
index 0000000..38b8692
Binary files /dev/null and b/theme/base/default-avatar-mini.png differ
diff --git a/theme/base/default-avatar-profile.png b/theme/base/default-avatar-profile.png
new file mode 100644 (file)
index 0000000..f8357d4
Binary files /dev/null and b/theme/base/default-avatar-profile.png differ
diff --git a/theme/base/default-avatar-stream.png b/theme/base/default-avatar-stream.png
new file mode 100644 (file)
index 0000000..6b63baa
Binary files /dev/null and b/theme/base/default-avatar-stream.png differ
diff --git a/theme/base/images/icons/twotone/green/admin.gif b/theme/base/images/icons/twotone/green/admin.gif
new file mode 100644 (file)
index 0000000..10fa431
Binary files /dev/null and b/theme/base/images/icons/twotone/green/admin.gif differ
diff --git a/theme/base/images/icons/twotone/green/arrow-left.gif b/theme/base/images/icons/twotone/green/arrow-left.gif
new file mode 100644 (file)
index 0000000..afed190
Binary files /dev/null and b/theme/base/images/icons/twotone/green/arrow-left.gif differ
diff --git a/theme/base/images/icons/twotone/green/arrow-right.gif b/theme/base/images/icons/twotone/green/arrow-right.gif
new file mode 100644 (file)
index 0000000..ee1707e
Binary files /dev/null and b/theme/base/images/icons/twotone/green/arrow-right.gif differ
diff --git a/theme/base/images/icons/twotone/green/clip-01.gif b/theme/base/images/icons/twotone/green/clip-01.gif
new file mode 100644 (file)
index 0000000..f2dee7e
Binary files /dev/null and b/theme/base/images/icons/twotone/green/clip-01.gif differ
diff --git a/theme/base/images/icons/twotone/green/clip-02.gif b/theme/base/images/icons/twotone/green/clip-02.gif
new file mode 100644 (file)
index 0000000..77a7297
Binary files /dev/null and b/theme/base/images/icons/twotone/green/clip-02.gif differ
diff --git a/theme/base/images/icons/twotone/green/disfavourite.gif b/theme/base/images/icons/twotone/green/disfavourite.gif
new file mode 100644 (file)
index 0000000..3946869
Binary files /dev/null and b/theme/base/images/icons/twotone/green/disfavourite.gif differ
diff --git a/theme/base/images/icons/twotone/green/edit.gif b/theme/base/images/icons/twotone/green/edit.gif
new file mode 100644 (file)
index 0000000..c746aca
Binary files /dev/null and b/theme/base/images/icons/twotone/green/edit.gif differ
diff --git a/theme/base/images/icons/twotone/green/favourite.gif b/theme/base/images/icons/twotone/green/favourite.gif
new file mode 100644 (file)
index 0000000..d93515e
Binary files /dev/null and b/theme/base/images/icons/twotone/green/favourite.gif differ
diff --git a/theme/base/images/icons/twotone/green/mail.gif b/theme/base/images/icons/twotone/green/mail.gif
new file mode 100644 (file)
index 0000000..1084c86
Binary files /dev/null and b/theme/base/images/icons/twotone/green/mail.gif differ
diff --git a/theme/base/images/icons/twotone/green/news.gif b/theme/base/images/icons/twotone/green/news.gif
new file mode 100644 (file)
index 0000000..712c685
Binary files /dev/null and b/theme/base/images/icons/twotone/green/news.gif differ
diff --git a/theme/base/images/icons/twotone/green/quote.gif b/theme/base/images/icons/twotone/green/quote.gif
new file mode 100644 (file)
index 0000000..4ba1f0c
Binary files /dev/null and b/theme/base/images/icons/twotone/green/quote.gif differ
diff --git a/theme/base/images/icons/twotone/green/reply.gif b/theme/base/images/icons/twotone/green/reply.gif
new file mode 100644 (file)
index 0000000..6ff01bb
Binary files /dev/null and b/theme/base/images/icons/twotone/green/reply.gif differ
diff --git a/theme/base/images/icons/twotone/green/shield.gif b/theme/base/images/icons/twotone/green/shield.gif
new file mode 100644 (file)
index 0000000..419d5ee
Binary files /dev/null and b/theme/base/images/icons/twotone/green/shield.gif differ
diff --git a/theme/base/images/icons/twotone/green/trash.gif b/theme/base/images/icons/twotone/green/trash.gif
new file mode 100644 (file)
index 0000000..78dd64a
Binary files /dev/null and b/theme/base/images/icons/twotone/green/trash.gif differ
diff --git a/theme/base/logo.png b/theme/base/logo.png
new file mode 100644 (file)
index 0000000..7c68b34
Binary files /dev/null and b/theme/base/logo.png differ
diff --git a/theme/biz/css/base.css b/theme/biz/css/base.css
new file mode 100644 (file)
index 0000000..696fd06
--- /dev/null
@@ -0,0 +1,1194 @@
+/** theme: biz base
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+* { margin:0; padding:0; }
+img { display:block; border:0; }
+a abbr { cursor: pointer; border-bottom:0; }
+table { border-collapse:collapse; }
+ol { list-style-position:inside; }
+html { font-size: 87.5%; background-color:#fff; height:100%; }
+body {
+background-color:#fff;
+color:#000;
+font-family:sans-serif;
+font-size:1em;
+line-height:1.65;
+position:relative;
+}
+h1,h2,h3,h4,h5,h6 {
+margin-bottom:7px;
+overflow:hidden;
+}
+h1 {
+font-size:1.4em;
+margin-bottom:18px;
+}
+#showstream h1 { display:none; }
+h2 { font-size:1.3em; }
+h3 { font-size:1.2em; }
+h4 { font-size:1.1em; }
+h5 { font-size:1em; }
+h6 { font-size:0.9em; }
+
+caption {
+font-weight:bold;
+}
+legend {
+font-weight:bold;
+font-size:1.3em;
+}
+input, textarea, select, option {
+padding:4px;
+font-family:sans-serif;
+font-size:1em;
+}
+input, textarea, select {
+border-width:2px;
+border-style: solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+input.submit {
+font-weight:bold;
+cursor:pointer;
+}
+textarea {
+overflow:auto;
+}
+option {
+padding-bottom:0;
+}
+fieldset {
+padding:0;
+border:0;
+}
+form ul li {
+list-style-type:none;
+margin:0 0 18px 0;
+}
+form label {
+font-weight:bold;
+}
+input.checkbox {
+position:relative;
+top:2px;
+left:0;
+border:0;
+}
+
+.error,
+.success {
+padding:4px 1.55%;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+margin-bottom:18px;
+}
+form label.submit {
+display:none;
+}
+
+.form_settings {
+clear:both;
+}
+
+.form_settings fieldset {
+margin-bottom:29px;
+}
+.form_settings input.remove {
+margin-left:11px;
+}
+.form_settings .form_data li {
+width:100%;
+float:left;
+}
+.form_settings .form_data label {
+float:left;
+}
+.form_settings .form_data textarea,
+.form_settings .form_data select,
+.form_settings .form_data input {
+margin-left:11px;
+float:left;
+}
+.form_settings .form_data input.submit {
+margin-left:0;
+}
+
+.form_settings label {
+margin-top:2px;
+width:113px;
+}
+
+.form_actions label {
+display:none;
+}
+.form_guide {
+font-style:italic;
+}
+
+.form_settings #settings_autosubscribe label {
+display:inline;
+font-weight:bold;
+}
+
+#form_settings_profile legend,
+#form_login legend,
+#form_register legend,
+#form_password legend,
+#form_settings_avatar legend,
+#newgroup legend,
+#editgroup legend,
+#form_tag_user legend,
+#form_remote_subscribe legend,
+#form_openid_login legend,
+#form_search legend,
+#form_invite legend,
+#form_notice_delete legend,
+#form_password_recover legend,
+#form_password_change legend {
+display:none;
+}
+
+.form_settings .form_data p.form_guide {
+clear:both;
+margin-left:124px;
+margin-bottom:0;
+}
+
+.form_settings p {
+margin-bottom:11px;
+}
+
+.form_settings input.checkbox {
+margin-top:3px;
+margin-left:0;
+}
+.form_settings label.checkbox {
+font-weight:normal;
+margin-top:0;
+margin-right:0;
+margin-left:11px;
+float:left;
+width:90%;
+}
+
+
+#form_login p.form_guide,
+#form_register #settings_rememberme p.form_guide,
+#form_openid_login #settings_rememberme p.form_guide,
+#settings_twitter_remove p.form_guide,
+#form_search ul.form_data #q {
+margin-left:0;
+}
+
+.form_settings .form_note {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+padding:0 7px;
+}
+
+
+.form_settings input.form_action-primary {
+padding:0;
+}
+.form_settings input.form_action-secondary {
+margin-left:29px;
+}
+
+
+#form_search .submit {
+margin-left:11px;
+}
+
+address {
+float:left;
+margin-bottom:18px;
+margin-left:18px;
+}
+address.vcard img.logo {
+margin-right:0;
+}
+address .fn {
+font-weight:bold;
+}
+address img + .fn {
+display:none;
+}
+
+#header {
+width:100%;
+position:relative;
+float:left;
+padding-top:18px;
+margin-bottom:18px;
+}
+
+#site_nav_global_primary {
+float:left;
+margin-right:18px;
+margin-bottom:11px;
+width:50%;
+}
+#site_nav_global_primary ul li {
+display:inline;
+margin-right:11px;
+}
+
+.system_notice dt {
+font-weight:bold;
+text-transform:uppercase;
+display:none;
+}
+
+#site_notice {
+float:right;
+clear:right;
+margin-top:7px;
+margin-right:18px;
+width:24%;
+}
+#page_notice {
+clear:both;
+margin-bottom:18px;
+}
+
+
+#anon_notice {
+float:left;
+width:45.4%;
+/*
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-width:2px;
+border-style:solid;
+*/
+line-height:1.5;
+font-size:1.1em;
+font-weight:bold;
+}
+
+
+#footer {
+float:left;
+width:64%;
+padding:18px;
+}
+
+#site_nav_local_views {
+width:14.5%;
+float:left;
+}
+#site_nav_local_views dt {
+display:none;
+}
+#site_nav_local_views li {
+list-style-type:none;
+}
+#site_nav_local_views a {
+display:block;
+text-decoration:none;
+padding:4px 11px;
+-moz-border-radius-topleft:4px;
+-moz-border-radius-bottomleft:4px;
+-webkit-border-top-left-radius:4px;
+-webkit-border-bottom-left-radius:4px;
+border-width:1px;
+border-style:solid;
+border-right:0;
+text-shadow: 2px 2px 2px #ddd;
+font-weight:bold;
+}
+#site_nav_local_views .nav {
+float:left;
+width:100%;
+}
+
+#site_nav_global_primary dt,
+#site_nav_global_secondary dt {
+display:none;
+}
+
+#site_nav_global_secondary {
+margin-bottom:11px;
+}
+
+#site_nav_global_secondary ul li {
+display:inline;
+margin-right:11px;
+}
+#export_data li a {
+padding-left:20px;
+}
+#export_data li a.foaf {
+padding-left:30px;
+}
+#export_data li a.export_vcard {
+padding-left:28px;
+}
+
+#export_data ul {
+display:inline;
+}
+#export_data li {
+list-style-type:none;
+display:inline;
+margin-left:11px;
+}
+#export_data li:first-child {
+margin-left:0;
+}
+
+#licenses {
+font-size:0.9em;
+}
+
+#licenses dt {
+font-weight:bold;
+display:none;
+}
+#licenses dd {
+margin-bottom:11px;
+line-height:1.5;
+}
+
+#site_content_license_cc {
+margin-bottom:0;
+}
+#site_content_license_cc img {
+display:inline;
+vertical-align:top;
+margin-right:4px;
+}
+
+#wrap {
+margin:0 auto;
+width:100%;
+min-width:760px;
+max-width:1003px;
+overflow:hidden;
+}
+
+#core {
+position:relative;
+width:100%;
+float:left;
+margin-bottom:1em;
+}
+
+#content {
+width:51.009%;
+min-height:259px;
+padding:1.795%;
+float:left;
+border-radius:7px;
+-moz-border-radius:7px;
+-moz-border-radius-topleft:0;
+-webkit-border-radius:7px;
+-webkit-border-top-left-radius:0;
+border-style:solid;
+border-width:1px;
+}
+#shownotice #content {
+min-height:0;
+}
+
+#content_inner {
+position:relative;
+width:100%;
+float:left;
+}
+
+#aside_primary {
+width:29.917%;
+min-height:259px;
+float:left;
+margin-left:0.385%;
+}
+
+#form_notice {
+width:45.664%;
+float:left;
+position:relative;
+line-height:1;
+}
+#form_notice fieldset {
+border:0;
+padding:0;
+position:relative;
+}
+#form_notice legend {
+display:none;
+}
+#form_notice textarea {
+float:left;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+width:80.789%;
+height:67px;
+line-height:1.5;
+padding:7px 7px 16px 7px;
+}
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice label[for=notice_data-attach] {
+text-indent:-9999px;
+}
+#form_notice label[for=notice_data-attach],
+#form_notice #notice_data-attach {
+position:absolute;
+top:25px;
+right:49px;
+width:16px;
+height:16px;
+cursor:pointer;
+}
+#form_notice #notice_data-attach {
+text-indent:-279px;
+}
+#form_notice #notice_submit label {
+display:none;
+}
+#form_notice .form_note {
+position:absolute;
+top:99px;
+right:98px;
+z-index:9;
+}
+#form_notice .form_note dt {
+font-weight:bold;
+display:none;
+}
+#notice_text-count {
+font-weight:bold;
+line-height:1.15;
+padding:1px 2px;
+}
+#form_notice #notice_action-submit {
+width:14%;
+height:47px;
+padding:0;
+position:absolute;
+bottom:0;
+right:0;
+}
+#form_notice label[for=to] {
+margin-top:7px;
+}
+#form_notice select[id=to] {
+margin-bottom:7px;
+margin-left:18px;
+float:left;
+}
+#form_notice .error {
+float:left;
+clear:both;
+width:96.9%;
+margin-bottom:0;
+line-height:1.618;
+}
+
+/* entity_profile */
+.entity_profile {
+position:relative;
+width:67.702%;
+min-height:123px;
+float:left;
+margin-bottom:18px;
+margin-left:0;
+overflow:hidden;
+}
+.entity_profile dt,
+#entity_statistics dt {
+font-weight:bold;
+}
+.entity_profile dd {
+display:inline;
+}
+
+.entity_profile .entity_depiction {
+float:left;
+width:96px;
+margin-right:18px;
+margin-bottom:18px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname,
+.entity_profile .entity_location,
+.entity_profile .entity_url,
+.entity_profile .entity_note,
+.entity_profile .entity_tags {
+margin-left:113px;
+margin-bottom:4px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname {
+margin-left:11px;
+display:inline;
+font-weight:bold;
+}
+.entity_profile .entity_nickname {
+margin-left:0;
+}
+
+.entity_profile .entity_fn dd:before {
+content: "(";
+font-weight:normal;
+}
+.entity_profile .entity_fn dd:after {
+content: ")";
+font-weight:normal;
+}
+
+.entity_profile dt {
+display:none;
+}
+.entity_profile h2 {
+display:none;
+}
+/* entity_profile */
+
+
+/*entity_actions*/
+.entity_actions {
+float:right;
+margin-left:4.35%;
+max-width:25%;
+}
+.entity_actions h2 {
+display:none;
+}
+.entity_actions ul {
+list-style-type:none;
+}
+.entity_actions li {
+margin-bottom:4px;
+}
+.entity_actions li:first-child {
+border-top:0;
+}
+.entity_actions fieldset {
+border:0;
+padding:0;
+}
+.entity_actions legend {
+display:none;
+}
+
+.entity_actions input.submit {
+display:block;
+text-align:left;
+width:100%;
+}
+.entity_actions a,
+.entity_nudge p,
+.entity_remote_subscribe {
+text-decoration:none;
+font-weight:bold;
+display:block;
+}
+
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.entity_edit a,
+.form_user_nudge input.submit,
+.entity_nudge p {
+border:0;
+padding-left:20px;
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p {
+padding:4px 4px 4px 23px;
+}
+
+.entity_remote_subscribe {
+padding:4px;
+border-width:2px;
+border-style:solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.entity_actions .accept {
+margin-bottom:18px;
+}
+
+.entity_tags ul {
+list-style-type:none;
+display:inline;
+}
+.entity_tags li {
+display:inline;
+margin-right:4px;
+}
+
+.aside .section {
+margin-bottom:18px;
+clear:both;
+float:left;
+width:87.985%;
+padding:6%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-width:1px;
+border-style:solid;
+}
+.aside .section h2 {
+text-transform:uppercase;
+font-size:1em;
+}
+
+#entity_statistics dt,
+#entity_statistics dd {
+display:inline;
+}
+#entity_statistics dt:after {
+content: ":";
+}
+
+.section ul.entities {
+float:left;
+width:100%;
+}
+.section .entities li {
+list-style-type:none;
+float:left;
+margin-right:7px;
+margin-bottom:7px;
+}
+.section .entities li .photo {
+margin-right:0;
+margin-bottom:0;
+}
+.section .entities li .fn {
+display:none;
+}
+
+.aside .section p,
+.aside .section .more {
+clear:both;
+}
+
+.profile .entity_profile {
+margin-bottom:0;
+min-height:60px;
+}
+
+
+.profile .form_group_join legend,
+.profile .form_group_leave legend,
+.profile .form_user_subscribe legend,
+.profile .form_user_unsubscribe legend {
+display:none;
+}
+
+.profiles {
+list-style-type:none;
+}
+.profile .entity_profile .entity_location {
+width:auto;
+clear:none;
+margin-left:11px;
+}
+.profile .entity_profile dl,
+.profile .entity_profile dd {
+display:inline;
+float:none;
+}
+.profile .entity_profile .entity_note,
+.profile .entity_profile .entity_url,
+.profile .entity_profile .entity_tags,
+.profile .entity_profile .form_subscription_edit {
+margin-left:59px;
+clear:none;
+display:block;
+width:auto;
+}
+.profile .entity_profile .entity_tags dt {
+display:inline;
+margin-right:11px;
+}
+
+
+.profile .entity_profile .form_subscription_edit label {
+font-weight:normal;
+margin-right:11px;
+}
+
+
+/* NOTICE */
+.notice,
+.profile {
+position:relative;
+padding-top:11px;
+padding-bottom:11px;
+clear:both;
+float:left;
+width:100%;
+border-top-width:1px;
+border-top-style:dotted;
+}
+.notices li {
+list-style-type:none;
+}
+.notices li.hover {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+/* NOTICES */
+#notices_primary {
+float:left;
+width:100%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+#notices_primary h2 {
+display:none;
+}
+.notice-data a span {
+display:block;
+padding-left:28px;
+}
+
+.notice .author {
+margin-right:11px;
+}
+
+.fn {
+overflow:hidden;
+}
+
+.notice .author .fn {
+font-weight:bold;
+}
+
+.vcard .photo {
+display:inline;
+margin-right:11px;
+float:left;
+}
+#shownotice .vcard .photo {
+margin-bottom:4px;
+}
+.vcard .url {
+text-decoration:none;
+}
+.vcard .url:hover {
+text-decoration:underline;
+}
+
+.notice .entry-title {
+float:left;
+width:100%;
+overflow:hidden;
+}
+#shownotice .notice .entry-title {
+font-size:2.2em;
+}
+
+.notice p.entry-content {
+display:inline;
+}
+
+#content .notice p.entry-content a:visited {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.notice p.entry-content .vcard a {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+.notice div.entry-content {
+clear:left;
+float:left;
+font-size:0.95em;
+margin-left:59px;
+width:65%;
+}
+#showstream .notice div.entry-content,
+#shownotice .notice div.entry-content {
+margin-left:0;
+}
+
+.notice .notice-options a,
+.notice .notice-options input {
+float:left;
+font-size:1.025em;
+}
+
+.notice div.entry-content dl,
+.notice div.entry-content dt,
+.notice div.entry-content dd {
+display:inline;
+}
+
+.notice div.entry-content .timestamp dt,
+.notice div.entry-content .response dt {
+display:none;
+}
+.notice div.entry-content .timestamp a {
+display:inline-block;
+}
+.notice div.entry-content .device dt {
+text-transform:lowercase;
+}
+
+
+.notice-options {
+padding-left:2%;
+float:left;
+width:50%;
+position:relative;
+font-size:0.95em;
+width:12.5%;
+float:right;
+}
+
+.notice-options a {
+float:left;
+}
+.notice-options .notice_delete,
+.notice-options .notice_reply,
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+position:absolute;
+top:0;
+}
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+left:0;
+}
+.notice-options .notice_reply {
+left:29px;
+}
+.notice-options .notice_delete {
+right:0;
+}
+.notice-options .notice_reply dt {
+display:none;
+}
+
+.notice-options input,
+.notice-options a {
+text-indent:-9999px;
+outline:none;
+}
+
+.notice-options .notice_reply a,
+.notice-options input.submit {
+display:block;
+border:0;
+}
+.notice-options .notice_reply a,
+.notice-options .notice_delete a {
+text-decoration:none;
+padding-left:16px;
+}
+
+.notice-options form input.submit {
+width:16px;
+padding:2px 0;
+}
+
+.notice-options .notice_delete dt,
+.notice-options .form_favor legend,
+.notice-options .form_disfavor legend {
+display:none;
+}
+.notice-options .notice_delete fieldset,
+.notice-options .form_favor fieldset,
+.notice-options .form_disfavor fieldset {
+border:0;
+padding:0;
+}
+
+
+#usergroups #new_group {
+float: left;
+margin-right: 2em;
+}
+#new_group, #group_search {
+margin-bottom:18px;
+}
+#new_group a {
+padding-left:20px;
+}
+
+
+#filter_tags {
+margin-bottom:11px;
+float:left;
+}
+#filter_tags dt {
+display:none;
+}
+#filter_tags ul {
+list-style-type:none;
+}
+#filter_tags ul li {
+float:left;
+margin-left:7px;
+padding-left:7px;
+border-left-width:1px;
+border-left-style:solid;
+}
+#filter_tags ul li.child_1 {
+margin-left:0;
+border-left:0;
+padding-left:0;
+}
+#filter_tags ul li#filter_tags_all a {
+font-weight:bold;
+margin-top:7px;
+float:left;
+}
+
+#filter_tags ul li#filter_tags_item label {
+margin-right:7px;
+}
+#filter_tags ul li#filter_tags_item label,
+#filter_tags ul li#filter_tags_item select {
+display:inline;
+}
+#filter_tags ul li#filter_tags_item p {
+float:left;
+margin-left:38px;
+}
+#filter_tags ul li#filter_tags_item input {
+position:relative;
+top:3px;
+left:3px;
+}
+
+
+
+.pagination {
+float:left;
+clear:both;
+width:100%;
+margin-top:18px;
+}
+
+.pagination dt {
+font-weight:bold;
+display:none;
+}
+
+.pagination .nav {
+float:left;
+width:100%;
+list-style-type:none;
+}
+
+.pagination .nav_prev {
+float:left;
+}
+.pagination .nav_next {
+float:right;
+}
+
+.pagination a {
+display:block;
+text-decoration:none;
+font-weight:bold;
+padding:7px;
+border-width:1px;
+border-style:solid;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-radius:7px;
+}
+
+.pagination .nav_prev a {
+padding-left:30px;
+}
+.pagination .nav_next a {
+padding-right:30px;
+}
+/* END: NOTICE */
+
+
+.hentry .entry-content p {
+margin-bottom:18px;
+}
+.system_notice ul,
+.instructions ul,
+.hentry entry-content ol,
+.hentry .entry-content ul {
+list-style-position:inside;
+}
+.hentry .entry-content li {
+margin-bottom:18px;
+}
+.hentry .entry-content li li {
+margin-left:18px;
+}
+
+
+
+
+/* TOP_POSTERS */
+.section tbody td {
+padding-right:11px;
+padding-bottom:11px;
+}
+.section .vcard .photo {
+margin-right:7px;
+margin-bottom:0;
+}
+
+.section .notice {
+padding-top:7px;
+padding-bottom:7px;
+border-top:0;
+}
+
+.section .notice:first-child {
+padding-top:0;
+}
+
+.section .notice .author {
+margin-right:0;
+}
+.section .notice .author .fn {
+display:none;
+}
+
+
+/* tagcloud */
+.tag-cloud {
+list-style-type:none;
+text-align:center;
+}
+.aside .tag-cloud {
+font-size:0.8em;
+}
+.tag-cloud li {
+display:inline;
+margin-right:7px;
+line-height:1.25;
+}
+.aside .tag-cloud li {
+line-height:1.5;
+}
+.tag-cloud li a {
+text-decoration:none;
+}
+#tagcloud.section dt {
+text-transform:uppercase;
+font-weight:bold;
+}
+.tag-cloud-1 {
+font-size:1em;
+}
+.tag-cloud-2 {
+font-size:1.25em;
+}
+.tag-cloud-3 {
+font-size:1.75em;
+}
+.tag-cloud-4 {
+font-size:2em;
+}
+.tag-cloud-5 {
+font-size:2.25em;
+}
+.tag-cloud-6 {
+font-size:2.75em;
+}
+.tag-cloud-7 {
+font-size:3.25em;
+}
+
+#publictagcloud #tagcloud.section dt {
+display:none;
+}
+
+#form_settings_photo .form_data {
+clear:both;
+}
+
+#form_settings_avatar li {
+width:auto;
+}
+#form_settings_avatar input {
+margin-left:0;
+}
+#avatar_original,
+#avatar_preview {
+float:left;
+}
+#avatar_preview {
+margin-left:29px;
+}
+#avatar_preview_view {
+height:96px;
+width:96px;
+margin-bottom:18px;
+overflow:hidden;
+}
+
+#settings_attach,
+#form_settings_avatar .form_actions {
+clear:both;
+}
+
+#form_settings_avatar .form_actions {
+margin-bottom:0;
+}
+
+#form_settings_design #settings_design_color .form_data,
+#form_settings_design #color-picker {
+float:left;
+}
+#form_settings_design #settings_design_color .form_data {
+width:400px;
+margin-right:28px;
+}
+
+.instructions ul {
+list-style-position:inside;
+}
+.instructions p,
+.instructions ul {
+margin-bottom:18px;
+}
+.help dt {
+display:none;
+}
+.guide {
+clear:both;
+}
diff --git a/theme/biz/css/display.css b/theme/biz/css/display.css
new file mode 100644 (file)
index 0000000..3af4c06
--- /dev/null
@@ -0,0 +1,259 @@
+/** theme: biz
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+@import url(base.css);
+
+html {
+background-color:#144A6E;
+}
+a:active {
+background-color:#F4F7E7;
+}
+body {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+font-size:1em;
+background:#144A6E url(../images/illustrations/illu_pattern-01.png) repeat-x;
+}
+
+address {
+margin-right:7.18%;
+}
+
+input, textarea, select, option {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+}
+input, textarea, select,
+.entity_remote_subscribe {
+border-color:#aaa;
+}
+#filter_tags ul li {
+border-color:#ddd;
+}
+
+.form_settings input.form_action-primary {
+background:none;
+}
+
+input.submit,
+#form_notice.warning #notice_text-count,
+.form_settings .form_note,
+.entity_remote_subscribe {
+background-color:#9BB43E;
+}
+
+input:focus, textarea:focus, select:focus,
+#form_notice.warning #notice_data-text {
+border-color:#9BB43E;
+}
+input.submit,
+.entity_remote_subscribe,
+#site_nav_local_views a {
+color:#fff;
+}
+
+a,
+#site_nav_local_views .current a,
+div.notice-options input,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.entity_nudge p,
+.form_settings input.form_action-primary {
+color:#002E6E;
+}
+
+#header a,
+#footer a {
+color:#87B4C8;
+}
+
+.notice,
+.profile {
+border-top-color:#CEE1E9;
+}
+.section .profile {
+border-top-color:#87B4C8;
+}
+
+#content .notice p.entry-content a:visited {
+background-color:#fcfcfc;
+}
+#content .notice p.entry-content .vcard a {
+background-color:#fcfffc;
+}
+
+.aside .section {
+background-color:#F1F5F8;
+background-position:100% 0;
+background-image:url(../images/illustrations/illu_pattern-02.png);
+background-repeat:no-repeat;
+}
+
+#notice_text-count {
+color:#333;
+}
+#form_notice.warning #notice_text-count {
+color:#000;
+}
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
+}
+
+#form_notice.processing #notice_action-submit {
+background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+cursor:wait;
+text-indent:-9999px;
+}
+
+#content,
+#site_nav_local_views a,
+.aside .section {
+border-color:#fff;
+}
+#content,
+#site_nav_local_views .current a {
+background-color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:rgba(135, 180, 200, 0.3);
+}
+#site_nav_local_views a:hover {
+background-color:rgba(255, 255, 255, 0.7);
+}
+
+.error {
+background-color:#F7E8E8;
+}
+.success {
+background-color:#EFF3DC;
+}
+
+#anon_notice {
+color:#fff;
+}
+
+#showstream #anon_notice {
+}
+
+#export_data li a {
+background-repeat:no-repeat;
+background-position:0 45%;
+}
+#export_data li a.rss {
+background-image:url(../../base/images/icons/icon_rss.png);
+}
+#export_data li a.atom {
+background-image:url(../../base/images/icons/icon_atom.png);
+}
+#export_data li a.foaf {
+background-image:url(../../base/images/icons/icon_foaf.gif);
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_nudge p {
+background-position: 0 40%;
+background-repeat: no-repeat;
+background-color:transparent;
+}
+.form_group_join input.submit,
+.form_group_leave input.submit
+.form_user_subscribe input.submit,
+.form_user_unsubscribe input.submit {
+background-color:#9BB43E;
+color:#fff;
+}
+.form_user_unsubscribe input.submit,
+.form_group_leave input.submit,
+.form_user_authorization input.reject {
+background-color:#87B4C8;
+}
+
+.entity_edit a {
+background-image:url(../../base/images/icons/twotone/green/edit.gif);
+}
+.entity_send-a-message a {
+background-image:url(../../base/images/icons/twotone/green/quote.gif);
+}
+.entity_nudge p,
+.form_user_nudge input.submit {
+background-image:url(../../base/images/icons/twotone/green/mail.gif);
+}
+.form_user_block input.submit,
+.form_user_unblock input.submit {
+background-image:url(../../base/images/icons/twotone/green/shield.gif);
+}
+
+/* NOTICES */
+.notices li.over {
+background-color:#fcfcfc;
+}
+
+.notice-options .notice_reply a,
+.notice-options form input.submit {
+background-color:transparent;
+}
+.notice-options .notice_reply a {
+background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+}
+.notice-options form.form_favor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+}
+.notice-options form.form_disfavor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+}
+.notice-options .notice_delete a {
+background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+}
+
+.notices div.entry-content,
+.notices div.notice-options {
+opacity:0.4;
+}
+.notices li.hover div.entry-content,
+.notices li.hover div.notice-options {
+opacity:1;
+}
+div.entry-content {
+color:#333;
+}
+div.notice-options a,
+div.notice-options input {
+font-family:sans-serif;
+}
+.notices li.hover {
+background-color:#fcfcfc;
+}
+/*END: NOTICES */
+
+#new_group a {
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+}
+
+.pagination .nav_prev a,
+.pagination .nav_next a {
+background-repeat:no-repeat;
+border-color:#CEE1E9;
+}
+.pagination .nav_prev a {
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
+background-position:10% 45%;
+}
+.pagination .nav_next a {
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
+background-position:90% 45%;
+}
diff --git a/theme/biz/css/ie.css b/theme/biz/css/ie.css
new file mode 100644 (file)
index 0000000..2f463bb
--- /dev/null
@@ -0,0 +1,9 @@
+/* IE specific styles */
+
+.notice-options input.submit {
+color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:#D0DFE7;
+}
diff --git a/theme/biz/default-avatar-mini.png b/theme/biz/default-avatar-mini.png
new file mode 100644 (file)
index 0000000..38b8692
Binary files /dev/null and b/theme/biz/default-avatar-mini.png differ
diff --git a/theme/biz/default-avatar-profile.png b/theme/biz/default-avatar-profile.png
new file mode 100644 (file)
index 0000000..f8357d4
Binary files /dev/null and b/theme/biz/default-avatar-profile.png differ
diff --git a/theme/biz/default-avatar-stream.png b/theme/biz/default-avatar-stream.png
new file mode 100644 (file)
index 0000000..6b63baa
Binary files /dev/null and b/theme/biz/default-avatar-stream.png differ
diff --git a/theme/biz/images/illustrations/illu_pattern-01.png b/theme/biz/images/illustrations/illu_pattern-01.png
new file mode 100644 (file)
index 0000000..79bb46b
Binary files /dev/null and b/theme/biz/images/illustrations/illu_pattern-01.png differ
diff --git a/theme/biz/images/illustrations/illu_pattern-02.png b/theme/biz/images/illustrations/illu_pattern-02.png
new file mode 100644 (file)
index 0000000..4438b75
Binary files /dev/null and b/theme/biz/images/illustrations/illu_pattern-02.png differ
diff --git a/theme/biz/logo.png b/theme/biz/logo.png
new file mode 100644 (file)
index 0000000..fdead6c
Binary files /dev/null and b/theme/biz/logo.png differ
diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css
new file mode 100644 (file)
index 0000000..12f186a
--- /dev/null
@@ -0,0 +1,1552 @@
+/** theme: cloudy
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+* { margin:0; padding:0; }
+img { display:block; border:0; }
+a abbr { cursor: pointer; border-bottom:0; }
+table { border-collapse:collapse; }
+ol { list-style-position:inside; }
+html { font-size: 100%; background-color:#fff; }
+body {
+background-color:#fff;
+color:#000;
+font-family:sans-serif;
+font-size:0.75em;
+line-height:normal;
+position:relative;
+height:100%;
+}
+h1,h2,h3,h4,h5,h6 {
+margin-bottom:7px;
+overflow:hidden;
+}
+h1 {
+font-size:1.4em;
+margin-bottom:18px;
+}
+#showstream h1 { display:none; }
+h2 { font-size:1.3em; }
+h3 { font-size:1.2em; }
+h4 { font-size:1.1em; }
+h5 { font-size:1em; }
+h6 { font-size:0.9em; }
+
+caption {
+font-weight:bold;
+}
+legend {
+font-weight:bold;
+font-size:1.3em;
+}
+input, textarea, select, option {
+padding:4px;
+font-family:sans-serif;
+font-size:1em;
+}
+input, textarea, select {
+border-width:2px;
+border-style: solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+input.submit {
+font-weight:bold;
+cursor:pointer;
+}
+textarea {
+overflow:auto;
+}
+option {
+padding-bottom:0;
+}
+fieldset {
+padding:0;
+border:0;
+}
+form ul li {
+list-style-type:none;
+margin:0 0 18px 0;
+}
+form label {
+font-weight:bold;
+}
+input.checkbox {
+position:relative;
+top:2px;
+left:0;
+border:0;
+}
+
+.error,
+.success {
+padding:4px 7px;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+margin-bottom:18px;
+}
+form label.submit {
+display:none;
+}
+
+.form_settings {
+clear:both;
+}
+
+.form_settings fieldset {
+margin-bottom:29px;
+}
+.form_settings input.remove {
+margin-left:11px;
+}
+.form_settings .form_data li {
+width:100%;
+float:left;
+}
+.form_settings .form_data label {
+float:left;
+}
+.form_settings .form_data textarea,
+.form_settings .form_data select,
+.form_settings .form_data input {
+margin-left:11px;
+float:left;
+}
+.form_settings .form_data input.submit {
+margin-left:0;
+}
+
+.form_settings label {
+margin-top:2px;
+width:143px;
+}
+
+.form_actions label {
+display:none;
+}
+.form_guide {
+font-style:italic;
+}
+
+.form_settings #settings_autosubscribe label {
+display:inline;
+font-weight:bold;
+}
+
+#form_settings_profile legend,
+#form_login legend,
+#form_register legend,
+#form_password legend,
+#form_settings_avatar legend,
+#newgroup legend,
+#editgroup legend,
+#form_tag_user legend,
+#form_remote_subscribe legend,
+#form_openid_login legend,
+#form_search legend,
+#form_invite legend,
+#form_notice_delete legend,
+#form_password_recover legend,
+#form_password_change legend {
+display:none;
+}
+
+.form_settings .form_data p.form_guide {
+clear:both;
+margin-left:155px;
+margin-bottom:0;
+}
+
+.form_settings p {
+margin-bottom:11px;
+}
+
+.form_settings input.checkbox {
+margin-top:0;
+margin-left:0;
+}
+.form_settings label.checkbox {
+font-weight:normal;
+margin-top:0;
+margin-right:0;
+margin-left:11px;
+float:left;
+width:90%;
+}
+
+
+#form_login p.form_guide,
+#form_register #settings_rememberme p.form_guide,
+#form_openid_login #settings_rememberme p.form_guide,
+#settings_twitter_remove p.form_guide,
+#form_search ul.form_data #q {
+margin-left:0;
+}
+
+.form_settings .form_note {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+padding:0 7px;
+}
+
+
+.form_settings input.form_action-primary {
+padding:0;
+}
+.form_settings input.form_action-secondary {
+margin-left:29px;
+}
+
+#form_search .submit {
+margin-left:11px;
+}
+
+address {
+float:left;
+margin-bottom:18px;
+margin-left:18px;
+}
+address.vcard img.logo {
+margin-right:0;
+}
+address .fn {
+font-weight:bold;
+}
+address img + .fn {
+display:none;
+}
+
+#header {
+width:100%;
+position:relative;
+float:left;
+padding-top:18px;
+margin-bottom:11px;
+z-index:1;
+}
+
+#site_nav_global_primary {
+float:right;
+margin-right:0;
+margin-bottom:11px;
+margin-left:18px;
+padding-top:7px;
+padding-bottom:7px;
+padding-right:11px;
+-moz-border-radius:4px;
+border-radius:4px;
+-webkit-border-radius:4px;
+}
+#site_nav_global_primary ul li {
+display:inline;
+margin-left:11px;
+}
+#site_nav_global_primary a {
+text-decoration:none;
+}
+
+.system_notice dt {
+font-weight:bold;
+text-transform:uppercase;
+display:none;
+}
+
+#site_notice {
+position:absolute;
+top:65px;
+right:18px;
+width:250px;
+width:24%;
+}
+#page_notice {
+clear:both;
+margin-bottom:18px;
+}
+
+
+#anon_notice {
+clear:both;
+width:99.8%;
+padding-top:36px;
+line-height:1.5;
+font-size:1.3em;
+font-weight:bold;
+}
+#anon_notice p {
+border-style:solid;
+border-width:1px;
+width:96%;
+padding:2%;
+}
+
+#footer {
+float:left;
+margin-bottom:1em;
+padding:7px;
+-moz-border-radius:4px;
+border-radius:4px;
+-webkit-border-radius:4px;
+}
+#footer a {
+text-decoration:none;
+}
+
+#site_nav_local_views {
+width:203px;
+float:right;
+margin-right:0;
+-moz-border-radius-topright:4px;
+border-radius-topright:4px;
+-webkit-border-top-right-radius:4px;
+}
+#site_nav_local_views dt {
+display:none;
+}
+#site_nav_local_views li {
+list-style-type:none;
+padding:0;
+border-width:1px;
+border-style:solid;
+border-top:0;
+border-right:0;
+}
+#site_nav_local_views a {
+text-decoration:none;
+padding:13px;
+border-width:1px;
+border-style:solid;
+border-bottom:0;
+text-shadow: 2px 2px 2px #ddd;
+font-weight:bold;
+font-size:1em;
+display:block;
+}
+#site_nav_local_views .nav {
+float:left;
+width:100%;
+}
+
+#site_nav_global_primary dt,
+#site_nav_global_secondary dt {
+display:none;
+}
+
+#site_nav_global_secondary {
+margin-bottom:11px;
+}
+
+#site_nav_global_secondary ul li {
+display:inline;
+margin-right:11px;
+}
+#export_data li a {
+padding-left:20px;
+}
+#export_data li a.foaf {
+padding-left:30px;
+}
+#export_data li a.export_vcard {
+padding-left:28px;
+}
+
+#export_data ul {
+display:inline;
+}
+#export_data li {
+list-style-type:none;
+display:inline;
+margin:0 18px 7px 0;
+float:left;
+}
+#export_data li:first-child {
+margin-left:0;
+}
+
+#licenses {
+font-size:0.9em;
+}
+
+#licenses dt {
+font-weight:bold;
+display:none;
+}
+#licenses dd {
+margin-bottom:11px;
+line-height:1.5;
+}
+
+#site_content_license_cc {
+margin-bottom:0;
+}
+#site_content_license_cc img {
+display:inline;
+vertical-align:top;
+margin-right:4px;
+}
+
+#wrap {
+margin:0 auto;
+width: 763px;
+min-width:760px;
+max-width:1003px;
+overflow:hidden;
+}
+
+#core {
+position:relative;
+width:100%;
+float:left;
+margin-bottom:1em;
+padding-top:10px;
+}
+
+#content {
+width:518px;
+min-height:322px;
+padding:20px;
+float:left;
+border-radius-topleft:4px;
+-moz-border-radius-topleft:4px;
+-webkit-border-top-left-radius:4px;
+border-style:solid;
+border-width:1px;
+}
+
+#content_inner {
+position:relative;
+width:100%;
+float:left;
+}
+
+#aside_primary {
+width:182px;
+min-height:259px;
+float:left;
+margin-left:0;
+padding:10px;
+border-width:1px;
+border-style:solid;
+border-right:0;
+border-top:0;
+}
+
+#form_notice {
+width:505px;
+line-height:1;
+position:absolute;
+top:200px;
+left:20px;
+z-index:9;
+}
+#form_notice fieldset {
+border:0;
+padding:0 0 50px 0;
+}
+#form_notice legend {
+display:none;
+}
+#form_notice textarea {
+float:left;
+width:505px;
+height:45px;
+line-height:1.5;
+padding:5px;
+border-width:1px;
+}
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice #notice_submit label {
+display:none;
+}
+#form_notice .form_note {
+position:absolute;
+top:-10px;
+right:-10px;
+z-index:9;
+font-family:Georgia, serif;
+font-size:1.7em;
+}
+#form_notice .form_note dt {
+font-weight:bold;
+display:none;
+}
+#notice_text-count {
+font-weight:bold;
+line-height:1.15;
+padding:1px 2px;
+}
+#form_notice #notice_action-submit {
+width:14%;
+height:35px;
+padding-top:0;
+padding-bottom:0;
+position:absolute;
+bottom:10px;
+right:-10px;
+}
+#form_notice label[for=to] {
+margin-top:7px;
+}
+#form_notice select[id=to] {
+margin-bottom:7px;
+margin-left:18px;
+float:left;
+}
+
+
+/* entity_profile */
+.entity_profile {
+position:relative;
+width:67.702%;
+min-height:123px;
+float:left;
+margin-bottom:18px;
+margin-left:0;
+overflow:hidden;
+}
+.entity_profile dt,
+#entity_statistics dt {
+font-weight:bold;
+}
+.entity_profile dd {
+display:inline;
+}
+
+.entity_profile .entity_depiction {
+float:left;
+width:96px;
+margin-right:18px;
+margin-bottom:18px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname,
+.entity_profile .entity_location,
+.entity_profile .entity_url,
+.entity_profile .entity_note,
+.entity_profile .entity_tags {
+margin-left:113px;
+margin-bottom:4px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname {
+margin-left:11px;
+display:inline;
+font-weight:bold;
+}
+.entity_profile .entity_nickname {
+margin-left:0;
+}
+
+.entity_profile .entity_fn dd:before {
+content: "(";
+font-weight:normal;
+}
+.entity_profile .entity_fn dd:after {
+content: ")";
+font-weight:normal;
+}
+
+.entity_profile dt {
+display:none;
+}
+.entity_profile h2 {
+display:none;
+}
+/* entity_profile */
+
+
+/*entity_actions*/
+.entity_actions {
+float:right;
+margin-left:4.35%;
+max-width:25%;
+}
+.entity_actions h2 {
+display:none;
+}
+.entity_actions ul {
+list-style-type:none;
+}
+.entity_actions li {
+margin-bottom:4px;
+}
+.entity_actions li:first-child {
+border-top:0;
+}
+.entity_actions fieldset {
+border:0;
+padding:0;
+}
+.entity_actions legend {
+display:none;
+}
+
+.entity_actions input.submit {
+display:block;
+text-align:left;
+width:100%;
+}
+.entity_actions a,
+.entity_nudge p,
+.entity_remote_subscribe {
+text-decoration:none;
+font-weight:bold;
+display:block;
+}
+
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.entity_edit a,
+.form_user_nudge input.submit,
+.entity_nudge p {
+border:0;
+padding-left:20px;
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p {
+padding:4px 4px 4px 23px;
+}
+
+.entity_remote_subscribe {
+padding:4px;
+border-width:2px;
+border-style:solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.entity_actions .accept {
+margin-bottom:18px;
+}
+
+.entity_tags ul {
+list-style-type:none;
+display:inline;
+}
+.entity_tags li {
+display:inline;
+margin-right:4px;
+}
+
+.aside .section {
+margin-bottom:18px;
+clear:both;
+float:left;
+width:100%;
+}
+.aside .section h2 {
+font-size:110%;
+text-transform:none;
+}
+
+#entity_statistics dt,
+#entity_statistics dd {
+display:inline;
+}
+#entity_statistics dt:after {
+content: ":";
+}
+
+.section ul.entities {
+float:left;
+width:100%;
+}
+.section .entities li {
+list-style-type:none;
+float:left;
+margin-right:7px;
+margin-bottom:7px;
+}
+.section .entities li .photo {
+margin-right:0;
+margin-bottom:0;
+}
+.section .entities li .fn {
+display:none;
+}
+
+.aside .section p,
+.aside .section .more {
+clear:both;
+}
+
+.profile .entity_profile {
+margin-bottom:0;
+min-height:60px;
+}
+
+
+.profile .form_group_join legend,
+.profile .form_group_leave legend,
+.profile .form_user_subscribe legend,
+.profile .form_user_unsubscribe legend {
+display:none;
+}
+
+.profiles {
+list-style-type:none;
+}
+.profile .entity_profile .entity_location {
+width:auto;
+clear:none;
+margin-left:11px;
+}
+.profile .entity_profile dl,
+.profile .entity_profile dd {
+display:inline;
+float:none;
+}
+.profile .entity_profile .entity_note,
+.profile .entity_profile .entity_url,
+.profile .entity_profile .entity_tags,
+.profile .entity_profile .form_subscription_edit {
+margin-left:59px;
+clear:none;
+display:block;
+width:auto;
+}
+.profile .entity_profile .entity_tags dt {
+display:inline;
+margin-right:11px;
+}
+
+
+.profile .entity_profile .form_subscription_edit label {
+font-weight:normal;
+margin-right:11px;
+}
+
+
+/* NOTICE */
+.notice,
+.profile {
+position:relative;
+padding-top:11px;
+padding-bottom:11px;
+clear:both;
+float:left;
+width:100%;
+border-top-width:1px;
+border-top-style:dotted;
+font-size:1.2em;
+}
+.notices li {
+list-style-type:none;
+line-height:1.1;
+width:94%;
+padding-right:5%;
+padding-left:1%;
+min-height:47px;
+}
+
+
+/* NOTICES */
+#notices_primary {
+float:left;
+width:100%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+#notices_primary h2 {
+display:none;
+}
+.notice-data a span {
+display:block;
+padding-left:28px;
+}
+
+.notice .author {
+margin-right:11px;
+}
+
+.fn {
+overflow:hidden;
+}
+
+.notice .author .fn {
+font-weight:bold;
+}
+
+.notice .author .photo {
+margin-bottom:0;
+}
+
+.vcard .photo {
+display:inline;
+margin-right:11px;
+margin-bottom:11px;
+float:left;
+}
+.vcard .url {
+text-decoration:none;
+}
+.vcard .url:hover {
+text-decoration:underline;
+}
+
+.notice .entry-title {
+float:none;
+display:inline;
+width:100%;
+overflow:hidden;
+}
+#shownotice .notice .entry-title {
+font-size:2.2em;
+}
+
+.notice p.entry-content {
+display:inline;
+}
+
+.notice p.entry-content .vcard a {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+.notice div.entry-content {
+font-size:0.95em;
+margin-left:59px;
+margin-top:3px;
+width:70%;
+font-family:Georgia, serif;
+font-style:italic;
+font-size:0.8em;
+display:block;
+}
+.notice div.entry-content a {
+text-decoration:none;
+}
+.notice div.entry-content a:hover {
+text-decoration:underline;
+}
+#showstream .notice div.entry-content {
+margin-left:0;
+}
+
+.notice .notice-options a,
+.notice .notice-options input {
+float:left;
+font-size:1.025em;
+}
+
+.notice div.entry-content dl,
+.notice div.entry-content dt,
+.notice div.entry-content dd {
+display:inline;
+}
+
+.notice div.entry-content .timestamp dt,
+.notice div.entry-content .response dt {
+display:none;
+}
+.notice div.entry-content .timestamp a {
+display:inline-block;
+}
+.notice div.entry-content .device dt {
+text-transform:lowercase;
+}
+
+
+
+.notice-data {
+position:absolute;
+top:18px;
+right:0;
+min-height:50px;
+margin-bottom:4px;
+}
+.notice .entry-content .notice-data dt {
+display:none;
+}
+
+.notice-data a {
+display:block;
+outline:none;
+}
+
+.notice-options {
+padding-left:2%;
+float:left;
+width:50%;
+font-size:0.95em;
+width:12.5%;
+float:right;
+display:none;
+}
+.notices li.hover div.notice-options {
+display:block;
+}
+
+
+.notice-options a {
+float:left;
+}
+.notice-options .notice_delete,
+.notice-options .notice_reply,
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+position:absolute;
+}
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+top:7px;
+right:7px;
+}
+.notice-options .notice_reply {
+top:30px;
+right:7px;
+}
+.notice-options .notice_delete {
+bottom:7px;
+right:7px;
+}
+.notice-options .notice_reply dt {
+display:none;
+}
+
+.notice-options input,
+.notice-options a {
+text-indent:-9999px;
+outline:none;
+}
+
+.notice-options .notice_reply a,
+.notice-options input.submit {
+display:block;
+border:0;
+}
+.notice-options .notice_reply a,
+.notice-options .notice_delete a {
+text-decoration:none;
+padding-left:16px;
+}
+
+.notice-options form input.submit {
+width:16px;
+padding:2px 0;
+}
+
+.notice-options .notice_delete dt,
+.notice-options .form_favor legend,
+.notice-options .form_disfavor legend {
+display:none;
+}
+.notice-options .notice_delete fieldset,
+.notice-options .form_favor fieldset,
+.notice-options .form_disfavor fieldset {
+border:0;
+padding:0;
+}
+
+
+#usergroups #new_group {
+float: left;
+margin-right: 2em;
+}
+#new_group, #group_search {
+margin-bottom:18px;
+}
+#new_group a {
+padding-left:20px;
+}
+
+
+#filter_tags {
+margin-bottom:11px;
+float:left;
+}
+#filter_tags dt {
+display:none;
+}
+#filter_tags ul {
+list-style-type:none;
+}
+#filter_tags ul li {
+float:left;
+margin-left:7px;
+padding-left:7px;
+border-left-width:1px;
+border-left-style:solid;
+}
+#filter_tags ul li.child_1 {
+margin-left:0;
+border-left:0;
+padding-left:0;
+}
+#filter_tags ul li#filter_tags_all a {
+font-weight:bold;
+margin-top:7px;
+float:left;
+}
+
+#filter_tags ul li#filter_tags_item label {
+margin-right:7px;
+}
+#filter_tags ul li#filter_tags_item label,
+#filter_tags ul li#filter_tags_item select {
+display:inline;
+}
+#filter_tags ul li#filter_tags_item p {
+float:left;
+margin-left:38px;
+}
+#filter_tags ul li#filter_tags_item input {
+position:relative;
+top:3px;
+left:3px;
+}
+
+
+
+.pagination {
+float:left;
+clear:both;
+width:100%;
+margin-top:18px;
+}
+
+.pagination dt {
+font-weight:bold;
+display:none;
+}
+
+.pagination .nav {
+float:left;
+width:100%;
+list-style-type:none;
+}
+
+.pagination .nav_prev {
+float:left;
+}
+.pagination .nav_next {
+float:right;
+}
+
+.pagination a {
+display:block;
+text-decoration:none;
+font-weight:bold;
+padding:7px;
+border-width:1px;
+border-style:solid;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-radius:7px;
+}
+
+.pagination .nav_prev a {
+padding-left:30px;
+}
+.pagination .nav_next a {
+padding-right:30px;
+}
+/* END: NOTICE */
+
+
+.hentry .entry-content p {
+margin-bottom:18px;
+}
+.hentry entry-content ol,
+.hentry .entry-content ul {
+list-style-position:inside;
+}
+.hentry .entry-content li {
+margin-bottom:18px;
+}
+.hentry .entry-content li li {
+margin-left:18px;
+}
+
+
+
+
+/* TOP_POSTERS */
+.section tbody td {
+padding-right:11px;
+padding-bottom:11px;
+}
+.section .vcard .photo {
+margin-right:7px;
+margin-bottom:0;
+}
+
+.section .notice {
+padding-top:7px;
+padding-bottom:7px;
+border-top:0;
+}
+
+.section .notice:first-child {
+padding-top:0;
+}
+
+.section .notice .author {
+margin-right:0;
+}
+.section .notice .author .fn {
+display:none;
+}
+
+
+/* tagcloud */
+.tag-cloud {
+list-style-type:none;
+text-align:center;
+}
+.aside .tag-cloud {
+font-size:0.8em;
+}
+.tag-cloud li {
+display:inline;
+margin-right:7px;
+line-height:1.25;
+}
+.aside .tag-cloud li {
+line-height:1.5;
+}
+.tag-cloud li a {
+text-decoration:none;
+}
+#tagcloud.section dt {
+text-transform:uppercase;
+font-weight:bold;
+}
+.tag-cloud-1 {
+font-size:1em;
+}
+.tag-cloud-2 {
+font-size:1.25em;
+}
+.tag-cloud-3 {
+font-size:1.75em;
+}
+.tag-cloud-4 {
+font-size:2em;
+}
+.tag-cloud-5 {
+font-size:2.25em;
+}
+.tag-cloud-6 {
+font-size:2.75em;
+}
+.tag-cloud-7 {
+font-size:3.25em;
+}
+
+#publictagcloud #tagcloud.section dt {
+display:none;
+}
+
+#form_settings_photo .form_data {
+clear:both;
+}
+
+#form_settings_avatar li {
+width:auto;
+}
+#form_settings_avatar input {
+margin-left:0;
+}
+#avatar_original,
+#avatar_preview {
+float:left;
+}
+#avatar_preview {
+margin-left:29px;
+}
+#avatar_preview_view {
+height:96px;
+width:96px;
+margin-bottom:18px;
+overflow:hidden;
+}
+
+#settings_attach,
+#form_settings_avatar .form_actions {
+clear:both;
+}
+
+#form_settings_avatar .form_actions {
+margin-bottom:0;
+}
+
+.instructions ul {
+list-style-position:inside;
+}
+.instructions p,
+.instructions ul {
+margin-bottom:18px;
+}
+.help dt {
+display:none;
+}
+.guide {
+clear:both;
+}
+
+#public.user_in #content,
+#groups.user_in #content,
+#publictagcloud.user_in #content,
+#featured.user_in #content,
+#favorited.user_in #content,
+#all.user_in #content,
+#replies.user_in #content,
+#showstream.user_in #content,
+#showfavorites.user_in #content,
+#inbox.user_in #content,
+#outbox.user_in #content,
+#subscriptions.user_in #content,
+#subscribers.user_in #content,
+#showgroup.user_in #content {
+padding-top:160px;
+}
+
+#profilesettings #form_notice,
+#avatarsettings #form_notice,
+#passwordsettings #form_notice,
+#emailsettings #form_notice,
+#openidsettings #form_notice,
+#othersettings #form_notice,
+#smssettings #form_notice,
+#twittersettings #form_notice,
+#imsettings #form_notice,
+#doc #form_notice,
+#usergroups #form_notice,
+#invite #form_notice,
+#deletenotice #form_notice,
+#newgroup #form_notice,
+#register #form_notice,
+#shownotice #form_notice,
+#confirmaddress #form_notice,
+#tag #form_notice {
+display:none;
+}
+
+
+html,
+body,
+a:active {
+background-color:#9AE4E8;
+}
+body {
+font-family:'Lucida Grande',sans-serif;
+background:#9AE4E8  url(../images/illustrations/illu_clouds-01.gif) 0 0 no-repeat;
+color:#333333;
+}
+#core {
+background:url(../images/illustrations/illu_arrow-up-01.gif) no-repeat 25px 0;
+}
+
+input, textarea, select, option {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+}
+input, textarea, select,
+.entity_remote_subscribe {
+border-color:#aaa;
+}
+#filter_tags ul li {
+border-color:#ddd;
+}
+
+.form_settings input.form_action-primary {
+background:none;
+}
+
+input.submit,
+#form_notice.warning #notice_text-count,
+#nav_register a,
+.form_settings .form_note,
+.entity_remote_subscribe {
+background-color:#9BB43E;
+}
+
+input:focus, textarea:focus, select:focus,
+#form_notice.warning #notice_data-text {
+border-color:#9BB43E;
+}
+input.submit,
+#nav_register a,
+.entity_remote_subscribe {
+color:#fff;
+}
+
+a,
+div.notice-options input,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.entity_nudge p,
+.form_settings input.form_action-primary {
+color:#0084B4;
+}
+
+.notice,
+.profile {
+border-top-color:#DDFFCC;
+}
+.section .profile {
+border-top-color:#87B4C8;
+}
+
+
+#content .notice p.entry-content a:visited {
+background-color:#fcfcfc;
+}
+#content .notice p.entry-content .vcard a {
+background-color:#fcfffc;
+}
+
+#aside_primary {
+background-color:#DDFFCC;
+}
+
+
+#notice_text-count {
+color:#333;
+}
+#form_notice.warning #notice_text-count {
+color:#000;
+}
+#form_notice.processing #notice_action-submit {
+background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+cursor:wait;
+text-indent:-9999px;
+}
+
+#content,
+#site_nav_local_views a,
+#aside_primary {
+border-color:#fff;
+}
+#content,
+#site_nav_local_views .current a {
+background-color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:rgba(135, 180, 200, 0.3);
+}
+#site_nav_local_views a:hover {
+background-color:rgba(255, 255, 255, 0.7);
+}
+
+
+.error {
+background-color:#F7E8E8;
+}
+.success {
+background-color:#EFF3DC;
+}
+
+
+#anon_notice {
+background-color:#FEFFDF;
+color:#333;
+border-color:#fff;
+}
+
+#showstream #anon_notice {
+background-color:#FEFFDF;
+}
+
+
+#export_data li a {
+background-repeat:no-repeat;
+background-position:0 45%;
+}
+#export_data li a.rss {
+background-image:url(../../base/images/icons/icon_rss.png);
+}
+#export_data li a.atom {
+background-image:url(../../base/images/icons/icon_atom.png);
+}
+#export_data li a.foaf {
+background-image:url(../../base/images/icons/icon_foaf.gif);
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_nudge p {
+background-position: 0 40%;
+background-repeat: no-repeat;
+background-color:transparent;
+}
+.form_group_join input.submit,
+.form_group_leave input.submit
+.form_user_subscribe input.submit,
+.form_user_unsubscribe input.submit {
+background-color:#9BB43E;
+color:#fff;
+}
+.form_user_unsubscribe input.submit,
+.form_group_leave input.submit {
+background-color:#87B4C8;
+}
+
+.entity_edit a {
+background-image:url(../images/icons/twotone/green/edit.gif);
+}
+.entity_send-a-message a {
+background-image:url(../images/icons/twotone/green/quote.gif);
+}
+.entity_nudge p,
+.form_user_nudge input.submit {
+background-image:url(../images/icons/twotone/green/mail.gif);
+}
+.form_user_block input.submit,
+.form_user_unblock input.submit {
+background-image:url(../images/icons/twotone/green/shield.gif);
+}
+
+
+
+/* NOTICES */
+.notices li.over {
+background-color:#fcfcfc;
+}
+
+.notice-options .notice_reply a,
+.notice-options form input.submit {
+background-color:transparent;
+}
+.notice-options .notice_reply a {
+background:transparent url(../images/icons/icon_reply.gif) no-repeat 0 45%;
+}
+.notice-options form.form_favor input.submit {
+background:transparent url(../images/icons/icon_favourite.gif) no-repeat 0 45%;
+}
+.notice-options form.form_disfavor input.submit {
+background:transparent url(../images/icons/icon_disfavourite.gif) no-repeat 0 45%;
+}
+.notice-options .notice_delete a {
+background:transparent url(../images/icons/icon_trash.gif) no-repeat 0 45%;
+}
+
+.notices div.entry-content,
+.notices div.notice-options {
+opacity:0.4;
+}
+.notices li.hover div.entry-content,
+.notices li.hover div.notice-options {
+opacity:1;
+}
+div.entry-content {
+color:#333;
+}
+div.notice-options a,
+div.notice-options input {
+font-family:sans-serif;
+}
+.notices li.hover {
+background-color:#fcfcfc;
+}
+/*END: NOTICES */
+
+
+#new_group a {
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+}
+
+.pagination .nav_prev a,
+.pagination .nav_next a {
+background-repeat:no-repeat;
+border-color:#DDFFCC;
+}
+.pagination .nav_prev a {
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
+background-position:10% 45%;
+}
+.pagination .nav_next a {
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
+background-position:90% 45%;
+}
+
+
+/*--------------------------------------*/
+
+#anon_notice {
+background:url(../images/illustrations/illu_unicorn-01.png) no-repeat 0 0;
+}
+#showstream #anon_notice,
+#content .notice p.entry-content a:visited,
+content .notice p.entry-content .vcard a {
+background-color:transparent;
+}
+
+#anon_notice p {
+background-color:#FEFFDF;
+border-color:#FFFF00;
+}
+
+
+#form_notice .form_note {
+color:#CCC;
+}
+input.submit {
+background-color:#eee;
+color:#666;
+}
+
+.notices li.hover {
+background-color:#F7F7F7;
+}
+
+
+.notice div.entry-content,
+.notice div.entry-content a {
+color:#999;
+}
+
+.notices div.entry-content,
+.notices div.notice-options {
+opacity:1;
+}
+
+#site_nav_local_views {
+background-color:#DDFFCC;
+}
+#site_nav_local_views li,
+#aside_primary {
+border-color:#BDDCAD;
+}
+#site_nav_local_views a,
+.aside .section h2 {
+background-color:transparent;
+border-color:transparent;
+color:#4C4C4C;
+}
+#site_nav_local_views .current {
+border-left-color:#fff;
+}
+
+#site_nav_local_views .current a,
+#site_nav_global_primary,
+#footer {
+background-color:#fff;
+}
+
diff --git a/theme/cloudy/css/ie.css b/theme/cloudy/css/ie.css
new file mode 100644 (file)
index 0000000..0951221
--- /dev/null
@@ -0,0 +1,34 @@
+/* IE specific styles */
+
+.notice-options input.submit {
+color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:#ddffcc;
+}
+
+#aside_primary {
+width:181px;
+}
+
+#form_notice,
+#anon_notice {
+top:158px;
+}
+
+#public #content,
+#groups #content,
+#publictagcloud #content,
+#featured #content,
+#favorited #content,
+#all #content,
+#replies #content,
+#showstream #content,
+#showfavorites #content,
+#inbox #content,
+#outbox #content,
+#subscriptions #content,
+#subscribers #content {
+padding-top:138px;
+}
diff --git a/theme/cloudy/default-avatar-mini.png b/theme/cloudy/default-avatar-mini.png
new file mode 100644 (file)
index 0000000..4fd8bd9
Binary files /dev/null and b/theme/cloudy/default-avatar-mini.png differ
diff --git a/theme/cloudy/default-avatar-profile.png b/theme/cloudy/default-avatar-profile.png
new file mode 100644 (file)
index 0000000..eb08571
Binary files /dev/null and b/theme/cloudy/default-avatar-profile.png differ
diff --git a/theme/cloudy/default-avatar-stream.png b/theme/cloudy/default-avatar-stream.png
new file mode 100644 (file)
index 0000000..926b8a9
Binary files /dev/null and b/theme/cloudy/default-avatar-stream.png differ
diff --git a/theme/cloudy/images/icons/icon_atom.png b/theme/cloudy/images/icons/icon_atom.png
new file mode 100644 (file)
index 0000000..6a001f1
Binary files /dev/null and b/theme/cloudy/images/icons/icon_atom.png differ
diff --git a/theme/cloudy/images/icons/icon_disfavourite.gif b/theme/cloudy/images/icons/icon_disfavourite.gif
new file mode 100644 (file)
index 0000000..2b02ac8
Binary files /dev/null and b/theme/cloudy/images/icons/icon_disfavourite.gif differ
diff --git a/theme/cloudy/images/icons/icon_favourite.gif b/theme/cloudy/images/icons/icon_favourite.gif
new file mode 100644 (file)
index 0000000..716ce35
Binary files /dev/null and b/theme/cloudy/images/icons/icon_favourite.gif differ
diff --git a/theme/cloudy/images/icons/icon_foaf.gif b/theme/cloudy/images/icons/icon_foaf.gif
new file mode 100644 (file)
index 0000000..f8f7844
Binary files /dev/null and b/theme/cloudy/images/icons/icon_foaf.gif differ
diff --git a/theme/cloudy/images/icons/icon_processing.gif b/theme/cloudy/images/icons/icon_processing.gif
new file mode 100644 (file)
index 0000000..d0bce15
Binary files /dev/null and b/theme/cloudy/images/icons/icon_processing.gif differ
diff --git a/theme/cloudy/images/icons/icon_reply.gif b/theme/cloudy/images/icons/icon_reply.gif
new file mode 100644 (file)
index 0000000..a4379a7
Binary files /dev/null and b/theme/cloudy/images/icons/icon_reply.gif differ
diff --git a/theme/cloudy/images/icons/icon_rss.png b/theme/cloudy/images/icons/icon_rss.png
new file mode 100644 (file)
index 0000000..0ccd1ce
Binary files /dev/null and b/theme/cloudy/images/icons/icon_rss.png differ
diff --git a/theme/cloudy/images/icons/icon_trash.gif b/theme/cloudy/images/icons/icon_trash.gif
new file mode 100644 (file)
index 0000000..916a332
Binary files /dev/null and b/theme/cloudy/images/icons/icon_trash.gif differ
diff --git a/theme/cloudy/images/icons/icon_vcard.gif b/theme/cloudy/images/icons/icon_vcard.gif
new file mode 100644 (file)
index 0000000..6d52947
Binary files /dev/null and b/theme/cloudy/images/icons/icon_vcard.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/arrow-left.gif b/theme/cloudy/images/icons/twotone/green/arrow-left.gif
new file mode 100644 (file)
index 0000000..afed190
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/arrow-left.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/arrow-right.gif b/theme/cloudy/images/icons/twotone/green/arrow-right.gif
new file mode 100644 (file)
index 0000000..ee1707e
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/arrow-right.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/edit.gif b/theme/cloudy/images/icons/twotone/green/edit.gif
new file mode 100644 (file)
index 0000000..c746aca
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/edit.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/mail.gif b/theme/cloudy/images/icons/twotone/green/mail.gif
new file mode 100644 (file)
index 0000000..1084c86
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/mail.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/news.gif b/theme/cloudy/images/icons/twotone/green/news.gif
new file mode 100644 (file)
index 0000000..712c685
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/news.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/quote.gif b/theme/cloudy/images/icons/twotone/green/quote.gif
new file mode 100644 (file)
index 0000000..4ba1f0c
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/quote.gif differ
diff --git a/theme/cloudy/images/icons/twotone/green/shield.gif b/theme/cloudy/images/icons/twotone/green/shield.gif
new file mode 100644 (file)
index 0000000..419d5ee
Binary files /dev/null and b/theme/cloudy/images/icons/twotone/green/shield.gif differ
diff --git a/theme/cloudy/images/illustrations/illu_arrow-up-01.gif b/theme/cloudy/images/illustrations/illu_arrow-up-01.gif
new file mode 100644 (file)
index 0000000..577be18
Binary files /dev/null and b/theme/cloudy/images/illustrations/illu_arrow-up-01.gif differ
diff --git a/theme/cloudy/images/illustrations/illu_clouds-01.gif b/theme/cloudy/images/illustrations/illu_clouds-01.gif
new file mode 100644 (file)
index 0000000..41cd622
Binary files /dev/null and b/theme/cloudy/images/illustrations/illu_clouds-01.gif differ
diff --git a/theme/cloudy/images/illustrations/illu_jcrop.gif b/theme/cloudy/images/illustrations/illu_jcrop.gif
new file mode 100644 (file)
index 0000000..72ea7cc
Binary files /dev/null and b/theme/cloudy/images/illustrations/illu_jcrop.gif differ
diff --git a/theme/cloudy/images/illustrations/illu_progress_loading-01.gif b/theme/cloudy/images/illustrations/illu_progress_loading-01.gif
new file mode 100644 (file)
index 0000000..82290f4
Binary files /dev/null and b/theme/cloudy/images/illustrations/illu_progress_loading-01.gif differ
diff --git a/theme/cloudy/images/illustrations/illu_unicorn-01.png b/theme/cloudy/images/illustrations/illu_unicorn-01.png
new file mode 100644 (file)
index 0000000..6cb51b2
Binary files /dev/null and b/theme/cloudy/images/illustrations/illu_unicorn-01.png differ
diff --git a/theme/cloudy/logo.png b/theme/cloudy/logo.png
new file mode 100644 (file)
index 0000000..fdead6c
Binary files /dev/null and b/theme/cloudy/logo.png differ
index 69a600cc286303440516d5038f8c59c6df5dd0ee..251d6706bee4de44330563b2b1279f4baf484ce7 100644 (file)
@@ -9,17 +9,16 @@
 
 @import url(../../base/css/display.css);
 
-html,
 body,
 a:active {
-background-color:#97BFD1;
+background-color:#CEE1E9;
 }
 body {
 font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
 font-size:1em;
 }
 address {
-margin-right:7.18%;
+margin-right:7.2%;
 }
 
 input, textarea, select, option {
@@ -27,13 +26,13 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
 }
 input, textarea, select,
 .entity_remote_subscribe {
-border-color:#aaa;
+border-color:#AAAAAA;
 }
 #filter_tags ul li {
-border-color:#97BFD1;
+border-color:#DDDDDD;
 }
 
-.form_settings input.form_action-secondary {
+.form_settings input.form_action-primary {
 background:none;
 }
 
@@ -41,76 +40,95 @@ input.submit,
 #form_notice.warning #notice_text-count,
 .form_settings .form_note,
 .entity_remote_subscribe {
-background-color:#A9BF4F;
+background-color:#9BB43E;
 }
 
 input:focus, textarea:focus, select:focus,
 #form_notice.warning #notice_data-text {
-border-color:#A9BF4F;
+border-color:#9BB43E;
+box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 }
 input.submit,
 .entity_remote_subscribe {
-color:#fff;
+color:#FFFFFF;
 }
 
 a,
 div.notice-options input,
 .form_user_block input.submit,
 .form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
 .entity_send-a-message a,
 .form_user_nudge input.submit,
 .entity_nudge p,
-.form_settings input.form_action-secondary {
+.form_settings input.form_action-primary,
+.form_make_admin input.submit {
 color:#002E6E;
 }
 
 .notice,
 .profile {
-border-top-color:#D1D9E4;
+border-top-color:#C8D1D5;
 }
 .section .profile {
-border-top-color:#97BFD1;
-}
-
-#content .notice p.entry-content a:visited {
-background-color:#fcfcfc;
-}
-#content .notice p.entry-content .vcard a {
-background-color:#fcfffc;
+border-top-color:#87B4C8;
 }
 
 #aside_primary {
-background-color:#CEE1E9;
+background-color:#C8D1D5;
 }
 
 #notice_text-count {
-color:#333;
+color:#333333;
 }
 #form_notice.warning #notice_text-count {
-color:#000;
+color:#000000;
+}
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
 }
+
 #form_notice.processing #notice_action-submit {
-background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
 text-indent:-9999px;
 }
 
+#content {
+box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
+-moz-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
+-webkit-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
+}
 #content,
 #site_nav_local_views a,
 #aside_primary {
-border-color:#fff;
+border-color:transparent;
 }
 #content,
 #site_nav_local_views .current a {
-background-color:#fff;
+background-color:#FFFFFF;
 }
 
+#site_nav_local_views li {
+box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
+-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
+-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
+}
 #site_nav_local_views a {
-background-color:rgba(255, 255, 255, 0.2);
+background-color:rgba(194, 194, 194, 0.5);
 }
 #site_nav_local_views a:hover {
 background-color:rgba(255, 255, 255, 0.7);
 }
+#site_nav_local_views .current a {
+text-shadow: rgba(194,194,194,0.5) 1px 1px 1px;
+}
 
 .error {
 background-color:#F7E8E8;
@@ -120,13 +138,13 @@ background-color:#EFF3DC;
 }
 
 #anon_notice {
-background-color:#97BFD1;
-color:#fff;
-border-color:#fff;
+background-color:#87B4C8;
+color:#FFFFFF;
+border-color:#FFFFFF;
 }
 
 #showstream #anon_notice {
-background-color:#A9BF4F;
+background-color:#9BB43E;
 }
 
 #export_data li a {
@@ -148,7 +166,10 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
 .form_user_nudge input.submit,
 .form_user_block input.submit,
 .form_user_unblock input.submit,
-.entity_nudge p {
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.entity_nudge p,
+.form_make_admin input.submit {
 background-position: 0 40%;
 background-repeat: no-repeat;
 background-color:transparent;
@@ -157,86 +178,109 @@ background-color:transparent;
 .form_group_leave input.submit
 .form_user_subscribe input.submit,
 .form_user_unsubscribe input.submit {
-background-color:#A9BF4F;
-color:#fff;
+background-color:#9BB43E;
+color:#FFFFFF;
 }
 .form_user_unsubscribe input.submit,
 .form_group_leave input.submit,
 .form_user_authorization input.reject {
-background-color:#97BFD1;
+background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../images/icons/twotone/green/edit.gif);
+background-image:url(../../base/images/icons/twotone/green/edit.gif);
 }
 .entity_send-a-message a {
-background-image:url(../images/icons/twotone/green/quote.gif);
+background-image:url(../../base/images/icons/twotone/green/quote.gif);
 }
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../images/icons/twotone/green/mail.gif);
+background-image:url(../../base/images/icons/twotone/green/mail.gif);
 }
 .form_user_block input.submit,
-.form_user_unblock input.submit {
-background-image:url(../images/icons/twotone/green/shield.gif);
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit {
+background-image:url(../../base/images/icons/twotone/green/shield.gif);
+}
+.form_make_admin input.submit {
+background-image:url(../../base/images/icons/twotone/green/admin.gif);
 }
 
 /* NOTICES */
-.notices li.over {
-background-color:#fcfcfc;
+.notice .attachment {
+background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+}
+#attachments .attachment {
+background:none;
 }
-
 .notice-options .notice_reply a,
 .notice-options form input.submit {
 background-color:transparent;
 }
 .notice-options .notice_reply a {
-background:transparent url(../images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
 }
 .notice-options .notice_delete a {
-background:transparent url(../images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
 }
 
 .notices div.entry-content,
 .notices div.notice-options {
 opacity:0.4;
 }
-.notices li.hover div.entry-content,
-.notices li.hover div.notice-options {
+.notices li:hover div.entry-content,
+.notices li:hover div.notice-options {
 opacity:1;
 }
 div.entry-content {
-color:#333;
+color:#333333;
 }
 div.notice-options a,
 div.notice-options input {
 font-family:sans-serif;
 }
-.notices li.hover {
-background-color:#fcfcfc;
+#content .notices li:hover {
+background-color:rgba(240, 240, 240, 0.2);
+}
+#conversation .notices li:hover {
+background-color:transparent;
+}
+
+.notices .notices {
+background-color:rgba(200, 200, 200, 0.050);
+}
+.notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.100);
+}
+.notices .notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.150);
+}
+.notices .notices .notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.300);
 }
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../images/icons/twotone/green/news.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
 }
 
 .pagination .nav_prev a,
 .pagination .nav_next a {
 background-repeat:no-repeat;
-border-color:#D1D9E4;
+border-color:#C8D1D5;
 }
 .pagination .nav_prev a {
-background-image:url(../images/icons/twotone/green/arrow-left.gif);
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
 background-position:10% 45%;
 }
 .pagination .nav_next a {
-background-image:url(../images/icons/twotone/green/arrow-right.gif);
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
 background-position:90% 45%;
 }
index 2b06768ea4687c35baf04dd3b8e7b086f0dfe7a7..cbbd49ce6ca6a87a7d928834d158d764ad1a55af 100644 (file)
@@ -1,9 +1,14 @@
 /* IE specific styles */
 
 .notice-options input.submit {
-color:#fff;
+color:#FFFFFF;
 }
-
 #site_nav_local_views a {
-background-color:#ACCCDA;
+background-color:#C8D1D5;
+}
+#form_notice .form_note + label {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+filter: alpha(opacity=0);
 }
diff --git a/theme/default/images/icons/icon_atom.jpg b/theme/default/images/icons/icon_atom.jpg
deleted file mode 100644 (file)
index 22853ed..0000000
Binary files a/theme/default/images/icons/icon_atom.jpg and /dev/null differ
diff --git a/theme/default/images/icons/icon_foaf.gif b/theme/default/images/icons/icon_foaf.gif
deleted file mode 100644 (file)
index f8f7844..0000000
Binary files a/theme/default/images/icons/icon_foaf.gif and /dev/null differ
diff --git a/theme/default/images/icons/icon_rss.jpg b/theme/default/images/icons/icon_rss.jpg
deleted file mode 100644 (file)
index da23422..0000000
Binary files a/theme/default/images/icons/icon_rss.jpg and /dev/null differ
diff --git a/theme/default/images/icons/icon_vcard.gif b/theme/default/images/icons/icon_vcard.gif
deleted file mode 100644 (file)
index 6d52947..0000000
Binary files a/theme/default/images/icons/icon_vcard.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/against.gif b/theme/default/images/icons/twotone/green/against.gif
deleted file mode 100644 (file)
index ca796c8..0000000
Binary files a/theme/default/images/icons/twotone/green/against.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-down.gif b/theme/default/images/icons/twotone/green/arrow-down.gif
deleted file mode 100644 (file)
index c709e58..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-down.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-downleft.gif b/theme/default/images/icons/twotone/green/arrow-downleft.gif
deleted file mode 100644 (file)
index a4a9803..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-downleft.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-downright.gif b/theme/default/images/icons/twotone/green/arrow-downright.gif
deleted file mode 100644 (file)
index 3e6001a..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-downright.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-left.gif b/theme/default/images/icons/twotone/green/arrow-left.gif
deleted file mode 100644 (file)
index afed190..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-left.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-right.gif b/theme/default/images/icons/twotone/green/arrow-right.gif
deleted file mode 100644 (file)
index ee1707e..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-right.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-up.gif b/theme/default/images/icons/twotone/green/arrow-up.gif
deleted file mode 100644 (file)
index d0f5fbe..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-up.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-upleft.gif b/theme/default/images/icons/twotone/green/arrow-upleft.gif
deleted file mode 100644 (file)
index 1e9e693..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-upleft.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/arrow-upright.gif b/theme/default/images/icons/twotone/green/arrow-upright.gif
deleted file mode 100644 (file)
index c7fecc8..0000000
Binary files a/theme/default/images/icons/twotone/green/arrow-upright.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/back-forth.gif b/theme/default/images/icons/twotone/green/back-forth.gif
deleted file mode 100644 (file)
index 33a9540..0000000
Binary files a/theme/default/images/icons/twotone/green/back-forth.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/bookmark.gif b/theme/default/images/icons/twotone/green/bookmark.gif
deleted file mode 100644 (file)
index 23f318e..0000000
Binary files a/theme/default/images/icons/twotone/green/bookmark.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/bulb.gif b/theme/default/images/icons/twotone/green/bulb.gif
deleted file mode 100644 (file)
index f70652c..0000000
Binary files a/theme/default/images/icons/twotone/green/bulb.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/calendar.gif b/theme/default/images/icons/twotone/green/calendar.gif
deleted file mode 100644 (file)
index a09b65a..0000000
Binary files a/theme/default/images/icons/twotone/green/calendar.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/calendar2.gif b/theme/default/images/icons/twotone/green/calendar2.gif
deleted file mode 100644 (file)
index 7884b02..0000000
Binary files a/theme/default/images/icons/twotone/green/calendar2.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/camera.gif b/theme/default/images/icons/twotone/green/camera.gif
deleted file mode 100644 (file)
index 1a85fba..0000000
Binary files a/theme/default/images/icons/twotone/green/camera.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/cart.gif b/theme/default/images/icons/twotone/green/cart.gif
deleted file mode 100644 (file)
index 47eaa0a..0000000
Binary files a/theme/default/images/icons/twotone/green/cart.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/caution.gif b/theme/default/images/icons/twotone/green/caution.gif
deleted file mode 100644 (file)
index 3ad2c32..0000000
Binary files a/theme/default/images/icons/twotone/green/caution.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/chart.gif b/theme/default/images/icons/twotone/green/chart.gif
deleted file mode 100644 (file)
index 136d745..0000000
Binary files a/theme/default/images/icons/twotone/green/chart.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/checkmark.gif b/theme/default/images/icons/twotone/green/checkmark.gif
deleted file mode 100644 (file)
index 892429d..0000000
Binary files a/theme/default/images/icons/twotone/green/checkmark.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/clipboard.gif b/theme/default/images/icons/twotone/green/clipboard.gif
deleted file mode 100644 (file)
index 9317bdc..0000000
Binary files a/theme/default/images/icons/twotone/green/clipboard.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/clock.gif b/theme/default/images/icons/twotone/green/clock.gif
deleted file mode 100644 (file)
index d1410f9..0000000
Binary files a/theme/default/images/icons/twotone/green/clock.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/closed-folder.gif b/theme/default/images/icons/twotone/green/closed-folder.gif
deleted file mode 100644 (file)
index 0410fc6..0000000
Binary files a/theme/default/images/icons/twotone/green/closed-folder.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/database.gif b/theme/default/images/icons/twotone/green/database.gif
deleted file mode 100644 (file)
index 29ce024..0000000
Binary files a/theme/default/images/icons/twotone/green/database.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/disfavourite.gif b/theme/default/images/icons/twotone/green/disfavourite.gif
deleted file mode 100644 (file)
index 3946869..0000000
Binary files a/theme/default/images/icons/twotone/green/disfavourite.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/diskette.gif b/theme/default/images/icons/twotone/green/diskette.gif
deleted file mode 100644 (file)
index e970b0a..0000000
Binary files a/theme/default/images/icons/twotone/green/diskette.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/document.gif b/theme/default/images/icons/twotone/green/document.gif
deleted file mode 100644 (file)
index 9c08f4a..0000000
Binary files a/theme/default/images/icons/twotone/green/document.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/double-arrow.gif b/theme/default/images/icons/twotone/green/double-arrow.gif
deleted file mode 100644 (file)
index 2e86482..0000000
Binary files a/theme/default/images/icons/twotone/green/double-arrow.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/edit.gif b/theme/default/images/icons/twotone/green/edit.gif
deleted file mode 100644 (file)
index c746aca..0000000
Binary files a/theme/default/images/icons/twotone/green/edit.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/eject.gif b/theme/default/images/icons/twotone/green/eject.gif
deleted file mode 100644 (file)
index 7e0906c..0000000
Binary files a/theme/default/images/icons/twotone/green/eject.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/exclaim.gif b/theme/default/images/icons/twotone/green/exclaim.gif
deleted file mode 100644 (file)
index 588e28c..0000000
Binary files a/theme/default/images/icons/twotone/green/exclaim.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/fastforward.gif b/theme/default/images/icons/twotone/green/fastforward.gif
deleted file mode 100644 (file)
index 28e4951..0000000
Binary files a/theme/default/images/icons/twotone/green/fastforward.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/favourite.gif b/theme/default/images/icons/twotone/green/favourite.gif
deleted file mode 100644 (file)
index d93515e..0000000
Binary files a/theme/default/images/icons/twotone/green/favourite.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/flag.gif b/theme/default/images/icons/twotone/green/flag.gif
deleted file mode 100644 (file)
index 68c8aee..0000000
Binary files a/theme/default/images/icons/twotone/green/flag.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/graph.gif b/theme/default/images/icons/twotone/green/graph.gif
deleted file mode 100644 (file)
index 0c1794b..0000000
Binary files a/theme/default/images/icons/twotone/green/graph.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/grow.gif b/theme/default/images/icons/twotone/green/grow.gif
deleted file mode 100644 (file)
index c4118d5..0000000
Binary files a/theme/default/images/icons/twotone/green/grow.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/headphones.gif b/theme/default/images/icons/twotone/green/headphones.gif
deleted file mode 100644 (file)
index 5be6c67..0000000
Binary files a/theme/default/images/icons/twotone/green/headphones.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/home.gif b/theme/default/images/icons/twotone/green/home.gif
deleted file mode 100644 (file)
index d2a3421..0000000
Binary files a/theme/default/images/icons/twotone/green/home.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/hourglass.gif b/theme/default/images/icons/twotone/green/hourglass.gif
deleted file mode 100644 (file)
index b62b948..0000000
Binary files a/theme/default/images/icons/twotone/green/hourglass.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/info.gif b/theme/default/images/icons/twotone/green/info.gif
deleted file mode 100644 (file)
index 86ef1f8..0000000
Binary files a/theme/default/images/icons/twotone/green/info.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/key.gif b/theme/default/images/icons/twotone/green/key.gif
deleted file mode 100644 (file)
index ccf357a..0000000
Binary files a/theme/default/images/icons/twotone/green/key.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/lock.gif b/theme/default/images/icons/twotone/green/lock.gif
deleted file mode 100644 (file)
index db00706..0000000
Binary files a/theme/default/images/icons/twotone/green/lock.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/mail.gif b/theme/default/images/icons/twotone/green/mail.gif
deleted file mode 100644 (file)
index 1084c86..0000000
Binary files a/theme/default/images/icons/twotone/green/mail.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/move.gif b/theme/default/images/icons/twotone/green/move.gif
deleted file mode 100644 (file)
index d2c30b1..0000000
Binary files a/theme/default/images/icons/twotone/green/move.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/music.gif b/theme/default/images/icons/twotone/green/music.gif
deleted file mode 100644 (file)
index 64b51d4..0000000
Binary files a/theme/default/images/icons/twotone/green/music.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/news.gif b/theme/default/images/icons/twotone/green/news.gif
deleted file mode 100644 (file)
index 712c685..0000000
Binary files a/theme/default/images/icons/twotone/green/news.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/note.gif b/theme/default/images/icons/twotone/green/note.gif
deleted file mode 100644 (file)
index bcc0b14..0000000
Binary files a/theme/default/images/icons/twotone/green/note.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/open-folder.gif b/theme/default/images/icons/twotone/green/open-folder.gif
deleted file mode 100644 (file)
index d41300a..0000000
Binary files a/theme/default/images/icons/twotone/green/open-folder.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/paper-clip.gif b/theme/default/images/icons/twotone/green/paper-clip.gif
deleted file mode 100644 (file)
index 1d45f1d..0000000
Binary files a/theme/default/images/icons/twotone/green/paper-clip.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/paper-clip2.gif b/theme/default/images/icons/twotone/green/paper-clip2.gif
deleted file mode 100644 (file)
index a8c7805..0000000
Binary files a/theme/default/images/icons/twotone/green/paper-clip2.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/pause.gif b/theme/default/images/icons/twotone/green/pause.gif
deleted file mode 100644 (file)
index ced0b64..0000000
Binary files a/theme/default/images/icons/twotone/green/pause.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/phone.gif b/theme/default/images/icons/twotone/green/phone.gif
deleted file mode 100644 (file)
index 69359f7..0000000
Binary files a/theme/default/images/icons/twotone/green/phone.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/play.gif b/theme/default/images/icons/twotone/green/play.gif
deleted file mode 100644 (file)
index 794ec85..0000000
Binary files a/theme/default/images/icons/twotone/green/play.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/plus.gif b/theme/default/images/icons/twotone/green/plus.gif
deleted file mode 100644 (file)
index 4407d0b..0000000
Binary files a/theme/default/images/icons/twotone/green/plus.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/print.gif b/theme/default/images/icons/twotone/green/print.gif
deleted file mode 100644 (file)
index 17727d5..0000000
Binary files a/theme/default/images/icons/twotone/green/print.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/question-mark.gif b/theme/default/images/icons/twotone/green/question-mark.gif
deleted file mode 100644 (file)
index 1689efc..0000000
Binary files a/theme/default/images/icons/twotone/green/question-mark.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/quote.gif b/theme/default/images/icons/twotone/green/quote.gif
deleted file mode 100644 (file)
index 4ba1f0c..0000000
Binary files a/theme/default/images/icons/twotone/green/quote.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/refresh.gif b/theme/default/images/icons/twotone/green/refresh.gif
deleted file mode 100644 (file)
index 8a8b814..0000000
Binary files a/theme/default/images/icons/twotone/green/refresh.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/reply.gif b/theme/default/images/icons/twotone/green/reply.gif
deleted file mode 100644 (file)
index 6ff01bb..0000000
Binary files a/theme/default/images/icons/twotone/green/reply.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/rewind.gif b/theme/default/images/icons/twotone/green/rewind.gif
deleted file mode 100644 (file)
index aca3ee3..0000000
Binary files a/theme/default/images/icons/twotone/green/rewind.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/search.gif b/theme/default/images/icons/twotone/green/search.gif
deleted file mode 100644 (file)
index c36463d..0000000
Binary files a/theme/default/images/icons/twotone/green/search.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/shield.gif b/theme/default/images/icons/twotone/green/shield.gif
deleted file mode 100644 (file)
index 419d5ee..0000000
Binary files a/theme/default/images/icons/twotone/green/shield.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/skip-back.gif b/theme/default/images/icons/twotone/green/skip-back.gif
deleted file mode 100644 (file)
index adca7aa..0000000
Binary files a/theme/default/images/icons/twotone/green/skip-back.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/skip.gif b/theme/default/images/icons/twotone/green/skip.gif
deleted file mode 100644 (file)
index ae5417f..0000000
Binary files a/theme/default/images/icons/twotone/green/skip.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/skull.gif b/theme/default/images/icons/twotone/green/skull.gif
deleted file mode 100644 (file)
index 0335067..0000000
Binary files a/theme/default/images/icons/twotone/green/skull.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/statusbar.gif b/theme/default/images/icons/twotone/green/statusbar.gif
deleted file mode 100644 (file)
index 47d61b1..0000000
Binary files a/theme/default/images/icons/twotone/green/statusbar.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/stop.gif b/theme/default/images/icons/twotone/green/stop.gif
deleted file mode 100644 (file)
index e0b108d..0000000
Binary files a/theme/default/images/icons/twotone/green/stop.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/template.gif b/theme/default/images/icons/twotone/green/template.gif
deleted file mode 100644 (file)
index 65c0c4a..0000000
Binary files a/theme/default/images/icons/twotone/green/template.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/text-bigger.gif b/theme/default/images/icons/twotone/green/text-bigger.gif
deleted file mode 100644 (file)
index 45e143b..0000000
Binary files a/theme/default/images/icons/twotone/green/text-bigger.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/text-smaller.gif b/theme/default/images/icons/twotone/green/text-smaller.gif
deleted file mode 100644 (file)
index a54d0c1..0000000
Binary files a/theme/default/images/icons/twotone/green/text-smaller.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/trash.gif b/theme/default/images/icons/twotone/green/trash.gif
deleted file mode 100644 (file)
index 78dd64a..0000000
Binary files a/theme/default/images/icons/twotone/green/trash.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/two-docs.gif b/theme/default/images/icons/twotone/green/two-docs.gif
deleted file mode 100644 (file)
index 97e54b9..0000000
Binary files a/theme/default/images/icons/twotone/green/two-docs.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/twotone.gif b/theme/default/images/icons/twotone/green/twotone.gif
deleted file mode 100644 (file)
index 45aad25..0000000
Binary files a/theme/default/images/icons/twotone/green/twotone.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/undo.gif b/theme/default/images/icons/twotone/green/undo.gif
deleted file mode 100644 (file)
index 6869b30..0000000
Binary files a/theme/default/images/icons/twotone/green/undo.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/user.gif b/theme/default/images/icons/twotone/green/user.gif
deleted file mode 100644 (file)
index c85460f..0000000
Binary files a/theme/default/images/icons/twotone/green/user.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/vegetable.gif b/theme/default/images/icons/twotone/green/vegetable.gif
deleted file mode 100644 (file)
index 4d421c1..0000000
Binary files a/theme/default/images/icons/twotone/green/vegetable.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/x.gif b/theme/default/images/icons/twotone/green/x.gif
deleted file mode 100644 (file)
index ffb2efe..0000000
Binary files a/theme/default/images/icons/twotone/green/x.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/zoom-in.gif b/theme/default/images/icons/twotone/green/zoom-in.gif
deleted file mode 100644 (file)
index a59a5bb..0000000
Binary files a/theme/default/images/icons/twotone/green/zoom-in.gif and /dev/null differ
diff --git a/theme/default/images/icons/twotone/green/zoom-out.gif b/theme/default/images/icons/twotone/green/zoom-out.gif
deleted file mode 100644 (file)
index c61f999..0000000
Binary files a/theme/default/images/icons/twotone/green/zoom-out.gif and /dev/null differ
diff --git a/theme/default/logo.png b/theme/default/logo.png
new file mode 100644 (file)
index 0000000..fdead6c
Binary files /dev/null and b/theme/default/logo.png differ
diff --git a/theme/h4ck3r/css/base.css b/theme/h4ck3r/css/base.css
new file mode 100644 (file)
index 0000000..41b3a77
--- /dev/null
@@ -0,0 +1,1139 @@
+/** theme: h4ck3r base
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+* { margin:0; padding:0; }
+img { display:block; border:0; }
+a abbr { cursor: pointer; border-bottom:0; }
+table { border-collapse:collapse; }
+ol { list-style-position:inside; }
+html { font-size: 100%; background-color:#fff; height:100%; }
+body {
+background-color:#fff;
+color:#000;
+font-family:sans-serif;
+font-size:1em;
+line-height:1.65;
+position:relative;
+}
+h1,h2,h3,h4,h5,h6 {
+margin-bottom:7px;
+overflow:hidden;
+}
+h1 {
+font-size:1.4em;
+margin-bottom:18px;
+}
+#showstream h1 { display:none; }
+h2 { font-size:1.3em; }
+h3 { font-size:1.2em; }
+h4 { font-size:1.1em; }
+h5 { font-size:1em; }
+h6 { font-size:0.9em; }
+
+caption {
+font-weight:bold;
+}
+legend {
+font-weight:bold;
+font-size:1.3em;
+}
+input, textarea, select, option {
+padding:4px;
+font-family:sans-serif;
+font-size:1em;
+}
+input, textarea, select {
+border-width:2px;
+border-style: solid;
+}
+
+input.submit {
+font-weight:bold;
+cursor:pointer;
+}
+textarea {
+overflow:auto;
+}
+option {
+padding-bottom:0;
+}
+fieldset {
+padding:0;
+border:0;
+}
+form ul li {
+list-style-type:none;
+margin:0 0 18px 0;
+}
+form label {
+font-weight:bold;
+}
+input.checkbox {
+position:relative;
+top:2px;
+left:0;
+border:0;
+}
+
+.error,
+.success {
+padding:4px 1.55%;
+margin-bottom:18px;
+}
+form label.submit {
+display:none;
+}
+
+.form_settings {
+clear:both;
+}
+
+.form_settings fieldset {
+margin-bottom:29px;
+}
+.form_settings input.remove {
+margin-left:11px;
+}
+.form_settings .form_data li {
+width:100%;
+float:left;
+}
+.form_settings .form_data label {
+float:left;
+}
+.form_settings .form_data textarea,
+.form_settings .form_data select,
+.form_settings .form_data input {
+margin-left:11px;
+float:left;
+}
+.form_settings .form_data input.submit {
+margin-left:0;
+}
+
+.form_settings label {
+margin-top:2px;
+width:152px;
+}
+
+.form_actions label {
+display:none;
+}
+.form_guide {
+font-style:italic;
+}
+
+.form_settings #settings_autosubscribe label {
+display:inline;
+font-weight:bold;
+}
+
+#form_settings_profile legend,
+#form_login legend,
+#form_register legend,
+#form_password legend,
+#form_settings_avatar legend,
+#newgroup legend,
+#editgroup legend,
+#form_tag_user legend,
+#form_remote_subscribe legend,
+#form_openid_login legend,
+#form_search legend,
+#form_invite legend,
+#form_notice_delete legend,
+#form_password_recover legend,
+#form_password_change legend {
+display:none;
+}
+
+.form_settings .form_data p.form_guide {
+clear:both;
+margin-left:163px;
+margin-bottom:0;
+}
+
+.form_settings p {
+margin-bottom:11px;
+}
+
+.form_settings input.checkbox {
+margin-top:3px;
+margin-left:0;
+}
+.form_settings label.checkbox {
+font-weight:normal;
+margin-top:0;
+margin-right:0;
+margin-left:11px;
+float:left;
+width:90%;
+}
+
+
+#form_login p.form_guide,
+#form_register #settings_rememberme p.form_guide,
+#form_openid_login #settings_rememberme p.form_guide,
+#settings_twitter_remove p.form_guide,
+#form_search ul.form_data #q {
+margin-left:0;
+}
+
+.form_settings .form_note {
+padding:0 7px;
+}
+
+
+.form_settings input.form_action-primary {
+padding:0;
+}
+.form_settings input.form_action-secondary {
+margin-left:29px;
+}
+
+#form_search .submit {
+margin-left:11px;
+}
+
+address {
+float:left;
+margin-bottom:18px;
+margin-left:18px;
+}
+address.vcard img.logo {
+margin-right:0;
+}
+address .fn {
+font-weight:bold;
+}
+address img + .fn {
+display:none;
+}
+
+#header {
+width:100%;
+position:relative;
+float:left;
+padding-top:18px;
+margin-bottom:29px;
+}
+
+#site_nav_global_primary {
+float:right;
+margin-right:18px;
+margin-bottom:11px;
+margin-left:18px;
+}
+#site_nav_global_primary ul li {
+display:inline;
+margin-left:11px;
+}
+
+.system_notice dt {
+font-weight:bold;
+text-transform:uppercase;
+display:none;
+}
+
+#site_notice {
+float:left;
+clear:right;
+margin-top:7px;
+margin-right:18px;
+width:31%;
+}
+#page_notice {
+clear:both;
+margin-bottom:18px;
+}
+
+
+#anon_notice {
+float:right;
+clear:right;
+width:41.2%;
+padding:1.1%;
+border-width:2px;
+border-style:dashed;
+line-height:1.5;
+font-size:1.1em;
+font-weight:bold;
+-moz-transform:skewX(-30deg) scale(0.85);
+-webkit-transform:skewX(-30deg) scale(0.85);
+}
+
+
+#footer {
+float:left;
+width:64%;
+padding:18px;
+}
+
+#site_nav_local_views {
+width:100%;
+float:right;
+}
+#site_nav_local_views dt {
+display:none;
+}
+#site_nav_local_views li {
+float:right;
+margin-left:11px;
+list-style-type:none;
+}
+#site_nav_local_views a {
+float:left;
+text-decoration:none;
+padding:4px 11px;
+border-width:1px;
+border-style:dashed;
+border-bottom:0;
+text-shadow: 2px 2px 2px #ddd;
+font-weight:bold;
+}
+#site_nav_local_views .nav {
+float:left;
+width:100%;
+}
+
+#site_nav_global_primary dt,
+#site_nav_global_secondary dt {
+display:none;
+}
+
+#site_nav_global_secondary {
+margin-bottom:11px;
+}
+
+#site_nav_global_secondary ul li {
+display:inline;
+margin-right:11px;
+}
+#export_data li a {
+padding-left:20px;
+}
+#export_data li a.foaf {
+padding-left:30px;
+}
+#export_data li a.export_vcard {
+padding-left:28px;
+}
+
+#export_data ul {
+display:inline;
+}
+#export_data li {
+list-style-type:none;
+display:inline;
+margin-left:11px;
+}
+#export_data li:first-child {
+margin-left:0;
+}
+
+#licenses {
+font-size:0.9em;
+}
+
+#licenses dt {
+font-weight:bold;
+display:none;
+}
+#licenses dd {
+margin-bottom:11px;
+line-height:1.5;
+}
+
+#site_content_license_cc {
+margin-bottom:0;
+}
+#site_content_license_cc img {
+display:inline;
+vertical-align:top;
+margin-right:4px;
+}
+
+#wrap {
+margin:0 auto;
+width:100%;
+min-width:760px;
+max-width:1003px;
+overflow:hidden;
+}
+
+#core {
+position:relative;
+width:100%;
+float:left;
+margin-bottom:1em;
+}
+
+#content {
+width:60.009%;
+min-height:259px;
+padding:1.795%;
+float:right;
+border-style:dashed;
+border-width:1px;
+}
+#shownotice #content {
+min-height:0;
+}
+
+#content_inner {
+position:relative;
+width:100%;
+float:left;
+}
+
+#aside_primary {
+width:27.917%;
+min-height:259px;
+float:right;
+margin-right:4.385%;
+padding:1.795%;
+border-width:1px;
+border-style:dashed;
+}
+
+#form_notice {
+width:43.664%;
+float:right;
+position:relative;
+line-height:1;
+}
+#form_notice fieldset {
+border:0;
+padding:0;
+position:relative;
+}
+#form_notice legend {
+display:none;
+}
+#form_notice textarea {
+float:left;
+width:80.789%;
+height:67px;
+line-height:1.5;
+padding:7px 7px 16px 7px;
+}
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice #notice_submit label {
+display:none;
+}
+#form_notice .form_note {
+position:absolute;
+top:99px;
+right:98px;
+z-index:9;
+}
+#form_notice .form_note dt {
+font-weight:bold;
+display:none;
+}
+#notice_text-count {
+font-weight:bold;
+line-height:1.15;
+padding:1px 2px;
+}
+#form_notice #notice_action-submit {
+width:14%;
+height:47px;
+padding:0;
+position:absolute;
+bottom:0;
+right:0;
+}
+#form_notice label[for=to] {
+margin-top:7px;
+}
+#form_notice select[id=to] {
+margin-bottom:7px;
+margin-left:18px;
+float:left;
+}
+#form_notice .error {
+float:left;
+clear:both;
+width:96.9%;
+margin-bottom:0;
+line-height:1.618;
+}
+
+/* entity_profile */
+.entity_profile {
+position:relative;
+width:67.702%;
+min-height:123px;
+float:left;
+margin-bottom:18px;
+margin-left:0;
+overflow:hidden;
+}
+.entity_profile dt,
+#entity_statistics dt {
+font-weight:bold;
+}
+.entity_profile dd {
+display:inline;
+}
+
+.entity_profile .entity_depiction {
+float:left;
+width:96px;
+margin-right:18px;
+margin-bottom:18px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname,
+.entity_profile .entity_location,
+.entity_profile .entity_url,
+.entity_profile .entity_note,
+.entity_profile .entity_tags {
+margin-left:113px;
+margin-bottom:4px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname {
+margin-left:11px;
+display:inline;
+font-weight:bold;
+}
+.entity_profile .entity_nickname {
+margin-left:0;
+}
+
+.entity_profile .entity_fn dd:before {
+content: "(";
+font-weight:normal;
+}
+.entity_profile .entity_fn dd:after {
+content: ")";
+font-weight:normal;
+}
+
+.entity_profile dt {
+display:none;
+}
+.entity_profile h2 {
+display:none;
+}
+/* entity_profile */
+
+
+/*entity_actions*/
+.entity_actions {
+float:right;
+margin-left:4.35%;
+max-width:25%;
+}
+.entity_actions h2 {
+display:none;
+}
+.entity_actions ul {
+list-style-type:none;
+}
+.entity_actions li {
+margin-bottom:4px;
+}
+.entity_actions li:first-child {
+border-top:0;
+}
+.entity_actions fieldset {
+border:0;
+padding:0;
+}
+.entity_actions legend {
+display:none;
+}
+
+.entity_actions input.submit {
+display:block;
+text-align:left;
+width:100%;
+}
+.entity_actions a,
+.entity_nudge p,
+.entity_remote_subscribe {
+text-decoration:none;
+font-weight:bold;
+display:block;
+}
+
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.entity_edit a,
+.form_user_nudge input.submit,
+.entity_nudge p {
+border:0;
+padding-left:20px;
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p {
+padding:4px 4px 4px 23px;
+}
+
+.entity_remote_subscribe {
+padding:4px;
+border-width:2px;
+border-style:solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.entity_actions .accept {
+margin-bottom:18px;
+}
+
+.entity_tags ul {
+list-style-type:none;
+display:inline;
+}
+.entity_tags li {
+display:inline;
+margin-right:4px;
+}
+
+.aside .section {
+margin-bottom:29px;
+clear:both;
+float:left;
+width:100%;
+}
+.aside .section h2 {
+text-transform:uppercase;
+font-size:1em;
+}
+
+#entity_statistics dt,
+#entity_statistics dd {
+display:inline;
+}
+#entity_statistics dt:after {
+content: ":";
+}
+
+.section ul.entities {
+float:left;
+width:100%;
+}
+.section .entities li {
+list-style-type:none;
+float:left;
+margin-right:7px;
+margin-bottom:7px;
+}
+.section .entities li .photo {
+margin-right:0;
+margin-bottom:0;
+}
+.section .entities li .fn {
+display:none;
+}
+
+.aside .section p,
+.aside .section .more {
+clear:both;
+}
+
+.profile .entity_profile {
+margin-bottom:0;
+min-height:60px;
+}
+
+
+.profile .form_group_join legend,
+.profile .form_group_leave legend,
+.profile .form_user_subscribe legend,
+.profile .form_user_unsubscribe legend {
+display:none;
+}
+
+.profiles {
+list-style-type:none;
+}
+.profile .entity_profile .entity_location {
+width:auto;
+clear:none;
+margin-left:11px;
+}
+.profile .entity_profile dl,
+.profile .entity_profile dd {
+display:inline;
+float:none;
+}
+.profile .entity_profile .entity_note,
+.profile .entity_profile .entity_url,
+.profile .entity_profile .entity_tags,
+.profile .entity_profile .form_subscription_edit {
+margin-left:59px;
+clear:none;
+display:block;
+width:auto;
+}
+.profile .entity_profile .entity_tags dt {
+display:inline;
+margin-right:11px;
+}
+
+
+.profile .entity_profile .form_subscription_edit label {
+font-weight:normal;
+margin-right:11px;
+}
+
+
+/* NOTICE */
+.notice,
+.profile {
+position:relative;
+padding-top:11px;
+padding-bottom:11px;
+clear:both;
+float:left;
+width:100%;
+border-top-width:1px;
+border-top-style:dashed;
+}
+.notices li {
+list-style-type:none;
+}
+
+
+/* NOTICES */
+#notices_primary {
+float:left;
+width:100%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+#notices_primary h2 {
+display:none;
+}
+.notice-data a span {
+display:block;
+padding-left:28px;
+}
+
+.notice .author {
+margin-right:11px;
+}
+
+.fn {
+overflow:hidden;
+}
+
+.notice .author .fn {
+font-weight:bold;
+}
+
+.vcard .photo {
+display:inline;
+margin-right:11px;
+float:left;
+}
+#shownotice .vcard .photo {
+margin-bottom:4px;
+}
+.vcard .url {
+text-decoration:none;
+}
+.vcard .url:hover {
+text-decoration:underline;
+}
+
+.notice .entry-title {
+display:inline;
+width:100%;
+overflow:hidden;
+}
+#shownotice .notice .entry-title {
+font-size:2.2em;
+}
+
+.notice p.entry-content {
+display:inline;
+}
+
+#content .notice p.entry-content a:visited {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.notice p.entry-content .vcard a {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+.notice div.entry-content {
+float:left;
+font-size:0.95em;
+width:65%;
+}
+
+.notice .notice-options a,
+.notice .notice-options input {
+float:left;
+font-size:1.025em;
+}
+
+.notice div.entry-content dl,
+.notice div.entry-content dt,
+.notice div.entry-content dd {
+display:inline;
+}
+
+.notice div.entry-content .timestamp dt,
+.notice div.entry-content .response dt {
+display:none;
+}
+.notice div.entry-content .timestamp a {
+display:inline-block;
+}
+.notice div.entry-content .device dt {
+text-transform:lowercase;
+}
+
+
+.notice-options {
+padding-left:2%;
+float:left;
+width:50%;
+position:relative;
+font-size:0.95em;
+width:12.5%;
+float:right;
+}
+
+.notice-options a {
+float:left;
+}
+.notice-options .notice_delete,
+.notice-options .notice_reply,
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+position:absolute;
+top:0;
+}
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+left:0;
+}
+.notice-options .notice_reply {
+left:29px;
+}
+.notice-options .notice_delete {
+right:0;
+}
+.notice-options .notice_reply dt {
+display:none;
+}
+
+.notice-options input,
+.notice-options a {
+text-indent:-9999px;
+outline:none;
+}
+
+.notice-options .notice_reply a,
+.notice-options input.submit {
+display:block;
+border:0;
+}
+.notice-options .notice_reply a,
+.notice-options .notice_delete a {
+text-decoration:none;
+padding-left:16px;
+}
+
+.notice-options form input.submit {
+width:16px;
+padding:2px 0;
+}
+
+.notice-options .notice_delete dt,
+.notice-options .form_favor legend,
+.notice-options .form_disfavor legend {
+display:none;
+}
+.notice-options .notice_delete fieldset,
+.notice-options .form_favor fieldset,
+.notice-options .form_disfavor fieldset {
+border:0;
+padding:0;
+}
+
+
+#usergroups #new_group {
+float: left;
+margin-right: 2em;
+}
+#new_group, #group_search {
+margin-bottom:18px;
+}
+#new_group a {
+padding-left:20px;
+}
+
+
+#filter_tags {
+margin-bottom:11px;
+float:left;
+}
+#filter_tags dt {
+display:none;
+}
+#filter_tags ul {
+list-style-type:none;
+}
+#filter_tags ul li {
+float:left;
+margin-left:7px;
+padding-left:7px;
+border-left-width:1px;
+border-left-style:solid;
+}
+#filter_tags ul li.child_1 {
+margin-left:0;
+border-left:0;
+padding-left:0;
+}
+#filter_tags ul li#filter_tags_all a {
+font-weight:bold;
+margin-top:7px;
+float:left;
+}
+
+#filter_tags ul li#filter_tags_item label {
+margin-right:7px;
+}
+#filter_tags ul li#filter_tags_item label,
+#filter_tags ul li#filter_tags_item select {
+display:inline;
+}
+#filter_tags ul li#filter_tags_item p {
+float:left;
+margin-left:38px;
+}
+#filter_tags ul li#filter_tags_item input {
+position:relative;
+top:3px;
+left:3px;
+}
+
+
+
+.pagination {
+float:left;
+clear:both;
+width:100%;
+margin-top:18px;
+}
+
+.pagination dt {
+font-weight:bold;
+display:none;
+}
+
+.pagination .nav {
+float:left;
+width:100%;
+list-style-type:none;
+}
+
+.pagination .nav_prev {
+float:left;
+}
+.pagination .nav_next {
+float:right;
+}
+
+.pagination a {
+display:block;
+text-decoration:none;
+font-weight:bold;
+padding:7px;
+border-width:1px;
+border-style:solid;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-radius:7px;
+}
+
+.pagination .nav_prev a {
+padding-left:30px;
+}
+.pagination .nav_next a {
+padding-right:30px;
+}
+/* END: NOTICE */
+
+
+.hentry .entry-content p {
+margin-bottom:18px;
+}
+.system_notice ul,
+.instructions ul,
+.hentry entry-content ol,
+.hentry .entry-content ul {
+list-style-position:inside;
+}
+.hentry .entry-content li {
+margin-bottom:18px;
+}
+.hentry .entry-content li li {
+margin-left:18px;
+}
+
+
+
+
+/* TOP_POSTERS */
+.section tbody td {
+padding-right:11px;
+padding-bottom:11px;
+}
+.section .vcard .photo {
+margin-right:7px;
+margin-bottom:0;
+}
+
+.section .notice {
+padding-top:7px;
+padding-bottom:7px;
+border-top:0;
+}
+
+.section .notice:first-child {
+padding-top:0;
+}
+
+.section .notice .author {
+margin-right:0;
+}
+.section .notice .author .fn {
+display:none;
+}
+
+
+/* tagcloud */
+.tag-cloud {
+list-style-type:none;
+text-align:center;
+}
+.aside .tag-cloud {
+font-size:0.8em;
+}
+.tag-cloud li {
+display:inline;
+margin-right:7px;
+line-height:1.25;
+}
+.aside .tag-cloud li {
+line-height:1.5;
+}
+.tag-cloud li a {
+text-decoration:none;
+}
+#tagcloud.section dt {
+text-transform:uppercase;
+font-weight:bold;
+}
+.tag-cloud-1 {
+font-size:1em;
+}
+.tag-cloud-2 {
+font-size:1.25em;
+}
+.tag-cloud-3 {
+font-size:1.75em;
+}
+.tag-cloud-4 {
+font-size:2em;
+}
+.tag-cloud-5 {
+font-size:2.25em;
+}
+.tag-cloud-6 {
+font-size:2.75em;
+}
+.tag-cloud-7 {
+font-size:3.25em;
+}
+
+#publictagcloud #tagcloud.section dt {
+display:none;
+}
+
+#form_settings_photo .form_data {
+clear:both;
+}
+
+#form_settings_avatar li {
+width:auto;
+}
+#form_settings_avatar input {
+margin-left:0;
+}
+#avatar_original,
+#avatar_preview {
+float:left;
+}
+#avatar_preview {
+margin-left:29px;
+}
+#avatar_preview_view {
+height:96px;
+width:96px;
+margin-bottom:18px;
+overflow:hidden;
+}
+
+#settings_attach,
+#form_settings_avatar .form_actions {
+clear:both;
+}
+
+#form_settings_avatar .form_actions {
+margin-bottom:0;
+}
+
+#form_settings_design #settings_design_color .form_data,
+#form_settings_design #color-picker {
+float:left;
+}
+#form_settings_design #settings_design_color .form_data {
+width:400px;
+margin-right:28px;
+}
+
+.instructions ul {
+list-style-position:inside;
+}
+.instructions p,
+.instructions ul {
+margin-bottom:18px;
+}
+.help dt {
+display:none;
+}
+.guide {
+clear:both;
+}
diff --git a/theme/h4ck3r/css/display.css b/theme/h4ck3r/css/display.css
new file mode 100644 (file)
index 0000000..31d49a5
--- /dev/null
@@ -0,0 +1,236 @@
+/** theme: h4ck3r
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+@import url(base.css);
+
+html,
+body,
+a:active {
+background-color:#000;
+}
+
+body {
+background-image:url(../images/illustrations/illu_h4x0r1ng.gif);
+font-family: monospace;
+font-size:1em;
+color:#647819;
+}
+address {
+margin-right:7.18%;
+}
+
+input, textarea, select, option {
+font-family:  monospace;
+}
+input, textarea, select,
+.entity_remote_subscribe {
+border-color:#aaa;
+background-color:#000;
+color:#ccc;
+}
+#filter_tags ul li {
+border-color:#ddd;
+}
+
+.form_settings input.form_action-primary {
+background:none;
+}
+
+input.submit,
+#form_notice.warning #notice_text-count,
+.form_settings .form_note,
+.entity_remote_subscribe {
+background-color:rgba(0, 255, 0, 0.5);
+}
+
+input:focus, textarea:focus, select:focus,
+#form_notice.warning #notice_data-text {
+border-color:#9BB43E;
+}
+input.submit,
+.entity_remote_subscribe {
+color:#fff;
+}
+
+a,
+div.notice-options input,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.entity_nudge p,
+.form_settings input.form_action-primary {
+color:#0f0;
+}
+
+.notice,
+.profile {
+border-top-color:#333;
+}
+.section .profile {
+border-top-color:#87B4C8;
+}
+
+#aside_primary {
+background-color:rgba(0,128,0,0.3);
+}
+
+#notice_text-count {
+color:#0f0;
+}
+#form_notice.warning #notice_text-count {
+color:#000;
+}
+#form_notice.processing #notice_action-submit {
+background:#ccc url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+cursor:wait;
+text-indent:-9999px;
+}
+
+#content,
+#site_nav_local_views a,
+#aside_primary {
+border-color:#50964D;
+}
+#content,
+#site_nav_local_views .current a {
+background-color:rgba(0, 0, 0, 0.698);
+}
+
+#site_nav_local_views a {
+background-color:rgba(0, 200, 0, 0.3);
+}
+#site_nav_local_views a:hover {
+background-color:rgba(255, 255, 255, 0.4);
+}
+
+.error {
+background-color:#F7E8E8;
+}
+.success {
+background-color:#EFF3DC;
+}
+
+#anon_notice {
+color:#ccc;
+border-color:#50964D;
+}
+
+#showstream #anon_notice {
+}
+
+#export_data li a {
+background-repeat:no-repeat;
+background-position:0 45%;
+}
+#export_data li a.rss {
+background-image:url(../../base/images/icons/icon_rss.png);
+}
+#export_data li a.atom {
+background-image:url(../../base/images/icons/icon_atom.png);
+}
+#export_data li a.foaf {
+background-image:url(../../base/images/icons/icon_foaf.gif);
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_nudge p {
+background-position: 0 40%;
+background-repeat: no-repeat;
+background-color:transparent;
+}
+.form_group_join input.submit,
+.form_group_leave input.submit
+.form_user_subscribe input.submit,
+.form_user_unsubscribe input.submit {
+background-color:#9BB43E;
+color:#ccc;
+}
+.form_user_unsubscribe input.submit,
+.form_group_leave input.submit,
+.form_user_authorization input.reject {
+background-color:#87B4C8;
+}
+
+.entity_edit a {
+background-image:url(../../base/images/icons/twotone/green/edit.gif);
+}
+.entity_send-a-message a {
+background-image:url(../../base/images/icons/twotone/green/quote.gif);
+}
+.entity_nudge p,
+.form_user_nudge input.submit {
+background-image:url(../../base/images/icons/twotone/green/mail.gif);
+}
+.form_user_block input.submit,
+.form_user_unblock input.submit {
+background-image:url(../../base/images/icons/twotone/green/shield.gif);
+}
+
+/* NOTICES */
+.notices li.over {
+background-color:#fcfcfc;
+}
+
+.notice-options .notice_reply a,
+.notice-options form input.submit {
+background-color:transparent;
+}
+.notice-options .notice_reply a {
+background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+}
+.notice-options form.form_favor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+}
+.notice-options form.form_disfavor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+}
+.notice-options .notice_delete a {
+background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+}
+
+.notices div.entry-content,
+.notices div.notice-options {
+opacity:0.4;
+}
+.notices li.hover div.entry-content,
+.notices li.hover div.notice-options {
+opacity:1;
+}
+div.entry-content {
+color:#ccc;
+}
+div.notice-options a,
+div.notice-options input {
+font-family:sans-serif;
+}
+
+/*END: NOTICES */
+
+#new_group a {
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+}
+
+.pagination .nav_prev a,
+.pagination .nav_next a {
+background-repeat:no-repeat;
+border-color:#000;
+}
+.pagination .nav_prev a {
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
+background-position:10% 45%;
+}
+.pagination .nav_next a {
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
+background-position:90% 45%;
+}
diff --git a/theme/h4ck3r/css/ie.css b/theme/h4ck3r/css/ie.css
new file mode 100644 (file)
index 0000000..2f463bb
--- /dev/null
@@ -0,0 +1,9 @@
+/* IE specific styles */
+
+.notice-options input.submit {
+color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:#D0DFE7;
+}
diff --git a/theme/h4ck3r/default-avatar-mini.png b/theme/h4ck3r/default-avatar-mini.png
new file mode 100644 (file)
index 0000000..38b8692
Binary files /dev/null and b/theme/h4ck3r/default-avatar-mini.png differ
diff --git a/theme/h4ck3r/default-avatar-profile.png b/theme/h4ck3r/default-avatar-profile.png
new file mode 100644 (file)
index 0000000..f8357d4
Binary files /dev/null and b/theme/h4ck3r/default-avatar-profile.png differ
diff --git a/theme/h4ck3r/default-avatar-stream.png b/theme/h4ck3r/default-avatar-stream.png
new file mode 100644 (file)
index 0000000..6b63baa
Binary files /dev/null and b/theme/h4ck3r/default-avatar-stream.png differ
diff --git a/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif b/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif
new file mode 100644 (file)
index 0000000..c233af3
Binary files /dev/null and b/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif differ
diff --git a/theme/h4ck3r/logo.png b/theme/h4ck3r/logo.png
new file mode 100644 (file)
index 0000000..fdead6c
Binary files /dev/null and b/theme/h4ck3r/logo.png differ
index d05578d4395c1794affa3030d44c07f3ffbe8e54..42a4573a741af070a7ff4452969ab57e77ae75de 100644 (file)
@@ -9,7 +9,6 @@
 
 @import url(../../base/css/display.css);
 
-html,
 body,
 a:active {
 background-color:#F0F2F5;
@@ -19,7 +18,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
 font-size:1em;
 }
 address {
-margin-right:7.18%;
+margin-right:7.2%;
 }
 
 input, textarea, select, option {
@@ -27,13 +26,13 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
 }
 input, textarea, select,
 .entity_remote_subscribe {
-border-color:#aaa;
+border-color:#AAAAAA;
 }
 #filter_tags ul li {
-border-color:#ddd;
+border-color:#DDDDDD;
 }
 
-.form_settings input.form_action-secondary {
+.form_settings input.form_action-primary {
 background:none;
 }
 
@@ -47,20 +46,26 @@ background-color:#9BB43E;
 input:focus, textarea:focus, select:focus,
 #form_notice.warning #notice_data-text {
 border-color:#9BB43E;
+box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
 }
 input.submit,
 .entity_remote_subscribe {
-color:#fff;
+color:#FFFFFF;
 }
 
 a,
 div.notice-options input,
 .form_user_block input.submit,
 .form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
 .entity_send-a-message a,
 .form_user_nudge input.submit,
 .entity_nudge p,
-.form_settings input.form_action-secondary {
+.form_settings input.form_action-primary,
+.form_make_admin input.submit {
 color:#002E6E;
 }
 
@@ -72,45 +77,58 @@ border-top-color:#CEE1E9;
 border-top-color:#87B4C8;
 }
 
-#content .notice p.entry-content a:visited {
-background-color:#fcfcfc;
-}
-#content .notice p.entry-content .vcard a {
-background-color:#fcfffc;
-}
-
 #aside_primary {
 background-color:#CEE1E9;
 }
 
 #notice_text-count {
-color:#333;
+color:#333333;
 }
 #form_notice.warning #notice_text-count {
-color:#000;
+color:#000000;
 }
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
+}
+
 #form_notice.processing #notice_action-submit {
-background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
 text-indent:-9999px;
 }
 
+#content {
+box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
+-moz-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
+-webkit-box-shadow:5px 7px 7px rgba(194, 194, 194, 0.3);
+}
 #content,
 #site_nav_local_views a,
 #aside_primary {
-border-color:#fff;
+border-color:transparent;
 }
 #content,
 #site_nav_local_views .current a {
-background-color:#fff;
+background-color:#FFFFFF;
 }
 
+#site_nav_local_views li {
+box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
+-moz-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
+-webkit-box-shadow:3px 7px 5px rgba(194, 194, 194, 0.5);
+}
 #site_nav_local_views a {
-background-color:rgba(135, 180, 200, 0.3);
+background-color:rgba(194, 194, 194, 0.5);
 }
 #site_nav_local_views a:hover {
 background-color:rgba(255, 255, 255, 0.7);
 }
+#site_nav_local_views .current a {
+text-shadow: rgba(194,194,194,0.5) 1px 1px 1px;
+}
 
 .error {
 background-color:#F7E8E8;
@@ -121,8 +139,8 @@ background-color:#EFF3DC;
 
 #anon_notice {
 background-color:#87B4C8;
-color:#fff;
-border-color:#fff;
+color:#FFFFFF;
+border-color:#FFFFFF;
 }
 
 #showstream #anon_notice {
@@ -148,7 +166,10 @@ background-image:url(../../base/images/icons/icon_foaf.gif);
 .form_user_nudge input.submit,
 .form_user_block input.submit,
 .form_user_unblock input.submit,
-.entity_nudge p {
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.entity_nudge p,
+.form_make_admin input.submit {
 background-position: 0 40%;
 background-repeat: no-repeat;
 background-color:transparent;
@@ -158,7 +179,7 @@ background-color:transparent;
 .form_user_subscribe input.submit,
 .form_user_unsubscribe input.submit {
 background-color:#9BB43E;
-color:#fff;
+color:#FFFFFF;
 }
 .form_user_unsubscribe input.submit,
 .form_group_leave input.submit,
@@ -167,64 +188,87 @@ background-color:#87B4C8;
 }
 
 .entity_edit a {
-background-image:url(../images/icons/twotone/green/edit.gif);
+background-image:url(../../base/images/icons/twotone/green/edit.gif);
 }
 .entity_send-a-message a {
-background-image:url(../images/icons/twotone/green/quote.gif);
+background-image:url(../../base/images/icons/twotone/green/quote.gif);
 }
 .entity_nudge p,
 .form_user_nudge input.submit {
-background-image:url(../images/icons/twotone/green/mail.gif);
+background-image:url(../../base/images/icons/twotone/green/mail.gif);
 }
 .form_user_block input.submit,
-.form_user_unblock input.submit {
-background-image:url(../images/icons/twotone/green/shield.gif);
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit {
+background-image:url(../../base/images/icons/twotone/green/shield.gif);
+}
+.form_make_admin input.submit {
+background-image:url(../../base/images/icons/twotone/green/admin.gif);
 }
 
 /* NOTICES */
-.notices li.over {
-background-color:#fcfcfc;
+.notice .attachment {
+background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+}
+#attachments .attachment {
+background:none;
 }
-
 .notice-options .notice_reply a,
 .notice-options form input.submit {
 background-color:transparent;
 }
 .notice-options .notice_reply a {
-background:transparent url(../images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
 }
 .notice-options form.form_favor input.submit {
-background:transparent url(../images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
 }
 .notice-options form.form_disfavor input.submit {
-background:transparent url(../images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
 }
 .notice-options .notice_delete a {
-background:transparent url(../images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
 }
 
 .notices div.entry-content,
 .notices div.notice-options {
 opacity:0.4;
 }
-.notices li.hover div.entry-content,
-.notices li.hover div.notice-options {
+.notices li:hover div.entry-content,
+.notices li:hover div.notice-options {
 opacity:1;
 }
 div.entry-content {
-color:#333;
+color:#333333;
 }
 div.notice-options a,
 div.notice-options input {
 font-family:sans-serif;
 }
-.notices li.hover {
-background-color:#fcfcfc;
+#content .notices li:hover {
+background-color:rgba(240, 240, 240, 0.2);
+}
+#conversation .notices li:hover {
+background-color:transparent;
+}
+
+.notices .notices {
+background-color:rgba(200, 200, 200, 0.050);
+}
+.notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.100);
+}
+.notices .notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.150);
+}
+.notices .notices .notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.300);
 }
 /*END: NOTICES */
 
 #new_group a {
-background:transparent url(../images/icons/twotone/green/news.gif) no-repeat 0 45%;
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
 }
 
 .pagination .nav_prev a,
@@ -233,10 +277,10 @@ background-repeat:no-repeat;
 border-color:#CEE1E9;
 }
 .pagination .nav_prev a {
-background-image:url(../images/icons/twotone/green/arrow-left.gif);
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
 background-position:10% 45%;
 }
 .pagination .nav_next a {
-background-image:url(../images/icons/twotone/green/arrow-right.gif);
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
 background-position:90% 45%;
 }
index 2f463bb44dfee9a14fd382a3b745bfe2a274429f..97cabc30a534a505d2b1ea92c6407aedd087493a 100644 (file)
@@ -1,9 +1,14 @@
 /* IE specific styles */
 
 .notice-options input.submit {
-color:#fff;
+color:#FFFFFF;
 }
-
 #site_nav_local_views a {
-background-color:#D0DFE7;
+background-color:#D9DADB;
+}
+#form_notice .form_note + label {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+filter: alpha(opacity=0);
 }
diff --git a/theme/identica/images/icons/icon_atom.jpg b/theme/identica/images/icons/icon_atom.jpg
deleted file mode 100644 (file)
index 22853ed..0000000
Binary files a/theme/identica/images/icons/icon_atom.jpg and /dev/null differ
diff --git a/theme/identica/images/icons/icon_foaf.gif b/theme/identica/images/icons/icon_foaf.gif
deleted file mode 100644 (file)
index f8f7844..0000000
Binary files a/theme/identica/images/icons/icon_foaf.gif and /dev/null differ
diff --git a/theme/identica/images/icons/icon_rss.jpg b/theme/identica/images/icons/icon_rss.jpg
deleted file mode 100644 (file)
index da23422..0000000
Binary files a/theme/identica/images/icons/icon_rss.jpg and /dev/null differ
diff --git a/theme/identica/images/icons/icon_vcard.gif b/theme/identica/images/icons/icon_vcard.gif
deleted file mode 100644 (file)
index 6d52947..0000000
Binary files a/theme/identica/images/icons/icon_vcard.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/against.gif b/theme/identica/images/icons/twotone/green/against.gif
deleted file mode 100644 (file)
index ca796c8..0000000
Binary files a/theme/identica/images/icons/twotone/green/against.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-down.gif b/theme/identica/images/icons/twotone/green/arrow-down.gif
deleted file mode 100644 (file)
index c709e58..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-down.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-downleft.gif b/theme/identica/images/icons/twotone/green/arrow-downleft.gif
deleted file mode 100644 (file)
index a4a9803..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-downleft.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-downright.gif b/theme/identica/images/icons/twotone/green/arrow-downright.gif
deleted file mode 100644 (file)
index 3e6001a..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-downright.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-left.gif b/theme/identica/images/icons/twotone/green/arrow-left.gif
deleted file mode 100644 (file)
index afed190..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-left.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-right.gif b/theme/identica/images/icons/twotone/green/arrow-right.gif
deleted file mode 100644 (file)
index ee1707e..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-right.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-up.gif b/theme/identica/images/icons/twotone/green/arrow-up.gif
deleted file mode 100644 (file)
index d0f5fbe..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-up.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-upleft.gif b/theme/identica/images/icons/twotone/green/arrow-upleft.gif
deleted file mode 100644 (file)
index 1e9e693..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-upleft.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/arrow-upright.gif b/theme/identica/images/icons/twotone/green/arrow-upright.gif
deleted file mode 100644 (file)
index c7fecc8..0000000
Binary files a/theme/identica/images/icons/twotone/green/arrow-upright.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/back-forth.gif b/theme/identica/images/icons/twotone/green/back-forth.gif
deleted file mode 100644 (file)
index 33a9540..0000000
Binary files a/theme/identica/images/icons/twotone/green/back-forth.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/bookmark.gif b/theme/identica/images/icons/twotone/green/bookmark.gif
deleted file mode 100644 (file)
index 23f318e..0000000
Binary files a/theme/identica/images/icons/twotone/green/bookmark.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/bulb.gif b/theme/identica/images/icons/twotone/green/bulb.gif
deleted file mode 100644 (file)
index f70652c..0000000
Binary files a/theme/identica/images/icons/twotone/green/bulb.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/calendar.gif b/theme/identica/images/icons/twotone/green/calendar.gif
deleted file mode 100644 (file)
index a09b65a..0000000
Binary files a/theme/identica/images/icons/twotone/green/calendar.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/calendar2.gif b/theme/identica/images/icons/twotone/green/calendar2.gif
deleted file mode 100644 (file)
index 7884b02..0000000
Binary files a/theme/identica/images/icons/twotone/green/calendar2.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/camera.gif b/theme/identica/images/icons/twotone/green/camera.gif
deleted file mode 100644 (file)
index 1a85fba..0000000
Binary files a/theme/identica/images/icons/twotone/green/camera.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/cart.gif b/theme/identica/images/icons/twotone/green/cart.gif
deleted file mode 100644 (file)
index 47eaa0a..0000000
Binary files a/theme/identica/images/icons/twotone/green/cart.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/caution.gif b/theme/identica/images/icons/twotone/green/caution.gif
deleted file mode 100644 (file)
index 3ad2c32..0000000
Binary files a/theme/identica/images/icons/twotone/green/caution.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/chart.gif b/theme/identica/images/icons/twotone/green/chart.gif
deleted file mode 100644 (file)
index 136d745..0000000
Binary files a/theme/identica/images/icons/twotone/green/chart.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/checkmark.gif b/theme/identica/images/icons/twotone/green/checkmark.gif
deleted file mode 100644 (file)
index 892429d..0000000
Binary files a/theme/identica/images/icons/twotone/green/checkmark.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/clipboard.gif b/theme/identica/images/icons/twotone/green/clipboard.gif
deleted file mode 100644 (file)
index 9317bdc..0000000
Binary files a/theme/identica/images/icons/twotone/green/clipboard.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/clock.gif b/theme/identica/images/icons/twotone/green/clock.gif
deleted file mode 100644 (file)
index d1410f9..0000000
Binary files a/theme/identica/images/icons/twotone/green/clock.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/closed-folder.gif b/theme/identica/images/icons/twotone/green/closed-folder.gif
deleted file mode 100644 (file)
index 0410fc6..0000000
Binary files a/theme/identica/images/icons/twotone/green/closed-folder.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/database.gif b/theme/identica/images/icons/twotone/green/database.gif
deleted file mode 100644 (file)
index 29ce024..0000000
Binary files a/theme/identica/images/icons/twotone/green/database.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/disfavourite.gif b/theme/identica/images/icons/twotone/green/disfavourite.gif
deleted file mode 100644 (file)
index 3946869..0000000
Binary files a/theme/identica/images/icons/twotone/green/disfavourite.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/diskette.gif b/theme/identica/images/icons/twotone/green/diskette.gif
deleted file mode 100644 (file)
index e970b0a..0000000
Binary files a/theme/identica/images/icons/twotone/green/diskette.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/document.gif b/theme/identica/images/icons/twotone/green/document.gif
deleted file mode 100644 (file)
index 9c08f4a..0000000
Binary files a/theme/identica/images/icons/twotone/green/document.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/double-arrow.gif b/theme/identica/images/icons/twotone/green/double-arrow.gif
deleted file mode 100644 (file)
index 2e86482..0000000
Binary files a/theme/identica/images/icons/twotone/green/double-arrow.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/edit.gif b/theme/identica/images/icons/twotone/green/edit.gif
deleted file mode 100644 (file)
index c746aca..0000000
Binary files a/theme/identica/images/icons/twotone/green/edit.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/eject.gif b/theme/identica/images/icons/twotone/green/eject.gif
deleted file mode 100644 (file)
index 7e0906c..0000000
Binary files a/theme/identica/images/icons/twotone/green/eject.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/exclaim.gif b/theme/identica/images/icons/twotone/green/exclaim.gif
deleted file mode 100644 (file)
index 588e28c..0000000
Binary files a/theme/identica/images/icons/twotone/green/exclaim.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/fastforward.gif b/theme/identica/images/icons/twotone/green/fastforward.gif
deleted file mode 100644 (file)
index 28e4951..0000000
Binary files a/theme/identica/images/icons/twotone/green/fastforward.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/favourite.gif b/theme/identica/images/icons/twotone/green/favourite.gif
deleted file mode 100644 (file)
index d93515e..0000000
Binary files a/theme/identica/images/icons/twotone/green/favourite.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/flag.gif b/theme/identica/images/icons/twotone/green/flag.gif
deleted file mode 100644 (file)
index 68c8aee..0000000
Binary files a/theme/identica/images/icons/twotone/green/flag.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/graph.gif b/theme/identica/images/icons/twotone/green/graph.gif
deleted file mode 100644 (file)
index 0c1794b..0000000
Binary files a/theme/identica/images/icons/twotone/green/graph.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/grow.gif b/theme/identica/images/icons/twotone/green/grow.gif
deleted file mode 100644 (file)
index c4118d5..0000000
Binary files a/theme/identica/images/icons/twotone/green/grow.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/headphones.gif b/theme/identica/images/icons/twotone/green/headphones.gif
deleted file mode 100644 (file)
index 5be6c67..0000000
Binary files a/theme/identica/images/icons/twotone/green/headphones.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/home.gif b/theme/identica/images/icons/twotone/green/home.gif
deleted file mode 100644 (file)
index d2a3421..0000000
Binary files a/theme/identica/images/icons/twotone/green/home.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/hourglass.gif b/theme/identica/images/icons/twotone/green/hourglass.gif
deleted file mode 100644 (file)
index b62b948..0000000
Binary files a/theme/identica/images/icons/twotone/green/hourglass.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/info.gif b/theme/identica/images/icons/twotone/green/info.gif
deleted file mode 100644 (file)
index 86ef1f8..0000000
Binary files a/theme/identica/images/icons/twotone/green/info.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/key.gif b/theme/identica/images/icons/twotone/green/key.gif
deleted file mode 100644 (file)
index ccf357a..0000000
Binary files a/theme/identica/images/icons/twotone/green/key.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/lock.gif b/theme/identica/images/icons/twotone/green/lock.gif
deleted file mode 100644 (file)
index db00706..0000000
Binary files a/theme/identica/images/icons/twotone/green/lock.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/mail.gif b/theme/identica/images/icons/twotone/green/mail.gif
deleted file mode 100644 (file)
index 1084c86..0000000
Binary files a/theme/identica/images/icons/twotone/green/mail.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/move.gif b/theme/identica/images/icons/twotone/green/move.gif
deleted file mode 100644 (file)
index d2c30b1..0000000
Binary files a/theme/identica/images/icons/twotone/green/move.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/music.gif b/theme/identica/images/icons/twotone/green/music.gif
deleted file mode 100644 (file)
index 64b51d4..0000000
Binary files a/theme/identica/images/icons/twotone/green/music.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/news.gif b/theme/identica/images/icons/twotone/green/news.gif
deleted file mode 100644 (file)
index 712c685..0000000
Binary files a/theme/identica/images/icons/twotone/green/news.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/note.gif b/theme/identica/images/icons/twotone/green/note.gif
deleted file mode 100644 (file)
index bcc0b14..0000000
Binary files a/theme/identica/images/icons/twotone/green/note.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/open-folder.gif b/theme/identica/images/icons/twotone/green/open-folder.gif
deleted file mode 100644 (file)
index d41300a..0000000
Binary files a/theme/identica/images/icons/twotone/green/open-folder.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/paper-clip.gif b/theme/identica/images/icons/twotone/green/paper-clip.gif
deleted file mode 100644 (file)
index 1d45f1d..0000000
Binary files a/theme/identica/images/icons/twotone/green/paper-clip.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/paper-clip2.gif b/theme/identica/images/icons/twotone/green/paper-clip2.gif
deleted file mode 100644 (file)
index a8c7805..0000000
Binary files a/theme/identica/images/icons/twotone/green/paper-clip2.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/pause.gif b/theme/identica/images/icons/twotone/green/pause.gif
deleted file mode 100644 (file)
index ced0b64..0000000
Binary files a/theme/identica/images/icons/twotone/green/pause.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/phone.gif b/theme/identica/images/icons/twotone/green/phone.gif
deleted file mode 100644 (file)
index 69359f7..0000000
Binary files a/theme/identica/images/icons/twotone/green/phone.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/play.gif b/theme/identica/images/icons/twotone/green/play.gif
deleted file mode 100644 (file)
index 794ec85..0000000
Binary files a/theme/identica/images/icons/twotone/green/play.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/plus.gif b/theme/identica/images/icons/twotone/green/plus.gif
deleted file mode 100644 (file)
index 4407d0b..0000000
Binary files a/theme/identica/images/icons/twotone/green/plus.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/print.gif b/theme/identica/images/icons/twotone/green/print.gif
deleted file mode 100644 (file)
index 17727d5..0000000
Binary files a/theme/identica/images/icons/twotone/green/print.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/question-mark.gif b/theme/identica/images/icons/twotone/green/question-mark.gif
deleted file mode 100644 (file)
index 1689efc..0000000
Binary files a/theme/identica/images/icons/twotone/green/question-mark.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/quote.gif b/theme/identica/images/icons/twotone/green/quote.gif
deleted file mode 100644 (file)
index 4ba1f0c..0000000
Binary files a/theme/identica/images/icons/twotone/green/quote.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/refresh.gif b/theme/identica/images/icons/twotone/green/refresh.gif
deleted file mode 100644 (file)
index 8a8b814..0000000
Binary files a/theme/identica/images/icons/twotone/green/refresh.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/reply.gif b/theme/identica/images/icons/twotone/green/reply.gif
deleted file mode 100644 (file)
index 6ff01bb..0000000
Binary files a/theme/identica/images/icons/twotone/green/reply.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/rewind.gif b/theme/identica/images/icons/twotone/green/rewind.gif
deleted file mode 100644 (file)
index aca3ee3..0000000
Binary files a/theme/identica/images/icons/twotone/green/rewind.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/search.gif b/theme/identica/images/icons/twotone/green/search.gif
deleted file mode 100644 (file)
index c36463d..0000000
Binary files a/theme/identica/images/icons/twotone/green/search.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/shield.gif b/theme/identica/images/icons/twotone/green/shield.gif
deleted file mode 100644 (file)
index 419d5ee..0000000
Binary files a/theme/identica/images/icons/twotone/green/shield.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/skip-back.gif b/theme/identica/images/icons/twotone/green/skip-back.gif
deleted file mode 100644 (file)
index adca7aa..0000000
Binary files a/theme/identica/images/icons/twotone/green/skip-back.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/skip.gif b/theme/identica/images/icons/twotone/green/skip.gif
deleted file mode 100644 (file)
index ae5417f..0000000
Binary files a/theme/identica/images/icons/twotone/green/skip.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/skull.gif b/theme/identica/images/icons/twotone/green/skull.gif
deleted file mode 100644 (file)
index 0335067..0000000
Binary files a/theme/identica/images/icons/twotone/green/skull.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/statusbar.gif b/theme/identica/images/icons/twotone/green/statusbar.gif
deleted file mode 100644 (file)
index 47d61b1..0000000
Binary files a/theme/identica/images/icons/twotone/green/statusbar.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/stop.gif b/theme/identica/images/icons/twotone/green/stop.gif
deleted file mode 100644 (file)
index e0b108d..0000000
Binary files a/theme/identica/images/icons/twotone/green/stop.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/template.gif b/theme/identica/images/icons/twotone/green/template.gif
deleted file mode 100644 (file)
index 65c0c4a..0000000
Binary files a/theme/identica/images/icons/twotone/green/template.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/text-bigger.gif b/theme/identica/images/icons/twotone/green/text-bigger.gif
deleted file mode 100644 (file)
index 45e143b..0000000
Binary files a/theme/identica/images/icons/twotone/green/text-bigger.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/text-smaller.gif b/theme/identica/images/icons/twotone/green/text-smaller.gif
deleted file mode 100644 (file)
index a54d0c1..0000000
Binary files a/theme/identica/images/icons/twotone/green/text-smaller.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/trash.gif b/theme/identica/images/icons/twotone/green/trash.gif
deleted file mode 100644 (file)
index 78dd64a..0000000
Binary files a/theme/identica/images/icons/twotone/green/trash.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/two-docs.gif b/theme/identica/images/icons/twotone/green/two-docs.gif
deleted file mode 100644 (file)
index 97e54b9..0000000
Binary files a/theme/identica/images/icons/twotone/green/two-docs.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/twotone.gif b/theme/identica/images/icons/twotone/green/twotone.gif
deleted file mode 100644 (file)
index 45aad25..0000000
Binary files a/theme/identica/images/icons/twotone/green/twotone.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/undo.gif b/theme/identica/images/icons/twotone/green/undo.gif
deleted file mode 100644 (file)
index 6869b30..0000000
Binary files a/theme/identica/images/icons/twotone/green/undo.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/user.gif b/theme/identica/images/icons/twotone/green/user.gif
deleted file mode 100644 (file)
index c85460f..0000000
Binary files a/theme/identica/images/icons/twotone/green/user.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/vegetable.gif b/theme/identica/images/icons/twotone/green/vegetable.gif
deleted file mode 100644 (file)
index 4d421c1..0000000
Binary files a/theme/identica/images/icons/twotone/green/vegetable.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/x.gif b/theme/identica/images/icons/twotone/green/x.gif
deleted file mode 100644 (file)
index ffb2efe..0000000
Binary files a/theme/identica/images/icons/twotone/green/x.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/zoom-in.gif b/theme/identica/images/icons/twotone/green/zoom-in.gif
deleted file mode 100644 (file)
index a59a5bb..0000000
Binary files a/theme/identica/images/icons/twotone/green/zoom-in.gif and /dev/null differ
diff --git a/theme/identica/images/icons/twotone/green/zoom-out.gif b/theme/identica/images/icons/twotone/green/zoom-out.gif
deleted file mode 100644 (file)
index c61f999..0000000
Binary files a/theme/identica/images/icons/twotone/green/zoom-out.gif and /dev/null differ
diff --git a/theme/otalk/css/base.css b/theme/otalk/css/base.css
new file mode 100644 (file)
index 0000000..b399925
--- /dev/null
@@ -0,0 +1,1211 @@
+/** theme: otalk base
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+* { margin:0; padding:0; }
+img { display:block; border:0; }
+a abbr { cursor: pointer; border-bottom:0; }
+table { border-collapse:collapse; }
+ol { list-style-position:inside; }
+html { font-size: 87.5%; background-color:#fff; }
+body {
+background-color:#fff;
+color:#000;
+font-family:sans-serif;
+font-size:1em;
+line-height:1.65;
+position:relative;
+}
+h1,h2,h3,h4,h5,h6 {
+margin-bottom:7px;
+overflow:hidden;
+}
+h1 {
+font-size:1.4em;
+margin-bottom:18px;
+}
+#showstream h1 { display:none; }
+h2 { font-size:1.3em; }
+h3 { font-size:1.2em; }
+h4 { font-size:1.1em; }
+h5 { font-size:1em; }
+h6 { font-size:0.9em; }
+
+caption {
+font-weight:bold;
+}
+legend {
+font-weight:bold;
+font-size:1.3em;
+}
+input, textarea, select, option {
+padding:4px;
+font-family:sans-serif;
+font-size:1em;
+}
+input, textarea, select {
+border-width:2px;
+border-style: solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+input.submit {
+font-weight:bold;
+cursor:pointer;
+}
+textarea {
+overflow:auto;
+}
+option {
+padding-bottom:0;
+}
+fieldset {
+padding:0;
+border:0;
+}
+form ul li {
+list-style-type:none;
+margin:0 0 18px 0;
+}
+form label {
+font-weight:bold;
+}
+input.checkbox {
+position:relative;
+top:2px;
+left:0;
+border:0;
+}
+
+.error,
+.success {
+padding:4px 7px;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+margin-bottom:18px;
+}
+form label.submit {
+display:none;
+}
+
+.form_settings {
+clear:both;
+}
+
+.form_settings fieldset {
+margin-bottom:29px;
+}
+.form_settings input.remove {
+margin-left:11px;
+}
+.form_settings .form_data li {
+width:100%;
+float:left;
+}
+.form_settings .form_data label {
+float:left;
+}
+.form_settings .form_data textarea,
+.form_settings .form_data select,
+.form_settings .form_data input {
+margin-left:11px;
+float:left;
+}
+.form_settings .form_data input.submit {
+margin-left:0;
+}
+
+.form_settings label {
+margin-top:2px;
+width:152px;
+}
+
+.form_actions label {
+display:none;
+}
+.form_guide {
+font-style:italic;
+}
+
+.form_settings #settings_autosubscribe label {
+display:inline;
+font-weight:bold;
+}
+
+#form_settings_profile legend,
+#form_login legend,
+#form_register legend,
+#form_password legend,
+#form_settings_avatar legend,
+#newgroup legend,
+#editgroup legend,
+#form_tag_user legend,
+#form_remote_subscribe legend,
+#form_openid_login legend,
+#form_search legend,
+#form_invite legend,
+#form_notice_delete legend,
+#form_password_recover legend,
+#form_password_change legend {
+display:none;
+}
+
+.form_settings .form_data p.form_guide {
+clear:both;
+margin-left:163px;
+margin-bottom:0;
+}
+
+.form_settings p {
+margin-bottom:11px;
+}
+
+.form_settings input.checkbox {
+margin-top:3px;
+margin-left:0;
+}
+.form_settings label.checkbox {
+font-weight:normal;
+margin-top:0;
+margin-right:0;
+margin-left:11px;
+float:left;
+width:90%;
+}
+
+
+#form_login p.form_guide,
+#form_register #settings_rememberme p.form_guide,
+#form_openid_login #settings_rememberme p.form_guide,
+#settings_twitter_remove p.form_guide,
+#form_search ul.form_data #q {
+margin-left:0;
+}
+
+.form_settings .form_note {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+padding:0 7px;
+}
+
+
+.form_settings input.form_action-primary {
+padding:0;
+}
+.form_settings input.form_action-secondary {
+margin-left:29px;
+}
+
+#form_search .submit {
+margin-left:11px;
+}
+
+address {
+float:left;
+margin-bottom:18px;
+margin-left:18px;
+}
+address.vcard img.logo {
+margin-right:0;
+}
+address .fn {
+font-weight:bold;
+}
+address img + .fn {
+display:none;
+}
+
+#header {
+width:100%;
+position:relative;
+float:left;
+padding-top:18px;
+margin-bottom:29px;
+}
+
+#site_nav_global_primary {
+float:right;
+margin-right:18px;
+margin-bottom:11px;
+margin-left:18px;
+}
+#site_nav_global_primary ul li {
+display:inline;
+margin-left:11px;
+}
+
+.system_notice dt {
+font-weight:bold;
+text-transform:uppercase;
+display:none;
+}
+
+#site_notice {
+position:absolute;
+top:65px;
+right:18px;
+width:250px;
+width:24%;
+}
+#page_notice {
+clear:both;
+margin-bottom:18px;
+}
+
+
+#anon_notice {
+float:left;
+width:43.2%;
+padding:1.1%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-width:2px;
+border-style:solid;
+line-height:1.5;
+font-size:1.1em;
+font-weight:bold;
+}
+
+
+#footer {
+float:left;
+width:64%;
+padding:18px;
+}
+
+#site_nav_local_views {
+float:left;
+}
+#site_nav_local_views dt {
+display:none;
+}
+#site_nav_local_views li {
+float:left;
+margin-right:18px;
+list-style-type:none;
+}
+#site_nav_local_views a {
+float:left;
+text-decoration:none;
+padding:4px 11px;
+-moz-border-radius-topleft:4px;
+-moz-border-radius-topright:4px;
+-webkit-border-top-left-radius:4px;
+-webkit-border-top-right-radius:4px;
+border-width:0;
+border-style:solid;
+border-bottom:0;
+text-shadow: 2px 2px 2px #ddd;
+font-weight:bold;
+}
+#site_nav_local_views .nav {
+float:left;
+width:100%;
+border-bottom-width:1px;
+border-bottom-style:solid;
+}
+
+#site_nav_global_primary dt,
+#site_nav_global_secondary dt {
+display:none;
+}
+
+#site_nav_global_secondary {
+margin-bottom:11px;
+}
+
+#site_nav_global_secondary ul li {
+display:inline;
+margin-right:11px;
+}
+#export_data li a {
+padding-left:20px;
+}
+#export_data li a.foaf {
+padding-left:30px;
+}
+#export_data li a.export_vcard {
+padding-left:28px;
+}
+
+#export_data ul {
+display:inline;
+}
+#export_data li {
+list-style-type:none;
+display:inline;
+margin-left:11px;
+}
+#export_data li:first-child {
+margin-left:0;
+}
+
+#licenses {
+font-size:0.9em;
+}
+
+#licenses dt {
+font-weight:bold;
+display:none;
+}
+#licenses dd {
+margin-bottom:11px;
+line-height:1.5;
+}
+
+#site_content_license_cc {
+margin-bottom:0;
+}
+#site_content_license_cc img {
+display:inline;
+vertical-align:top;
+margin-right:4px;
+}
+
+#wrap {
+margin:0 auto;
+width:100%;
+min-width:760px;
+max-width:1003px;
+overflow:hidden;
+}
+
+#core {
+position:relative;
+width:100%;
+float:left;
+margin-bottom:1em;
+}
+
+#content {
+width:67.9%;
+min-height:259px;
+padding-top:1.795%;
+padding-bottom:1.795%;
+float:left;
+clear:left;
+border-radius:7px;
+-moz-border-radius:7px;
+-moz-border-radius-topleft:0;
+-webkit-border-radius:7px;
+-webkit-border-top-left-radius:0;
+border-style:solid;
+border-width:0;
+margin-bottom:18px;
+}
+
+#content_inner {
+position:relative;
+width:100%;
+float:left;
+}
+
+#aside_primary {
+width:27.917%;
+min-height:259px;
+float:left;
+padding:1.795%;
+margin-left:0.385%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-width:1px;
+border-style:solid;
+}
+
+#form_notice {
+width:45.664%;
+float:left;
+position:relative;
+line-height:1;
+}
+#form_notice fieldset {
+border:0;
+padding:0;
+}
+#form_notice legend {
+display:none;
+}
+#form_notice textarea {
+float:left;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+width:80.789%;
+height:67px;
+line-height:1.5;
+padding:7px 7px 16px 7px;
+}
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice #notice_submit label {
+display:none;
+}
+#form_notice .form_note {
+position:absolute;
+top:99px;
+right:98px;
+z-index:9;
+}
+#form_notice .form_note dt {
+font-weight:bold;
+display:none;
+}
+#notice_text-count {
+font-weight:bold;
+line-height:1.15;
+padding:1px 2px;
+}
+#form_notice #notice_action-submit {
+width:14%;
+height:47px;
+padding:0;
+position:absolute;
+bottom:0;
+right:0;
+}
+#form_notice label[for=to] {
+margin-top:7px;
+}
+#form_notice select[id=to] {
+margin-bottom:7px;
+margin-left:18px;
+float:left;
+}
+
+
+/* entity_profile */
+.entity_profile {
+position:relative;
+width:521px;
+min-height:123px;
+float:left;
+margin-bottom:18px;
+margin-left:0;
+overflow:hidden;
+}
+.entity_profile dt,
+#entity_statistics dt {
+font-weight:bold;
+}
+.entity_profile dd {
+display:inline;
+}
+
+.entity_profile .entity_depiction {
+float:left;
+width:96px;
+margin-right:18px;
+margin-bottom:18px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname,
+.entity_profile .entity_location,
+.entity_profile .entity_url,
+.entity_profile .entity_note,
+.entity_profile .entity_tags {
+margin-left:113px;
+margin-bottom:4px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname {
+margin-left:11px;
+display:inline;
+font-weight:bold;
+}
+.entity_profile .entity_nickname {
+margin-left:0;
+}
+
+.entity_profile .entity_fn dd:before {
+content: "(";
+font-weight:normal;
+}
+.entity_profile .entity_fn dd:after {
+content: ")";
+font-weight:normal;
+}
+
+.entity_profile dt {
+display:none;
+}
+.entity_profile h2 {
+display:none;
+}
+/* entity_profile */
+
+
+/*entity_actions*/
+.entity_actions {
+float:left;
+margin-left:4.35%;
+max-width:25%;
+}
+.entity_actions h2 {
+display:none;
+}
+.entity_actions ul {
+list-style-type:none;
+}
+.entity_actions li {
+margin-bottom:4px;
+}
+.entity_actions li:first-child {
+border-top:0;
+}
+.entity_actions fieldset {
+border:0;
+padding:0;
+}
+.entity_actions legend {
+display:none;
+}
+
+.entity_actions input.submit {
+display:block;
+text-align:left;
+width:100%;
+}
+.entity_actions a,
+.entity_nudge p,
+.entity_remote_subscribe {
+text-decoration:none;
+font-weight:bold;
+display:block;
+}
+
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.entity_edit a,
+.form_user_nudge input.submit,
+.entity_nudge p {
+border:0;
+padding-left:20px;
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p {
+padding:4px 4px 4px 23px;
+}
+
+.entity_remote_subscribe {
+padding:4px;
+border-width:2px;
+border-style:solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.entity_actions .accept {
+margin-bottom:18px;
+}
+
+.entity_tags ul {
+list-style-type:none;
+display:inline;
+}
+.entity_tags li {
+display:inline;
+margin-right:4px;
+}
+
+.aside .section {
+margin-bottom:29px;
+clear:both;
+float:left;
+width:100%;
+}
+.aside .section h2 {
+text-transform:uppercase;
+font-size:1em;
+}
+
+#entity_statistics dt,
+#entity_statistics dd {
+display:inline;
+}
+#entity_statistics dt:after {
+content: ":";
+}
+
+.section ul.entities {
+float:left;
+width:100%;
+}
+.section .entities li {
+list-style-type:none;
+float:left;
+margin-right:7px;
+margin-bottom:7px;
+}
+.section .entities li .photo {
+margin-right:0;
+margin-bottom:0;
+}
+.section .entities li .fn {
+display:none;
+}
+
+.aside .section p,
+.aside .section .more {
+clear:both;
+}
+
+.profile .entity_profile {
+margin-bottom:0;
+min-height:60px;
+}
+
+
+.profile .form_group_join legend,
+.profile .form_group_leave legend,
+.profile .form_user_subscribe legend,
+.profile .form_user_unsubscribe legend {
+display:none;
+}
+
+.profiles {
+list-style-type:none;
+}
+.profile .entity_profile .entity_location {
+width:auto;
+clear:none;
+margin-left:11px;
+}
+.profile .entity_profile dl,
+.profile .entity_profile dd {
+display:inline;
+float:none;
+}
+.profile .entity_profile .entity_note,
+.profile .entity_profile .entity_url,
+.profile .entity_profile .entity_tags,
+.profile .entity_profile .form_subscription_edit {
+margin-left:59px;
+clear:none;
+display:block;
+width:auto;
+}
+.profile .entity_profile .entity_tags dt {
+display:inline;
+margin-right:11px;
+}
+
+
+.profile .entity_profile .form_subscription_edit label {
+font-weight:normal;
+margin-right:11px;
+}
+
+
+/* NOTICE */
+.notice,
+.profile {
+position:relative;
+clear:both;
+float:left;
+width:100%;
+border-width:0;
+border-style:solid;
+margin-bottom:29px;
+}
+.notices li {
+list-style-type:none;
+}
+
+#content .notice {
+width:37%;
+margin-left:17px;
+margin-bottom:47px;
+clear:none;
+overflow:hidden;
+padding: 0 0 0 65px;
+min-height:235px;
+}
+
+#aside_primary .notice {
+margin-bottom:18px;
+}
+
+#shownotice #content .notice {
+width:96%;
+}
+
+
+/* NOTICES */
+#notices_primary {
+float:left;
+width:100%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+#notices_primary h2 {
+display:none;
+}
+.notice-data a span {
+display:block;
+padding-left:28px;
+}
+
+.notice .author {
+margin-right:11px;
+}
+
+#content .notice .author {
+/*overflow:hidden;*/
+}
+
+.fn {
+overflow:hidden;
+}
+
+.notice .author .fn {
+font-weight:bold;
+}
+
+.notice .author .photo {
+margin-bottom:0;
+}
+
+#content .notice .author .photo {
+margin-left:-83px;
+padding-right:17px;
+}
+
+
+.vcard .photo {
+display:inline;
+margin-right:11px;
+margin-bottom:11px;
+float:left;
+}
+.vcard .url {
+text-decoration:none;
+}
+.vcard .url:hover {
+text-decoration:underline;
+}
+
+.notice .entry-title {
+float:left;
+width:100%;
+overflow:hidden;
+}
+#content .notice .entry-title {
+overflow:visible;
+margin-bottom:11px;
+padding:18px;
+width:85%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+min-height:161px;
+}
+
+#shownotice .notice .entry-title {
+font-size:2.2em;
+}
+
+.notice p.entry-content {
+display:inline;
+}
+
+#content .notice p.entry-content
+overflow:hidden;
+}
+
+.notice p.entry-content .vcard a {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+.notice div.entry-content {
+clear:left;
+float:left;
+font-size:0.95em;
+}
+#showstream .notice div.entry-content {
+margin-left:0;
+}
+
+.notice .notice-options a,
+.notice .notice-options input {
+float:left;
+font-size:1.025em;
+}
+
+.notice div.entry-content dl,
+.notice div.entry-content dt,
+.notice div.entry-content dd {
+display:inline;
+}
+
+.notice div.entry-content .timestamp dt,
+.notice div.entry-content .response dt {
+display:none;
+}
+.notice div.entry-content .timestamp a {
+display:inline-block;
+}
+.notice div.entry-content .device dt {
+text-transform:lowercase;
+}
+
+
+
+.notice-data {
+position:absolute;
+top:18px;
+right:0;
+min-height:50px;
+margin-bottom:4px;
+}
+.notice .entry-content .notice-data dt {
+display:none;
+}
+
+.notice-data a {
+display:block;
+outline:none;
+}
+
+.notice-options {
+position:absolute;
+top:120px;
+left:30px;
+font-size:0.95em;
+}
+
+.notice-options a {
+float:left;
+}
+.notice-options .notice_delete,
+.notice-options .notice_reply,
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+position:absolute;
+left:0;
+}
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+top:0;
+}
+.notice-options .notice_reply {
+top:29px;
+}
+.notice-options .notice_delete {
+top:58px;
+}
+.notice-options .notice_reply dt {
+display:none;
+}
+
+.notice-options input,
+.notice-options a {
+text-indent:-9999px;
+outline:none;
+}
+
+.notice-options .notice_reply a,
+.notice-options input.submit {
+display:block;
+border:0;
+}
+.notice-options .notice_reply a,
+.notice-options .notice_delete a {
+text-decoration:none;
+padding-left:16px;
+}
+
+.notice-options form input.submit {
+width:16px;
+padding:2px 0;
+}
+
+.notice-options .notice_delete dt,
+.notice-options .form_favor legend,
+.notice-options .form_disfavor legend {
+display:none;
+}
+.notice-options .notice_delete fieldset,
+.notice-options .form_favor fieldset,
+.notice-options .form_disfavor fieldset {
+border:0;
+padding:0;
+}
+
+
+#usergroups #new_group {
+float: left;
+margin-right: 2em;
+}
+#new_group, #group_search {
+margin-bottom:18px;
+}
+#new_group a {
+padding-left:20px;
+}
+
+
+#filter_tags {
+margin-bottom:11px;
+float:left;
+}
+#filter_tags dt {
+display:none;
+}
+#filter_tags ul {
+list-style-type:none;
+}
+#filter_tags ul li {
+float:left;
+margin-left:7px;
+padding-left:7px;
+border-left-width:1px;
+border-left-style:solid;
+}
+#filter_tags ul li.child_1 {
+margin-left:0;
+border-left:0;
+padding-left:0;
+}
+#filter_tags ul li#filter_tags_all a {
+font-weight:bold;
+margin-top:7px;
+float:left;
+}
+
+#filter_tags ul li#filter_tags_item label {
+margin-right:7px;
+}
+#filter_tags ul li#filter_tags_item label,
+#filter_tags ul li#filter_tags_item select {
+display:inline;
+}
+#filter_tags ul li#filter_tags_item p {
+float:left;
+margin-left:38px;
+}
+#filter_tags ul li#filter_tags_item input {
+position:relative;
+top:3px;
+left:3px;
+}
+
+
+
+.pagination {
+float:left;
+clear:both;
+width:100%;
+margin-top:18px;
+}
+
+.pagination dt {
+font-weight:bold;
+display:none;
+}
+
+.pagination .nav {
+float:left;
+width:100%;
+list-style-type:none;
+}
+
+.pagination .nav_prev {
+float:left;
+}
+.pagination .nav_next {
+float:right;
+}
+
+.pagination a {
+display:block;
+text-decoration:none;
+font-weight:bold;
+padding:7px;
+border-width:1px;
+border-style:solid;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-radius:7px;
+}
+
+.pagination .nav_prev a {
+padding-left:30px;
+}
+.pagination .nav_next a {
+padding-right:30px;
+}
+/* END: NOTICE */
+
+
+.hentry .entry-content p {
+margin-bottom:18px;
+}
+.hentry entry-content ol,
+.hentry .entry-content ul {
+list-style-position:inside;
+}
+.hentry .entry-content li {
+margin-bottom:18px;
+}
+.hentry .entry-content li li {
+margin-left:18px;
+}
+
+
+
+
+/* TOP_POSTERS */
+.section tbody td {
+padding-right:11px;
+padding-bottom:11px;
+}
+.section .vcard .photo {
+margin-right:7px;
+margin-bottom:0;
+}
+
+.section .notice {
+padding-top:7px;
+padding-bottom:7px;
+border-top:0;
+}
+
+.section .notice:first-child {
+padding-top:0;
+}
+
+.section .notice .author {
+margin-right:0;
+}
+.section .notice .author .fn {
+display:none;
+}
+
+
+/* tagcloud */
+.tag-cloud {
+list-style-type:none;
+text-align:center;
+}
+.aside .tag-cloud {
+font-size:0.8em;
+}
+.tag-cloud li {
+display:inline;
+margin-right:7px;
+line-height:1.25;
+}
+.aside .tag-cloud li {
+line-height:1.5;
+}
+.tag-cloud li a {
+text-decoration:none;
+}
+#tagcloud.section dt {
+text-transform:uppercase;
+font-weight:bold;
+}
+.tag-cloud-1 {
+font-size:1em;
+}
+.tag-cloud-2 {
+font-size:1.25em;
+}
+.tag-cloud-3 {
+font-size:1.75em;
+}
+.tag-cloud-4 {
+font-size:2em;
+}
+.tag-cloud-5 {
+font-size:2.25em;
+}
+.tag-cloud-6 {
+font-size:2.75em;
+}
+.tag-cloud-7 {
+font-size:3.25em;
+}
+
+#publictagcloud #tagcloud.section dt {
+display:none;
+}
+
+#form_settings_photo .form_data {
+clear:both;
+}
+
+#form_settings_avatar li {
+width:auto;
+}
+#form_settings_avatar input {
+margin-left:0;
+}
+#avatar_original,
+#avatar_preview {
+float:left;
+}
+#avatar_preview {
+margin-left:29px;
+}
+#avatar_preview_view {
+height:96px;
+width:96px;
+margin-bottom:18px;
+overflow:hidden;
+}
+
+#settings_attach,
+#form_settings_avatar .form_actions {
+clear:both;
+}
+
+#form_settings_avatar .form_actions {
+margin-bottom:0;
+}
+
+#form_settings_design #settings_design_color .form_data,
+#form_settings_design #color-picker {
+float:left;
+}
+#form_settings_design #settings_design_color .form_data {
+width:400px;
+margin-right:28px;
+}
+
+.instructions ul {
+list-style-position:inside;
+}
+.instructions p,
+.instructions ul {
+margin-bottom:18px;
+}
+.help dt {
+display:none;
+}
+.guide {
+clear:both;
+}
diff --git a/theme/otalk/css/display.css b/theme/otalk/css/display.css
new file mode 100644 (file)
index 0000000..d2a4719
--- /dev/null
@@ -0,0 +1,292 @@
+/** theme: otalk
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+@import url(base.css);
+
+html {
+}
+
+html,
+body,
+a:active {
+}
+body {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+font-size:1em;
+background:#ddd url(../images/illustrations/illu_pattern-01.png) repeat 0 0;
+background-color:rgba(127, 127, 127, 0.1);
+}
+address {
+margin-right:7.18%;
+}
+
+input, textarea, select, option {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+}
+input, textarea, select,
+.entity_remote_subscribe {
+border-color:#aaa;
+}
+#filter_tags ul li {
+border-color:#ddd;
+}
+
+.form_settings input.form_action-primary {
+background:none;
+}
+
+input.submit,
+#form_notice.warning #notice_text-count,
+.form_settings .form_note,
+.entity_remote_subscribe {
+background-color:#9BB43E;
+}
+
+input:focus, textarea:focus, select:focus,
+#form_notice.warning #notice_data-text {
+border-color:#9BB43E;
+}
+input.submit,
+.entity_remote_subscribe {
+color:#fff;
+}
+
+a,
+div.notice-options input,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.entity_nudge p,
+.form_settings input.form_action-primary {
+color:#8F0000;
+}
+
+.notice,
+.profile {
+border-color:#CEE1E9;
+}
+#content .notice .entry-title,
+input, textarea, select, option,
+.pagination .nav_prev a,
+.pagination .nav_next a {
+background-color:rgba(255,255,255,0.8);
+}
+
+#content .notices li.hover .entry-title {
+background-color:rgba(255,255,255,0.9);
+}
+
+#content .notice:nth-child(1) .entry-title {
+background-color:rgba(255,255,255,0.95);
+}
+#content .notice:nth-child(2) .entry-title {
+background-color:rgba(255,255,255,0.9);
+}
+#content .notice:nth-child(3) .entry-title {
+background-color:rgba(255,255,255,0.8);
+}
+#content .notice:nth-child(4) .entry-title {
+background-color:rgba(255,255,255,0.7);
+}
+#content .notice:nth-child(5) .entry-title {
+background-color:rgba(255,255,255,0.6);
+}
+#content .notice:nth-child(6) .entry-title {
+background-color:rgba(255,255,255,0.5);
+}
+#content .notice:nth-child(7) .entry-title {
+background-color:rgba(255,255,255,0.4);
+}
+#content .notice:nth-child(8) .entry-title {
+background-color:rgba(255,255,255,0.3);
+}
+#content .notice:nth-child(9) .entry-title {
+background-color:rgba(255,255,255,0.2);
+}
+#content .notice:nth-child(10) {
+background-color:rgba(255,255,255,0.1);
+}
+
+
+#content .notice .author .photo {
+background:url(../images/illustrations/illu_arrow-left-01.gif) no-repeat 100% 0;
+}
+
+.section .profile {
+border-top-color:#87B4C8;
+}
+
+#aside_primary {
+background-color:rgba(206, 225, 233,0.5);
+}
+
+#notice_text-count {
+color:#333;
+}
+#form_notice.warning #notice_text-count {
+color:#000;
+}
+#form_notice.processing #notice_action-submit {
+background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+cursor:wait;
+text-indent:-9999px;
+}
+
+#content,
+#site_nav_local_views .nav,
+#site_nav_local_views a,
+#aside_primary {
+border-color:#fff;
+}
+#content,
+#site_nav_local_views .current a {
+background-color:transparent;
+/*background-color:red;*/
+}
+
+#site_nav_local_views .current a {
+background-color:transparent;
+}
+
+#site_nav_local_views a {
+background-color:rgba(127, 127, 127, 0.2);
+}
+#site_nav_local_views a:hover {
+background-color:rgba(255, 255, 255, 0.8);
+}
+
+.error {
+background-color:#F7E8E8;
+}
+.success {
+background-color:#EFF3DC;
+}
+
+#anon_notice {
+background-color:rgba(206, 225, 233, 0.7);
+color:#fff;
+border-color:#fff;
+}
+
+#showstream #anon_notice {
+background-color:rgba(155, 180, 62, 0.7);
+}
+
+#export_data li a {
+background-repeat:no-repeat;
+background-position:0 45%;
+}
+#export_data li a.rss {
+background-image:url(../../base/images/icons/icon_rss.png);
+}
+#export_data li a.atom {
+background-image:url(../../base/images/icons/icon_atom.png);
+}
+#export_data li a.foaf {
+background-image:url(../../base/images/icons/icon_foaf.gif);
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.entity_nudge p {
+background-position: 0 40%;
+background-repeat: no-repeat;
+background-color:transparent;
+}
+.form_group_join input.submit,
+.form_group_leave input.submit
+.form_user_subscribe input.submit,
+.form_user_unsubscribe input.submit {
+background-color:#9BB43E;
+color:#fff;
+}
+.form_user_unsubscribe input.submit,
+.form_group_leave input.submit,
+.form_user_authorization input.reject {
+background-color:#87B4C8;
+}
+
+.entity_edit a {
+background-image:url(../../base/images/icons/twotone/green/edit.gif);
+}
+.entity_send-a-message a {
+background-image:url(../../base/images/icons/twotone/green/quote.gif);
+}
+.entity_nudge p,
+.form_user_nudge input.submit {
+background-image:url(../../base/images/icons/twotone/green/mail.gif);
+}
+.form_user_block input.submit,
+.form_user_unblock input.submit {
+background-image:url(../../base/images/icons/twotone/green/shield.gif);
+}
+
+/* NOTICES */
+.notices li.over {
+background-color:#fcfcfc;
+}
+
+.notice-options .notice_reply a,
+.notice-options form input.submit {
+background-color:transparent;
+}
+.notice-options .notice_reply a {
+background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+}
+.notice-options form.form_favor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+}
+.notice-options form.form_disfavor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+}
+.notice-options .notice_delete a {
+background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+}
+
+.notices div.entry-content,
+.notices div.notice-options {
+opacity:0.4;
+}
+.notices li.hover div.entry-content,
+.notices li.hover div.notice-options {
+opacity:1;
+}
+div.entry-content {
+color:#333;
+}
+div.notice-options a,
+div.notice-options input {
+font-family:sans-serif;
+}
+.notices li.hover {
+/*background-color:#fcfcfc;*/
+}
+/*END: NOTICES */
+
+#new_group a {
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+}
+
+.pagination .nav_prev a,
+.pagination .nav_next a {
+background-repeat:no-repeat;
+border-color:#CEE1E9;
+}
+.pagination .nav_prev a {
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
+background-position:10% 45%;
+}
+.pagination .nav_next a {
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
+background-position:90% 45%;
+}
diff --git a/theme/otalk/css/ie.css b/theme/otalk/css/ie.css
new file mode 100644 (file)
index 0000000..2f463bb
--- /dev/null
@@ -0,0 +1,9 @@
+/* IE specific styles */
+
+.notice-options input.submit {
+color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:#D0DFE7;
+}
diff --git a/theme/otalk/default-avatar-mini.png b/theme/otalk/default-avatar-mini.png
new file mode 100644 (file)
index 0000000..38b8692
Binary files /dev/null and b/theme/otalk/default-avatar-mini.png differ
diff --git a/theme/otalk/default-avatar-profile.png b/theme/otalk/default-avatar-profile.png
new file mode 100644 (file)
index 0000000..f8357d4
Binary files /dev/null and b/theme/otalk/default-avatar-profile.png differ
diff --git a/theme/otalk/default-avatar-stream.png b/theme/otalk/default-avatar-stream.png
new file mode 100644 (file)
index 0000000..6b63baa
Binary files /dev/null and b/theme/otalk/default-avatar-stream.png differ
diff --git a/theme/otalk/images/illustrations/illu_arrow-left-01.gif b/theme/otalk/images/illustrations/illu_arrow-left-01.gif
new file mode 100644 (file)
index 0000000..1977759
Binary files /dev/null and b/theme/otalk/images/illustrations/illu_arrow-left-01.gif differ
diff --git a/theme/otalk/images/illustrations/illu_pattern-01.png b/theme/otalk/images/illustrations/illu_pattern-01.png
new file mode 100644 (file)
index 0000000..5a72eaf
Binary files /dev/null and b/theme/otalk/images/illustrations/illu_pattern-01.png differ
diff --git a/theme/otalk/logo.png b/theme/otalk/logo.png
new file mode 100644 (file)
index 0000000..fdead6c
Binary files /dev/null and b/theme/otalk/logo.png differ
diff --git a/theme/pigeonthoughts/css/base.css b/theme/pigeonthoughts/css/base.css
new file mode 100644 (file)
index 0000000..9866e2d
--- /dev/null
@@ -0,0 +1,1269 @@
+/** theme: pigeonthoughts base
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+* { margin:0; padding:0; }
+img { display:block; border:0; }
+a abbr { cursor: pointer; border-bottom:0; }
+table { border-collapse:collapse; }
+ol { list-style-position:inside; }
+html { font-size: 87.5%; }
+body {
+background-color:#FFFFFF;
+color:#000;
+font-family:sans-serif;
+font-size:1em;
+line-height:1.65;
+position:relative;
+margin-left:183px;
+}
+h1,h2,h3,h4,h5,h6 {
+margin-bottom:7px;
+overflow:hidden;
+}
+h1 {
+font-size:1.4em;
+margin-bottom:18px;
+}
+#showstream h1 { display:none; }
+h2 { font-size:1.3em; }
+h3 { font-size:1.2em; }
+h4 { font-size:1.1em; }
+h5 { font-size:1em; }
+h6 { font-size:0.9em; }
+
+caption {
+font-weight:bold;
+}
+legend {
+font-weight:bold;
+font-size:1.3em;
+}
+input, textarea, select, option {
+padding:4px;
+font-family:sans-serif;
+font-size:1em;
+}
+input, textarea, select {
+border-width:2px;
+border-style: solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+input.submit {
+font-weight:bold;
+cursor:pointer;
+}
+textarea {
+overflow:auto;
+}
+option {
+padding-bottom:0;
+}
+fieldset {
+padding:0;
+border:0;
+}
+form ul li {
+list-style-type:none;
+margin:0 0 18px 0;
+}
+form label {
+font-weight:bold;
+}
+input.checkbox,
+input.radio {
+position:relative;
+top:2px;
+left:0;
+border:0;
+}
+
+.error,
+.success {
+padding:4px 1.55%;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+margin-bottom:18px;
+}
+form label.submit {
+display:none;
+}
+
+.form_settings {
+clear:both;
+}
+
+.form_settings fieldset {
+margin-bottom:29px;
+}
+.form_settings input.remove {
+margin-left:11px;
+}
+.form_settings .form_data li {
+width:100%;
+float:left;
+}
+.form_settings .form_data label {
+float:left;
+}
+.form_settings .form_data textarea,
+.form_settings .form_data select,
+.form_settings .form_data input {
+margin-left:11px;
+float:left;
+}
+.form_settings .form_data input.submit {
+margin-left:0;
+}
+
+.form_settings label {
+margin-top:2px;
+width:152px;
+}
+
+.form_actions label {
+display:none;
+}
+.form_guide {
+font-style:italic;
+}
+
+.form_settings #settings_autosubscribe label {
+display:inline;
+font-weight:bold;
+}
+
+#form_settings_profile legend,
+#form_login legend,
+#form_register legend,
+#form_password legend,
+#form_settings_avatar legend,
+#newgroup legend,
+#editgroup legend,
+#form_tag_user legend,
+#form_remote_subscribe legend,
+#form_openid_login legend,
+#form_search legend,
+#form_invite legend,
+#form_notice_delete legend,
+#form_password_recover legend,
+#form_password_change legend,
+.form_entity_block legend {
+display:none;
+}
+
+.form_settings .form_data p.form_guide {
+clear:both;
+margin-left:163px;
+margin-bottom:0;
+}
+
+.form_settings p {
+margin-bottom:11px;
+}
+
+.form_settings input.checkbox {
+margin-top:3px;
+margin-left:0;
+}
+.form_settings label.checkbox {
+font-weight:normal;
+margin-top:0;
+margin-right:0;
+margin-left:11px;
+float:left;
+width:90%;
+}
+.form_settings label.radio {
+margin-top:0;
+margin-right:47px;
+margin-left:11px;
+width:auto;
+}
+
+#form_login p.form_guide,
+#form_register #settings_rememberme p.form_guide,
+#form_openid_login #settings_rememberme p.form_guide,
+#settings_twitter_remove p.form_guide,
+#form_search ul.form_data #q,
+#design_background-image_onoff p.form_guide {
+margin-left:0;
+}
+
+.form_settings .form_note {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+padding:0 7px;
+}
+
+
+.form_settings input.form_action-primary {
+padding:0;
+}
+.form_settings input.form_action-secondary {
+margin-left:29px;
+}
+
+#form_search .submit {
+margin-left:11px;
+}
+
+address {
+float:right;
+margin-bottom:18px;
+margin-right:18px;
+}
+address.vcard img.logo {
+margin-right:0;
+}
+address .fn {
+font-weight:bold;
+}
+address img + .fn {
+display:none;
+}
+
+#header {
+width:98.5%;
+position:relative;
+float:left;
+padding-top:18px;
+padding-left:18px;
+margin-bottom:29px;
+}
+
+#site_nav_global_primary {
+float:left;
+margin-right:18px;
+margin-bottom:11px;
+}
+#site_nav_global_primary ul li {
+display:inline;
+margin-right:11px;
+}
+
+.system_notice dt {
+font-weight:bold;
+text-transform:uppercase;
+display:none;
+}
+
+#site_notice {
+float:right;
+margin-top:7px;
+margin-right:18px;
+width:26%;
+}
+#page_notice {
+clear:both;
+margin-bottom:18px;
+}
+
+
+#anon_notice {
+float:left;
+width:50.2%;
+line-height:1.5;
+font-size:1.1em;
+font-weight:bold;
+}
+
+
+#footer {
+float:left;
+width:64%;
+padding:18px;
+}
+
+#site_nav_local_views {
+width:183px;
+float:left;
+margin-bottom:29px;
+position:fixed;
+top:179px;
+left:0;
+}
+#site_nav_local_views dt {
+display:none;
+}
+#site_nav_local_views li {
+list-style-type:none;
+}
+#site_nav_local_views a {
+text-decoration:none;
+padding:4px 11px;
+text-shadow: 1px 1px 1px #ddd;
+font-weight:bold;
+display:block;
+}
+#site_nav_local_views .nav {
+float:left;
+width:100%;
+}
+
+#site_nav_global_primary dt,
+#site_nav_global_secondary dt {
+display:none;
+}
+
+#site_nav_global_secondary {
+margin-bottom:11px;
+}
+
+#site_nav_global_secondary ul li {
+display:inline;
+margin-right:11px;
+}
+#export_data li a {
+padding-left:20px;
+}
+#export_data li a.foaf {
+padding-left:30px;
+}
+#export_data li a.export_vcard {
+padding-left:28px;
+}
+
+#export_data ul {
+display:inline;
+}
+#export_data li {
+list-style-type:none;
+display:inline;
+margin-left:11px;
+}
+#export_data li:first-child {
+margin-left:0;
+}
+
+#licenses {
+font-size:0.9em;
+}
+
+#licenses dt {
+font-weight:bold;
+display:none;
+}
+#licenses dd {
+margin-bottom:11px;
+line-height:1.5;
+}
+
+#site_content_license_cc {
+margin-bottom:0;
+}
+#site_content_license_cc img {
+display:inline;
+vertical-align:top;
+margin-right:4px;
+}
+
+#wrap {
+width:100%;
+min-width:760px;
+max-width:1003px;
+overflow:hidden;
+}
+
+#core {
+position:relative;
+width:100%;
+float:left;
+margin-bottom:1em;
+}
+
+#content {
+width:49.009%;
+min-height:259px;
+float:left;
+padding:0 18px;
+}
+#shownotice #content {
+min-height:0;
+}
+
+#content_inner {
+position:relative;
+width:100%;
+float:left;
+}
+
+#aside_primary {
+width:45.917%;
+min-height:259px;
+float:left;
+margin-left:1.385%;
+padding-bottom:47px;
+}
+
+#form_notice {
+width:45.664%;
+float:left;
+position:relative;
+line-height:1;
+}
+#form_notice fieldset {
+border:0;
+padding:0;
+position:relative;
+}
+#form_notice legend {
+display:none;
+}
+#form_notice textarea {
+float:left;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+width:80.789%;
+height:46px;
+line-height:1.5;
+padding:7px 7px 16px 7px;
+position:relative;
+z-index:2;
+}
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice label[for=notice_data-attach],
+#form_notice #notice_data-attach {
+position:absolute;
+top:25px;
+cursor:pointer;
+}
+#form_notice label[for=notice_data-attach] {
+text-indent:-9999px;
+left:394px;
+width:16px;
+height:16px;
+}
+#form_notice #notice_data-attach {
+left:183px;
+padding:0;
+height:16px;
+}
+#form_notice .form_note {
+position:absolute;
+top:76px;
+right:98px;
+z-index:9;
+}
+#form_notice .form_note dt {
+font-weight:bold;
+display:none;
+}
+#notice_text-count {
+font-weight:bold;
+line-height:1.15;
+padding:1px 2px;
+}
+#form_notice #notice_action-submit {
+width:14%;
+height:47px;
+padding:0;
+position:absolute;
+bottom:0;
+right:0;
+}
+#form_notice label[for=to] {
+margin-top:7px;
+}
+#form_notice select[id=to] {
+margin-bottom:7px;
+margin-left:18px;
+float:left;
+}
+#form_notice .error {
+float:left;
+clear:both;
+width:96.9%;
+margin-bottom:0;
+line-height:1.618;
+}
+
+/* entity_profile */
+.entity_profile {
+position:relative;
+width:67.702%;
+min-height:123px;
+float:left;
+margin-bottom:18px;
+margin-left:0;
+overflow:hidden;
+}
+.entity_profile dt,
+#entity_statistics dt {
+font-weight:bold;
+}
+.entity_profile dd {
+display:inline;
+}
+
+.entity_profile .entity_depiction {
+float:left;
+width:96px;
+margin-right:18px;
+margin-bottom:18px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname,
+.entity_profile .entity_location,
+.entity_profile .entity_url,
+.entity_profile .entity_note,
+.entity_profile .entity_tags {
+margin-left:113px;
+margin-bottom:4px;
+}
+
+.entity_profile .entity_fn,
+.entity_profile .entity_nickname {
+margin-left:11px;
+display:inline;
+}
+.entity_profile .entity_nickname {
+margin-left:0;
+}
+.entity_profile .fn,
+.entity_profile .nickname {
+font-size:1.1em;
+font-weight:bold;
+}
+.entity_profile .entity_fn dd:before {
+content: "(";
+font-weight:normal;
+}
+.entity_profile .entity_fn dd:after {
+content: ")";
+font-weight:normal;
+}
+
+.entity_profile dt {
+display:none;
+}
+.entity_profile h2 {
+display:none;
+}
+/* entity_profile */
+
+
+/*entity_actions*/
+.entity_actions {
+float:right;
+margin-left:4.35%;
+max-width:25%;
+}
+.entity_actions h2 {
+display:none;
+}
+.entity_actions ul {
+list-style-type:none;
+}
+.entity_actions li {
+margin-bottom:4px;
+}
+.entity_actions li:first-child {
+border-top:0;
+}
+.entity_actions fieldset {
+border:0;
+padding:0;
+}
+.entity_actions legend {
+display:none;
+}
+
+.entity_actions input.submit {
+display:block;
+text-align:left;
+width:100%;
+}
+.entity_actions a,
+.entity_nudge p,
+.entity_remote_subscribe {
+text-decoration:none;
+font-weight:bold;
+display:block;
+}
+
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.entity_send-a-message a,
+.entity_edit a,
+.form_user_nudge input.submit,
+.entity_nudge p,
+.form_make_admin input.submit {
+border:0;
+padding-left:20px;
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.entity_nudge p {
+padding:4px 4px 4px 23px;
+}
+
+.entity_remote_subscribe {
+padding:4px;
+border-width:2px;
+border-style:solid;
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.entity_actions .accept {
+margin-bottom:18px;
+}
+
+.entity_tags ul {
+list-style-type:none;
+display:inline;
+}
+.entity_tags li {
+display:inline;
+margin-right:4px;
+}
+
+.aside .section {
+margin-bottom:29px;
+float:right;
+width:44%;
+padding:1%;
+border-width:1px;
+border-style:solid;
+margin-left:2.5%;
+}
+.aside .section h2 {
+text-transform:uppercase;
+font-size:1em;
+}
+
+#entity_statistics dt,
+#entity_statistics dd {
+display:inline;
+}
+#entity_statistics dt:after {
+content: ":";
+}
+
+.section ul.entities {
+float:left;
+width:100%;
+}
+.section .entities li {
+list-style-type:none;
+float:left;
+margin-right:7px;
+margin-bottom:7px;
+display:inline;
+}
+.section .entities li .photo {
+margin-right:0;
+margin-bottom:0;
+}
+.section .entities li .fn {
+display:none;
+}
+
+.aside .section p,
+.aside .section .more {
+clear:both;
+}
+
+.profile .entity_profile {
+margin-bottom:0;
+min-height:60px;
+}
+
+
+.profile .form_group_join legend,
+.profile .form_group_leave legend,
+.profile .form_user_subscribe legend,
+.profile .form_user_unsubscribe legend {
+display:none;
+}
+
+.profiles {
+list-style-type:none;
+}
+.profile .entity_profile .entity_location {
+width:auto;
+clear:none;
+margin-left:11px;
+}
+.profile .entity_profile dl,
+.profile .entity_profile dd {
+display:inline;
+float:none;
+}
+.profile .entity_profile .entity_note,
+.profile .entity_profile .entity_url,
+.profile .entity_profile .entity_tags,
+.profile .entity_profile .form_subscription_edit {
+margin-left:59px;
+clear:none;
+display:block;
+width:auto;
+}
+.profile .entity_profile .entity_tags dt {
+display:inline;
+margin-right:11px;
+}
+
+
+.profile .entity_profile .form_subscription_edit label {
+font-weight:normal;
+margin-right:11px;
+}
+
+
+/* NOTICE */
+.notice,
+.profile {
+position:relative;
+padding-top:11px;
+padding-bottom:11px;
+clear:both;
+float:left;
+width:96.41%;
+border-width:1px;
+border-style:solid;
+margin-bottom:11px;
+}
+.notices li {
+list-style-type:none;
+}
+.notices .notices {
+margin-top:7px;
+margin-left:5%;
+width:95%;
+float:left;
+}
+
+#aside_primary .notice,
+#aside_primary .profile {
+border:0;
+margin-bottom:11px;
+}
+
+/* NOTICES */
+#notices_primary {
+float:left;
+width:100%;
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+#notices_primary h2 {
+display:none;
+}
+.notice-data a span {
+display:block;
+padding-left:28px;
+}
+
+.notice .author {
+margin-right:11px;
+}
+
+.fn {
+overflow:hidden;
+}
+
+.notice .author .fn {
+font-weight:bold;
+}
+
+.vcard .photo {
+display:inline;
+margin-right:11px;
+float:left;
+}
+#shownotice .vcard .photo {
+margin-bottom:4px;
+}
+.vcard .url {
+text-decoration:none;
+}
+.vcard .url:hover {
+text-decoration:underline;
+}
+
+.notice .entry-title {
+float:left;
+width:100%;
+overflow:hidden;
+}
+.notice .entry-title.ov {
+overflow:visible;
+}
+#shownotice .notice .entry-title {
+font-size:2.2em;
+}
+
+.notice p.entry-content {
+display:inline;
+}
+
+#content .notice p.entry-content a:visited {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+.notice p.entry-content .vcard a {
+border-radius:4px;
+-moz-border-radius:4px;
+-webkit-border-radius:4px;
+}
+
+.notice div.entry-content {
+clear:left;
+float:left;
+font-size:0.95em;
+margin-left:59px;
+width:60%;
+}
+#showstream .notice div.entry-content,
+#shownotice .notice div.entry-content {
+margin-left:0;
+}
+
+.notice .notice-options a,
+.notice .notice-options input {
+float:left;
+font-size:1.025em;
+}
+
+.notice div.entry-content dl,
+.notice div.entry-content dt,
+.notice div.entry-content dd {
+display:inline;
+}
+
+.notice div.entry-content .timestamp dt,
+.notice div.entry-content .response dt {
+display:none;
+}
+.notice div.entry-content .timestamp a {
+display:inline-block;
+}
+.notice div.entry-content .device dt {
+text-transform:lowercase;
+}
+
+.notice-options {
+position:relative;
+font-size:0.95em;
+width:90px;
+float:right;
+margin-right:11px;
+}
+
+.notice-options a {
+float:left;
+}
+.notice-options .notice_delete,
+.notice-options .notice_reply,
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+position:absolute;
+top:0;
+}
+.notice-options .form_favor,
+.notice-options .form_disfavor {
+left:0;
+}
+.notice-options .notice_reply {
+left:29px;
+}
+.notice-options .notice_delete {
+right:0;
+}
+.notice-options .notice_reply dt {
+display:none;
+}
+
+.notice-options input,
+.notice-options a {
+text-indent:-9999px;
+outline:none;
+}
+
+.notice-options .notice_reply a,
+.notice-options input.submit {
+display:block;
+border:0;
+}
+.notice-options .notice_reply a,
+.notice-options .notice_delete a {
+text-decoration:none;
+padding-left:16px;
+}
+
+.notice-options form input.submit {
+width:16px;
+padding:2px 0;
+}
+
+.notice-options .notice_delete dt,
+.notice-options .form_favor legend,
+.notice-options .form_disfavor legend {
+display:none;
+}
+.notice-options .notice_delete fieldset,
+.notice-options .form_favor fieldset,
+.notice-options .form_disfavor fieldset {
+border:0;
+padding:0;
+}
+
+.notice .attachment {
+position:relative;
+padding-left:16px;
+}
+#attachments .attachment {
+padding-left:0;
+}
+.notice .attachment img {
+position:absolute;
+top:18px;
+left:0;
+z-index:99;
+}
+#shownotice .notice .attachment img {
+position:static;
+}
+
+#attachments {
+clear:both;
+float:left;
+width:100%;
+margin-top:18px;
+}
+#attachments dt {
+font-weight:bold;
+font-size:1.3em;
+margin-bottom:4px;
+}
+
+#attachments ol li {
+margin-bottom:18px;
+list-style-type:decimal;
+float:left;
+clear:both;
+}
+
+#jOverlayContent,
+#jOverlayContent #content,
+#jOverlayContent #content_inner {
+width: auto !important;
+margin-bottom:0;
+}
+#jOverlayContent #content {
+padding:11px;
+min-height:auto;
+}
+#jOverlayContent .external span {
+display:block;
+margin-bottom:11px;
+}
+#jOverlayContent button {
+position:absolute;
+top:0;
+right:0;
+width:29px;
+height:29px;
+text-align:center;
+font-weight:bold;
+padding:0;
+}
+#jOverlayContent h1 {
+max-width:475px;
+}
+#jOverlayContent #content {
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+
+#usergroups #new_group {
+float: left;
+margin-right: 2em;
+}
+#new_group, #group_search {
+margin-bottom:18px;
+}
+#new_group a {
+padding-left:20px;
+}
+
+
+#filter_tags {
+margin-bottom:11px;
+float:left;
+}
+#filter_tags dt {
+display:none;
+}
+#filter_tags ul {
+list-style-type:none;
+}
+#filter_tags ul li {
+float:left;
+margin-left:7px;
+padding-left:7px;
+border-left-width:1px;
+border-left-style:solid;
+}
+#filter_tags ul li.child_1 {
+margin-left:0;
+border-left:0;
+padding-left:0;
+}
+#filter_tags ul li#filter_tags_all a {
+font-weight:bold;
+margin-top:7px;
+float:left;
+}
+
+#filter_tags ul li#filter_tags_item label {
+margin-right:7px;
+}
+#filter_tags ul li#filter_tags_item label,
+#filter_tags ul li#filter_tags_item select {
+display:inline;
+}
+#filter_tags ul li#filter_tags_item p {
+float:left;
+margin-left:38px;
+}
+#filter_tags ul li#filter_tags_item input {
+position:relative;
+top:3px;
+left:3px;
+}
+
+
+
+.pagination {
+float:left;
+clear:both;
+width:100%;
+margin-top:18px;
+}
+
+.pagination dt {
+font-weight:bold;
+display:none;
+}
+
+.pagination .nav {
+float:left;
+width:100%;
+list-style-type:none;
+}
+
+.pagination .nav_prev {
+float:left;
+}
+.pagination .nav_next {
+float:right;
+}
+
+.pagination a {
+display:block;
+text-decoration:none;
+font-weight:bold;
+padding:7px;
+border-width:1px;
+border-style:solid;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+border-radius:7px;
+}
+
+.pagination .nav_prev a {
+padding-left:30px;
+}
+.pagination .nav_next a {
+padding-right:30px;
+}
+/* END: NOTICE */
+
+
+.hentry .entry-content p {
+margin-bottom:18px;
+}
+.system_notice ul,
+.instructions ul,
+.hentry entry-content ol,
+.hentry .entry-content ul {
+list-style-position:inside;
+}
+.hentry .entry-content li {
+margin-bottom:18px;
+}
+.hentry .entry-content li li {
+margin-left:18px;
+}
+
+
+/* TOP_POSTERS */
+.section tbody td {
+padding-right:11px;
+padding-bottom:11px;
+}
+.section .vcard .photo {
+margin-right:7px;
+margin-bottom:0;
+}
+
+.section .notice {
+padding-top:7px;
+padding-bottom:7px;
+border-top:0;
+}
+
+.section .notice:first-child {
+padding-top:0;
+}
+
+.section .notice .author {
+margin-right:0;
+}
+.section .notice .author .fn {
+display:none;
+}
+
+
+/* tagcloud */
+.tag-cloud {
+list-style-type:none;
+text-align:center;
+}
+.aside .tag-cloud {
+font-size:0.8em;
+}
+.tag-cloud li {
+display:inline;
+margin-right:7px;
+line-height:1.25;
+}
+.aside .tag-cloud li {
+line-height:1.5;
+}
+.tag-cloud li a {
+text-decoration:none;
+}
+#tagcloud.section dt {
+text-transform:uppercase;
+font-weight:bold;
+}
+.tag-cloud-1 {
+font-size:1em;
+}
+.tag-cloud-2 {
+font-size:1.25em;
+}
+.tag-cloud-3 {
+font-size:1.75em;
+}
+.tag-cloud-4 {
+font-size:2em;
+}
+.tag-cloud-5 {
+font-size:2.25em;
+}
+.tag-cloud-6 {
+font-size:2.75em;
+}
+.tag-cloud-7 {
+font-size:3.25em;
+}
+
+#publictagcloud #tagcloud.section dt {
+display:none;
+}
+
+#form_settings_photo .form_data {
+clear:both;
+}
+
+#form_settings_avatar li {
+width:auto;
+}
+#form_settings_avatar input {
+margin-left:0;
+}
+#avatar_original,
+#avatar_preview {
+float:left;
+}
+#avatar_preview {
+margin-left:29px;
+}
+#avatar_preview_view {
+height:96px;
+width:96px;
+margin-bottom:18px;
+overflow:hidden;
+}
+
+#settings_attach,
+#form_settings_avatar .form_actions {
+clear:both;
+}
+
+#form_settings_avatar .form_actions {
+margin-bottom:0;
+}
+
+#form_settings_design #settings_design_color .form_data,
+#form_settings_design #color-picker {
+float:left;
+}
+#form_settings_design #settings_design_color .form_data {
+width:400px;
+margin-right:28px;
+}
+
+#settings_design_color .form_data li {
+width:33%;
+}
+#settings_design_color .form_data label {
+float:none;
+display:block;
+}
+#settings_design_color .form_data .swatch {
+padding:11px;
+margin-left:0;
+}
+
+.instructions ul {
+list-style-position:inside;
+}
+.instructions p,
+.instructions ul {
+margin-bottom:18px;
+}
+.help dt {
+display:none;
+}
+.guide {
+clear:both;
+}
diff --git a/theme/pigeonthoughts/css/display.css b/theme/pigeonthoughts/css/display.css
new file mode 100644 (file)
index 0000000..01af500
--- /dev/null
@@ -0,0 +1,340 @@
+/** theme: pigeonthoughts
+ *
+ * @package   Laconica
+ * @author Sarven Capadisli <csarven@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+@import url(base.css);
+
+html {
+background:url(../images/illustrations/illu_pigeons-01.png) no-repeat 0 100%;
+}
+
+body,
+a:active {
+background-color:#AEA187;
+}
+body {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+font-size:1em;
+}
+address {
+margin-left:2%;
+}
+
+input, textarea, select, option {
+font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
+}
+input, textarea, select,
+.entity_remote_subscribe {
+border-color:#AAAAAA;
+}
+#filter_tags ul li {
+border-color:#DDDDDD;
+}
+
+.form_settings input.form_action-primary {
+background:none;
+}
+
+input.submit,
+#form_notice.warning #notice_text-count,
+.form_settings .form_note,
+.entity_remote_subscribe {
+background-color:#8F0000;
+}
+
+input:focus, textarea:focus, select:focus,
+#form_notice.warning #notice_data-text {
+border-color:#8F0000;
+box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+-moz-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+-webkit-box-shadow:3px 3px 3px rgba(194, 194, 194, 0.3);
+}
+input.submit,
+.entity_remote_subscribe {
+color:#FFFFFF;
+}
+
+a,
+div.notice-options input,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.entity_nudge p,
+.form_settings input.form_action-primary,
+.form_make_admin input.submit {
+color:#000000;
+}
+
+.notice,
+.profile {
+border-color:#000000;
+}
+.notice a,
+.profile a {
+color:#FFFFFF;
+}
+
+.notice:nth-child(3n-1),
+.profile:nth-child(3n-1) {
+border-color:#FFFFFF;
+}
+.notice:nth-child(3n-1) a,
+.profile:nth-child(3n-1) a {
+color:#7F1114;
+}
+.notice:nth-child(3n),
+.profile:nth-child(3n) {
+border-color:#7F1114;
+}
+.notice:nth-child(3n) a,
+.profile:nth-child(3n) a {
+color:#000000;
+}
+
+.aside .section .notice,
+.aside .section .profile,
+.aside .section .notice:nth-child(3n-1),
+.aside .section .profile:nth-child(3n-1),
+.aside .section .notice:nth-child(3n),
+.aside .section .profile:nth-child(3n) {
+background-color:transparent;
+color:#000000;
+}
+
+
+.aside .section {
+border-color:#FFFFFF;
+background-color:#FFFFFF;
+color:#000000;
+}
+
+.aside .section:nth-child(n) {
+border-color:#000000;
+background-color:#000000;
+color:#FFFFFF;
+}
+.aside .section:nth-child(3n-1) {
+border-color:#FFFFFF;
+background-color:#FFFFFF;
+color:#000000;
+}
+.aside .section:nth-child(3n) {
+background-color:#7F1114;
+border-color:#7F1114;
+color:#000000;
+}
+.aside .section a {
+color:#7F1114;
+}
+.aside .section:nth-child(3n-1) a {
+color:#7F1114;
+}
+.aside .section:nth-child(3n) a {
+color:#FFFFFF;
+}
+
+
+.section .profile {
+border-top-color:#87B4C8;
+}
+
+#aside_primary {
+background:url(../images/illustrations/illu_pigeons-02.png) no-repeat 10% 100%;
+}
+
+#notice_text-count {
+color:#333333;
+}
+#form_notice.warning #notice_text-count {
+color:#000000;
+}
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
+}
+
+#form_notice.processing #notice_action-submit {
+background:#FFFFFF url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+cursor:wait;
+text-indent:-9999px;
+}
+
+#content,
+#site_nav_local_views a {
+border-color:#FFFFFF;
+}
+#site_nav_local_views .current a {
+background-color:rgba(143, 0, 0, 0.8);
+color:#FFFFFF;
+}
+
+#site_nav_local_views a {
+background-color:rgba(255, 255, 255, 0.5);
+}
+#site_nav_local_views a:hover {
+background-color:rgba(255, 255, 255, 0.9);
+color:#8F0000;
+}
+#site_nav_local_views .current a {
+text-shadow: rgba(194,194,194,0.5) 1px 1px 1px;
+}
+
+.error {
+background-color:#F7E8E8;
+}
+.success {
+background-color:#EFF3DC;
+}
+
+#anon_notice {
+color:#000000;
+}
+
+
+#export_data li a {
+background-repeat:no-repeat;
+background-position:0 45%;
+}
+#export_data li a.rss {
+background-image:url(../../base/images/icons/icon_rss.png);
+}
+#export_data li a.atom {
+background-image:url(../../base/images/icons/icon_atom.png);
+}
+#export_data li a.foaf {
+background-image:url(../../base/images/icons/icon_foaf.gif);
+}
+
+.entity_edit a,
+.entity_send-a-message a,
+.form_user_nudge input.submit,
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit,
+.entity_nudge p,
+.form_make_admin input.submit {
+background-position: 0 40%;
+background-repeat: no-repeat;
+background-color:transparent;
+}
+.form_group_join input.submit,
+.form_group_leave input.submit
+.form_user_subscribe input.submit,
+.form_user_unsubscribe input.submit {
+background-color:#8F0000;
+color:#FFFFFF;
+}
+.form_user_unsubscribe input.submit,
+.form_group_leave input.submit,
+.form_user_authorization input.reject {
+background-color:#87B4C8;
+}
+
+.entity_edit a {
+background-image:url(../../base/images/icons/twotone/green/edit.gif);
+}
+.entity_send-a-message a {
+background-image:url(../../base/images/icons/twotone/green/quote.gif);
+}
+.entity_nudge p,
+.form_user_nudge input.submit {
+background-image:url(../../base/images/icons/twotone/green/mail.gif);
+}
+.form_user_block input.submit,
+.form_user_unblock input.submit,
+.form_group_block input.submit,
+.form_group_unblock input.submit {
+background-image:url(../../base/images/icons/twotone/green/shield.gif);
+}
+.form_make_admin input.submit {
+background-image:url(../../base/images/icons/twotone/green/admin.gif);
+}
+
+/* NOTICES */
+.notice .attachment {
+background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+}
+#attachments .attachment {
+background:none;
+}
+.notice-options .notice_reply a,
+.notice-options form input.submit {
+background-color:transparent;
+}
+.notice-options .notice_reply a {
+background:transparent url(../../base/images/icons/twotone/green/reply.gif) no-repeat 0 45%;
+}
+.notice-options form.form_favor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/favourite.gif) no-repeat 0 45%;
+}
+.notice-options form.form_disfavor input.submit {
+background:transparent url(../../base/images/icons/twotone/green/disfavourite.gif) no-repeat 0 45%;
+}
+.notice-options .notice_delete a {
+background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-repeat 0 45%;
+}
+
+.notices div.entry-content,
+.notices div.notice-options {
+opacity:0.4;
+}
+.notices li:hover div.entry-content,
+.notices li:hover div.notice-options {
+opacity:1;
+}
+div.entry-content {
+color:#333333;
+}
+div.notice-options a,
+div.notice-options input {
+font-family:sans-serif;
+}
+#content .notices li:hover {
+background-color:transparent;
+}
+#conversation .notices li:hover {
+background-color:transparent;
+}
+
+.notices .notices {
+background-color:rgba(200, 200, 200, 0.050);
+}
+.notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.100);
+}
+.notices .notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.150);
+}
+.notices .notices .notices .notices .notices {
+background-color:rgba(200, 200, 200, 0.300);
+}
+/*END: NOTICES */
+
+#new_group a {
+background:transparent url(../../base/images/icons/twotone/green/news.gif) no-repeat 0 45%;
+}
+
+.pagination .nav_prev a,
+.pagination .nav_next a {
+background-repeat:no-repeat;
+border-color:#000000;
+}
+.pagination .nav_prev a {
+background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
+background-position:10% 45%;
+}
+.pagination .nav_next a {
+background-image:url(../../base/images/icons/twotone/green/arrow-right.gif);
+background-position:90% 45%;
+}
diff --git a/theme/pigeonthoughts/css/ie.css b/theme/pigeonthoughts/css/ie.css
new file mode 100644 (file)
index 0000000..2f463bb
--- /dev/null
@@ -0,0 +1,9 @@
+/* IE specific styles */
+
+.notice-options input.submit {
+color:#fff;
+}
+
+#site_nav_local_views a {
+background-color:#D0DFE7;
+}
diff --git a/theme/pigeonthoughts/default-avatar-mini.png b/theme/pigeonthoughts/default-avatar-mini.png
new file mode 100644 (file)
index 0000000..38b8692
Binary files /dev/null and b/theme/pigeonthoughts/default-avatar-mini.png differ
diff --git a/theme/pigeonthoughts/default-avatar-profile.png b/theme/pigeonthoughts/default-avatar-profile.png
new file mode 100644 (file)
index 0000000..f8357d4
Binary files /dev/null and b/theme/pigeonthoughts/default-avatar-profile.png differ
diff --git a/theme/pigeonthoughts/default-avatar-stream.png b/theme/pigeonthoughts/default-avatar-stream.png
new file mode 100644 (file)
index 0000000..6b63baa
Binary files /dev/null and b/theme/pigeonthoughts/default-avatar-stream.png differ
diff --git a/theme/pigeonthoughts/images/illustrations/illu_pigeons-01.png b/theme/pigeonthoughts/images/illustrations/illu_pigeons-01.png
new file mode 100644 (file)
index 0000000..4fdaaeb
Binary files /dev/null and b/theme/pigeonthoughts/images/illustrations/illu_pigeons-01.png differ
diff --git a/theme/pigeonthoughts/images/illustrations/illu_pigeons-02.png b/theme/pigeonthoughts/images/illustrations/illu_pigeons-02.png
new file mode 100644 (file)
index 0000000..187c6c8
Binary files /dev/null and b/theme/pigeonthoughts/images/illustrations/illu_pigeons-02.png differ
diff --git a/theme/pigeonthoughts/logo.png b/theme/pigeonthoughts/logo.png
new file mode 100644 (file)
index 0000000..fdead6c
Binary files /dev/null and b/theme/pigeonthoughts/logo.png differ
index 4998b3c988b48b553c1fe4de0df8425f5a5ca5ba..83b5a61d0d6d9627518ad735516d30a863bd7cfa 100644 (file)
@@ -23,14 +23,16 @@ Only alter this file if you want to change the layout of the site. Please note t
 ./default/css/display.css contains only the background images and colour rules:
 This file is a good basis for creating your own theme.
 
+Let's create a theme:
 
-1. Copy over the default theme to start off (replace 'mytheme'):
-cp -r ./default ./mytheme
+1. To start off, copy over the default theme:
+cp -r default mytheme
 
 2. Edit your mytheme stylesheet:
-nano ./mytheme/css/display.css
+nano mytheme/css/display.css
 
-3. Search and replace a colour or a path to the background image of your choice.
+a) Search and replace your colours and background images, or
+b) Create your own layout either importing a separate stylesheet (e.g., change to @import url(base.css);) or simply place it before the rest of the rules.
 
 4. Set /config.php to load 'mytheme':
 $config['site']['theme'] = 'mytheme';