Merge branch '0.7.x' of git://gitorious.org/laconica/dev into 0.7.x

This commit is contained in:
Tobias Diekershoff 2009-04-10 14:25:25 +02:00
commit 4277a6818c
76 changed files with 2077 additions and 1020 deletions

148
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
=============
@ -257,9 +193,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.)
@ -267,7 +203,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
@ -757,7 +693,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
@ -925,6 +861,16 @@ dupelimit: Time in which it's not OK for the same person to post the
same notice; default = 60 seconds.
logo: URL of an image file to use as the logo for the site. Overrides
the logo in the theme, if any.
ssl: Whether to use SSL and https:// URLs for some or all pages.
Possible values are 'always' (use it for all pages), 'never'
(don't use it for any pages), or 'sometimes' (use it for
sensitive pages that include passwords like login and registration,
but not for regular pages). Default to 'never'.
sslserver: use an alternate server name for SSL URLs, like
'secure.example.org'. You should be careful to set cookie
parameters correctly so that both the SSL server and the
"normal" server can access the session cookie and
preferably other cookies as well.
db
--
@ -1169,6 +1115,20 @@ banned: an array of usernames and/or profile IDs of 'banned' profiles.
not be accepted at all. (Compare with blacklisted users above,
whose posts just won't show up in the public stream.)
newuser
-------
Options with new users.
default: nickname of a user account to automatically subscribe new
users to. Typically this would be system account for e.g.
service updates or announcements. Users are able to unsub
if they want. Default is null; no auto subscribe.
welcome: nickname of a user account that sends welcome messages to new
users. Can be the same as 'default' account, although on
busy servers it may be a good idea to keep that one just for
'urgent' messages. Default is null; no message.
Troubleshooting
===============
@ -1181,7 +1141,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.
@ -1270,6 +1230,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

@ -93,7 +93,8 @@ class BlockAction extends Action
if ($this->arg('no')) {
$cur = common_current_user();
$other = Profile::staticGet('id', $this->arg('blockto'));
common_redirect(common_local_url('showstream', array('nickname' => $other->nickname)));
common_redirect(common_local_url('showstream', array('nickname' => $other->nickname)),
303);
} elseif ($this->arg('yes')) {
$this->blockProfile();
} elseif ($this->arg('blockto')) {
@ -102,7 +103,6 @@ class BlockAction extends Action
}
}
function showContent() {
$this->areYouSureForm();
}
@ -110,7 +110,7 @@ class BlockAction extends Action
function title() {
return _('Block user');
}
function showNoticeForm() {
// nop
}
@ -178,10 +178,11 @@ class BlockAction extends Action
}
if ($action) {
common_redirect(common_local_url($action, $args));
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('subscriptions',
array('nickname' => $cur->nickname)));
array('nickname' => $cur->nickname)),
303);
}
}
}

View File

@ -141,6 +141,6 @@ class DeletenoticeAction extends DeleteAction
$url = common_local_url('public');
}
common_redirect($url);
common_redirect($url, 303);
}
}

View File

@ -49,7 +49,7 @@ class DisfavorAction extends Action
{
/**
* Class handler.
*
*
* @param array $args query arguments
*
* @return void
@ -100,7 +100,8 @@ class DisfavorAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url('showfavorites',
array('nickname' => $user->nickname)));
array('nickname' => $user->nickname)),
303);
}
}
}

View File

@ -166,7 +166,6 @@ class EditgroupAction extends Action
return;
}
$nickname = common_canonical_nickname($this->trimmed('nickname'));
$fullname = $this->trimmed('fullname');
$homepage = $this->trimmed('homepage');
@ -221,7 +220,7 @@ class EditgroupAction extends Action
if ($this->group->nickname != $orig->nickname) {
common_redirect(common_local_url('editgroup',
array('nickname' => $nickname)),
307);
303);
} else {
$this->showForm(_('Options saved.'));
}

View File

@ -52,7 +52,7 @@ class FavorAction extends Action
{
/**
* Class handler.
*
*
* @param array $args query arguments
*
* @return void
@ -100,13 +100,14 @@ class FavorAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url('showfavorites',
array('nickname' => $user->nickname)));
array('nickname' => $user->nickname)),
303);
}
}
/**
* Notifies a user when his notice is favorited.
*
*
* @param class $notice favorited notice
* @param class $user user declaring a favorite
*

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;
}
@ -145,6 +145,22 @@ class FavoritedAction extends Action
$this->elementEnd('div');
}
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.');
}
else {
$message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!');
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
/**
* Local navigation
*
@ -198,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

@ -107,6 +107,7 @@ class FeaturedAction extends Action
$featured_nicks = common_config('nickname', 'featured');
if (count($featured_nicks) > 0) {
$quoted = array();
@ -118,7 +119,7 @@ class FeaturedAction extends Action
$user = new User;
$user->whereAdd(sprintf('nickname IN (%s)', implode(',', $quoted)));
$user->limit(($this->page - 1) * PROFILES_PER_PAGE, PROFILES_PER_PAGE + 1);
$user->orderBy('user.nickname ASC');
$user->orderBy(common_database_tablename('user') .'.nickname ASC');
$user->find();
@ -145,4 +146,4 @@ class FeaturedAction extends Action
$this->page, 'featured');
}
}
}
}

View File

@ -139,7 +139,7 @@ class FinishaddopenidAction extends Action
oid_set_last($display);
common_redirect(common_local_url('openidsettings'));
common_redirect(common_local_url('openidsettings'), 303);
}
}

View File

@ -271,7 +271,8 @@ class FinishopenidloginAction extends Action
common_rememberme($user);
}
unset($_SESSION['openid_rememberme']);
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)));
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
303);
}
function connectUser()
@ -324,7 +325,7 @@ class FinishopenidloginAction extends Action
array('nickname' =>
$nickname));
}
common_redirect($url);
common_redirect($url, 303);
}
function bestNewNickname($display, $sreg)

View File

@ -230,7 +230,8 @@ class FinishremotesubscribeAction extends Action
# show up close to the top of the page
common_redirect(common_local_url('subscribers', array('nickname' =>
$user->nickname)));
$user->nickname)),
303);
}
function add_avatar($profile, $url)

View File

@ -33,7 +33,24 @@ class FoafAction extends Action
function prepare($args)
{
parent::prepare($args);
$this->nickname = $this->trimmed('nickname');
$nickname_arg = $this->arg('nickname');
if (empty($nickname_arg)) {
$this->clientError(_('No such user.'), 404);
return false;
}
$this->nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
if ($nickname_arg != $this->nickname) {
common_redirect(common_local_url('foaf',
array('nickname' => $this->nickname)),
301);
return false;
}
$this->user = User::staticGet('nickname', $this->nickname);
@ -122,20 +139,30 @@ class FoafAction extends Action
if ($sub->find()) {
while ($sub->fetch()) {
if ($sub->token) {
if (!empty($sub->token)) {
$other = Remote_profile::staticGet('id', $sub->subscribed);
} else {
$other = User::staticGet('id', $sub->subscribed);
}
if (!$other) {
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);
$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);
// Get people who subscribe to user
$sub = new Subscription();
@ -156,25 +183,36 @@ class FoafAction extends Action
if (array_key_exists($other->uri, $person)) {
$person[$other->uri][0] = BOTH;
} else {
$person[$other->uri] = array(LISTENER, $other);
$person[$other->uri] = array(LISTENER,
$other->id,
$other->nickname,
(empty($sub->token)) ? 'User' : 'Remote_profile');
}
$other->free();
$other = null;
unset($other);
}
}
$sub->free();
$sub = null;
unset($sub);
$this->elementEnd('Person');
foreach ($person as $uri => $p) {
$foaf_url = null;
if ($p[1] instanceof User) {
$foaf_url = common_local_url('foaf', array('nickname' => $p[1]->nickname));
list($type, $id, $nickname, $cls) = $p;
if ($cls == 'User') {
$foaf_url = common_local_url('foaf', array('nickname' => $nickname));
}
$this->profile = Profile::staticGet($p[1]->id);
$profile = Profile::staticGet($id);
$this->elementStart('Person', array('rdf:about' => $uri));
if ($p[0] == LISTENER || $p[0] == BOTH) {
if ($type == LISTENER || $type == BOTH) {
$this->element('knows', array('rdf:resource' => $this->user->uri));
}
$this->showMicrobloggingAccount($this->profile, ($p[1] instanceof User) ?
common_root_url() : null);
$this->showMicrobloggingAccount($profile, ($cls == 'User') ?
common_root_url() : null);
if ($foaf_url) {
$this->element('rdfs:seeAlso', array('rdf:resource' => $foaf_url));
}
@ -182,6 +220,9 @@ class FoafAction extends Action
if ($foaf_url) {
$this->showPpd($foaf_url, $uri);
}
$profile->free();
$profile = null;
unset($profile);
}
$this->elementEnd('rdf:RDF');

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

@ -143,7 +143,8 @@ class JoingroupAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url('groupmembers', array('nickname' =>
$this->group->nickname)));
$this->group->nickname)),
303);
}
}
}

View File

@ -147,7 +147,8 @@ class LeavegroupAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url('groupmembers', array('nickname' =>
$this->group->nickname)));
$this->group->nickname)),
303);
}
}
}

View File

@ -138,7 +138,7 @@ class LoginAction extends Action
$nickname));
}
common_redirect($url);
common_redirect($url, 303);
}
/**

View File

@ -46,10 +46,10 @@ require_once INSTALLDIR.'/lib/openid.php';
*/
class LogoutAction extends Action
{
/**
* This is read only.
*
*
* @return boolean true
*/
function isReadOnly()
@ -59,7 +59,7 @@ class LogoutAction extends Action
/**
* Class handler.
*
*
* @param array $args array of arguments
*
* @return nothing
@ -73,7 +73,7 @@ class LogoutAction extends Action
common_set_user(null);
common_real_login(false); // not logged in
common_forgetme(); // don't log back in!
common_redirect(common_local_url('public'));
common_redirect(common_local_url('public'), 303);
}
}
}

View File

@ -193,7 +193,7 @@ class NewgroupAction extends Action
$group->query('COMMIT');
common_redirect($group->homeUrl(), 307);
common_redirect($group->homeUrl(), 303);
}
function nicknameExists($nickname)

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

@ -50,7 +50,7 @@ class NudgeAction extends Action
{
/**
* Class handler.
*
*
* @param array $args array of arguments
*
* @return nothing
@ -75,7 +75,7 @@ class NudgeAction extends Action
// 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;
@ -100,7 +100,8 @@ class NudgeAction extends Action
} else {
// display a confirmation to the user
common_redirect(common_local_url('showstream',
array('nickname' => $other->nickname)));
array('nickname' => $other->nickname)),
303);
}
}

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,6 +166,22 @@ class PublicAction extends Action
$nav->show();
}
function showEmptyList()
{
$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!');
}
else {
$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));
$this->elementEnd('div');
}
/**
* Fill the content area
*
@ -189,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

@ -64,6 +64,22 @@ class PublictagcloudAction extends Action
common_config('site', 'name')));
}
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!');
}
else {
$message .= _('Why not [register an account](%%action.register%%) and be the first to post one!');
}
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
function showLocalNav()
{
$nav = new PublicGroupNav($this);
@ -126,6 +142,8 @@ class PublictagcloudAction extends Action
$this->elementEnd('dd');
$this->elementEnd('dl');
$this->elementEnd('div');
} else {
$this->showEmptyList();
}
}

View File

@ -97,9 +97,9 @@ class RemotesubscribeAction extends Action
'class' => 'form_settings',
'action' => common_local_url('remotesubscribe')));
$this->elementStart('fieldset');
$this->element('legend', 'Subscribe to a remote user');
$this->element('legend', _('Subscribe to a remote user'));
$this->hidden('token', common_session_token());
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('nickname', _('User nickname'), $this->nickname,
@ -407,7 +407,7 @@ class RemotesubscribeAction extends Action
# Redirect to authorization service
common_redirect($req->to_url());
common_redirect($req->to_url(), 303);
return;
}
}

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

@ -73,11 +73,17 @@ class ShowgroupAction extends Action
function title()
{
if (!empty($this->group->fullname)) {
$base = $this->group->fullname . ' (' . $this->group->nickname . ')';
} else {
$base = $this->group->nickname;
}
if ($this->page == 1) {
return sprintf(_("%s group"), $this->group->nickname);
return sprintf(_("%s group"), $base);
} else {
return sprintf(_("%s group, page %d"),
$this->group->nickname,
$base,
$this->page);
}
}

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;
@ -67,54 +63,21 @@ class ShowstreamAction extends Action
function title()
{
if (!empty($this->profile->fullname)) {
$base = $this->profile->fullname . ' (' . $this->user->nickname . ') ';
} else {
$base = $this->user->nickname;
}
if ($this->page == 1) {
return $this->user->nickname;
return $base;
} else {
return sprintf(_("%s, page %d"),
$this->user->nickname,
$base,
$this->page);
}
}
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)
{
@ -140,16 +103,6 @@ class ShowstreamAction extends Action
$nav->show();
}
function showPageTitle()
{
$user =& common_current_user();
if ($user && ($user->id == $this->profile->id)) {
$this->element('h1', NULL, _("Your profile"));
} else {
$this->element('h1', NULL, sprintf(_('%s\'s profile'), $this->profile->nickname));
}
}
function showPageNoticeBlock()
{
return;
@ -376,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'))) {
@ -554,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

@ -488,7 +488,8 @@ class SmssettingsAction extends ConnectSettingsAction
}
common_redirect(common_local_url('confirmaddress',
array('code' => $code)));
array('code' => $code)),
303);
}
/**

View File

@ -85,7 +85,8 @@ class SubeditAction extends Action
}
common_redirect(common_local_url('subscriptions',
array('nickname' => $cur->nickname)));
array('nickname' => $cur->nickname)),
303);
}
}
}

View File

@ -75,7 +75,8 @@ class SubscribeAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subscriptions', array('nickname' =>
$user->nickname)));
$user->nickname)),
303);
}
}
}

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
@ -117,7 +139,7 @@ class SubscriptionsList extends ProfileList
$this->out->elementStart('form', array('id' => 'subedit-' . $profile->id,
'method' => 'post',
'class' => 'form_subcription_edit',
'class' => 'form_subscription_edit',
'action' => common_local_url('subedit')));
$this->out->hidden('token', common_session_token());
$this->out->hidden('profile', $profile->id);

View File

@ -45,7 +45,7 @@ class SupAction extends Action
function availablePeriods()
{
static $periods = array(86400, 43200, 21600, 7200,
3600, 1800, 600, 300, 120,
3600, 1800, 600, 300, 120,
60, 30, 15);
$available = array();
foreach ($periods as $period) {

View File

@ -33,7 +33,9 @@ class TagAction extends Action
}
if ($this->tag != $taginput) {
common_redirect(common_local_url('tag', array('tag' => $this->tag)));
common_redirect(common_local_url('tag', array('tag' => $this->tag)),
301);
return false;
}
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;

View File

@ -221,7 +221,8 @@ class TagotherAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url($action, array('nickname' =>
$user->nickname)));
$user->nickname)),
303);
}
}

View File

@ -116,10 +116,11 @@ class UnblockAction extends Action
}
}
if ($action) {
common_redirect(common_local_url($action, $args));
common_redirect(common_local_url($action, $args), 303);
} else {
common_redirect(common_local_url('subscriptions',
array('nickname' => $cur->nickname)));
array('nickname' => $cur->nickname)),
303);
}
}
}

View File

@ -77,7 +77,8 @@ class UnsubscribeAction extends Action
$this->elementEnd('html');
} else {
common_redirect(common_local_url('subscriptions', array('nickname' =>
$user->nickname)));
$user->nickname)),
303);
}
}
}

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

@ -67,6 +67,8 @@ class Notice extends Memcached_DataObject
$this->blowSubsCache(true);
$this->query('BEGIN');
//Null any notices that are replies to this notice
$this->query(sprintf("UPDATE notice set reply_to = null WHERE reply_to = %d", $this->id));
$related = array('Reply',
'Fave',
'Notice_tag',

View 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,11 +17,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('LACONICA')) { exit(1); }
if (!defined('LACONICA')) {
exit(1);
}
/**
* Table Definition for user
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
require_once 'Validate.php';
@ -79,13 +82,13 @@ class User extends Memcached_DataObject
function isSubscribed($other)
{
assert(!is_null($other));
# XXX: cache results of this query
// XXX: cache results of this query
$sub = Subscription::pkeyGet(array('subscriber' => $this->id,
'subscribed' => $other->id));
return (is_null($sub)) ? false : true;
}
# 'update' won't write key columns, so we have to do it ourselves.
// 'update' won't write key columns, so we have to do it ourselves.
function updateKeys(&$orig)
{
@ -96,7 +99,7 @@ class User extends Memcached_DataObject
}
}
if (count($parts) == 0) {
# No changes
// No changes
return true;
}
$toupdate = implode(', ', $parts);
@ -117,7 +120,7 @@ class User extends Memcached_DataObject
function allowed_nickname($nickname)
{
# XXX: should already be validated for size, content, etc.
// XXX: should already be validated for size, content, etc.
static $blacklist = array('rss', 'xrds', 'doc', 'main',
'settings', 'notice', 'user',
'search', 'avatar', 'tag', 'tags',
@ -147,7 +150,7 @@ class User extends Memcached_DataObject
$sub->subscriber = $this->id;
$sub->subscribed = $other->id;
$sub->created = common_sql_now(); # current time
$sub->created = common_sql_now(); // current time
if (!$sub->insert()) {
return false;
@ -173,7 +176,7 @@ class User extends Memcached_DataObject
static function register($fields) {
# MAGICALLY put fields into current scope
// MAGICALLY put fields into current scope
extract($fields);
@ -211,11 +214,11 @@ class User extends Memcached_DataObject
$user->id = $id;
$user->nickname = $nickname;
if (!empty($password)) { # may not have a password for OpenID users
if (!empty($password)) { // may not have a password for OpenID users
$user->password = common_munge_password($password, $id);
}
# Users who respond to invite email have proven their ownership of that address
// Users who respond to invite email have proven their ownership of that address
if (!empty($code)) {
$invite = Invitation::staticGet($code);
@ -240,7 +243,7 @@ class User extends Memcached_DataObject
return false;
}
# Everyone is subscribed to themself
// Everyone is subscribed to themself
$subscription = new Subscription();
$subscription->subscriber = $user->id;
@ -273,16 +276,58 @@ class User extends Memcached_DataObject
$user->emailChanged();
}
// Default system subscription
$defnick = common_config('newuser', 'default');
if (!empty($defnick)) {
$defuser = User::staticGet('nickname', $defnick);
if (empty($defuser)) {
common_log(LOG_WARNING, sprintf("Default user %s does not exist.", $defnick),
__FILE__);
} else {
$defsub = new Subscription();
$defsub->subscriber = $user->id;
$defsub->subscribed = $defuser->id;
$defsub->created = $user->created;
$result = $defsub->insert();
if (!$result) {
common_log_db_error($defsub, 'INSERT', __FILE__);
return false;
}
}
}
$profile->query('COMMIT');
if ($email && !$user->email) {
mail_confirm_address($user, $confirm->code, $profile->nickname, $email);
}
// Welcome message
$welcome = common_config('newuser', 'welcome');
if (!empty($welcome)) {
$welcomeuser = User::staticGet('nickname', $welcome);
if (empty($welcomeuser)) {
common_log(LOG_WARNING, sprintf("Welcome user %s does not exist.", $defnick),
__FILE__);
} else {
$notice = Notice::saveNew($welcomeuser->id,
sprintf(_('Welcome to %1$s, @%2$s!'),
common_config('site', 'name'),
$user->nickname),
'system');
}
}
return $user;
}
# Things we do when the email changes
// Things we do when the email changes
function emailChanged()
{
@ -303,46 +348,46 @@ class User extends Memcached_DataObject
{
$cache = common_memcache();
# XXX: Kind of a hack.
// XXX: Kind of a hack.
if ($cache) {
# This is the stream of favorite notices, in rev chron
# order. This forces it into cache.
// This is the stream of favorite notices, in rev chron
// order. This forces it into cache.
$faves = $this->favoriteNotices(0, NOTICE_CACHE_WINDOW);
$cnt = 0;
while ($faves->fetch()) {
if ($faves->id < $notice->id) {
# If we passed it, it's not a fave
// If we passed it, it's not a fave
return false;
} else if ($faves->id == $notice->id) {
# If it matches a cached notice, then it's a fave
// If it matches a cached notice, then it's a fave
return true;
}
$cnt++;
}
# If we're not past the end of the cache window,
# then the cache has all available faves, so this one
# is not a fave.
// If we're not past the end of the cache window,
// then the cache has all available faves, so this one
// is not a fave.
if ($cnt < NOTICE_CACHE_WINDOW) {
return false;
}
# Otherwise, cache doesn't have all faves;
# fall through to the default
// Otherwise, cache doesn't have all faves;
// fall through to the default
}
$fave = Fave::pkeyGet(array('user_id' => $this->id,
'notice_id' => $notice->id));
return ((is_null($fave)) ? false : true);
}
function mutuallySubscribed($other)
{
return $this->isSubscribed($other) &&
$other->isSubscribed($this);
}
function mutuallySubscribedUsers()
{
# 3-way join; probably should get cached
$UT = common_config('db','type')=='pgsql'?'"user"':'user';
function mutuallySubscribedUsers()
{
// 3-way join; probably should get cached
$UT = common_config('db','type')=='pgsql'?'"user"':'user';
$qry = "SELECT $UT.* " .
"FROM subscription sub1 JOIN $UT ON sub1.subscribed = $UT.id " .
"JOIN subscription sub2 ON $UT.id = sub2.subscriber " .
@ -365,8 +410,8 @@ class User extends Memcached_DataObject
$offset, $limit, $since_id, $before_id, null, $since);
}
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$profile = $this->getProfile();
if (!$profile) {
return null;
@ -375,8 +420,8 @@ class User extends Memcached_DataObject
}
}
function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE)
{
function favoriteNotices($offset=0, $limit=NOTICES_PER_PAGE)
{
$qry =
'SELECT notice.* ' .
'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
@ -386,12 +431,12 @@ class User extends Memcached_DataObject
$offset, $limit);
}
function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
function noticesWithFriends($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$enabled = common_config('inboxes', 'enabled');
# Complicated code, depending on whether we support inboxes yet
# XXX: make this go away when inboxes become mandatory
// 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)) {
@ -401,13 +446,13 @@ class User extends Memcached_DataObject
'WHERE subscription.subscriber = %d ';
$order = null;
} else if ($enabled === true ||
($enabled == 'transitional' && $this->inboxed == 1)) {
($enabled == 'transitional' && $this->inboxed == 1)) {
$qry =
'SELECT notice.* ' .
'FROM notice JOIN notice_inbox ON notice.id = notice_inbox.notice_id ' .
'WHERE notice_inbox.user_id = %d ';
# NOTE: we override ORDER
// NOTE: we override ORDER
$order = null;
}
return Notice::getStream(sprintf($qry, $this->id),
@ -416,35 +461,34 @@ class User extends Memcached_DataObject
$order, $since);
}
function blowFavesCache()
{
function blowFavesCache()
{
$cache = common_memcache();
if ($cache) {
# Faves don't happen chronologically, so we need to blow
# ;last cache, too
// Faves don't happen chronologically, so we need to blow
// ;last cache, too
$cache->delete(common_cache_key('user:faves:'.$this->id));
$cache->delete(common_cache_key('user:faves:'.$this->id).';last');
}
}
function getSelfTags()
{
function getSelfTags()
{
return Profile_tag::getTags($this->id, $this->id);
}
function setSelfTags($newtags)
{
function setSelfTags($newtags)
{
return Profile_tag::setTags($this->id, $this->id, $newtags);
}
function block($other)
{
# Add a new block record
// Add a new block record
$block = new Profile_block();
# Begin a transaction
// Begin a transaction
$block->query('BEGIN');
@ -458,7 +502,7 @@ class User extends Memcached_DataObject
return false;
}
# Cancel their subscription, if it exists
// Cancel their subscription, if it exists
$sub = Subscription::pkeyGet(array('subscriber' => $other->id,
'subscribed' => $this->id));
@ -478,8 +522,7 @@ class User extends Memcached_DataObject
function unblock($other)
{
# Get the block record
// Get the block record
$block = Profile_block::get($this->id, $other->id);

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
@ -174,3 +178,13 @@ $config['sphinx']['port'] = 3312;
#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';

View File

@ -13,7 +13,7 @@ create table profile (
index profile_nickname_idx (nickname),
FULLTEXT(nickname, fullname, location, bio, homepage)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
create table avatar (
profile_id integer not null comment 'foreign key to profile table' references profile (id),
@ -73,7 +73,7 @@ create table user (
modified timestamp comment 'date this record was modified',
index user_smsemail_idx (smsemail)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
/* remote people */
@ -103,7 +103,6 @@ create table subscription (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table notice (
id integer auto_increment primary key comment 'unique identifier',
profile_id integer not null comment 'who made the update' references profile (id),
uri varchar(255) unique key comment 'universally unique identifier, usually a tag URI',
@ -119,7 +118,7 @@ create table notice (
index notice_profile_id_idx (profile_id),
index notice_created_idx (created),
FULLTEXT(content)
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_bin;
) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
create table notice_source (
code varchar(32) primary key not null comment 'source code',
@ -130,7 +129,6 @@ create table notice_source (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table reply (
notice_id integer not null comment 'notice that is the reply' references notice (id),
profile_id integer not null comment 'profile replied to' references profile (id),
modified timestamp not null comment 'date this record was modified',
@ -144,7 +142,6 @@ create table reply (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table fave (
notice_id integer not null comment 'notice that is the favorite' references notice (id),
user_id integer not null comment 'user who likes this notice' references user (id),
modified timestamp not null comment 'date this record was modified',
@ -321,7 +318,6 @@ create table invitation (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table message (
id integer auto_increment primary key comment 'unique identifier',
uri varchar(255) unique key comment 'universally unique identifier',
from_profile integer not null comment 'who the message is from' references profile (id),
@ -336,10 +332,9 @@ create table message (
index message_from_idx (from_profile),
index message_to_idx (to_profile),
index message_created_idx (created)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
create table notice_inbox (
user_id integer not null comment 'user receiving the message' references user (id),
notice_id integer not null comment 'notice received' references notice (id),
created datetime not null comment 'date the notice was created',
@ -362,7 +357,6 @@ create table profile_tag (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table profile_block (
blocker integer not null comment 'user making the block' references user (id),
blocked integer not null comment 'profile that is blocked' references profile (id),
modified timestamp comment 'date of blocking',
@ -372,7 +366,6 @@ create table profile_block (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table user_group (
id integer auto_increment primary key comment 'unique identifier',
nickname varchar(64) unique key comment 'nickname for addressing',
@ -391,10 +384,9 @@ create table user_group (
index user_group_nickname_idx (nickname)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
create table group_member (
group_id integer not null comment 'foreign key to user_group' references user_group (id),
profile_id integer not null comment 'foreign key to profile table' references profile (id),
is_admin boolean default false comment 'is this user an admin?',
@ -409,7 +401,6 @@ create table group_member (
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table related_group (
group_id integer not null comment 'foreign key to user_group' references user_group (id),
related_group_id integer not null comment 'foreign key to user_group' references user_group (id),

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()),
@ -45,4 +46,5 @@ VALUES
('twitux','Twitux','http://live.gnome.org/DanielMorales/Twitux', now()),
('twitvim','TwitVim','http://vim.sourceforge.net/scripts/script.php?script_id=2204', now()),
('urfastr','urfastr','http://urfastr.net/', now()),
('adium', 'Adium', 'http://www.adiumx.com/', now());
('adium', 'Adium', 'http://www.adiumx.com/', now()),
('yatca','Yatca','http://www.yatca.com/', 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

499
js/jquery.js vendored
View File

@ -1,13 +1,13 @@
/*!
* jQuery JavaScript Library v1.3.1
* jQuery JavaScript Library v1.3.2
* http://jquery.com/
*
* Copyright (c) 2009 John Resig
* Dual licensed under the MIT and GPL licenses.
* http://docs.jquery.com/License
*
* Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009)
* Revision: 6158
* Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
* Revision: 6246
*/
(function(){
@ -88,14 +88,16 @@ jQuery.fn = jQuery.prototype = {
this.context = selector.context;
}
return this.setArray(jQuery.makeArray(selector));
return this.setArray(jQuery.isArray( selector ) ?
selector :
jQuery.makeArray(selector));
},
// Start with an empty selector
selector: "",
// The current version of jQuery being used
jquery: "1.3.1",
jquery: "1.3.2",
// The number of elements contained in the matched element set
size: function() {
@ -108,7 +110,7 @@ jQuery.fn = jQuery.prototype = {
return num === undefined ?
// Return a 'clean' array
jQuery.makeArray( this ) :
Array.prototype.slice.call( this ) :
// Return just the object
this[ num ];
@ -278,23 +280,21 @@ jQuery.fn = jQuery.prototype = {
},
// For internal use only.
// Behaves like an Array's .push method, not like a jQuery method.
// Behaves like an Array's method, not like a jQuery method.
push: [].push,
sort: [].sort,
splice: [].splice,
find: function( selector ) {
if ( this.length === 1 && !/,/.test(selector) ) {
if ( this.length === 1 ) {
var ret = this.pushStack( [], "find", selector );
ret.length = 0;
jQuery.find( selector, this[0], ret );
return ret;
} else {
var elems = jQuery.map(this, function(elem){
return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
return jQuery.find( selector, elem );
});
return this.pushStack( /[^+>] [^+>]/.test( selector ) ?
jQuery.unique( elems ) :
elems, "find", selector );
})), "find", selector );
}
},
@ -310,33 +310,37 @@ jQuery.fn = jQuery.prototype = {
// attributes in IE that are actually only stored
// as properties will not be copied (such as the
// the name attribute on an input).
var clone = this.cloneNode(true),
container = document.createElement("div");
container.appendChild(clone);
return jQuery.clean([container.innerHTML])[0];
var html = this.outerHTML;
if ( !html ) {
var div = this.ownerDocument.createElement("div");
div.appendChild( this.cloneNode(true) );
html = div.innerHTML;
}
return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
} else
return this.cloneNode(true);
});
// Need to set the expando to null on the cloned set if it exists
// removeData doesn't work here, IE removes it from the original as well
// this is primarily for IE but the data expando shouldn't be copied over in any browser
var clone = ret.find("*").andSelf().each(function(){
if ( this[ expando ] !== undefined )
this[ expando ] = null;
});
// Copy the events from the original to the clone
if ( events === true )
this.find("*").andSelf().each(function(i){
if (this.nodeType == 3)
return;
var events = jQuery.data( this, "events" );
if ( events === true ) {
var orig = this.find("*").andSelf(), i = 0;
for ( var type in events )
for ( var handler in events[ type ] )
jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
ret.find("*").andSelf().each(function(){
if ( this.nodeName !== orig[i].nodeName )
return;
var events = jQuery.data( orig[i], "events" );
for ( var type in events ) {
for ( var handler in events[ type ] ) {
jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
}
}
i++;
});
}
// Return the cloned set
return ret;
@ -355,14 +359,18 @@ jQuery.fn = jQuery.prototype = {
},
closest: function( selector ) {
var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null;
var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
closer = 0;
return this.map(function(){
var cur = this;
while ( cur && cur.ownerDocument ) {
if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) )
if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
jQuery.data(cur, "closest", closer);
return cur;
}
cur = cur.parentNode;
closer++;
}
});
},
@ -475,7 +483,7 @@ jQuery.fn = jQuery.prototype = {
html: function( value ) {
return value === undefined ?
(this[0] ?
this[0].innerHTML :
this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
null) :
this.empty().append( value );
},
@ -507,13 +515,13 @@ jQuery.fn = jQuery.prototype = {
if ( this[0] ) {
var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
first = fragment.firstChild,
extra = this.length > 1 ? fragment.cloneNode(true) : fragment;
first = fragment.firstChild;
if ( first )
for ( var i = 0, l = this.length; i < l; i++ )
callback.call( root(this[i], first), i > 0 ? extra.cloneNode(true) : fragment );
callback.call( root(this[i], first), this.length > 1 || i > 0 ?
fragment.cloneNode(true) : fragment );
if ( scripts )
jQuery.each( scripts, evalScript );
}
@ -636,9 +644,7 @@ jQuery.extend({
// Evalulates a script in a global context
globalEval: function( data ) {
data = jQuery.trim( data );
if ( data ) {
if ( data && /\S/.test(data) ) {
// Inspired by code by Andrea Giammarchi
// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
var head = document.getElementsByTagName("head")[0] || document.documentElement,
@ -741,26 +747,32 @@ jQuery.extend({
elem.style[ name ] = old[ name ];
},
css: function( elem, name, force ) {
css: function( elem, name, force, extra ) {
if ( name == "width" || name == "height" ) {
var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
function getWH() {
val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
var padding = 0, border = 0;
if ( extra === "border" )
return;
jQuery.each( which, function() {
padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
if ( !extra )
val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
if ( extra === "margin" )
val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
else
val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
});
val -= Math.round(padding + border);
}
if ( jQuery(elem).is(":visible") )
if ( elem.offsetWidth !== 0 )
getWH();
else
jQuery.swap( elem, props, getWH );
return Math.max(0, val);
return Math.max(0, Math.round(val));
}
return jQuery.curCSS( elem, name, force );
@ -866,7 +878,7 @@ jQuery.extend({
});
// Trim whitespace, otherwise indexOf won't work as expected
var tags = jQuery.trim( elem ).toLowerCase();
var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
var wrap =
// option or optgroup
@ -906,11 +918,12 @@ jQuery.extend({
if ( !jQuery.support.tbody ) {
// String was a <table>, *may* have spurious <tbody>
var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
div.firstChild && div.firstChild.childNodes :
var hasBody = /<tbody/i.test(elem),
tbody = !tags.indexOf("<table") && !hasBody ?
div.firstChild && div.firstChild.childNodes :
// String was a bare <thead> or <tfoot>
wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
wrap[1] == "<table>" && !hasBody ?
div.childNodes :
[];
@ -1189,13 +1202,16 @@ jQuery.each({
insertAfter: "after",
replaceAll: "replaceWith"
}, function(name, original){
jQuery.fn[ name ] = function() {
var args = arguments;
jQuery.fn[ name ] = function( selector ) {
var ret = [], insert = jQuery( selector );
return this.each(function(){
for ( var i = 0, length = args.length; i < length; i++ )
jQuery( args[ i ] )[ original ]( this );
});
for ( var i = 0, l = insert.length; i < l; i++ ) {
var elems = (i > 0 ? this.clone(true) : this).get();
jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
ret = ret.concat( elems );
}
return this.pushStack( ret, name, selector );
};
});
@ -1234,7 +1250,7 @@ jQuery.each({
empty: function() {
// Remove element nodes and prevent memory leaks
jQuery( ">*", this ).remove();
jQuery(this).children().remove();
// Remove any remaining nodes
while ( this.firstChild )
@ -1402,7 +1418,7 @@ jQuery.fn.extend({
*/
(function(){
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g,
var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
done = 0,
toString = Object.prototype.toString;
@ -1507,6 +1523,19 @@ var Sizzle = function(selector, context, results, seed) {
if ( extra ) {
Sizzle( extra, context, results, seed );
if ( sortOrder ) {
hasDuplicate = false;
results.sort(sortOrder);
if ( hasDuplicate ) {
for ( var i = 1; i < results.length; i++ ) {
if ( results[i] === results[i-1] ) {
results.splice(i--, 1);
}
}
}
}
}
return results;
@ -1548,7 +1577,8 @@ Sizzle.find = function(expr, context, isXML){
};
Sizzle.filter = function(expr, set, inplace, not){
var old = expr, result = [], curLoop = set, match, anyFound;
var old = expr, result = [], curLoop = set, match, anyFound,
isXMLFilter = set && set[0] && isXML(set[0]);
while ( expr && set.length ) {
for ( var type in Expr.filter ) {
@ -1561,7 +1591,7 @@ Sizzle.filter = function(expr, set, inplace, not){
}
if ( Expr.preFilter[ type ] ) {
match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not );
match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
if ( !match ) {
anyFound = found = true;
@ -1606,8 +1636,6 @@ Sizzle.filter = function(expr, set, inplace, not){
}
}
expr = expr.replace(/\s*,\s*/, "");
// Improper expression
if ( expr == old ) {
if ( anyFound == null ) {
@ -1645,26 +1673,33 @@ var Expr = Sizzle.selectors = {
}
},
relative: {
"+": function(checkSet, part){
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];
if ( elem ) {
var cur = elem.previousSibling;
while ( cur && cur.nodeType !== 1 ) {
cur = cur.previousSibling;
}
checkSet[i] = typeof part === "string" ?
cur || false :
cur === part;
"+": function(checkSet, part, isXML){
var isPartStr = typeof part === "string",
isTag = isPartStr && !/\W/.test(part),
isPartStrNotTag = isPartStr && !isTag;
if ( isTag && !isXML ) {
part = part.toUpperCase();
}
for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
if ( (elem = checkSet[i]) ) {
while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
elem || false :
elem === part;
}
}
if ( typeof part === "string" ) {
if ( isPartStrNotTag ) {
Sizzle.filter( part, checkSet, true );
}
},
">": function(checkSet, part, isXML){
if ( typeof part === "string" && !/\W/.test(part) ) {
var isPartStr = typeof part === "string";
if ( isPartStr && !/\W/.test(part) ) {
part = isXML ? part : part.toUpperCase();
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
@ -1678,19 +1713,19 @@ var Expr = Sizzle.selectors = {
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];
if ( elem ) {
checkSet[i] = typeof part === "string" ?
checkSet[i] = isPartStr ?
elem.parentNode :
elem.parentNode === part;
}
}
if ( typeof part === "string" ) {
if ( isPartStr ) {
Sizzle.filter( part, checkSet, true );
}
}
},
"": function(checkSet, part, isXML){
var doneName = "done" + (done++), checkFn = dirCheck;
var doneName = done++, checkFn = dirCheck;
if ( !part.match(/\W/) ) {
var nodeCheck = part = isXML ? part : part.toUpperCase();
@ -1700,7 +1735,7 @@ var Expr = Sizzle.selectors = {
checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
},
"~": function(checkSet, part, isXML){
var doneName = "done" + (done++), checkFn = dirCheck;
var doneName = done++, checkFn = dirCheck;
if ( typeof part === "string" && !part.match(/\W/) ) {
var nodeCheck = part = isXML ? part : part.toUpperCase();
@ -1718,8 +1753,16 @@ var Expr = Sizzle.selectors = {
}
},
NAME: function(match, context, isXML){
if ( typeof context.getElementsByName !== "undefined" && !isXML ) {
return context.getElementsByName(match[1]);
if ( typeof context.getElementsByName !== "undefined" ) {
var ret = [], results = context.getElementsByName(match[1]);
for ( var i = 0, l = results.length; i < l; i++ ) {
if ( results[i].getAttribute("name") === match[1] ) {
ret.push( results[i] );
}
}
return ret.length === 0 ? null : ret;
}
},
TAG: function(match, context){
@ -1727,13 +1770,16 @@ var Expr = Sizzle.selectors = {
}
},
preFilter: {
CLASS: function(match, curLoop, inplace, result, not){
CLASS: function(match, curLoop, inplace, result, not, isXML){
match = " " + match[1].replace(/\\/g, "") + " ";
var elem;
for ( var i = 0; (elem = curLoop[i]) != null; i++ ) {
if ( isXML ) {
return match;
}
for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
if ( elem ) {
if ( not ^ (" " + elem.className + " ").indexOf(match) >= 0 ) {
if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
if ( !inplace )
result.push( elem );
} else if ( inplace ) {
@ -1764,14 +1810,14 @@ var Expr = Sizzle.selectors = {
}
// TODO: Move to normal caching system
match[0] = "done" + (done++);
match[0] = done++;
return match;
},
ATTR: function(match){
ATTR: function(match, curLoop, inplace, result, not, isXML){
var name = match[1].replace(/\\/g, "");
if ( Expr.attrMap[name] ) {
if ( !isXML && Expr.attrMap[name] ) {
match[1] = Expr.attrMap[name];
}
@ -1784,7 +1830,7 @@ var Expr = Sizzle.selectors = {
PSEUDO: function(match, curLoop, inplace, result, not){
if ( match[1] === "not" ) {
// If we're dealing with a complex expression, or a simple one
if ( match[3].match(chunker).length > 1 ) {
if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
match[3] = Sizzle(match[3], null, null, curLoop);
} else {
var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
@ -1793,7 +1839,7 @@ var Expr = Sizzle.selectors = {
}
return false;
}
} else if ( Expr.match.POS.test( match[0] ) ) {
} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
return true;
}
@ -1890,47 +1936,6 @@ var Expr = Sizzle.selectors = {
}
},
filter: {
CHILD: function(elem, match){
var type = match[1], parent = elem.parentNode;
var doneName = match[0];
if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) {
var count = 1;
for ( var node = parent.firstChild; node; node = node.nextSibling ) {
if ( node.nodeType == 1 ) {
node.nodeIndex = count++;
}
}
parent[ doneName ] = count - 1;
}
if ( type == "first" ) {
return elem.nodeIndex == 1;
} else if ( type == "last" ) {
return elem.nodeIndex == parent[ doneName ];
} else if ( type == "only" ) {
return parent[ doneName ] == 1;
} else if ( type == "nth" ) {
var add = false, first = match[2], last = match[3];
if ( first == 1 && last == 0 ) {
return true;
}
if ( first == 0 ) {
if ( elem.nodeIndex == last ) {
add = true;
}
} else if ( (elem.nodeIndex - last) % first == 0 && (elem.nodeIndex - last) / first >= 0 ) {
add = true;
}
return add;
}
},
PSEUDO: function(elem, match, i, array){
var name = match[1], filter = Expr.filters[ name ];
@ -1950,6 +1955,49 @@ var Expr = Sizzle.selectors = {
return true;
}
},
CHILD: function(elem, match){
var type = match[1], node = elem;
switch (type) {
case 'only':
case 'first':
while (node = node.previousSibling) {
if ( node.nodeType === 1 ) return false;
}
if ( type == 'first') return true;
node = elem;
case 'last':
while (node = node.nextSibling) {
if ( node.nodeType === 1 ) return false;
}
return true;
case 'nth':
var first = match[2], last = match[3];
if ( first == 1 && last == 0 ) {
return true;
}
var doneName = match[0],
parent = elem.parentNode;
if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
var count = 0;
for ( node = parent.firstChild; node; node = node.nextSibling ) {
if ( node.nodeType === 1 ) {
node.nodeIndex = ++count;
}
}
parent.sizcache = doneName;
}
var diff = elem.nodeIndex - last;
if ( first == 0 ) {
return diff == 0;
} else {
return ( diff % first == 0 && diff / first >= 0 );
}
}
},
ID: function(elem, match){
return elem.nodeType === 1 && elem.getAttribute("id") === match;
},
@ -1957,10 +2005,20 @@ var Expr = Sizzle.selectors = {
return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
},
CLASS: function(elem, match){
return match.test( elem.className );
return (" " + (elem.className || elem.getAttribute("class")) + " ")
.indexOf( match ) > -1;
},
ATTR: function(elem, match){
var result = Expr.attrHandle[ match[1] ] ? Expr.attrHandle[ match[1] ]( elem ) : elem[ match[1] ] || elem.getAttribute( match[1] ), value = result + "", type = match[2], check = match[4];
var name = match[1],
result = Expr.attrHandle[ name ] ?
Expr.attrHandle[ name ]( elem ) :
elem[ name ] != null ?
elem[ name ] :
elem.getAttribute( name ),
value = result + "",
type = match[2],
check = match[4];
return result == null ?
type === "!=" :
type === "=" ?
@ -1969,8 +2027,8 @@ var Expr = Sizzle.selectors = {
value.indexOf(check) >= 0 :
type === "~=" ?
(" " + value + " ").indexOf(check) >= 0 :
!match[4] ?
result :
!check ?
value && result !== false :
type === "!=" ?
value != check :
type === "^=" ?
@ -2036,6 +2094,39 @@ try {
};
}
var sortOrder;
if ( document.documentElement.compareDocumentPosition ) {
sortOrder = function( a, b ) {
var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
if ( ret === 0 ) {
hasDuplicate = true;
}
return ret;
};
} else if ( "sourceIndex" in document.documentElement ) {
sortOrder = function( a, b ) {
var ret = a.sourceIndex - b.sourceIndex;
if ( ret === 0 ) {
hasDuplicate = true;
}
return ret;
};
} else if ( document.createRange ) {
sortOrder = function( a, b ) {
var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
aRange.selectNode(a);
aRange.collapse(true);
bRange.selectNode(b);
bRange.collapse(true);
var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
if ( ret === 0 ) {
hasDuplicate = true;
}
return ret;
};
}
// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
@ -2099,7 +2190,8 @@ try {
// Check to see if an attribute returns normalized href attributes
div.innerHTML = "<a href='#'></a>";
if ( div.firstChild && div.firstChild.getAttribute("href") !== "#" ) {
if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
div.firstChild.getAttribute("href") !== "#" ) {
Expr.attrHandle.href = function(elem){
return elem.getAttribute("href", 2);
};
@ -2136,29 +2228,50 @@ if ( document.querySelectorAll ) (function(){
Sizzle.matches = oldSizzle.matches;
})();
if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) {
if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
var div = document.createElement("div");
div.innerHTML = "<div class='test e'></div><div class='test'></div>";
// Opera can't find a second classname (in 9.6)
if ( div.getElementsByClassName("e").length === 0 )
return;
// Safari caches class attributes, doesn't catch changes (in 3.2)
div.lastChild.className = "e";
if ( div.getElementsByClassName("e").length === 1 )
return;
Expr.order.splice(1, 0, "CLASS");
Expr.find.CLASS = function(match, context) {
return context.getElementsByClassName(match[1]);
Expr.find.CLASS = function(match, context, isXML) {
if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
return context.getElementsByClassName(match[1]);
}
};
}
})();
function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
var sibDir = dir == "previousSibling" && !isXML;
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];
if ( elem ) {
if ( sibDir && elem.nodeType === 1 ){
elem.sizcache = doneName;
elem.sizset = i;
}
elem = elem[dir];
var match = false;
while ( elem && elem.nodeType ) {
var done = elem[doneName];
if ( done ) {
match = checkSet[ done ];
while ( elem ) {
if ( elem.sizcache === doneName ) {
match = checkSet[elem.sizset];
break;
}
if ( elem.nodeType === 1 && !isXML )
elem[doneName] = i;
if ( elem.nodeType === 1 && !isXML ){
elem.sizcache = doneName;
elem.sizset = i;
}
if ( elem.nodeName === cur ) {
match = elem;
@ -2174,22 +2287,28 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
}
function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
var sibDir = dir == "previousSibling" && !isXML;
for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];
if ( elem ) {
if ( sibDir && elem.nodeType === 1 ) {
elem.sizcache = doneName;
elem.sizset = i;
}
elem = elem[dir];
var match = false;
while ( elem && elem.nodeType ) {
if ( elem[doneName] ) {
match = checkSet[ elem[doneName] ];
while ( elem ) {
if ( elem.sizcache === doneName ) {
match = checkSet[elem.sizset];
break;
}
if ( elem.nodeType === 1 ) {
if ( !isXML )
elem[doneName] = i;
if ( !isXML ) {
elem.sizcache = doneName;
elem.sizset = i;
}
if ( typeof cur !== "string" ) {
if ( elem === cur ) {
match = true;
@ -2248,15 +2367,11 @@ jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;
Sizzle.selectors.filters.hidden = function(elem){
return "hidden" === elem.type ||
jQuery.css(elem, "display") === "none" ||
jQuery.css(elem, "visibility") === "hidden";
return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};
Sizzle.selectors.filters.visible = function(elem){
return "hidden" !== elem.type &&
jQuery.css(elem, "display") !== "none" &&
jQuery.css(elem, "visibility") !== "hidden";
return elem.offsetWidth > 0 || elem.offsetHeight > 0;
};
Sizzle.selectors.filters.animated = function(elem){
@ -2552,7 +2667,8 @@ jQuery.event = {
var all, handlers;
event = arguments[0] = jQuery.event.fix( event || window.event );
event.currentTarget = this;
// Namespaced event handlers
var namespaces = event.type.split(".");
event.type = namespaces.shift();
@ -2883,9 +2999,13 @@ function liveHandler( event ){
}
});
elems.sort(function(a,b) {
return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
});
jQuery.each(elems, function(){
if ( this.fn.call(this.elem, event, this.fn.data) === false )
stop = false;
return (stop = false);
});
return stop;
@ -2949,7 +3069,7 @@ function bindReady(){
// If IE and not an iframe
// continually check to see if the document is ready
if ( document.documentElement.doScroll && typeof window.frameElement === "undefined" ) (function(){
if ( document.documentElement.doScroll && window == window.top ) (function(){
if ( jQuery.isReady ) return;
try {
@ -3079,12 +3199,11 @@ jQuery( window ).bind( 'unload', function(){
// document.body must exist before we can do this
jQuery(function(){
var div = document.createElement("div");
div.style.width = "1px";
div.style.paddingLeft = "1px";
div.style.width = div.style.paddingLeft = "1px";
document.body.appendChild( div );
jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
document.body.removeChild( div );
document.body.removeChild( div ).style.display = 'none';
});
})();
@ -3175,7 +3294,7 @@ jQuery.fn.extend({
.filter(function(){
return this.name && !this.disabled &&
(this.checked || /select|textarea/i.test(this.nodeName) ||
/text|hidden|password/i.test(this.type));
/text|hidden|password|search/i.test(this.type));
})
.map(function(i, elem){
var val = jQuery(this).val();
@ -3371,6 +3490,9 @@ jQuery.extend({
done = true;
success();
complete();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
head.removeChild( script );
}
};
@ -3686,9 +3808,15 @@ jQuery.fn.extend({
elemdisplay[ tagName ] = display;
}
this[i].style.display = jQuery.data(this[i], "olddisplay", display);
jQuery.data(this[i], "olddisplay", display);
}
}
// Set the display of the elements in a second loop
// to avoid the constant reflow
for ( var i = 0, l = this.length; i < l; i++ ){
this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
}
return this;
}
@ -3702,8 +3830,14 @@ jQuery.fn.extend({
var old = jQuery.data(this[i], "olddisplay");
if ( !old && old !== "none" )
jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
}
// Set the display of the elements in a second loop
// to avoid the constant reflow
for ( var i = 0, l = this.length; i < l; i++ ){
this[i].style.display = "none";
}
return this;
}
},
@ -3915,7 +4049,7 @@ jQuery.fx.prototype = {
t.elem = this.elem;
if ( t() && jQuery.timers.push(t) == 1 ) {
if ( t() && jQuery.timers.push(t) && !timerId ) {
timerId = setInterval(function(){
var timers = jQuery.timers;
@ -3925,6 +4059,7 @@ jQuery.fx.prototype = {
if ( !timers.length ) {
clearInterval( timerId );
timerId = undefined;
}
}, 13);
}
@ -4193,22 +4328,21 @@ jQuery.each( ['Left', 'Top'], function(i, name) {
jQuery.each([ "Height", "Width" ], function(i, name){
var tl = i ? "Left" : "Top", // top or left
br = i ? "Right" : "Bottom"; // bottom or right
br = i ? "Right" : "Bottom", // bottom or right
lower = name.toLowerCase();
// innerHeight and innerWidth
jQuery.fn["inner" + name] = function(){
return this[ name.toLowerCase() ]() +
num(this, "padding" + tl) +
num(this, "padding" + br);
return this[0] ?
jQuery.css( this[0], lower, false, "padding" ) :
null;
};
// outerHeight and outerWidth
jQuery.fn["outer" + name] = function(margin) {
return this["inner" + name]() +
num(this, "border" + tl + "Width") +
num(this, "border" + br + "Width") +
(margin ?
num(this, "margin" + tl) + num(this, "margin" + br) : 0);
return this[0] ?
jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
null;
};
var type = name.toLowerCase();
@ -4238,4 +4372,5 @@ jQuery.each([ "Height", "Width" ], function(i, name){
this.css( type, typeof size === "string" ? size : size + "px" );
};
});})();
});
})();

10
js/jquery.min.js vendored

File diff suppressed because one or more lines are too long

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));
}
}
/**
@ -326,7 +335,9 @@ class Action extends HTMLOutputter // lawsuit
*/
function showBody()
{
$this->elementStart('body', array('id' => $this->trimmed('action')));
$this->elementStart('body', (common_current_user()) ? array('id' => $this->trimmed('action'),
'class' => 'user_in')
: array('id' => $this->trimmed('action')));
$this->elementStart('div', array('id' => 'wrap'));
if (Event::handle('StartShowHeader', array($this))) {
$this->showHeader();
@ -400,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');
@ -414,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');
@ -604,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));
@ -919,11 +936,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);
@ -87,6 +87,8 @@ $config =
'closed' => false,
'inviteonly' => false,
'private' => false,
'ssl' => 'never',
'sslserver' => null,
'dupelimit' => 60), # default for same person saying the same thing
'syslog' =>
array('appname' => 'laconica', # for syslog
@ -151,6 +153,9 @@ $config =
array('notify' => array()),
'inboxes' =>
array('enabled' => true), # on by default for new sites
'newuser' =>
array('subscribe' => null,
'welcome' => null),
);
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');

View File

@ -57,9 +57,14 @@ class FeaturedUsersSection extends ProfileSection
$quoted[] = "'$nick'";
}
$table = "user";
if(common_config('db','quote_identifiers')) {
$table = '"' . $table . '"';
}
$qry = 'SELECT profile.* ' .
'FROM profile JOIN user on profile.id = user.id ' .
'WHERE user.nickname in (' . implode(',', $quoted) . ') ' .
'FROM profile JOIN '. $table .' on profile.id = '. $table .'.id ' .
'WHERE '. $table .'.nickname in (' . implode(',', $quoted) . ') ' .
'ORDER BY profile.created DESC ';
$limit = PROFILES_PER_SECTION + 1;

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()

View File

@ -258,8 +258,12 @@ class NoticeListItem extends Widget
function showAuthor()
{
$this->out->elementStart('span', 'vcard author');
$this->out->elementStart('a', array('href' => $this->profile->profileurl,
'class' => 'url'));
$attrs = array('href' => $this->profile->profileurl,
'class' => 'url');
if (!empty($this->profile->fullname)) {
$attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ') ';
}
$this->out->elementStart('a', $attrs);
$this->showAvatar();
$this->showNickname();
$this->out->elementEnd('a');
@ -387,6 +391,7 @@ class NoticeListItem extends Widget
case 'xmpp':
case 'mail':
case 'omb':
case 'system':
case 'api':
$this->out->element('dd', null, $source_name);
break;

View File

@ -160,7 +160,7 @@ function oid_authenticate($openid_url, $returnto, $immediate=false)
$auth_request->addExtension($sreg_request);
}
$trust_root = common_path('');
$trust_root = common_root_url(true);
$process_url = common_local_url($returnto);
if ($auth_request->shouldSendRedirect()) {
@ -171,7 +171,7 @@ function oid_authenticate($openid_url, $returnto, $immediate=false)
} else if (Auth_OpenID::isFailure($redirect_url)) {
return sprintf(_('Could not redirect to server: %s'), $redirect_url->message);
} else {
common_redirect($redirect_url);
common_redirect($redirect_url, 303);
}
} else {
// Generate form markup and render it.

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

@ -107,6 +107,9 @@ class Router
$m->connect('main/'.$a, array('action' => $a));
}
$m->connect('main/sup/:seconds', array('action' => 'sup'),
array('seconds' => '[0-9]+'));
$m->connect('main/tagother/:id', array('action' => 'tagother'));
// these take a code

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

@ -78,9 +78,9 @@ class SettingsAction extends Action
common_set_returnto($this->selfUrl());
$user = common_current_user();
if ($user->hasOpenID()) {
common_redirect(common_local_url('openidlogin'));
common_redirect(common_local_url('openidlogin'), 303);
} else {
common_redirect(common_local_url('login'));
common_redirect(common_local_url('login'), 303);
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();

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";
}
}
}
}
}
@ -218,7 +354,7 @@ function is_twitter_bound($notice, $flink) {
return true;
}
}
return false;
}
@ -226,10 +362,10 @@ function broadcast_twitter($notice)
{
$success = true;
$flink = Foreign_link::getByUserID($notice->profile_id,
$flink = Foreign_link::getByUserID($notice->profile_id,
TWITTER_SERVICE);
// XXX: Not sure WHERE to check whether a notice should go to
// 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)) {
@ -244,7 +380,7 @@ function broadcast_twitter($notice)
$options = array(
CURLOPT_USERPWD => "$twitter_user:$twitter_password",
CURLOPT_POST => true,
CURLOPT_POSTFIELDS =>
CURLOPT_POSTFIELDS =>
array(
'status' => $statustxt,
'source' => common_config('integration', 'source')
@ -292,7 +428,6 @@ function broadcast_twitter($notice)
$success = false;
}
}
return $success;
}

View File

@ -581,10 +581,8 @@ function common_shorten_link($url, $reverse = false)
function common_xml_safe_str($str)
{
$xmlStr = htmlentities(iconv('UTF-8', 'UTF-8//IGNORE', $str), ENT_NOQUOTES, 'UTF-8');
// Replace control, formatting, and surrogate characters with '*', ala Twitter
return preg_replace('/[\p{Cc}\p{Cf}\p{Cs}]/u', '*', $str);
// Neutralize control codes and surrogates
return preg_replace('/[\p{Cc}\p{Cs}]/u', '*', $str);
}
function common_tag_link($tag)
@ -622,9 +620,13 @@ function common_at_link($sender_id, $nickname)
$url = $recipient->profileurl;
}
$xs = new XMLStringer(false);
$attrs = array('href' => $url,
'class' => 'url');
if (!empty($recipient->fullname)) {
$attrs['title'] = $recipient->fullname . ' (' . $recipient->nickname . ')';
}
$xs->elementStart('span', 'vcard');
$xs->elementStart('a', array('href' => $url,
'class' => 'url'));
$xs->elementStart('a', $attrs);
$xs->element('span', 'fn nickname', $nickname);
$xs->elementEnd('a');
$xs->elementEnd('span');
@ -639,10 +641,14 @@ function common_group_link($sender_id, $nickname)
$sender = Profile::staticGet($sender_id);
$group = User_group::staticGet('nickname', common_canonical_nickname($nickname));
if ($group && $sender->isMember($group)) {
$attrs = array('href' => $group->permalink(),
'class' => 'url');
if (!empty($group->fullname)) {
$attrs['title'] = $group->fullname . ' (' . $group->nickname . ')';
}
$xs = new XMLStringer();
$xs->elementStart('span', 'vcard');
$xs->elementStart('a', array('href' => $group->permalink(),
'class' => 'url'));
$xs->elementStart('a', $attrs);
$xs->element('span', 'fn nickname', $nickname);
$xs->elementEnd('a');
$xs->elementEnd('span');
@ -713,25 +719,46 @@ function common_relative_profile($sender, $nickname, $dt=null)
function common_local_url($action, $args=null, $params=null, $fragment=null)
{
static $sensitive = array('login', 'register', 'passwordsettings',
'twittersettings', 'finishopenidlogin',
'finishaddopenid', 'api');
$r = Router::get();
$path = $r->build($action, $args, $params, $fragment);
$ssl = in_array($action, $sensitive);
if (common_config('site','fancy')) {
$url = common_path(mb_substr($path, 1));
$url = common_path(mb_substr($path, 1), $ssl);
} else {
if (mb_strpos($path, '/index.php') === 0) {
$url = common_path(mb_substr($path, 1));
$url = common_path(mb_substr($path, 1), $ssl);
} else {
$url = common_path('index.php'.$path);
$url = common_path('index.php'.$path, $ssl);
}
}
return $url;
}
function common_path($relative)
function common_path($relative, $ssl=false)
{
$pathpart = (common_config('site', 'path')) ? common_config('site', 'path')."/" : '';
return "http://".common_config('site', 'server').'/'.$pathpart.$relative;
if (($ssl && (common_config('site', 'ssl') === 'sometimes'))
|| common_config('site', 'ssl') === 'always') {
$proto = 'https';
if (is_string(common_config('site', 'sslserver')) &&
mb_strlen(common_config('site', 'sslserver')) > 0) {
$serverpart = common_config('site', 'sslserver');
} else {
$serverpart = common_config('site', 'server');
}
} else {
$proto = 'http';
$serverpart = common_config('site', 'server');
}
return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
}
function common_date_string($dt)
@ -821,7 +848,7 @@ function common_redirect($url, $code=307)
303 => "See Other",
307 => "Temporary Redirect");
header("Status: ${code} $status[$code]");
header('HTTP/1.1 '.$code.' '.$status[$code]);
header("Location: $url");
$xo = new XMLOutputter();
@ -923,9 +950,9 @@ function common_profile_url($nickname)
// Should make up a reasonable root URL
function common_root_url()
function common_root_url($ssl=false)
{
return common_path('');
return common_path('', $ssl);
}
// returns $bytes bytes of random data as a hexadecimal string
@ -1294,3 +1321,16 @@ function common_compatible_license($from, $to)
// XXX: better compatibility check needed here!
return ($from == $to);
}
/**
* returns a quoted table name, if required according to config
*/
function common_database_tablename($tablename)
{
if(common_config('db','quote_identifiers')) {
$tablename = '"'. $tablename .'"';
}
//table prefixes could be added here later
return $tablename;
}

View File

@ -105,12 +105,13 @@ class LinkbackPlugin extends Plugin
$pb = $match[1];
}
$tb = $this->getTrackback($result->body, $result->final_url);
if (!empty($tb)) {
$this->trackback($result->final_url, $tb);
} else if (!empty($pb)) {
if (!empty($pb)) {
$this->pingback($result->final_url, $pb);
} else {
$tb = $this->getTrackback($result->body, $result->final_url);
if (!empty($tb)) {
$this->trackback($result->final_url, $tb);
}
}
return $orig;

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;
@ -216,6 +217,9 @@ margin-right:0;
address .fn {
font-weight:bold;
}
address img + .fn {
display:none;
}
#header {
width:100%;
@ -247,6 +251,7 @@ position:absolute;
top:65px;
right:18px;
width:250px;
width:24%;
}
#page_notice {
clear:both;
@ -256,9 +261,8 @@ margin-bottom:18px;
#anon_notice {
float:left;
width:432px;
width:28.052em;
padding:11px;
width:43.2%;
padding:1.1%;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
@ -366,7 +370,9 @@ margin-right:4px;
#wrap {
margin:0 auto;
width:1003px;
width:100%;
min-width:760px;
max-width:1003px;
overflow:hidden;
}
@ -378,14 +384,17 @@ margin-bottom:1em;
}
#content {
width:644px;
padding:18px;
width:64.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;
}
#content_inner {
@ -395,10 +404,11 @@ float:left;
}
#aside_primary {
width:280px;
width:27.917%;
min-height:259px;
float:left;
margin-left:4px;
padding:18px;
margin-left:0.385%;
padding:1.795%;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
@ -406,9 +416,8 @@ border-width:1px;
border-style:solid;
}
/*Start: FORM NOTICE*/
#form_notice {
width:458px;
width:45.664%;
float:left;
position:relative;
line-height:1;
@ -425,7 +434,7 @@ float:left;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
width:370px;
width:80.789%;
height:67px;
line-height:1.5;
padding:7px 7px 16px 7px;
@ -455,27 +464,27 @@ line-height:1.15;
padding:1px 2px;
}
#form_notice #notice_action-submit {
width:60px;
padding:8px;
width:14%;
height:47px;
padding:0;
position:absolute;
bottom:0;
right:0;
}
#form_notice label[for=to] {
margin-top:11px;
margin-top:7px;
}
#form_notice select[id=to] {
margin-bottom:7px;
margin-left:18px;
float:left;
}
/*end FORM NOTICE*/
/* entity_profile */
.entity_profile {
position:relative;
width:475px;
width:67.702%;
min-height:123px;
float:left;
margin-bottom:18px;
@ -505,7 +514,6 @@ margin-bottom:18px;
.entity_profile .entity_tags {
margin-left:113px;
margin-bottom:4px;
width:322px;
}
.entity_profile .entity_fn,
@ -533,14 +541,14 @@ display:none;
.entity_profile h2 {
display:none;
}
/* entity_profile */
/*entity_actions*/
.entity_actions {
float:right;
margin-left:28px;
margin-left:4.35%;
max-width:25%;
}
.entity_actions h2 {
display:none;
@ -681,7 +689,7 @@ float:none;
.profile .entity_profile .entity_note,
.profile .entity_profile .entity_url,
.profile .entity_profile .entity_tags,
.profile .entity_profile .form_subcription_edit {
.profile .entity_profile .form_subscription_edit {
margin-left:59px;
clear:none;
display:block;
@ -693,7 +701,7 @@ margin-right:11px;
}
.profile .entity_profile .form_subcription_edit label {
.profile .entity_profile .form_subscription_edit label {
font-weight:normal;
margin-right:11px;
}
@ -908,8 +916,6 @@ display:none;
border:0;
padding:0;
}
/*END: NOTICES */
#new_group, #group_search {
@ -1140,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

@ -1,8 +1,21 @@
/* IE specific styles */
#aside_primary {
padding-left:11px;
legend {
margin-left:-7px;
}
input.checkbox {
top:0;
}
#form_notice textarea {
width:78%;
}
#form_notice #notice_action-submit {
width:17%;
max-width:17%;
}
#anon_notice {
max-width:39%;
}
.notice-options input.submit {
font-size:0;
margin-top:3px;
@ -11,15 +24,9 @@ text-align:right;
text-indent:0;
width:24px;
}
input.checkbox {
top:0;
}
legend {
margin-left:-7px;
}
.notice div.entry-content .timestamp a {
margin-right:4px;
}
.entity_profile {
width:64%;
}

View File

@ -1,4 +1,17 @@
/* IE6 specific styles */
address {
margin-left:7px;
}
address .fn {
display:none;
}
#content {
width:70%;
}
#aside_primary {
padding:5%;
width:29.5%;
}
.entity_profile .entity_nickname,
.entity_profile .entity_location,
.entity_profile .entity_url,
@ -9,6 +22,9 @@ margin-left:0;
.entity_profile .entity_depiction {
margin-bottom:123px;
}
.entity_actions {
width:20%;
}
.notice div.entry-content {
width:63%;
}

View File

@ -21,7 +21,7 @@ p { orphans: 2; widows: 1; }
.entity_actions,
.notice-options,
#aside_primary,
.form_subcription_edit .submit {
.form_subscription_edit .submit {
display:none;
}

View File

@ -17,10 +17,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
font-size:1em;
}
address {
margin-right:71px;
}
address .fn {
display:none;
margin-right:7.18%;
}
input, textarea, select, option {
@ -40,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;
@ -51,7 +47,6 @@ input:focus, textarea:focus, select:focus,
border-color:#A9BF4F;
}
input.submit,
#nav_register a,
.entity_remote_subscribe {
color:#fff;
}
@ -100,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 {
@ -125,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

@ -17,10 +17,7 @@ font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
font-size:1em;
}
address {
margin-right:71px;
}
address .fn {
display:none;
margin-right:7.18%;
}
input, textarea, select, option {
@ -40,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;
@ -51,7 +47,6 @@ input:focus, textarea:focus, select:focus,
border-color:#9BB43E;
}
input.submit,
#nav_register a,
.entity_remote_subscribe {
color:#fff;
}
@ -100,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 {
@ -124,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>