Merge branch '0.7.x' into 0.8.x
This commit is contained in:
commit
e5657ca4bb
124
README
124
README
@ -2,8 +2,8 @@
|
||||
README
|
||||
------
|
||||
|
||||
Laconica 0.7.2.1 ("Talk about the Passion")
|
||||
11 March 2009
|
||||
Laconica 0.7.3 ("You Are The Everything")
|
||||
7 April 2009
|
||||
|
||||
This is the README file for Laconica, the Open Source microblogging
|
||||
platform. It includes installation instructions, descriptions of
|
||||
@ -71,93 +71,29 @@ for additional terms.
|
||||
New this version
|
||||
================
|
||||
|
||||
This is a minor bug-fix and feature release since version 0.7.1,
|
||||
released Feb 9 2009. Notable changes this version:
|
||||
This is a minor bug-fix and feature release since version 0.7.2.1,
|
||||
released Mar 11 2009. Notable changes this version:
|
||||
|
||||
- First version of a web-based installer
|
||||
- Use Net_URL_Mapper instead of mod_rewrite to map "fancy URLs",
|
||||
for a much simpler installation and use of PATH_INFO on sites
|
||||
that don't have mod_rewrite.
|
||||
- A plugin framework for system events, to make it easier to build
|
||||
server-side plugins.
|
||||
- A plugin for Google Analytics
|
||||
- A plugin to use blogspam.net to check notices for spam
|
||||
- A plugin to send linkbacks for notices about blog posts
|
||||
- Configurable check for duplicate notices in a specific time
|
||||
period
|
||||
- Better Atom feeds
|
||||
- First implementation of Twitter Search API
|
||||
- Add streamlined mobile device-friendly styles when enabled in config.
|
||||
- A queue server for sending notices to Twitter
|
||||
- A queue server for sending notices to Facebook
|
||||
- A queue server for sending notices to a ping server
|
||||
- Fixed a bug in nonces for OAuth in OpenMicroBlogging
|
||||
- Fixed bugs in transfer of avatars in OpenMicroBlogging
|
||||
- @-links go to permalinks for local users
|
||||
- Better handling of DB errors (instead of dreaded DB_DataObject blank
|
||||
screen)
|
||||
- Initial version of an RPM spec file
|
||||
- More consistent display of notices in notice search
|
||||
- A stylesheet for printed output
|
||||
- "Social graph" methods for Twitter API
|
||||
- Documentation for the JavaScript badge
|
||||
- Debugged a ton of problems that happened with E_NOTICE on
|
||||
- Better caching in RSS feeds
|
||||
- Optionally send email when an @-message is received
|
||||
- Automatically add tags for every group message
|
||||
- Add framebusting JavaScript to help avoid clickjacking attacks.
|
||||
- Optionally ignore some notice sources for public page.
|
||||
- Add default SMS carriers and notice sources to distribution file.
|
||||
- Change titles to use mixed case instead of all uppercase.
|
||||
- Use exceptions for error handling.
|
||||
|
||||
Changes in version 0.7.1:
|
||||
|
||||
- Vast improvement in auto-linking to URLs.
|
||||
- Link to group search from user's group page
|
||||
- Improved interface in Facebook application
|
||||
- Fix bad redirects in delete notice
|
||||
- Updated PostgreSQL database creation script
|
||||
- Show filesize in avatar/logo upload
|
||||
- Vastly improved avatar/logo upload
|
||||
- Allow re-authentication with OpenID
|
||||
- Correctly link hashtabs inside parens and brackets
|
||||
- Group and avatar image transparency works
|
||||
- Better handling of commands through the Web and Ajax channels
|
||||
- Fix links for profile page feeds
|
||||
- Fixed destroy method in API
|
||||
- Fix endpoint of Connect menu when XMPP disabled
|
||||
- Show number of group members
|
||||
- Enable configuration files in /etc/laconica/
|
||||
|
||||
Changes in version 0.7.0:
|
||||
|
||||
- Support for groups. Users can join groups and send themed notices
|
||||
to those groups. All other members of the group receive the notices.
|
||||
- Laconica-specific extensions to the Twitter API.
|
||||
- A Facebook application.
|
||||
- A massive UI redesign. The HTML generated by Laconica has changed
|
||||
significantly, to make theming easier and to give a more open look
|
||||
by default. Also, sidebar.
|
||||
- Massive code hygiene changes to move towards compliance with the PEAR
|
||||
coding standards and to support the new UI redesign.
|
||||
- Began the breakup of util.php -- moved about 30% of code to a views
|
||||
hierarchy.
|
||||
- UI elements for statistical information (like top posters or most
|
||||
popular groups) added in a sidebar.
|
||||
- include Javascript badge by Kent Brewster.
|
||||
- Updated online documentation.
|
||||
- Cropping of user avatars using Jcrop.
|
||||
- fix for Twitter bridge to not send "Expect:" headers.
|
||||
- add 'dm' as a synonym for 'd' in commands.
|
||||
- Upgrade upstream version of jQuery to 1.3.
|
||||
- Upgrade upstream version of PHP-OpenID to 2.1.2.
|
||||
- Move OpenMicroBlogging specification to its own repository.
|
||||
- Make tag-based RSS streams work.
|
||||
- Additional locales: Bulgarian, Catalan, Greek, Hebrew, simplified
|
||||
Chinese, Telugu, Taiwanese Chinese, Vietnamese,
|
||||
- PostgreSQL updates.
|
||||
- Nasty bug in Twitter bridge that wouldn't verify with Twitter
|
||||
- A plugin to allow a templating language for customization
|
||||
- A plugin for Piwik Analytics engine
|
||||
- A bookmarklet for posting a notice about a Web page you're reading
|
||||
- A welcome notice ('welcomebot') and default subscription for new users
|
||||
- Support for SSL for some or all pages on the site
|
||||
- Better handling of empty notice lists on many pages
|
||||
- Major improvements to the Twitter friend-sync offline processing
|
||||
- subscribers, subscriptions, groups are listed on the Personal page.
|
||||
- "Invite" link restored to main menu
|
||||
- Better memory handling in FOAF output
|
||||
- Fix for SUP support (FriendFeed)
|
||||
- Correct and intelligent redirect HTTP status codes
|
||||
- Fix DB collations for search and sort
|
||||
- Better H1s and Titles using user full names
|
||||
- Fixes to make the linkback plugin operational
|
||||
- Better indication that a notice is being published by Ajax (spinner)
|
||||
- Better and unified Atom output
|
||||
- Hiding "register" and "join now" messages when site is closed
|
||||
- ping, twitter and facebook queuehandlers working better
|
||||
- Updated RPM spec
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
@ -261,9 +197,9 @@ especially if you've previously installed PHP/MySQL packages.
|
||||
1. Unpack the tarball you downloaded on your Web server. Usually a
|
||||
command like this will work:
|
||||
|
||||
tar zxf laconica-0.7.2.1.tar.gz
|
||||
tar zxf laconica-0.7.3.tar.gz
|
||||
|
||||
...which will make a laconica-0.7.2.1 subdirectory in your current
|
||||
...which will make a laconica-0.7.3 subdirectory in your current
|
||||
directory. (If you don't have shell access on your Web server, you
|
||||
may have to unpack the tarball on your local computer and FTP the
|
||||
files to the server.)
|
||||
@ -271,7 +207,7 @@ especially if you've previously installed PHP/MySQL packages.
|
||||
2. Move the tarball to a directory of your choosing in your Web root
|
||||
directory. Usually something like this will work:
|
||||
|
||||
mv laconica-0.7.2.1 /var/www/mublog
|
||||
mv laconica-0.7.3 /var/www/mublog
|
||||
|
||||
This will make your Laconica instance available in the mublog path of
|
||||
your server, like "http://example.net/mublog". "microblog" or
|
||||
@ -761,7 +697,7 @@ Upgrading
|
||||
If you've been using Laconica 0.6, 0.5 or lower, or if you've been
|
||||
tracking the "git" version of the software, you will probably want
|
||||
to upgrade and keep your existing data. There is no automated upgrade
|
||||
procedure in Laconica 0.7.2.1. Try these step-by-step instructions; read
|
||||
procedure in Laconica 0.7.3. Try these step-by-step instructions; read
|
||||
to the end first before trying them.
|
||||
|
||||
0. Download Laconica and set up all the prerequisites as if you were
|
||||
@ -1209,7 +1145,7 @@ repository (see below), and you get a compilation error ("unexpected
|
||||
T_STRING") in the browser, check to see that you don't have any
|
||||
conflicts in your code.
|
||||
|
||||
If you upgraded to Laconica 0.7.2.1 without reading the "Notice inboxes"
|
||||
If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes"
|
||||
section above, and all your users' 'Personal' tabs are empty, read the
|
||||
"Notice inboxes" section above.
|
||||
|
||||
@ -1298,6 +1234,8 @@ if anyone's been overlooked in error.
|
||||
* Leslie Michael Orchard
|
||||
* Eric Helgeson
|
||||
* Ken Sedgwick
|
||||
* Brian Hendrickson
|
||||
* Tobias Diekershoff
|
||||
|
||||
Thanks also to the developers of our upstream library code and to the
|
||||
thousands of people who have tried out Identi.ca, installed Laconi.ca,
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -104,9 +104,9 @@ class FavoritedAction extends Action
|
||||
{
|
||||
parent::prepare($args);
|
||||
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
|
||||
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -143,14 +143,11 @@ class FavoritedAction extends Action
|
||||
$this->elementStart('div', 'instructions');
|
||||
$this->raw($output);
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
$favorite = new Fave;
|
||||
|
||||
if ($favorite->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = _('Favorite notices appear on this page but noone has favorited one yet.') . ' ';
|
||||
function showEmptyList()
|
||||
{
|
||||
$message = _('Favorite notices appear on this page but no one has favorited one yet.') . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.');
|
||||
@ -159,7 +156,7 @@ class FavoritedAction extends Action
|
||||
$message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'blankfiller');
|
||||
$this->elementStart('div', 'guide');
|
||||
$this->raw(common_markup_to_html($message));
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
@ -217,6 +214,10 @@ class FavoritedAction extends Action
|
||||
|
||||
$cnt = $nl->show();
|
||||
|
||||
if ($cnt == 0) {
|
||||
$this->showEmptyList();
|
||||
}
|
||||
|
||||
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
|
||||
$this->page, 'favorited');
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,20 +166,9 @@ class PublicAction extends Action
|
||||
$nav->show();
|
||||
}
|
||||
|
||||
function showPageNotice()
|
||||
function showEmptyList()
|
||||
{
|
||||
$notice = new Notice;
|
||||
|
||||
if (!$notice) {
|
||||
$this->serverError(_('Could not retrieve public stream.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($notice->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = _('This is the public timeline for %%site.name%% but noone has posted anything yet.') . ' ';
|
||||
$message = _('This is the public timeline for %%site.name%% but no one has posted anything yet.') . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$message .= _('Be the first to post!');
|
||||
@ -188,7 +177,7 @@ class PublicAction extends Action
|
||||
$message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'blankfiller');
|
||||
$this->elementStart('div', 'guide');
|
||||
$this->raw(common_markup_to_html($message));
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
@ -216,6 +205,10 @@ class PublicAction extends Action
|
||||
|
||||
$cnt = $nl->show();
|
||||
|
||||
if ($cnt == 0) {
|
||||
$this->showEmptyList();
|
||||
}
|
||||
|
||||
$this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE,
|
||||
$this->page, 'public');
|
||||
}
|
||||
|
@ -62,13 +62,11 @@ class PublictagcloudAction extends Action
|
||||
$this->element('p', 'instructions',
|
||||
sprintf(_('These are most popular recent tags on %s '),
|
||||
common_config('site', 'name')));
|
||||
}
|
||||
|
||||
$tags = new Notice_tag;
|
||||
if ($tags->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message = _('Noone has posted a notice with a [hashtag](%%doc.tags%%) yet.') . ' ';
|
||||
function showEmptyList()
|
||||
{
|
||||
$message = _('No one has posted a notice with a [hashtag](%%doc.tags%%) yet.') . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$message .= _('Be the first to post one!');
|
||||
@ -77,7 +75,7 @@ class PublictagcloudAction extends Action
|
||||
$message .= _('Why not [register an account](%%action.register%%) and be the first to post one!');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'blankfiller');
|
||||
$this->elementStart('div', 'guide');
|
||||
$this->raw(common_markup_to_html($message));
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
@ -144,6 +142,8 @@ class PublictagcloudAction extends Action
|
||||
$this->elementEnd('dd');
|
||||
$this->elementEnd('dl');
|
||||
$this->elementEnd('div');
|
||||
} else {
|
||||
$this->showEmptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,8 @@ require_once INSTALLDIR.'/lib/feedlist.php';
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class ShowstreamAction extends Action
|
||||
class ShowstreamAction extends ProfileAction
|
||||
{
|
||||
var $user = null;
|
||||
var $page = null;
|
||||
var $profile = null;
|
||||
|
||||
function isReadOnly()
|
||||
{
|
||||
return true;
|
||||
@ -82,45 +78,6 @@ class ShowstreamAction extends Action
|
||||
}
|
||||
}
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
|
||||
$nickname_arg = $this->arg('nickname');
|
||||
$nickname = common_canonical_nickname($nickname_arg);
|
||||
|
||||
// Permanent redirect on non-canonical nickname
|
||||
|
||||
if ($nickname_arg != $nickname) {
|
||||
$args = array('nickname' => $nickname);
|
||||
if ($this->arg('page') && $this->arg('page') != 1) {
|
||||
$args['page'] = $this->arg['page'];
|
||||
}
|
||||
common_redirect(common_local_url('showstream', $args), 301);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->user = User::staticGet('nickname', $nickname);
|
||||
|
||||
if (!$this->user) {
|
||||
$this->clientError(_('No such user.'), 404);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->profile = $this->user->getProfile();
|
||||
|
||||
if (!$this->profile) {
|
||||
$this->serverError(_('User has no profile.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
|
||||
@ -372,167 +329,41 @@ class ShowstreamAction extends Action
|
||||
_('Subscribe'));
|
||||
}
|
||||
|
||||
function showEmptyListMessage()
|
||||
{
|
||||
$message = sprintf(_('This is the timeline for %s but %s hasn\'t posted anything yet.'), $this->user->nickname, $this->user->nickname) . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$current_user = common_current_user();
|
||||
if ($this->user->id === $current_user->id) {
|
||||
$message .= _('Seen anything interesting recently? You haven\'t posted any notices yet, now would be a good time to start :)');
|
||||
} else {
|
||||
$message .= sprintf(_('You can try to nudge %s or [post something to his or her attention](%%%%action.newnotice%%%%?status_textarea=%s).'), $this->user->nickname, '@' . $this->user->nickname);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
$this->raw(common_markup_to_html($message));
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showNotices()
|
||||
{
|
||||
$notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
|
||||
|
||||
$pnl = new ProfileNoticeList($notice, $this);
|
||||
$cnt = $pnl->show();
|
||||
if (0 == $cnt) {
|
||||
$this->showEmptyListMessage();
|
||||
}
|
||||
|
||||
$this->pagination($this->page>1, $cnt>NOTICES_PER_PAGE, $this->page,
|
||||
'showstream', array('nickname' => $this->user->nickname));
|
||||
}
|
||||
|
||||
function showSections()
|
||||
{
|
||||
$this->showSubscriptions();
|
||||
$this->showSubscribers();
|
||||
$this->showGroups();
|
||||
$this->showStatistics();
|
||||
$cloud = new PersonalTagCloudSection($this, $this->user);
|
||||
$cloud->show();
|
||||
}
|
||||
|
||||
function showSubscriptions()
|
||||
{
|
||||
$profile = $this->user->getSubscriptions(0, PROFILES_PER_MINILIST + 1);
|
||||
|
||||
$this->elementStart('div', array('id' => 'entity_subscriptions',
|
||||
'class' => 'section'));
|
||||
|
||||
$this->element('h2', null, _('Subscriptions'));
|
||||
|
||||
if ($profile) {
|
||||
$pml = new ProfileMiniList($profile, $this->user, $this);
|
||||
$cnt = $pml->show();
|
||||
if ($cnt == 0) {
|
||||
$this->element('p', null, _('(None)'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($cnt > PROFILES_PER_MINILIST) {
|
||||
$this->elementStart('p');
|
||||
$this->element('a', array('href' => common_local_url('subscriptions',
|
||||
array('nickname' => $this->profile->nickname)),
|
||||
'class' => 'more'),
|
||||
_('All subscriptions'));
|
||||
$this->elementEnd('p');
|
||||
}
|
||||
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showSubscribers()
|
||||
{
|
||||
$profile = $this->user->getSubscribers(0, PROFILES_PER_MINILIST + 1);
|
||||
|
||||
$this->elementStart('div', array('id' => 'entity_subscribers',
|
||||
'class' => 'section'));
|
||||
|
||||
$this->element('h2', null, _('Subscribers'));
|
||||
|
||||
if ($profile) {
|
||||
$pml = new ProfileMiniList($profile, $this->user, $this);
|
||||
$cnt = $pml->show();
|
||||
if ($cnt == 0) {
|
||||
$this->element('p', null, _('(None)'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($cnt > PROFILES_PER_MINILIST) {
|
||||
$this->elementStart('p');
|
||||
$this->element('a', array('href' => common_local_url('subscribers',
|
||||
array('nickname' => $this->profile->nickname)),
|
||||
'class' => 'more'),
|
||||
_('All subscribers'));
|
||||
$this->elementEnd('p');
|
||||
}
|
||||
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showStatistics()
|
||||
{
|
||||
// XXX: WORM cache this
|
||||
$subs = new Subscription();
|
||||
$subs->subscriber = $this->profile->id;
|
||||
$subs_count = (int) $subs->count() - 1;
|
||||
|
||||
$subbed = new Subscription();
|
||||
$subbed->subscribed = $this->profile->id;
|
||||
$subbed_count = (int) $subbed->count() - 1;
|
||||
|
||||
$notices = new Notice();
|
||||
$notices->profile_id = $this->profile->id;
|
||||
$notice_count = (int) $notices->count();
|
||||
|
||||
$this->elementStart('div', array('id' => 'entity_statistics',
|
||||
'class' => 'section'));
|
||||
|
||||
$this->element('h2', null, _('Statistics'));
|
||||
|
||||
// Other stats...?
|
||||
$this->elementStart('dl', 'entity_member-since');
|
||||
$this->element('dt', null, _('Member since'));
|
||||
$this->element('dd', null, date('j M Y',
|
||||
strtotime($this->profile->created)));
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementStart('dl', 'entity_subscriptions');
|
||||
$this->elementStart('dt');
|
||||
$this->element('a', array('href' => common_local_url('subscriptions',
|
||||
array('nickname' => $this->profile->nickname))),
|
||||
_('Subscriptions'));
|
||||
$this->elementEnd('dt');
|
||||
$this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementStart('dl', 'entity_subscribers');
|
||||
$this->elementStart('dt');
|
||||
$this->element('a', array('href' => common_local_url('subscribers',
|
||||
array('nickname' => $this->profile->nickname))),
|
||||
_('Subscribers'));
|
||||
$this->elementEnd('dt');
|
||||
$this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementStart('dl', 'entity_notices');
|
||||
$this->element('dt', null, _('Notices'));
|
||||
$this->element('dd', null, (is_int($notice_count)) ? $notice_count : '0');
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showGroups()
|
||||
{
|
||||
$groups = $this->user->getGroups(0, GROUPS_PER_MINILIST + 1);
|
||||
|
||||
$this->elementStart('div', array('id' => 'entity_groups',
|
||||
'class' => 'section'));
|
||||
|
||||
$this->element('h2', null, _('Groups'));
|
||||
|
||||
if ($groups) {
|
||||
$gml = new GroupMiniList($groups, $this->user, $this);
|
||||
$cnt = $gml->show();
|
||||
if ($cnt == 0) {
|
||||
$this->element('p', null, _('(None)'));
|
||||
}
|
||||
}
|
||||
|
||||
if ($cnt > GROUPS_PER_MINILIST) {
|
||||
$this->elementStart('p');
|
||||
$this->element('a', array('href' => common_local_url('usergroups',
|
||||
array('nickname' => $this->profile->nickname)),
|
||||
'class' => 'more'),
|
||||
_('All groups'));
|
||||
$this->elementEnd('p');
|
||||
}
|
||||
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showAnonymousMessage()
|
||||
{
|
||||
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
@ -550,6 +381,12 @@ class ShowstreamAction extends Action
|
||||
$this->elementEnd('div');
|
||||
}
|
||||
|
||||
function showSections()
|
||||
{
|
||||
parent::showSections();
|
||||
$cloud = new PersonalTagCloudSection($this, $this->user);
|
||||
$cloud->show();
|
||||
}
|
||||
}
|
||||
|
||||
// We don't show the author for a profile, since we already know who it is!
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -21,6 +21,7 @@ VALUES
|
||||
('mbpidgin','mbpidgin','http://code.google.com/p/microblog-purple/', now()),
|
||||
('Mobidentica', 'Mobidentica', 'http://www.substanceofcode.com/software/mobidentica/', now()),
|
||||
('moconica','Moconica','http://moconica.com/', now()),
|
||||
('peoplebrowsr', 'PeopleBrowsr', 'http://www.peoplebrowsr.com/', now()),
|
||||
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
|
||||
('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
|
||||
('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),
|
||||
|
7
doc-src/bookmarklet
Normal file
7
doc-src/bookmarklet
Normal 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>
|
@ -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
|
@ -97,9 +97,18 @@ class Action extends HTMLOutputter // lawsuit
|
||||
$this->startHTML();
|
||||
Event::handle('EndShowHTML', array($this));
|
||||
}
|
||||
if (Event::handle('StartShowHead', array($this))) {
|
||||
$this->showHead();
|
||||
Event::handle('EndShowHead', array($this));
|
||||
}
|
||||
if (Event::handle('StartShowBody', array($this))) {
|
||||
$this->showBody();
|
||||
Event::handle('EndShowBody', array($this));
|
||||
}
|
||||
if (Event::handle('StartEndHTML', array($this))) {
|
||||
$this->endHTML();
|
||||
Event::handle('EndEndHTML', array($this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,13 +411,8 @@ class Action extends HTMLOutputter // lawsuit
|
||||
if ($user) {
|
||||
$this->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
|
||||
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
|
||||
}
|
||||
$this->menuItem(common_local_url('peoplesearch'),
|
||||
_('Search'), _('Search for people or text'), false, 'nav_search');
|
||||
if ($user) {
|
||||
$this->menuItem(common_local_url('profilesettings'),
|
||||
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
|
||||
|
||||
if (common_config('xmpp', 'enabled')) {
|
||||
$this->menuItem(common_local_url('imsettings'),
|
||||
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
|
||||
@ -416,20 +420,28 @@ class Action extends HTMLOutputter // lawsuit
|
||||
$this->menuItem(common_local_url('smssettings'),
|
||||
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
|
||||
}
|
||||
$this->menuItem(common_local_url('invite'),
|
||||
_('Invite'),
|
||||
sprintf(_('Invite friends and colleagues to join you on %s'),
|
||||
common_config('site', 'name')),
|
||||
false, 'nav_invitecontact');
|
||||
$this->menuItem(common_local_url('logout'),
|
||||
_('Logout'), _('Logout from the site'), false, 'nav_logout');
|
||||
} else {
|
||||
$this->menuItem(common_local_url('login'),
|
||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||
}
|
||||
else {
|
||||
if (!common_config('site', 'closed')) {
|
||||
$this->menuItem(common_local_url('register'),
|
||||
_('Register'), _('Create an account'), false, 'nav_register');
|
||||
}
|
||||
$this->menuItem(common_local_url('openidlogin'),
|
||||
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
|
||||
$this->menuItem(common_local_url('login'),
|
||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||
}
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'help')),
|
||||
_('Help'), _('Help me!'), false, 'nav_help');
|
||||
$this->menuItem(common_local_url('peoplesearch'),
|
||||
_('Search'), _('Search for people or text'), false, 'nav_search');
|
||||
Event::handle('EndPrimaryNav', array($this));
|
||||
}
|
||||
$this->elementEnd('ul');
|
||||
@ -606,7 +618,10 @@ class Action extends HTMLOutputter // lawsuit
|
||||
{
|
||||
$this->elementStart('div', array('id' => 'aside_primary',
|
||||
'class' => 'aside'));
|
||||
if (Event::handle('StartShowExportData', array($this))) {
|
||||
$this->showExportData();
|
||||
Event::handle('EndShowExportData', array($this));
|
||||
}
|
||||
if (Event::handle('StartShowSections', array($this))) {
|
||||
$this->showSections();
|
||||
Event::handle('EndShowSections', array($this));
|
||||
@ -923,11 +938,15 @@ class Action extends HTMLOutputter // lawsuit
|
||||
*
|
||||
* @return string current URL
|
||||
*/
|
||||
|
||||
function selfUrl()
|
||||
{
|
||||
$action = $this->trimmed('action');
|
||||
$args = $this->args;
|
||||
unset($args['action']);
|
||||
if (array_key_exists('submit', $args)) {
|
||||
unset($args['submit']);
|
||||
}
|
||||
foreach (array_keys($_COOKIE) as $cookie) {
|
||||
unset($args[$cookie]);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -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,
|
||||
|
@ -137,6 +137,9 @@ class MailboxAction extends PersonalAction
|
||||
$message->free();
|
||||
unset($message);
|
||||
}
|
||||
else {
|
||||
$this->element('p', 'guide', _('You have no private messages. You can send private message to engage other users in conversation. People can send you messages for your eyes only.'));
|
||||
}
|
||||
}
|
||||
|
||||
function getMessages()
|
||||
|
242
lib/profileaction.php
Normal file
242
lib/profileaction.php
Normal 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');
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
217
lib/twitter.php
217
lib/twitter.php
@ -19,7 +19,7 @@
|
||||
|
||||
if (!defined('LACONICA')) { exit(1); }
|
||||
|
||||
define("TWITTER_SERVICE", 1); // Twitter is foreign_service ID 1
|
||||
define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
|
||||
|
||||
function get_twitter_data($uri, $screen_name, $password)
|
||||
{
|
||||
@ -45,6 +45,10 @@ function get_twitter_data($uri, $screen_name, $password)
|
||||
if ($errmsg) {
|
||||
common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
|
||||
__FILE__);
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
@ -52,63 +56,141 @@ function get_twitter_data($uri, $screen_name, $password)
|
||||
return $data;
|
||||
}
|
||||
|
||||
function twitter_user_info($screen_name, $password)
|
||||
function twitter_json_data($uri, $screen_name, $password)
|
||||
{
|
||||
$json_data = get_twitter_data($uri, $screen_name, $password);
|
||||
|
||||
$uri = "http://twitter.com/users/show/$screen_name.json";
|
||||
$data = get_twitter_data($uri, $screen_name, $password);
|
||||
if (!$json_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($json_data);
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$twit_user = json_decode($data);
|
||||
|
||||
if (!$twit_user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $twit_user;
|
||||
return $data;
|
||||
}
|
||||
|
||||
function update_twitter_user($fuser, $twitter_id, $screen_name)
|
||||
function twitter_user_info($screen_name, $password)
|
||||
{
|
||||
$uri = "http://twitter.com/users/show/$screen_name.json";
|
||||
return twitter_json_data($uri, $screen_name, $password);
|
||||
}
|
||||
|
||||
$original = clone($fuser);
|
||||
$fuser->nickname = $screen_name;
|
||||
$fuser->uri = 'http://twitter.com/' . $screen_name;
|
||||
$result = $fuser->updateKeys($original);
|
||||
function twitter_friends_ids($screen_name, $password)
|
||||
{
|
||||
$uri = "http://twitter.com/friends/ids/$screen_name.json";
|
||||
return twitter_json_data($uri, $screen_name, $password);
|
||||
}
|
||||
|
||||
function update_twitter_user($twitter_id, $screen_name)
|
||||
{
|
||||
$uri = 'http://twitter.com/' . $screen_name;
|
||||
|
||||
$fuser = new Foreign_user();
|
||||
|
||||
$fuser->query('BEGIN');
|
||||
|
||||
// Dropping down to SQL because regular db_object udpate stuff doesn't seem
|
||||
// to work so good with tables that have multiple column primary keys
|
||||
|
||||
// Any time we update the uri for a forein user we have to make sure there
|
||||
// are no dupe entries first -- unique constraint on the uri column
|
||||
|
||||
$qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
|
||||
$qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
|
||||
|
||||
$result = $fuser->query($qry);
|
||||
|
||||
if ($result) {
|
||||
common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the user
|
||||
$qry = 'UPDATE foreign_user SET nickname = ';
|
||||
$qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
|
||||
$qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
|
||||
|
||||
$result = $fuser->query($qry);
|
||||
|
||||
if (!$result) {
|
||||
common_log(LOG_WARNING,
|
||||
"Couldn't update foreign_user data for Twitter user: $screen_name");
|
||||
common_log_db_error($fuser, 'UPDATE', __FILE__);
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - ";
|
||||
print common_log_objstring($fuser) . "\n";
|
||||
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
print "DB_DataObject Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$fuser->query('COMMIT');
|
||||
|
||||
$fuser->free();
|
||||
unset($fuser);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function add_twitter_user($twitter_id, $screen_name)
|
||||
{
|
||||
|
||||
$new_uri = 'http://twitter.com/' . $screen_name;
|
||||
|
||||
// Clear out any bad old foreign_users with the new user's legit URL
|
||||
// This can happen when users move around or fakester accounts get
|
||||
// repoed, and things like that.
|
||||
$luser = new Foreign_user();
|
||||
$luser->uri = $new_uri;
|
||||
$luser->service = TWITTER_SERVICE;
|
||||
$result = $luser->delete();
|
||||
|
||||
if ($result) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Removed invalid Twitter user squatting on uri: $new_uri\n";
|
||||
}
|
||||
}
|
||||
|
||||
$luser->free();
|
||||
unset($luser);
|
||||
|
||||
// Otherwise, create a new Twitter user
|
||||
$fuser = DB_DataObject::factory('foreign_user');
|
||||
$fuser = new Foreign_user();
|
||||
|
||||
$fuser->nickname = $screen_name;
|
||||
$fuser->uri = 'http://twitter.com/' . $screen_name;
|
||||
$fuser->id = $twitter_id;
|
||||
$fuser->service = TWITTER_SERVICE; // Twitter
|
||||
$fuser->service = TWITTER_SERVICE;
|
||||
$fuser->created = common_sql_now();
|
||||
$result = $fuser->insert();
|
||||
|
||||
if (!$result) {
|
||||
common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
|
||||
common_log_db_error($fuser, 'INSERT', __FILE__);
|
||||
return false;
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
|
||||
print common_log_objstring($fuser) . "\n";
|
||||
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
print "DB_DataObject Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
} else {
|
||||
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Added new Twitter user: $screen_name ($twitter_id).\n";
|
||||
}
|
||||
}
|
||||
|
||||
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
|
||||
|
||||
return true;
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Creates or Updates a Twitter user
|
||||
@ -117,53 +199,87 @@ function save_twitter_user($twitter_id, $screen_name)
|
||||
|
||||
// Check to see whether the Twitter user is already in the system,
|
||||
// and update its screen name and uri if so.
|
||||
$fuser = Foreign_user::getForeignUser($twitter_id, 1);
|
||||
$fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
|
||||
|
||||
if ($fuser) {
|
||||
|
||||
$result = true;
|
||||
|
||||
// Only update if Twitter screen name has changed
|
||||
if ($fuser->nickname != $screen_name) {
|
||||
$result = update_twitter_user($twitter_id, $screen_name);
|
||||
|
||||
common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
|
||||
"$fuser->id to $screen_name, was $fuser->nickname");
|
||||
|
||||
return update_twitter_user($fuser, $twitter_id, $screen_name);
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print 'Updated nickname (and URI) for Twitter user ' .
|
||||
"$fuser->id to $screen_name, was $fuser->nickname\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} else {
|
||||
return add_twitter_user($twitter_id, $screen_name);
|
||||
}
|
||||
|
||||
$fuser->free();
|
||||
unset($fuser);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function retreive_twitter_friends($twitter_id, $screen_name, $password)
|
||||
{
|
||||
$friends = array();
|
||||
|
||||
$uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
|
||||
$twitter_user = twitter_user_info($screen_name, $password);
|
||||
$friends_ids = twitter_friends_ids($screen_name, $password);
|
||||
|
||||
// Calculate how many pages to get...
|
||||
$pages = ceil($twitter_user->friends_count / 100);
|
||||
|
||||
if ($pages == 0) {
|
||||
common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame.");
|
||||
if (!$friends_ids) {
|
||||
return $friends;
|
||||
}
|
||||
|
||||
$friends = array();
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Twitter 'social graph' ids method says $screen_name has " .
|
||||
count($friends_ids) . " friends.\n";
|
||||
}
|
||||
|
||||
// Calculate how many pages to get...
|
||||
$pages = ceil(count($friends_ids) / 100);
|
||||
|
||||
if ($pages == 0) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - $screen_name seems to have no friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "$screen_name seems to have no friends.\n";
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $pages; $i++) {
|
||||
|
||||
$data = get_twitter_data($uri . $i, $screen_name, $password);
|
||||
|
||||
if (!$data) {
|
||||
return null;
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't retrieve page $i of $screen_name's friends.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$more_friends = json_decode($data);
|
||||
|
||||
if (!$more_friends) {
|
||||
return null;
|
||||
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - No data for page $i of $screen_name's friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "No data for page $i of $screen_name's friends.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$friends = array_merge($friends, $more_friends);
|
||||
@ -177,19 +293,27 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
|
||||
|
||||
$friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
|
||||
|
||||
if (is_null($friends)) {
|
||||
common_debug("Twitter bridge - Couldn't get friends data from Twitter.");
|
||||
if (empty($friends)) {
|
||||
common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't get friends data from Twitter for $screen_name.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
|
||||
$friend_name = $friend->screen_name;
|
||||
$friend_id = $friend->id;
|
||||
$friend_id = (int) $friend->id;
|
||||
|
||||
// Update or create the Foreign_user record
|
||||
if (!save_twitter_user($friend_id, $friend_name)) {
|
||||
return false;
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't save $screen_name's friend, $friend_name.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if there's a related local user
|
||||
@ -199,8 +323,20 @@ function save_twitter_friends($user, $twitter_id, $screen_name, $password)
|
||||
|
||||
// Get associated user and subscribe her
|
||||
$friend_user = User::staticGet('id', $flink->user_id);
|
||||
subs_subscribe_to($user, $friend_user);
|
||||
common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
|
||||
if (!empty($friend_user)) {
|
||||
$result = subs_subscribe_to($user, $friend_user);
|
||||
|
||||
if ($result === true) {
|
||||
common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print("Subscribed $friend_user->nickname to $user->nickname.\n");
|
||||
}
|
||||
} else {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "$result ($friend_user->nickname to $user->nickname)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,4 +431,3 @@ function broadcast_twitter($notice)
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
344
plugins/TemplatePlugin.php
Normal file
344
plugins/TemplatePlugin.php
Normal 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];
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
@ -83,12 +83,13 @@ left:0;
|
||||
border:0;
|
||||
}
|
||||
|
||||
#page_notice .error,
|
||||
#page_notice .success {
|
||||
.error,
|
||||
.success {
|
||||
padding:4px 7px;
|
||||
border-radius:4px;
|
||||
-moz-border-radius:4px;
|
||||
-webkit-border-radius:4px;
|
||||
margin-bottom:18px;
|
||||
}
|
||||
form label.submit {
|
||||
display:none;
|
||||
@ -384,6 +385,7 @@ margin-bottom:1em;
|
||||
|
||||
#content {
|
||||
width:64.009%;
|
||||
min-height:259px;
|
||||
padding:1.795%;
|
||||
float:left;
|
||||
border-radius:7px;
|
||||
@ -403,8 +405,9 @@ float:left;
|
||||
|
||||
#aside_primary {
|
||||
width:27.917%;
|
||||
min-height:259px;
|
||||
float:left;
|
||||
margin-left:0.395%;
|
||||
margin-left:0.385%;
|
||||
padding:1.795%;
|
||||
border-radius:7px;
|
||||
-moz-border-radius:7px;
|
||||
@ -469,7 +472,7 @@ bottom:0;
|
||||
right:0;
|
||||
}
|
||||
#form_notice label[for=to] {
|
||||
margin-top:11px;
|
||||
margin-top:7px;
|
||||
}
|
||||
#form_notice select[id=to] {
|
||||
margin-bottom:7px;
|
||||
@ -1143,4 +1146,16 @@ clear:both;
|
||||
margin-bottom:0;
|
||||
}
|
||||
|
||||
|
||||
.instructions ul {
|
||||
list-style-position:inside;
|
||||
}
|
||||
.instructions p,
|
||||
.instructions ul {
|
||||
margin-bottom:18px;
|
||||
}
|
||||
.help dt {
|
||||
display:none;
|
||||
}
|
||||
.guide {
|
||||
clear:both;
|
||||
}
|
@ -37,7 +37,6 @@ background:none;
|
||||
|
||||
input.submit,
|
||||
#form_notice.warning #notice_text-count,
|
||||
#nav_register a,
|
||||
.form_settings .form_note,
|
||||
.entity_remote_subscribe {
|
||||
background-color:#A9BF4F;
|
||||
@ -48,7 +47,6 @@ input:focus, textarea:focus, select:focus,
|
||||
border-color:#A9BF4F;
|
||||
}
|
||||
input.submit,
|
||||
#nav_register a,
|
||||
.entity_remote_subscribe {
|
||||
color:#fff;
|
||||
}
|
||||
@ -97,13 +95,6 @@ cursor:wait;
|
||||
text-indent:-9999px;
|
||||
}
|
||||
|
||||
|
||||
#nav_register a {
|
||||
text-decoration:none;
|
||||
font-weight:bold;
|
||||
padding:2px 4px;
|
||||
}
|
||||
|
||||
#content,
|
||||
#site_nav_local_views a,
|
||||
#aside_primary {
|
||||
@ -122,10 +113,10 @@ background-color:rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
|
||||
#page_notice .error {
|
||||
.error {
|
||||
background-color:#F7E8E8;
|
||||
}
|
||||
#page_notice .success {
|
||||
.success {
|
||||
background-color:#EFF3DC;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,6 @@ background:none;
|
||||
|
||||
input.submit,
|
||||
#form_notice.warning #notice_text-count,
|
||||
#nav_register a,
|
||||
.form_settings .form_note,
|
||||
.entity_remote_subscribe {
|
||||
background-color:#9BB43E;
|
||||
@ -48,7 +47,6 @@ input:focus, textarea:focus, select:focus,
|
||||
border-color:#9BB43E;
|
||||
}
|
||||
input.submit,
|
||||
#nav_register a,
|
||||
.entity_remote_subscribe {
|
||||
color:#fff;
|
||||
}
|
||||
@ -97,12 +95,6 @@ cursor:wait;
|
||||
text-indent:-9999px;
|
||||
}
|
||||
|
||||
#nav_register a {
|
||||
text-decoration:none;
|
||||
font-weight:bold;
|
||||
padding:2px 4px;
|
||||
}
|
||||
|
||||
#content,
|
||||
#site_nav_local_views a,
|
||||
#aside_primary {
|
||||
@ -121,10 +113,10 @@ background-color:rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
|
||||
#page_notice .error {
|
||||
.error {
|
||||
background-color:#F7E8E8;
|
||||
}
|
||||
#page_notice .success {
|
||||
.success {
|
||||
background-color:#EFF3DC;
|
||||
}
|
||||
|
||||
|
47
tpl/index.php
Normal file
47
tpl/index.php
Normal 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>
|
Loading…
Reference in New Issue
Block a user