Merge branch '0.7.x' into 0.8.x

This commit is contained in:
Sarven Capadisli 2009-04-08 22:55:13 +00:00
commit e5657ca4bb
35 changed files with 1296 additions and 649 deletions

124
README
View File

@ -2,8 +2,8 @@
README
------
Laconica 0.7.2.1 ("Talk about the Passion")
11 March 2009
Laconica 0.7.3 ("You Are The Everything")
7 April 2009
This is the README file for Laconica, the Open Source microblogging
platform. It includes installation instructions, descriptions of
@ -71,93 +71,29 @@ for additional terms.
New this version
================
This is a minor bug-fix and feature release since version 0.7.1,
released Feb 9 2009. Notable changes this version:
This is a minor bug-fix and feature release since version 0.7.2.1,
released Mar 11 2009. Notable changes this version:
- First version of a web-based installer
- Use Net_URL_Mapper instead of mod_rewrite to map "fancy URLs",
for a much simpler installation and use of PATH_INFO on sites
that don't have mod_rewrite.
- A plugin framework for system events, to make it easier to build
server-side plugins.
- A plugin for Google Analytics
- A plugin to use blogspam.net to check notices for spam
- A plugin to send linkbacks for notices about blog posts
- Configurable check for duplicate notices in a specific time
period
- Better Atom feeds
- First implementation of Twitter Search API
- Add streamlined mobile device-friendly styles when enabled in config.
- A queue server for sending notices to Twitter
- A queue server for sending notices to Facebook
- A queue server for sending notices to a ping server
- Fixed a bug in nonces for OAuth in OpenMicroBlogging
- Fixed bugs in transfer of avatars in OpenMicroBlogging
- @-links go to permalinks for local users
- Better handling of DB errors (instead of dreaded DB_DataObject blank
screen)
- Initial version of an RPM spec file
- More consistent display of notices in notice search
- A stylesheet for printed output
- "Social graph" methods for Twitter API
- Documentation for the JavaScript badge
- Debugged a ton of problems that happened with E_NOTICE on
- Better caching in RSS feeds
- Optionally send email when an @-message is received
- Automatically add tags for every group message
- Add framebusting JavaScript to help avoid clickjacking attacks.
- Optionally ignore some notice sources for public page.
- Add default SMS carriers and notice sources to distribution file.
- Change titles to use mixed case instead of all uppercase.
- Use exceptions for error handling.
Changes in version 0.7.1:
- Vast improvement in auto-linking to URLs.
- Link to group search from user's group page
- Improved interface in Facebook application
- Fix bad redirects in delete notice
- Updated PostgreSQL database creation script
- Show filesize in avatar/logo upload
- Vastly improved avatar/logo upload
- Allow re-authentication with OpenID
- Correctly link hashtabs inside parens and brackets
- Group and avatar image transparency works
- Better handling of commands through the Web and Ajax channels
- Fix links for profile page feeds
- Fixed destroy method in API
- Fix endpoint of Connect menu when XMPP disabled
- Show number of group members
- Enable configuration files in /etc/laconica/
Changes in version 0.7.0:
- Support for groups. Users can join groups and send themed notices
to those groups. All other members of the group receive the notices.
- Laconica-specific extensions to the Twitter API.
- A Facebook application.
- A massive UI redesign. The HTML generated by Laconica has changed
significantly, to make theming easier and to give a more open look
by default. Also, sidebar.
- Massive code hygiene changes to move towards compliance with the PEAR
coding standards and to support the new UI redesign.
- Began the breakup of util.php -- moved about 30% of code to a views
hierarchy.
- UI elements for statistical information (like top posters or most
popular groups) added in a sidebar.
- include Javascript badge by Kent Brewster.
- Updated online documentation.
- Cropping of user avatars using Jcrop.
- fix for Twitter bridge to not send "Expect:" headers.
- add 'dm' as a synonym for 'd' in commands.
- Upgrade upstream version of jQuery to 1.3.
- Upgrade upstream version of PHP-OpenID to 2.1.2.
- Move OpenMicroBlogging specification to its own repository.
- Make tag-based RSS streams work.
- Additional locales: Bulgarian, Catalan, Greek, Hebrew, simplified
Chinese, Telugu, Taiwanese Chinese, Vietnamese,
- PostgreSQL updates.
- Nasty bug in Twitter bridge that wouldn't verify with Twitter
- A plugin to allow a templating language for customization
- A plugin for Piwik Analytics engine
- A bookmarklet for posting a notice about a Web page you're reading
- A welcome notice ('welcomebot') and default subscription for new users
- Support for SSL for some or all pages on the site
- Better handling of empty notice lists on many pages
- Major improvements to the Twitter friend-sync offline processing
- subscribers, subscriptions, groups are listed on the Personal page.
- "Invite" link restored to main menu
- Better memory handling in FOAF output
- Fix for SUP support (FriendFeed)
- Correct and intelligent redirect HTTP status codes
- Fix DB collations for search and sort
- Better H1s and Titles using user full names
- Fixes to make the linkback plugin operational
- Better indication that a notice is being published by Ajax (spinner)
- Better and unified Atom output
- Hiding "register" and "join now" messages when site is closed
- ping, twitter and facebook queuehandlers working better
- Updated RPM spec
Prerequisites
=============
@ -261,9 +197,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.2.1.tar.gz
tar zxf laconica-0.7.3.tar.gz
...which will make a laconica-0.7.2.1 subdirectory in your current
...which will make a laconica-0.7.3 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.)
@ -271,7 +207,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.2.1 /var/www/mublog
mv laconica-0.7.3 /var/www/mublog
This will make your Laconica instance available in the mublog path of
your server, like "http://example.net/mublog". "microblog" or
@ -761,7 +697,7 @@ Upgrading
If you've been using Laconica 0.6, 0.5 or lower, or if you've been
tracking the "git" version of the software, you will probably want
to upgrade and keep your existing data. There is no automated upgrade
procedure in Laconica 0.7.2.1. Try these step-by-step instructions; read
procedure in Laconica 0.7.3. 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
@ -1209,7 +1145,7 @@ repository (see below), and you get a compilation error ("unexpected
T_STRING") in the browser, check to see that you don't have any
conflicts in your code.
If you upgraded to Laconica 0.7.2.1 without reading the "Notice inboxes"
If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes"
section above, and all your users' 'Personal' tabs are empty, read the
"Notice inboxes" section above.
@ -1298,6 +1234,8 @@ if anyone's been overlooked in error.
* Leslie Michael Orchard
* Eric Helgeson
* Ken Sedgwick
* Brian Hendrickson
* Tobias Diekershoff
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,

View File

@ -23,31 +23,13 @@ require_once INSTALLDIR.'/lib/personalgroupnav.php';
require_once INSTALLDIR.'/lib/noticelist.php';
require_once INSTALLDIR.'/lib/feedlist.php';
class AllAction extends Action
class AllAction extends ProfileAction
{
var $user = null;
var $page = null;
function isReadOnly()
{
return true;
}
function prepare($args)
{
parent::prepare($args);
$nickname = common_canonical_nickname($this->arg('nickname'));
$this->user = User::staticGet('nickname', $nickname);
$this->page = $this->trimmed('page');
if (!$this->page) {
$this->page = 1;
}
common_set_returnto($this->selfUrl());
return true;
}
function handle($args)
{
parent::handle($args);
@ -93,6 +75,27 @@ class AllAction extends Action
$nav->show();
}
function showEmptyListMessage()
{
$message = sprintf(_('This is the timeline for %s and friends but no one has posted anything yet.'), $this->user->nickname) . ' ';
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message .= _('Try subscribing to more people, [join a group](%%action.groups) or post something yourself.');
} else {
$message .= sprintf(_('You can try to [nudge %s](../%s) from his profile or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
function showContent()
{
$notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
@ -101,6 +104,10 @@ class AllAction extends Action
$cnt = $nl->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'all', array('nickname' => $this->user->nickname));
}

View File

@ -104,9 +104,9 @@ class FavoritedAction extends Action
{
parent::prepare($args);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
common_set_returnto($this->selfUrl());
return true;
}
@ -143,14 +143,11 @@ class FavoritedAction extends Action
$this->elementStart('div', 'instructions');
$this->raw($output);
$this->elementEnd('div');
}
$favorite = new Fave;
if ($favorite->count()) {
return;
}
$message = _('Favorite notices appear on this page but noone has favorited one yet.') . ' ';
function showEmptyList()
{
$message = _('Favorite notices appear on this page but no one has favorited one yet.') . ' ';
if (common_logged_in()) {
$message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.');
@ -159,7 +156,7 @@ class FavoritedAction extends Action
$message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!');
}
$this->elementStart('div', 'blankfiller');
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
@ -217,6 +214,10 @@ class FavoritedAction extends Action
$cnt = $nl->show();
if ($cnt == 0) {
$this->showEmptyList();
}
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'favorited');
}

View File

@ -72,12 +72,23 @@ class GroupsearchAction extends SearchAction
$terms = preg_split('/[\s,]+/', $q);
$results = new GroupSearchResults($user_group, $terms, $this);
$results->show();
} else {
$this->element('p', 'error', _('No results'));
}
$user_group->free();
$this->pagination($page > 1, $cnt > GROUPS_PER_PAGE,
$user_group->free();
$this->pagination($page > 1, $cnt > GROUPS_PER_PAGE,
$page, 'groupsearch', array('q' => $q));
} else {
$this->element('p', 'error', _('No results.'));
$this->searchSuggestions($q);
if (common_logged_in()) {
$message = _('If you can\'t find the group you\'re looking for, you can [create it](%%action.newgroup%%) yourself.');
}
else {
$message = _('Why not [register an account](%%action.register%%) and [create the group](%%action.newgroup%%) yourself!');
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
$user_group->free();
}
}
}
@ -98,10 +109,5 @@ class GroupSearchResults extends GroupList
{
return preg_replace($this->pattern, '<strong>\\1</strong>', htmlspecialchars($text));
}
function isReadOnly()
{
return true;
}
}

View File

@ -114,22 +114,27 @@ class NoticesearchAction extends SearchAction
$cnt = $notice->find();
}
if ($cnt === 0) {
$this->element('p', 'error', _('No results'));
$this->element('p', 'error', _('No results.'));
$this->searchSuggestions($q);
if (common_logged_in()) {
$message = sprintf(_('Be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
}
else {
$message = sprintf(_('Why not [register an account](%%%%action.register%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
return;
}
$terms = preg_split('/[\s,]+/', $q);
$nl = new SearchNoticeList($notice, $this, $terms);
$cnt = $nl->show();
$this->pagination($page > 1, $cnt > NOTICES_PER_PAGE,
$page, 'noticesearch', array('q' => $q));
}
function isReadOnly()
{
return true;
}
}
class SearchNoticeList extends NoticeList {

View File

@ -60,14 +60,8 @@ class PeoplesearchAction extends SearchAction
function showResults($q, $page)
{
$profile = new Profile();
// lcase it for comparison
// $q = strtolower($q);
$search_engine = $profile->getSearchEngine('identica_people');
$search_engine->set_sort_mode('chron');
// Ask for an extra to see if there's more.
$search_engine->limit((($page-1)*PROFILES_PER_PAGE), PROFILES_PER_PAGE + 1);
@ -81,14 +75,15 @@ class PeoplesearchAction extends SearchAction
$terms = preg_split('/[\s,]+/', $q);
$results = new PeopleSearchResults($profile, $terms, $this);
$results->show();
} else {
$this->element('p', 'error', _('No results'));
}
$profile->free();
$this->pagination($page > 1, $cnt > PROFILES_PER_PAGE,
$profile->free();
$this->pagination($page > 1, $cnt > PROFILES_PER_PAGE,
$page, 'peoplesearch', array('q' => $q));
} else {
$this->element('p', 'error', _('No results.'));
$this->searchSuggestions($q);
$profile->free();
}
}
}

View File

@ -166,20 +166,9 @@ class PublicAction extends Action
$nav->show();
}
function showPageNotice()
function showEmptyList()
{
$notice = new Notice;
if (!$notice) {
$this->serverError(_('Could not retrieve public stream.'));
return;
}
if ($notice->count()) {
return;
}
$message = _('This is the public timeline for %%site.name%% but noone has posted anything yet.') . ' ';
$message = _('This is the public timeline for %%site.name%% but no one has posted anything yet.') . ' ';
if (common_logged_in()) {
$message .= _('Be the first to post!');
@ -188,7 +177,7 @@ class PublicAction extends Action
$message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
}
$this->elementStart('div', 'blankfiller');
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
@ -216,6 +205,10 @@ class PublicAction extends Action
$cnt = $nl->show();
if ($cnt == 0) {
$this->showEmptyList();
}
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'public');
}

View File

@ -62,13 +62,11 @@ class PublictagcloudAction extends Action
$this->element('p', 'instructions',
sprintf(_('These are most popular recent tags on %s '),
common_config('site', 'name')));
}
$tags = new Notice_tag;
if ($tags->count()) {
return;
}
$message = _('Noone has posted a notice with a [hashtag](%%doc.tags%%) yet.') . ' ';
function showEmptyList()
{
$message = _('No one has posted a notice with a [hashtag](%%doc.tags%%) yet.') . ' ';
if (common_logged_in()) {
$message .= _('Be the first to post one!');
@ -77,7 +75,7 @@ class PublictagcloudAction extends Action
$message .= _('Why not [register an account](%%action.register%%) and be the first to post one!');
}
$this->elementStart('div', 'blankfiller');
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
@ -144,6 +142,8 @@ class PublictagcloudAction extends Action
$this->elementEnd('dd');
$this->elementEnd('dl');
$this->elementEnd('div');
} else {
$this->showEmptyList();
}
}

View File

@ -166,12 +166,36 @@ class RepliesAction extends Action
$nl = new NoticeList($notice, $this);
$cnt = $nl->show();
if (0 === $cnt) {
$this->showEmptyListMessage();
}
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'replies',
array('nickname' => $this->user->nickname));
}
function showEmptyListMessage()
{
$message = sprintf(_('This is the timeline showing replies to %s but %s hasn\'t received a notice to his attention yet.'), $this->user->nickname, $this->user->nickname) . ' ';
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message .= _('You can engage other users in a conversation, subscribe to more people or [join groups](%%action.groups%%).');
} else {
$message .= sprintf(_('You can try to [nudge %s](../%s) or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%s).'), $this->user->nickname, $this->user->nickname, '@' . $this->user->nickname);
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
function isReadOnly()
{
return true;

View File

@ -162,6 +162,25 @@ class ShowfavoritesAction extends Action
$nav->show();
}
function showEmptyListMessage()
{
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message = _('You haven\'t chosen any favorite notices yet. Click the fave button on notices you like to bookmark them for later or shed a spotlight on them.');
} else {
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Post something interesting they would add to their favorites :)'), $this->user->nickname);
}
}
else {
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to thier favorites :)'), $this->user->nickname);
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
/**
* Show the content
*
@ -183,9 +202,17 @@ class ShowfavoritesAction extends Action
$nl = new NoticeList($notice, $this);
$cnt = $nl->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
$this->page, 'showfavorites',
array('nickname' => $this->user->nickname));
}
function showPageNotice() {
$this->element('p', 'instructions', _('This is a way to share what you like.'));
}
}

View File

@ -54,12 +54,8 @@ require_once INSTALLDIR.'/lib/feedlist.php';
* @link http://laconi.ca/
*/
class ShowstreamAction extends Action
class ShowstreamAction extends ProfileAction
{
var $user = null;
var $page = null;
var $profile = null;
function isReadOnly()
{
return true;
@ -82,45 +78,6 @@ class ShowstreamAction extends Action
}
}
function prepare($args)
{
parent::prepare($args);
$nickname_arg = $this->arg('nickname');
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) {
$args = array('nickname' => $nickname);
if ($this->arg('page') && $this->arg('page') != 1) {
$args['page'] = $this->arg['page'];
}
common_redirect(common_local_url('showstream', $args), 301);
return false;
}
$this->user = User::staticGet('nickname', $nickname);
if (!$this->user) {
$this->clientError(_('No such user.'), 404);
return false;
}
$this->profile = $this->user->getProfile();
if (!$this->profile) {
$this->serverError(_('User has no profile.'));
return false;
}
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
common_set_returnto($this->selfUrl());
return true;
}
function handle($args)
{
@ -372,167 +329,41 @@ class ShowstreamAction extends Action
_('Subscribe'));
}
function showEmptyListMessage()
{
$message = sprintf(_('This is the timeline for %s but %s hasn\'t posted anything yet.'), $this->user->nickname, $this->user->nickname) . ' ';
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message .= _('Seen anything interesting recently? You haven\'t posted any notices yet, now would be a good time to start :)');
} else {
$message .= sprintf(_('You can try to nudge %s or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%s).'), $this->user->nickname, '@' . $this->user->nickname);
}
}
else {
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
function showNotices()
{
$notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
$pnl = new ProfileNoticeList($notice, $this);
$cnt = $pnl->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
'showstream', array('nickname' => $this->user->nickname));
}
function showSections()
{
$this->showSubscriptions();
$this->showSubscribers();
$this->showGroups();
$this->showStatistics();
$cloud = new PersonalTagCloudSection($this, $this->user);
$cloud->show();
}
function showSubscriptions()
{
$profile = $this->user->getSubscriptions(0, PROFILES_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_subscriptions',
'class' => 'section'));
$this->element('h2', null, _('Subscriptions'));
if ($profile) {
$pml = new ProfileMiniList($profile, $this->user, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
}
}
if ($cnt > PROFILES_PER_MINILIST) {
$this->elementStart('p');
$this->element('a', array('href' => common_local_url('subscriptions',
array('nickname' => $this->profile->nickname)),
'class' => 'more'),
_('All subscriptions'));
$this->elementEnd('p');
}
$this->elementEnd('div');
}
function showSubscribers()
{
$profile = $this->user->getSubscribers(0, PROFILES_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_subscribers',
'class' => 'section'));
$this->element('h2', null, _('Subscribers'));
if ($profile) {
$pml = new ProfileMiniList($profile, $this->user, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
}
}
if ($cnt > PROFILES_PER_MINILIST) {
$this->elementStart('p');
$this->element('a', array('href' => common_local_url('subscribers',
array('nickname' => $this->profile->nickname)),
'class' => 'more'),
_('All subscribers'));
$this->elementEnd('p');
}
$this->elementEnd('div');
}
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();
$this->elementStart('div', array('id' => 'entity_statistics',
'class' => 'section'));
$this->element('h2', null, _('Statistics'));
// Other stats...?
$this->elementStart('dl', 'entity_member-since');
$this->element('dt', null, _('Member since'));
$this->element('dd', null, date('j M Y',
strtotime($this->profile->created)));
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_subscriptions');
$this->elementStart('dt');
$this->element('a', array('href' => common_local_url('subscriptions',
array('nickname' => $this->profile->nickname))),
_('Subscriptions'));
$this->elementEnd('dt');
$this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_subscribers');
$this->elementStart('dt');
$this->element('a', array('href' => common_local_url('subscribers',
array('nickname' => $this->profile->nickname))),
_('Subscribers'));
$this->elementEnd('dt');
$this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
$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->elementEnd('dl');
$this->elementEnd('div');
}
function showGroups()
{
$groups = $this->user->getGroups(0, GROUPS_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_groups',
'class' => 'section'));
$this->element('h2', null, _('Groups'));
if ($groups) {
$gml = new GroupMiniList($groups, $this->user, $this);
$cnt = $gml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
}
}
if ($cnt > GROUPS_PER_MINILIST) {
$this->elementStart('p');
$this->element('a', array('href' => common_local_url('usergroups',
array('nickname' => $this->profile->nickname)),
'class' => 'more'),
_('All groups'));
$this->elementEnd('p');
}
$this->elementEnd('div');
}
function showAnonymousMessage()
{
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
@ -550,6 +381,12 @@ class ShowstreamAction extends Action
$this->elementEnd('div');
}
function showSections()
{
parent::showSections();
$cloud = new PersonalTagCloudSection($this, $this->user);
$cloud->show();
}
}
// We don't show the author for a profile, since we already know who it is!

View File

@ -88,6 +88,9 @@ class SubscribersAction extends GalleryAction
if ($subscribers) {
$subscribers_list = new SubscribersList($subscribers, $this->user, $this);
$cnt = $subscribers_list->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
}
$subscribers->free();
@ -96,6 +99,25 @@ class SubscribersAction extends GalleryAction
$this->page, 'subscribers',
array('nickname' => $this->user->nickname));
}
function showEmptyListMessage()
{
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message = _('You have no subscribers. Try subscribing to people you know and they might return the favor');
} else {
$message = sprintf(_('%s has no subscribers. Want to be the first?'), $this->user->nickname);
}
}
else {
$message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.register%%%%) and be the first?'), $this->user->nickname);
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
}
class SubscribersList extends ProfileList

View File

@ -95,6 +95,9 @@ class SubscriptionsAction extends GalleryAction
if ($subscriptions) {
$subscriptions_list = new SubscriptionsList($subscriptions, $this->user, $this);
$cnt = $subscriptions_list->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
}
$subscriptions->free();
@ -103,6 +106,25 @@ class SubscriptionsAction extends GalleryAction
$this->page, 'subscriptions',
array('nickname' => $this->user->nickname));
}
function showEmptyListMessage()
{
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message = _('You\'re not listening to anyone\'s notices right now, try subscribing to people you know. Try [people search](%%action.peoplesearch%%), look for members in groups you\'re interested in and in our [featured users](%%action.featured%%). If you\'re a [Twitter user](%%action.twittersettings%%), you can automatically subscribe to people you already follow there.');
} else {
$message = sprintf(_('%s is not listening to anyone.'), $this->user->nickname);
}
}
else {
$message = sprintf(_('%s is not listening to anyone.'), $this->user->nickname);
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
}
class SubscriptionsList extends ProfileList

View File

@ -25,7 +25,7 @@ define('TIMESTAMP_THRESHOLD', 300);
class UserauthorizationAction extends Action
{
var $error;
var $req;
var $params;
function handle($args)
{
@ -35,8 +35,8 @@ class UserauthorizationAction extends Action
# CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$req = $this->getStoredRequest();
$this->showForm($req, _('There was a problem with your session token. '.
$params = $this->getStoredParams();
$this->showForm($params, _('There was a problem with your session token. '.
'Try again, please.'));
return;
}
@ -50,18 +50,13 @@ class UserauthorizationAction extends Action
common_redirect(common_local_url('login'));
return;
}
try {
# this must be a new request
$req = $this->getNewRequest();
if (!$req) {
$this->clientError(_('No request found!'));
}
# XXX: only validate new requests, since nonce is one-time use
$this->validateRequest($req);
$this->storeRequest($req);
$this->showForm($req);
$this->validateRequest();
$this->storeParams($_GET);
$this->showForm($_GET);
} catch (OAuthException $e) {
$this->clearRequest();
$this->clearParams();
$this->clientError($e->getMessage());
return;
}
@ -69,9 +64,9 @@ class UserauthorizationAction extends Action
}
}
function showForm($req, $error=null)
function showForm($params, $error=null)
{
$this->req = $req;
$this->params = $params;
$this->error = $error;
$this->showPage();
}
@ -91,16 +86,16 @@ class UserauthorizationAction extends Action
function showContent()
{
$req = $this->req;
$params = $this->params;
$nickname = $req->get_parameter('omb_listenee_nickname');
$profile = $req->get_parameter('omb_listenee_profile');
$license = $req->get_parameter('omb_listenee_license');
$fullname = $req->get_parameter('omb_listenee_fullname');
$homepage = $req->get_parameter('omb_listenee_homepage');
$bio = $req->get_parameter('omb_listenee_bio');
$location = $req->get_parameter('omb_listenee_location');
$avatar = $req->get_parameter('omb_listenee_avatar');
$nickname = $params['omb_listenee_nickname'];
$profile = $params['omb_listenee_profile'];
$license = $params['omb_listenee_license'];
$fullname = $params['omb_listenee_fullname'];
$homepage = $params['omb_listenee_homepage'];
$bio = $params['omb_listenee_bio'];
$location = $params['omb_listenee_location'];
$avatar = $params['omb_listenee_avatar'];
$this->elementStart('div', 'profile');
if ($avatar) {
@ -147,56 +142,56 @@ class UserauthorizationAction extends Action
function sendAuthorization()
{
$req = $this->getStoredRequest();
$params = $this->getStoredParams();
if (!$req) {
if (!$params) {
$this->clientError(_('No authorization request!'));
return;
}
$callback = $req->get_parameter('oauth_callback');
$callback = $params['oauth_callback'];
if ($this->arg('accept')) {
if (!$this->authorizeToken($req)) {
if (!$this->authorizeToken($params)) {
$this->clientError(_('Error authorizing token'));
}
if (!$this->saveRemoteProfile($req)) {
if (!$this->saveRemoteProfile($params)) {
$this->clientError(_('Error saving remote profile'));
}
if (!$callback) {
$this->showAcceptMessage($req->get_parameter('oauth_token'));
$this->showAcceptMessage($params['oauth_token']);
} else {
$params = array();
$params['oauth_token'] = $req->get_parameter('oauth_token');
$params['omb_version'] = OMB_VERSION_01;
$user = User::staticGet('uri', $req->get_parameter('omb_listener'));
$newparams = array();
$newparams['oauth_token'] = $params['oauth_token'];
$newparams['omb_version'] = OMB_VERSION_01;
$user = User::staticGet('uri', $params['omb_listener']);
$profile = $user->getProfile();
if (!$profile) {
common_log_db_error($user, 'SELECT', __FILE__);
$this->serverError(_('User without matching profile'));
return;
}
$params['omb_listener_nickname'] = $user->nickname;
$params['omb_listener_profile'] = common_local_url('showstream',
$newparams['omb_listener_nickname'] = $user->nickname;
$newparams['omb_listener_profile'] = common_local_url('showstream',
array('nickname' => $user->nickname));
if (!is_null($profile->fullname)) {
$params['omb_listener_fullname'] = $profile->fullname;
$newparams['omb_listener_fullname'] = $profile->fullname;
}
if (!is_null($profile->homepage)) {
$params['omb_listener_homepage'] = $profile->homepage;
$newparams['omb_listener_homepage'] = $profile->homepage;
}
if (!is_null($profile->bio)) {
$params['omb_listener_bio'] = $profile->bio;
$newparams['omb_listener_bio'] = $profile->bio;
}
if (!is_null($profile->location)) {
$params['omb_listener_location'] = $profile->location;
$newparams['omb_listener_location'] = $profile->location;
}
$avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE);
if ($avatar) {
$params['omb_listener_avatar'] = $avatar->url;
$newparams['omb_listener_avatar'] = $avatar->url;
}
$parts = array();
foreach ($params as $k => $v) {
foreach ($newparams as $k => $v) {
$parts[] = $k . '=' . OAuthUtil::urlencode_rfc3986($v);
}
$query_string = implode('&', $parts);
@ -214,12 +209,10 @@ class UserauthorizationAction extends Action
}
}
function authorizeToken(&$req)
function authorizeToken(&$params)
{
$consumer_key = $req->get_parameter('oauth_consumer_key');
$token_field = $req->get_parameter('oauth_token');
$token_field = $params['oauth_token'];
$rt = new Token();
$rt->consumer_key = $consumer_key;
$rt->tok = $token_field;
$rt->type = 0;
$rt->state = 0;
@ -235,21 +228,21 @@ class UserauthorizationAction extends Action
# XXX: refactor with similar code in finishremotesubscribe.php
function saveRemoteProfile(&$req)
function saveRemoteProfile(&$params)
{
# FIXME: we should really do this when the consumer comes
# back for an access token. If they never do, we've got stuff in a
# weird state.
$nickname = $req->get_parameter('omb_listenee_nickname');
$fullname = $req->get_parameter('omb_listenee_fullname');
$profile_url = $req->get_parameter('omb_listenee_profile');
$homepage = $req->get_parameter('omb_listenee_homepage');
$bio = $req->get_parameter('omb_listenee_bio');
$location = $req->get_parameter('omb_listenee_location');
$avatar_url = $req->get_parameter('omb_listenee_avatar');
$nickname = $params['omb_listenee_nickname'];
$fullname = $params['omb_listenee_fullname'];
$profile_url = $params['omb_listenee_profile'];
$homepage = $params['omb_listenee_homepage'];
$bio = $params['omb_listenee_bio'];
$location = $params['omb_listenee_location'];
$avatar_url = $params['omb_listenee_avatar'];
$listenee = $req->get_parameter('omb_listenee');
$listenee = $params['omb_listenee'];
$remote = Remote_profile::staticGet('uri', $listenee);
if ($remote) {
@ -309,14 +302,11 @@ class UserauthorizationAction extends Action
}
$user = common_current_user();
$datastore = omb_oauth_datastore();
$consumer = $this->getConsumer($datastore, $req);
$token = $this->getToken($datastore, $req, $consumer);
$sub = new Subscription();
$sub->subscriber = $user->id;
$sub->subscribed = $remote->id;
$sub->token = $token->key; # NOTE: request token, not valid for use!
$sub->token = $params['oauth_token']; # NOTE: request token, not valid for use!
$sub->created = DB_DataObject_Cast::dateTime(); # current time
if (!$sub->insert()) {
@ -360,65 +350,59 @@ class UserauthorizationAction extends Action
common_show_footer();
}
function storeRequest($req)
function storeParams($params)
{
common_ensure_session();
$_SESSION['userauthorizationrequest'] = $req;
$_SESSION['userauthorizationparams'] = $params;
}
function clearRequest()
function clearParams()
{
common_ensure_session();
unset($_SESSION['userauthorizationrequest']);
unset($_SESSION['userauthorizationparams']);
}
function getStoredRequest()
function getStoredParams()
{
common_ensure_session();
$req = $_SESSION['userauthorizationrequest'];
return $req;
}
function getNewRequest()
{
common_remove_magic_from_request();
$req = OAuthRequest::from_request();
return $req;
$params = $_SESSION['userauthorizationparams'];
return $params;
}
# Throws an OAuthException if anything goes wrong
function validateRequest(&$req)
function validateRequest()
{
# OAuth stuff -- have to copy from OAuth.php since they're
# all private methods, and there's no user-authentication method
$this->checkVersion($req);
$datastore = omb_oauth_datastore();
$consumer = $this->getConsumer($datastore, $req);
$token = $this->getToken($datastore, $req, $consumer);
$this->checkTimestamp($req);
$this->checkNonce($datastore, $req, $consumer, $token);
$this->checkSignature($req, $consumer, $token);
$this->validateOmb($req);
/* Find token.
TODO: If no token is passed the user should get a prompt to enter it
according to OAuth Core 1.0 */
$t = new Token();
$t->tok = $_GET['oauth_token'];
$t->type = 0;
if (!$t->find(true)) {
throw new OAuthException("Invalid request token: " . $_GET['oauth_token']);
}
$this->validateOmb();
return true;
}
function validateOmb(&$req)
function validateOmb()
{
foreach (array('omb_version', 'omb_listener', 'omb_listenee',
'omb_listenee_profile', 'omb_listenee_nickname',
'omb_listenee_license') as $param)
{
if (is_null($req->get_parameter($param))) {
if (!isset($_GET[$param]) || is_null($_GET[$param])) {
throw new OAuthException("Required parameter '$param' not found");
}
}
# Now, OMB stuff
$version = $req->get_parameter('omb_version');
$version = $_GET['omb_version'];
if ($version != OMB_VERSION_01) {
throw new OAuthException("OpenMicroBlogging version '$version' not supported");
}
$listener = $req->get_parameter('omb_listener');
$listener = $_GET['omb_listener'];
$user = User::staticGet('uri', $listener);
if (!$user) {
throw new OAuthException("Listener URI '$listener' not found here");
@ -427,7 +411,7 @@ class UserauthorizationAction extends Action
if ($cur->id != $user->id) {
throw new OAuthException("Can't add for another user!");
}
$listenee = $req->get_parameter('omb_listenee');
$listenee = $_GET['omb_listenee'];
if (!Validate::uri($listenee) &&
!common_valid_tag($listenee)) {
throw new OAuthException("Listenee URI '$listenee' not a recognizable URI");
@ -450,13 +434,13 @@ class UserauthorizationAction extends Action
throw new OAuthException("Already subscribed to user!");
}
}
$nickname = $req->get_parameter('omb_listenee_nickname');
$nickname = $_GET['omb_listenee_nickname'];
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
}
$profile = $req->get_parameter('omb_listenee_profile');
$profile = $_GET['omb_listenee_profile'];
if (!common_valid_http_url($profile)) {
throw new OAuthException("Invalid profile URL '$profile'.");
}
@ -465,7 +449,7 @@ class UserauthorizationAction extends Action
throw new OAuthException("Profile URL '$profile' is for a local user.");
}
$license = $req->get_parameter('omb_listenee_license');
$license = $_GET['omb_listenee_license'];
if (!common_valid_http_url($license)) {
throw new OAuthException("Invalid license URL '$license'.");
}
@ -474,23 +458,23 @@ class UserauthorizationAction extends Action
throw new OAuthException("Listenee stream license '$license' not compatible with site license '$site_license'.");
}
# optional stuff
$fullname = $req->get_parameter('omb_listenee_fullname');
$fullname = $_GET['omb_listenee_fullname'];
if ($fullname && mb_strlen($fullname) > 255) {
throw new OAuthException("Full name '$fullname' too long.");
}
$homepage = $req->get_parameter('omb_listenee_homepage');
$homepage = $_GET['omb_listenee_homepage'];
if ($homepage && (!common_valid_http_url($homepage) || mb_strlen($homepage) > 255)) {
throw new OAuthException("Invalid homepage '$homepage'");
}
$bio = $req->get_parameter('omb_listenee_bio');
$bio = $_GET['omb_listenee_bio'];
if ($bio && mb_strlen($bio) > 140) {
throw new OAuthException("Bio too long '$bio'");
}
$location = $req->get_parameter('omb_listenee_location');
$location = $_GET['omb_listenee_location'];
if ($location && mb_strlen($location) > 255) {
throw new OAuthException("Location too long '$location'");
}
$avatar = $req->get_parameter('omb_listenee_avatar');
$avatar = $_GET['omb_listenee_avatar'];
if ($avatar) {
if (!common_valid_http_url($avatar) || strlen($avatar) > 255) {
throw new OAuthException("Invalid avatar URL '$avatar'");
@ -507,7 +491,7 @@ class UserauthorizationAction extends Action
throw new OAuthException("Wrong image type for '$avatar'");
}
}
$callback = $req->get_parameter('oauth_callback');
$callback = $_GET['oauth_callback'];
if ($callback && !common_valid_http_url($callback)) {
throw new OAuthException("Invalid callback URL '$callback'");
}
@ -515,92 +499,4 @@ class UserauthorizationAction extends Action
throw new OAuthException("Callback URL '$callback' is for local site.");
}
}
# Snagged from OAuthServer
function checkVersion(&$req)
{
$version = $req->get_parameter("oauth_version");
if (!$version) {
$version = 1.0;
}
if ($version != 1.0) {
throw new OAuthException("OAuth version '$version' not supported");
}
return $version;
}
# Snagged from OAuthServer
function getConsumer($datastore, $req)
{
$consumer_key = @$req->get_parameter("oauth_consumer_key");
if (!$consumer_key) {
throw new OAuthException("Invalid consumer key");
}
$consumer = $datastore->lookup_consumer($consumer_key);
if (!$consumer) {
throw new OAuthException("Invalid consumer");
}
return $consumer;
}
# Mostly cadged from OAuthServer
function getToken($datastore, &$req, $consumer)
{/*{{{*/
$token_field = @$req->get_parameter('oauth_token');
$token = $datastore->lookup_token($consumer, 'request', $token_field);
if (!$token) {
throw new OAuthException("Invalid $token_type token: $token_field");
}
return $token;
}
function checkTimestamp(&$req)
{
$timestamp = @$req->get_parameter('oauth_timestamp');
$now = time();
if ($now - $timestamp > TIMESTAMP_THRESHOLD) {
throw new OAuthException("Expired timestamp, yours $timestamp, ours $now");
}
}
# NOTE: don't call twice on the same request; will fail!
function checkNonce(&$datastore, &$req, $consumer, $token)
{
$timestamp = @$req->get_parameter('oauth_timestamp');
$nonce = @$req->get_parameter('oauth_nonce');
$found = $datastore->lookup_nonce($consumer, $token, $nonce, $timestamp);
if ($found) {
throw new OAuthException("Nonce already used");
}
return true;
}
function checkSignature(&$req, $consumer, $token)
{
$signature_method = $this->getSignatureMethod($req);
$signature = $req->get_parameter('oauth_signature');
$valid_sig = $signature_method->check_signature($req,
$consumer,
$token,
$signature);
if (!$valid_sig) {
throw new OAuthException("Invalid signature");
}
}
function getSignatureMethod(&$req)
{
$signature_method = @$req->get_parameter("oauth_signature_method");
if (!$signature_method) {
$signature_method = "PLAINTEXT";
}
if ($signature_method != 'HMAC-SHA1') {
throw new OAuthException("Signature method '$signature_method' not supported.");
}
return omb_hmac_sha1();
}
}

View File

@ -139,10 +139,28 @@ class UsergroupsAction extends Action
if ($groups) {
$gl = new GroupList($groups, $this->user, $this);
$cnt = $gl->show();
if (0 == $cnt) {
$this->showEmptyListMessage();
}
}
$this->pagination($this->page > 1, $cnt > GROUPS_PER_PAGE,
$this->page, 'usergroups',
array('nickname' => $this->user->nickname));
}
function showEmptyListMessage()
{
$message = sprintf(_('%s is not a member of any group.'), $this->user->nickname) . ' ';
if (common_logged_in()) {
$current_user = common_current_user();
if ($this->user->id === $current_user->id) {
$message .= _('Try [searching for groups](%%action.groupsearch%%) and joining them.');
}
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
}

View File

@ -163,6 +163,10 @@ $config['sphinx']['port'] = 3312;
# 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

View File

@ -21,6 +21,7 @@ VALUES
('mbpidgin','mbpidgin','http://code.google.com/p/microblog-purple/', now()),
('Mobidentica', 'Mobidentica', 'http://www.substanceofcode.com/software/mobidentica/', now()),
('moconica','Moconica','http://moconica.com/', now()),
('peoplebrowsr', 'PeopleBrowsr', 'http://www.peoplebrowsr.com/', now()),
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),

7
doc-src/bookmarklet Normal file
View File

@ -0,0 +1,7 @@
A bookmarklet is a small piece of javascript code used as a bookmark. This one will let you post to %%site.name%% simply by selecting some text on a page and pressing the bookmarklet.
Drag-and-drop the following link to your bookmarks bar or right-click it and add it to your browser favorites to keep it handy.
<MTMarkdownOptions output='raw'>
<a href="javascript:var%20d=document,w=window,e=w.getSelection,k=d.getSelection,x=d.selection,s=(e?e():(k)?k():(x?x.createRange().text:0)),f='http://%%site.server%%/%%site.path%%/index.php?action=newnotice',l=d.location,e=encodeURIComponent,g=f+'&status_textarea=%22'+((e(s))?e(s):e(document.title))+'%22 from '+l.href;function%20a(){if(!w.open(g,'t','toolbar=0,resizable=0,scrollbars=1,status=1,width=800,height=570')){l.href=g;}}a();void(0);">Post to %%site.name%%</a>
</MTMarkdownOptions>

View File

@ -30,3 +30,5 @@ Here are some documents that you might find helpful in understanding
* [OpenMicroBlogging](%%doc.openmublog%%) - subscribing to remote users
* [Privacy](%%doc.privacy%%) - %%site.name%%'s privacy policy
* [Source](%%doc.source%%) - How to get the Laconica source code
* [Badge](%%doc.badge%%) - How to put a Laconica badge on your blog or homepage
* [Bookmarklet](%%doc.bookmarklet%%) - Bookmarklet for posting Web pages

View File

@ -97,9 +97,18 @@ class Action extends HTMLOutputter // lawsuit
$this->startHTML();
Event::handle('EndShowHTML', array($this));
}
if (Event::handle('StartShowHead', array($this))) {
$this->showHead();
Event::handle('EndShowHead', array($this));
}
if (Event::handle('StartShowBody', array($this))) {
$this->showBody();
Event::handle('EndShowBody', array($this));
}
if (Event::handle('StartEndHTML', array($this))) {
$this->endHTML();
Event::handle('EndEndHTML', array($this));
}
}
/**
@ -402,13 +411,8 @@ class Action extends HTMLOutputter // lawsuit
if ($user) {
$this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
}
$this->menuItem(common_local_url('peoplesearch'),
_('Search'), _('Search for people or text'), false, 'nav_search');
if ($user) {
$this->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$this->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
@ -416,20 +420,28 @@ 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');
$this->menuItem(common_local_url('logout'),
_('Logout'), _('Logout from the site'), false, 'nav_logout');
} else {
$this->menuItem(common_local_url('login'),
_('Login'), _('Login to the site'), false, 'nav_login');
}
else {
if (!common_config('site', 'closed')) {
$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');
}
$this->menuItem(common_local_url('doc', array('title' => 'help')),
_('Help'), _('Help me!'), false, 'nav_help');
$this->menuItem(common_local_url('peoplesearch'),
_('Search'), _('Search for people or text'), false, 'nav_search');
Event::handle('EndPrimaryNav', array($this));
}
$this->elementEnd('ul');
@ -606,7 +618,10 @@ class Action extends HTMLOutputter // lawsuit
{
$this->elementStart('div', array('id' => 'aside_primary',
'class' => 'aside'));
if (Event::handle('StartShowExportData', array($this))) {
$this->showExportData();
Event::handle('EndShowExportData', array($this));
}
if (Event::handle('StartShowSections', array($this))) {
$this->showSections();
Event::handle('EndShowSections', array($this));
@ -923,11 +938,15 @@ class Action extends HTMLOutputter // lawsuit
*
* @return string current URL
*/
function selfUrl()
{
$action = $this->trimmed('action');
$args = $this->args;
unset($args['action']);
if (array_key_exists('submit', $args)) {
unset($args['submit']);
}
foreach (array_keys($_COOKIE) as $cookie) {
unset($args[$cookie]);
}

View File

@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
define('LACONICA_VERSION', '0.7.2.1');
define('LACONICA_VERSION', '0.7.3');
define('AVATAR_PROFILE_SIZE', 96);
define('AVATAR_STREAM_SIZE', 48);

View File

@ -50,7 +50,7 @@ class GalleryAction extends Action
if ($this->arg('page') && $this->arg('page') != 1) {
$args['page'] = $this->arg['page'];
}
common_redirect(common_local_url('subscriptions', $args), 301);
common_redirect(common_local_url($this->trimmed('action'), $args), 301);
return false;
}
@ -71,6 +71,7 @@ class GalleryAction extends Action
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
$this->tag = $this->trimmed('tag');
$this->q = $this->trimmed('q');
return true;
}
@ -87,7 +88,7 @@ class GalleryAction extends Action
# Post from the tag dropdown; redirect to a GET
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
common_redirect($this->selfUrl(), 307);
common_redirect($this->selfUrl(), 303);
return;
}
@ -136,7 +137,7 @@ class GalleryAction extends Action
'method' => 'post'));
$this->dropdown('tag', _('Tag'), $content,
_('Choose a tag to narrow list'), false, $tag);
$this->submit('go', _('Go'));
$this->submit('submit', _('Go'));
$this->elementEnd('form');
$this->elementEnd('li');
$this->elementEnd('ul');

View File

@ -33,7 +33,7 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/grouplist.php';
define('GROUPS_PER_MINILIST', 80);
define('GROUPS_PER_MINILIST', 27);
/**
* Widget to show a list of groups, good for sidebar
@ -75,8 +75,9 @@ class GroupMiniList extends GroupList
'href' => $this->group->homeUrl(),
'rel' => 'contact group',
'class' => 'url'));
$logo = ($this->group->stream_logo) ?
$this->group->stream_logo : User_group::defaultLogo(AVATAR_STREAM_SIZE);
$logo = ($this->group->mini_logo) ?
$this->group->mini_logo : User_group::defaultLogo(AVATAR_MINI_SIZE);
$this->out->element('img', array('src' => $logo,
'width' => AVATAR_MINI_SIZE,

View File

@ -137,6 +137,9 @@ class MailboxAction extends PersonalAction
$message->free();
unset($message);
}
else {
$this->element('p', 'guide', _('You have no private messages. You can send private message to engage other users in conversation. People can send you messages for your eyes only.'));
}
}
function getMessages()

242
lib/profileaction.php Normal file
View File

@ -0,0 +1,242 @@
<?php
/**
* Laconica, the distributed open-source microblogging tool
*
* Common parent of Personal and Profile 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 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);
}
require_once INSTALLDIR.'/lib/profileminilist.php';
require_once INSTALLDIR.'/lib/groupminilist.php';
/**
* Profile action common superclass
*
* Abstracts out common code from profile and personal tabs
*
* @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 ProfileAction extends Action
{
var $user = null;
var $page = null;
var $profile = null;
function prepare($args)
{
parent::prepare($args);
$nickname_arg = $this->arg('nickname');
$nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $nickname) {
$args = array('nickname' => $nickname);
if ($this->arg('page') && $this->arg('page') != 1) {
$args['page'] = $this->arg['page'];
}
common_redirect(common_local_url($this->trimmed('action'), $args), 301);
return false;
}
$this->user = User::staticGet('nickname', $nickname);
if (!$this->user) {
$this->clientError(_('No such user.'), 404);
return false;
}
$this->profile = $this->user->getProfile();
if (!$this->profile) {
$this->serverError(_('User has no profile.'));
return false;
}
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
common_set_returnto($this->selfUrl());
return true;
}
function showSections()
{
$this->showSubscriptions();
$this->showSubscribers();
$this->showGroups();
$this->showStatistics();
}
function showSubscriptions()
{
$profile = $this->user->getSubscriptions(0, PROFILES_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_subscriptions',
'class' => 'section'));
$this->element('h2', null, _('Subscriptions'));
if ($profile) {
$pml = new ProfileMiniList($profile, $this->user, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
}
}
if ($cnt > PROFILES_PER_MINILIST) {
$this->elementStart('p');
$this->element('a', array('href' => common_local_url('subscriptions',
array('nickname' => $this->profile->nickname)),
'class' => 'more'),
_('All subscriptions'));
$this->elementEnd('p');
}
$this->elementEnd('div');
}
function showSubscribers()
{
$profile = $this->user->getSubscribers(0, PROFILES_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_subscribers',
'class' => 'section'));
$this->element('h2', null, _('Subscribers'));
if ($profile) {
$pml = new ProfileMiniList($profile, $this->user, $this);
$cnt = $pml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
}
}
if ($cnt > PROFILES_PER_MINILIST) {
$this->elementStart('p');
$this->element('a', array('href' => common_local_url('subscribers',
array('nickname' => $this->profile->nickname)),
'class' => 'more'),
_('All subscribers'));
$this->elementEnd('p');
}
$this->elementEnd('div');
}
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();
$this->elementStart('div', array('id' => 'entity_statistics',
'class' => 'section'));
$this->element('h2', null, _('Statistics'));
// Other stats...?
$this->elementStart('dl', 'entity_member-since');
$this->element('dt', null, _('Member since'));
$this->element('dd', null, date('j M Y',
strtotime($this->profile->created)));
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_subscriptions');
$this->elementStart('dt');
$this->element('a', array('href' => common_local_url('subscriptions',
array('nickname' => $this->profile->nickname))),
_('Subscriptions'));
$this->elementEnd('dt');
$this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
$this->elementEnd('dl');
$this->elementStart('dl', 'entity_subscribers');
$this->elementStart('dt');
$this->element('a', array('href' => common_local_url('subscribers',
array('nickname' => $this->profile->nickname))),
_('Subscribers'));
$this->elementEnd('dt');
$this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
$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->elementEnd('dl');
$this->elementEnd('div');
}
function showGroups()
{
$groups = $this->user->getGroups(0, GROUPS_PER_MINILIST + 1);
$this->elementStart('div', array('id' => 'entity_groups',
'class' => 'section'));
$this->element('h2', null, _('Groups'));
if ($groups) {
$gml = new GroupMiniList($groups, $this->user, $this);
$cnt = $gml->show();
if ($cnt == 0) {
$this->element('p', null, _('(None)'));
}
}
if ($cnt > GROUPS_PER_MINILIST) {
$this->elementStart('p');
$this->element('a', array('href' => common_local_url('usergroups',
array('nickname' => $this->profile->nickname)),
'class' => 'more'),
_('All groups'));
$this->elementEnd('p');
}
$this->elementEnd('div');
}
}

View File

@ -33,7 +33,7 @@ if (!defined('LACONICA')) {
require_once INSTALLDIR.'/lib/profilelist.php';
define('PROFILES_PER_MINILIST', 80);
define('PROFILES_PER_MINILIST', 27);
/**
* Widget to show a list of profiles, good for sidebar

View File

@ -133,5 +133,31 @@ class SearchAction extends Action
$this->showResults($q, $page);
}
}
function searchSuggestions($q) {
$qe = urlencode($q);
$message = sprintf(_(<<<E_O_T
* Make sure all words are spelled correctly.
* Try different keywords.
* Try more general keywords.
* Try fewer keywords.
You can also try your search on other engines:
* [Twingly](http://www.twingly.com/search?q=%s&content=microblog&site=identi.ca)
* [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)
E_O_T
), $qe, $qe, $qe, $qe);
$this->elementStart('dl', array('id' => 'help_search', 'class' => 'help'));
$this->element('dt', null, _('Search help'));
$this->elementStart('dd', 'instructions');
$this->raw(common_markup_to_html($message));
$this->elementEnd('dd');
$this->elementEnd('div');
}
}

View File

@ -19,7 +19,7 @@
if (!defined('LACONICA')) { exit(1); }
define("TWITTER_SERVICE", 1); // Twitter is foreign_service ID 1
define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
function get_twitter_data($uri, $screen_name, $password)
{
@ -45,6 +45,10 @@ function get_twitter_data($uri, $screen_name, $password)
if ($errmsg) {
common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
__FILE__);
if (defined('SCRIPT_DEBUG')) {
print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
}
}
curl_close($ch);
@ -52,63 +56,141 @@ function get_twitter_data($uri, $screen_name, $password)
return $data;
}
function twitter_user_info($screen_name, $password)
function twitter_json_data($uri, $screen_name, $password)
{
$json_data = get_twitter_data($uri, $screen_name, $password);
$uri = "http://twitter.com/users/show/$screen_name.json";
$data = get_twitter_data($uri, $screen_name, $password);
if (!$json_data) {
return false;
}
$data = json_decode($json_data);
if (!$data) {
return false;
}
$twit_user = json_decode($data);
if (!$twit_user) {
return false;
}
return $twit_user;
return $data;
}
function update_twitter_user($fuser, $twitter_id, $screen_name)
function twitter_user_info($screen_name, $password)
{
$uri = "http://twitter.com/users/show/$screen_name.json";
return twitter_json_data($uri, $screen_name, $password);
}
$original = clone($fuser);
$fuser->nickname = $screen_name;
$fuser->uri = 'http://twitter.com/' . $screen_name;
$result = $fuser->updateKeys($original);
function twitter_friends_ids($screen_name, $password)
{
$uri = "http://twitter.com/friends/ids/$screen_name.json";
return twitter_json_data($uri, $screen_name, $password);
}
function update_twitter_user($twitter_id, $screen_name)
{
$uri = 'http://twitter.com/' . $screen_name;
$fuser = new Foreign_user();
$fuser->query('BEGIN');
// Dropping down to SQL because regular db_object udpate stuff doesn't seem
// to work so good with tables that have multiple column primary keys
// Any time we update the uri for a forein user we have to make sure there
// are no dupe entries first -- unique constraint on the uri column
$qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
$qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
$result = $fuser->query($qry);
if ($result) {
common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
if (defined('SCRIPT_DEBUG')) {
print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
}
}
// Update the user
$qry = 'UPDATE foreign_user SET nickname = ';
$qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
$qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
$result = $fuser->query($qry);
if (!$result) {
common_log(LOG_WARNING,
"Couldn't update foreign_user data for Twitter user: $screen_name");
common_log_db_error($fuser, 'UPDATE', __FILE__);
if (defined('SCRIPT_DEBUG')) {
print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - ";
print common_log_objstring($fuser) . "\n";
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
print "DB_DataObject Error: " . $error->getMessage() . "\n";
}
return false;
}
$fuser->query('COMMIT');
$fuser->free();
unset($fuser);
return true;
}
function add_twitter_user($twitter_id, $screen_name)
{
$new_uri = 'http://twitter.com/' . $screen_name;
// Clear out any bad old foreign_users with the new user's legit URL
// This can happen when users move around or fakester accounts get
// repoed, and things like that.
$luser = new Foreign_user();
$luser->uri = $new_uri;
$luser->service = TWITTER_SERVICE;
$result = $luser->delete();
if ($result) {
common_log(LOG_WARNING,
"Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
if (defined('SCRIPT_DEBUG')) {
print "Removed invalid Twitter user squatting on uri: $new_uri\n";
}
}
$luser->free();
unset($luser);
// Otherwise, create a new Twitter user
$fuser = DB_DataObject::factory('foreign_user');
$fuser = new Foreign_user();
$fuser->nickname = $screen_name;
$fuser->uri = 'http://twitter.com/' . $screen_name;
$fuser->id = $twitter_id;
$fuser->service = TWITTER_SERVICE; // Twitter
$fuser->service = TWITTER_SERVICE;
$fuser->created = common_sql_now();
$result = $fuser->insert();
if (!$result) {
common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
common_log(LOG_WARNING,
"Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
common_log_db_error($fuser, 'INSERT', __FILE__);
return false;
if (defined('SCRIPT_DEBUG')) {
print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
print common_log_objstring($fuser) . "\n";
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
print "DB_DataObject Error: " . $error->getMessage() . "\n";
}
} else {
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
if (defined('SCRIPT_DEBUG')) {
print "Added new Twitter user: $screen_name ($twitter_id).\n";
}
}
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
return true;
return $result;
}
// Creates or Updates a Twitter user
@ -117,53 +199,87 @@ function save_twitter_user($twitter_id, $screen_name)
// Check to see whether the Twitter user is already in the system,
// and update its screen name and uri if so.
$fuser = Foreign_user::getForeignUser($twitter_id, 1);
$fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
if ($fuser) {
$result = true;
// Only update if Twitter screen name has changed
if ($fuser->nickname != $screen_name) {
$result = update_twitter_user($twitter_id, $screen_name);
common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
"$fuser->id to $screen_name, was $fuser->nickname");
return update_twitter_user($fuser, $twitter_id, $screen_name);
if (defined('SCRIPT_DEBUG')) {
print 'Updated nickname (and URI) for Twitter user ' .
"$fuser->id to $screen_name, was $fuser->nickname\n";
}
}
return $result;
} else {
return add_twitter_user($twitter_id, $screen_name);
}
$fuser->free();
unset($fuser);
return true;
}
function retreive_twitter_friends($twitter_id, $screen_name, $password)
{
$friends = array();
$uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
$twitter_user = twitter_user_info($screen_name, $password);
$friends_ids = twitter_friends_ids($screen_name, $password);
// Calculate how many pages to get...
$pages = ceil($twitter_user->friends_count / 100);
if ($pages == 0) {
common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
if (!$friends_ids) {
return $friends;
}
$friends = array();
if (defined('SCRIPT_DEBUG')) {
print "Twitter 'social graph' ids method says $screen_name has " .
count($friends_ids) . " friends.\n";
}
// Calculate how many pages to get...
$pages = ceil(count($friends_ids) / 100);
if ($pages == 0) {
common_log(LOG_WARNING,
"Twitter bridge - $screen_name seems to have no friends.");
if (defined('SCRIPT_DEBUG')) {
print "$screen_name seems to have no friends.\n";
}
}
for ($i = 1; $i <= $pages; $i++) {
$data = get_twitter_data($uri . $i, $screen_name, $password);
if (!$data) {
return null;
common_log(LOG_WARNING,
"Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
if (defined('SCRIPT_DEBUG')) {
print "Couldn't retrieve page $i of $screen_name's friends.\n";
}
continue;
}
$more_friends = json_decode($data);
if (!$more_friends) {
return null;
common_log(LOG_WARNING,
"Twitter bridge - No data for page $i of $screen_name's friends.");
if (defined('SCRIPT_DEBUG')) {
print "No data for page $i of $screen_name's friends.\n";
}
continue;
}
$friends = array_merge($friends, $more_friends);
@ -177,19 +293,27 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
$friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
if (is_null($friends)) {
common_debug("Twitter bridge - Couldn't get friends data from Twitter.");
if (empty($friends)) {
common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
if (defined('SCRIPT_DEBUG')) {
print "Couldn't get friends data from Twitter for $screen_name.\n";
}
return false;
}
foreach ($friends as $friend) {
$friend_name = $friend->screen_name;
$friend_id = $friend->id;
$friend_id = (int) $friend->id;
// Update or create the Foreign_user record
if (!save_twitter_user($friend_id, $friend_name)) {
return false;
common_log(LOG_WARNING,
"Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
if (defined('SCRIPT_DEBUG')) {
print "Couldn't save $screen_name's friend, $friend_name.\n";
}
continue;
}
// Check to see if there's a related local user
@ -199,8 +323,20 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
// Get associated user and subscribe her
$friend_user = User::staticGet('id', $flink->user_id);
subs_subscribe_to($user, $friend_user);
common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
if (!empty($friend_user)) {
$result = subs_subscribe_to($user, $friend_user);
if ($result === true) {
common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
if (defined('SCRIPT_DEBUG')) {
print("Subscribed $friend_user->nickname to $user->nickname.\n");
}
} else {
if (defined('SCRIPT_DEBUG')) {
print "$result ($friend_user->nickname to $user->nickname)\n";
}
}
}
}
}
@ -295,4 +431,3 @@ function broadcast_twitter($notice)
return $success;
}

344
plugins/TemplatePlugin.php Normal file
View File

@ -0,0 +1,344 @@
<?php
/**
* Plugin to render old skool templates
*
* Captures rendered parts from the output buffer, passes them through a template file: tpl/index.html
* Adds an API method at index.php/template/update which lets you overwrite the template file
* Requires username/password and a single POST parameter called "template"
* The method is disabled unless the user is #1, the first user of the system
*
* @category Plugin
* @package Laconica
* @author Brian Hendrickson <brian@megapump.com>
* @copyright 2009 Megapump, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://megapump.com/
*/
if (!defined('LACONICA')) {
exit(1);
}
define('TEMPLATEPLUGIN_VERSION', '0.1');
class TemplatePlugin extends Plugin {
var $blocks = array();
function __construct() {
parent::__construct();
}
// capture the RouterInitialized event
// and connect a new API method
// for updating the template
function onRouterInitialized( &$m ) {
$m->connect( 'template/update', array(
'action' => 'template',
));
}
// <%styles%>
// <%scripts%>
// <%search%>
// <%feeds%>
// <%description%>
// <%head%>
function onStartShowHead( &$act ) {
$this->clear_xmlWriter($act);
$act->extraHead();
$this->blocks['head'] = $act->xw->flush();
$act->showStylesheets();
$this->blocks['styles'] = $act->xw->flush();
$act->showScripts();
$this->blocks['scripts'] = $act->xw->flush();
$act->showFeeds();
$this->blocks['feeds'] = $act->xw->flush();
$act->showOpenSearch();
$this->blocks['search'] = $act->xw->flush();
$act->showDescription();
$this->blocks['description'] = $act->xw->flush();
return false;
}
// <%bodytext%>
function onStartShowContentBlock( &$act ) {
$this->clear_xmlWriter($act);
return true;
}
function onEndShowContentBlock( &$act ) {
$this->blocks['bodytext'] = $act->xw->flush();
}
// <%localnav%>
function onStartShowLocalNavBlock( &$act ) {
$this->clear_xmlWriter($act);
return true;
}
function onEndShowLocalNavBlock( &$act ) {
$this->blocks['localnav'] = $act->xw->flush();
}
// <%export%>
function onStartShowExportData( &$act ) {
$this->clear_xmlWriter($act);
return true;
}
function onEndShowExportData( &$act ) {
$this->blocks['export'] = $act->xw->flush();
}
// <%subscriptions%>
// <%subscribers%>
// <%groups%>
// <%statistics%>
// <%cloud%>
// <%groupmembers%>
// <%groupstatistics%>
// <%groupcloud%>
// <%popular%>
// <%groupsbyposts%>
// <%featuredusers%>
// <%groupsbymembers%>
function onStartShowSections( &$act ) {
global $action;
$this->clear_xmlWriter($act);
switch ($action) {
case "showstream":
$act->showSubscriptions();
$this->blocks['subscriptions'] = $act->xw->flush();
$act->showSubscribers();
$this->blocks['subscribers'] = $act->xw->flush();
$act->showGroups();
$this->blocks['groups'] = $act->xw->flush();
$act->showStatistics();
$this->blocks['statistics'] = $act->xw->flush();
$cloud = new PersonalTagCloudSection($act, $act->user);
$cloud->show();
$this->blocks['cloud'] = $act->xw->flush();
break;
case "showgroup":
$act->showMembers();
$this->blocks['groupmembers'] = $act->xw->flush();
$act->showStatistics();
$this->blocks['groupstatistics'] = $act->xw->flush();
$cloud = new GroupTagCloudSection($act, $act->group);
$cloud->show();
$this->blocks['groupcloud'] = $act->xw->flush();
break;
case "public":
$pop = new PopularNoticeSection($act);
$pop->show();
$this->blocks['popular'] = $act->xw->flush();
$gbp = new GroupsByPostsSection($act);
$gbp->show();
$this->blocks['groupsbyposts'] = $act->xw->flush();
$feat = new FeaturedUsersSection($act);
$feat->show();
$this->blocks['featuredusers'] = $act->xw->flush();
break;
case "groups":
$gbp = new GroupsByPostsSection($act);
$gbp->show();
$this->blocks['groupsbyposts'] = $act->xw->flush();
$gbm = new GroupsByMembersSection($act);
$gbm->show();
$this->blocks['groupsbymembers'] = $act->xw->flush();
break;
}
return false;
}
// <%logo%>
// <%nav%>
// <%notice%>
// <%noticeform%>
function onStartShowHeader( &$act ) {
$this->clear_xmlWriter($act);
$act->showLogo();
$this->blocks['logo'] = $act->xw->flush();
$act->showPrimaryNav();
$this->blocks['nav'] = $act->xw->flush();
$act->showSiteNotice();
$this->blocks['notice'] = $act->xw->flush();
if (common_logged_in()) {
$act->showNoticeForm();
} else {
$act->showAnonymousMessage();
}
$this->blocks['noticeform'] = $act->xw->flush();
return false;
}
// <%secondarynav%>
// <%licenses%>
function onStartShowFooter( &$act ) {
$this->clear_xmlWriter($act);
$act->showSecondaryNav();
$this->blocks['secondarynav'] = $act->xw->flush();
$act->showLicenses();
$this->blocks['licenses'] = $act->xw->flush();
return false;
}
// capture the EndHTML event
// and include the template
function onEndEndHTML($act) {
global $action, $tags;
// set the action and title values
$vars = array(
'action'=>$action,
'title'=>$act->title(). " - ". common_config('site', 'name')
);
// use the PHP template
// unless laconica config:
// $config['template']['mode'] = 'html';
if (!(common_config('template', 'mode') == 'html')) {
$tpl_file = 'tpl/index.php';
$tags = array_merge($vars,$this->blocks);
include $tpl_file;
return;
}
$tpl_file = 'tpl/index.html';
// read the static template
$output = file_get_contents( $tpl_file );
$tags = array();
// get a list of the <%tags%> in the template
$pattern='/<%([a-z]+)%>/';
if ( 1 <= preg_match_all( $pattern, $output, $found ))
$tags[] = $found;
// for each found tag, set its value from the rendered blocks
foreach( $tags[0][1] as $pos=>$tag ) {
if (isset($this->blocks[$tag]))
$vars[$tag] = $this->blocks[$tag];
// didn't find a block for the tag
elseif (!isset($vars[$tag]))
$vars[$tag] = '';
}
// replace the tags in the template
foreach( $vars as $key=>$val )
$output = str_replace( '<%'.$key.'%>', $val, $output );
echo $output;
return true;
}
// catching the StartShowHTML event to halt the rendering
function onStartShowHTML( &$act ) {
$this->clear_xmlWriter($act);
return true;
}
// clear the xmlWriter
function clear_xmlWriter( &$act ) {
$act->xw->openMemory();
$act->xw->setIndent(true);
}
}
/**
* Action for updating the template remotely
*
* "template/update" -- a POST method that requires a single
* parameter "template", containing the new template code
*
* @category Plugin
* @package Laconica
* @author Brian Hendrickson <brian@megapump.com>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://megapump.com/
*
*/
class TemplateAction extends Action
{
function prepare($args) {
parent::prepare($args);
return true;
}
function handle($args) {
parent::handle($args);
if (!isset($_SERVER['PHP_AUTH_USER'])) {
// not authenticated, show login form
header('WWW-Authenticate: Basic realm="Laconica API"');
// cancelled the browser login form
$this->clientError(_('Authentication error!'), $code = 401);
} else {
$nick = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
// check username and password
$user = common_check_user($nick,$pass);
if ($user) {
// verify that user is admin
if (!($user->id == 1))
$this->clientError(_('only User #1 can update the template'), $code = 401);
// open the old template
$tpl_file = 'tpl/index.html';
$fp = fopen( $tpl_file, 'w+' );
// overwrite with the new template
fwrite($fp, $this->arg('template'));
fclose($fp);
header('HTTP/1.1 200 OK');
header('Content-type: text/plain');
print "Template Updated!";
} else {
// bad username and password
$this->clientError(_('Authentication error!'), $code = 401);
}
}
}
}
/**
* Function for retrieving a laconica display section
*
* requires one parameter, the name of the section
* section names are listed in the comments of the TemplatePlugin class
*
* @category Plugin
* @package Laconica
* @author Brian Hendrickson <brian@megapump.com>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://megapump.com/
*
*/
function section($tagname) {
global $tags;
if (isset($tags[$tagname]))
return $tags[$tagname];
}

View File

@ -1,11 +1,14 @@
# This version needs to match the tarball and unpacked directory name.
%define LACVER 0.7.3
BuildRequires: php-pear
BuildRequires: httpd-devel
Name: laconica
Version: 0.7.2
Version: %{LACVER}
Release: 1%{?dist}
License: GAGPL v3 or later
Source: laconica-0.7.2.tar.gz
Source: laconica-%{version}.tar.gz
Group: Applications/Internet
Summary: Laconica, the Open Source microblogging platform
BuildArch: noarch
@ -49,6 +52,8 @@ cp -a * %{buildroot}%{wwwpath}
mkdir -p %{buildroot}%{_datadir}/laconica
cp -a db %{buildroot}%{_datadir}/laconica/db
mkdir -p %{buildroot}%{_datadir}/laconica/avatar
mkdir -p %{buildroot}%{_sysconfdir}/httpd/conf.d
cat > %{buildroot}%{_sysconfdir}/httpd/conf.d/laconica.conf <<"EOF"
Alias /laconica/ "/var/www/laconica/"
@ -74,6 +79,12 @@ rm -rf %buildroot
%config(noreplace) %{_sysconfdir}/httpd/conf.d/laconica.conf
%changelog
* Wed Apr 03 2009 Zach Copley <zach@controlyourself.ca> - 0.7.3
- Changed version number to 0.7.3.
* Fri Mar 13 2009 Ken Sedgwick <ksedgwic@bonsai.com> - 0.7.2.1-1
- Factored laconica version to the first line of the file.
* Wed Mar 03 2009 Zach Copley <zach@controlyourself.ca> - 0.7.2
- Changed version number to 0.7.2.

View File

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
# Abort if called from a web server
// 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();
@ -27,11 +27,16 @@ if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
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');
$flink = new Foreign_link();
$flink->service = 1; // Twitter
$flink->find();
$cnt = $flink->find();
print "Updating Twitter friends subscriptions for $cnt users.\n";
while ($flink->fetch()) {
@ -39,20 +44,30 @@ while ($flink->fetch()) {
$user = User::staticGet($flink->user_id);
print "Updating Twitter friends for user $user->nickname ($user->id)\n";
if (empty($user)) {
common_log(LOG_WARNING, "Unmatched user for ID " . $flink->user_id);
print "Unmatched user for ID $flink->user_id\n";
continue;
}
print "Updating Twitter friends for $user->nickname (Laconica ID: $user->id)... ";
$fuser = $flink->getForeignUser();
$result = save_twitter_friends($user, $fuser->id, $fuser->nickname, $flink->credentials);
if (empty($fuser)) {
common_log(LOG_WARNING, "Unmatched user for ID " . $flink->user_id);
print "Unmatched user for ID $flink->user_id\n";
continue;
}
if ($result == false) {
print "Problems updating Twitter friends! Check the log.\n";
exit(1);
$result = save_twitter_friends($user, $fuser->id,
$fuser->nickname, $flink->credentials);
if (defined('SCRIPT_DEBUG')) {
print "\nDONE\n";
} else {
print "DONE\n";
}
}
}
exit(0);

View File

@ -83,12 +83,13 @@ left:0;
border:0;
}
#page_notice .error,
#page_notice .success {
.error,
.success {
padding:4px 7px;
border-radius:4px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
margin-bottom:18px;
}
form label.submit {
display:none;
@ -384,6 +385,7 @@ margin-bottom:1em;
#content {
width:64.009%;
min-height:259px;
padding:1.795%;
float:left;
border-radius:7px;
@ -403,8 +405,9 @@ float:left;
#aside_primary {
width:27.917%;
min-height:259px;
float:left;
margin-left:0.395%;
margin-left:0.385%;
padding:1.795%;
border-radius:7px;
-moz-border-radius:7px;
@ -469,7 +472,7 @@ bottom:0;
right:0;
}
#form_notice label[for=to] {
margin-top:11px;
margin-top:7px;
}
#form_notice select[id=to] {
margin-bottom:7px;
@ -1143,4 +1146,16 @@ clear:both;
margin-bottom:0;
}
.instructions ul {
list-style-position:inside;
}
.instructions p,
.instructions ul {
margin-bottom:18px;
}
.help dt {
display:none;
}
.guide {
clear:both;
}

View File

@ -37,7 +37,6 @@ background:none;
input.submit,
#form_notice.warning #notice_text-count,
#nav_register a,
.form_settings .form_note,
.entity_remote_subscribe {
background-color:#A9BF4F;
@ -48,7 +47,6 @@ input:focus, textarea:focus, select:focus,
border-color:#A9BF4F;
}
input.submit,
#nav_register a,
.entity_remote_subscribe {
color:#fff;
}
@ -97,13 +95,6 @@ cursor:wait;
text-indent:-9999px;
}
#nav_register a {
text-decoration:none;
font-weight:bold;
padding:2px 4px;
}
#content,
#site_nav_local_views a,
#aside_primary {
@ -122,10 +113,10 @@ background-color:rgba(255, 255, 255, 0.7);
}
#page_notice .error {
.error {
background-color:#F7E8E8;
}
#page_notice .success {
.success {
background-color:#EFF3DC;
}

View File

@ -37,7 +37,6 @@ background:none;
input.submit,
#form_notice.warning #notice_text-count,
#nav_register a,
.form_settings .form_note,
.entity_remote_subscribe {
background-color:#9BB43E;
@ -48,7 +47,6 @@ input:focus, textarea:focus, select:focus,
border-color:#9BB43E;
}
input.submit,
#nav_register a,
.entity_remote_subscribe {
color:#fff;
}
@ -97,12 +95,6 @@ cursor:wait;
text-indent:-9999px;
}
#nav_register a {
text-decoration:none;
font-weight:bold;
padding:2px 4px;
}
#content,
#site_nav_local_views a,
#aside_primary {
@ -121,10 +113,10 @@ background-color:rgba(255, 255, 255, 0.7);
}
#page_notice .error {
.error {
background-color:#F7E8E8;
}
#page_notice .success {
.success {
background-color:#EFF3DC;
}

47
tpl/index.php Normal file
View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?> <!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" lang="en">
<head>
<title><?php echo section('title'); ?></title>
<?php echo section('styles'); ?>
<?php echo section('scripts'); ?>
<?php echo section('search'); ?>
<?php echo section('feeds'); ?>
<?php echo section('description'); ?>
<?php echo section('head'); ?>
</head>
<body id="<?php echo section('action'); ?>">
<div id="wrap">
<div id="header">
<?php echo section('logo'); ?>
<?php echo section('nav'); ?>
<?php echo section('notice'); ?>
<?php echo section('noticeform'); ?>
</div>
<div id="core">
<?php echo section('localnav'); ?>
<?php echo section('bodytext'); ?>
<div id="aside_primary" class="aside">
<?php echo section('export'); ?>
<?php echo section('subscriptions'); ?>
<?php echo section('subscribers'); ?>
<?php echo section('groups'); ?>
<?php echo section('statistics'); ?>
<?php echo section('cloud'); ?>
<?php echo section('groupmembers'); ?>
<?php echo section('groupstatistics'); ?>
<?php echo section('groupcloud'); ?>
<?php echo section('popular'); ?>
<?php echo section('groupsbyposts'); ?>
<?php echo section('featuredusers'); ?>
<?php echo section('groupsbymembers'); ?>
</div>
</div>
<div id="footer">
<?php echo section('secondarynav'); ?>
<?php echo section('licenses'); ?>
</div>
</div>
</body>
</html>