forked from GNUsocial/gnu-social
Merge branch 'testing'
This commit is contained in:
commit
5541565238
208
README
208
README
@ -2,8 +2,8 @@
|
||||
README
|
||||
------
|
||||
|
||||
Laconica 0.7.4 ("Can't Get There From Here")
|
||||
29 May 2009
|
||||
Laconica 0.8.0 ("Shiny Happy People")
|
||||
8 July 2009
|
||||
|
||||
This is the README file for Laconica, the Open Source microblogging
|
||||
platform. It includes installation instructions, descriptions of
|
||||
@ -71,29 +71,52 @@ for additional terms.
|
||||
New this version
|
||||
================
|
||||
|
||||
This is a minor bug-fix and feature release since version 0.7.3,
|
||||
released Apr 4 2009. Notable changes this version:
|
||||
This is a major feature release since version 0.7.4, released May 31
|
||||
2009. Notable changes this version:
|
||||
|
||||
- Improved handling of UTF-8 characters. The new code is *not* backwards
|
||||
compatible by default; see "Upgrading" below for instructions on
|
||||
converting existing databases to the correct character set.
|
||||
- Unroll joins for large queries. This greatly enhanced database
|
||||
performance -- up to 50x for some queries -- at the expense of making
|
||||
an extra DB hit for some queries.
|
||||
- Added an optional plugin to use WikiHashtags
|
||||
(http://hashtags.wikia.com/) for the sidebar on hashtag pages.
|
||||
- Optimized Twitter friend synchronization.
|
||||
- Better error handling for Ajax posting of notices, including
|
||||
HTTP errors and timeouts.
|
||||
- Experimental Comet plugin -- supports the cometd and the Bayeux
|
||||
protocol. Using this plugin, you can show "real time" updates on the
|
||||
public and tag pages. However, server configuration is complex.
|
||||
- If queues are enabled, update inboxes and memcached off-line. Speeds
|
||||
up posting considerably.
|
||||
- Correctly shorten links posted through XMPP.
|
||||
- <link> elements for pagination, supported by some browsers like Opera.
|
||||
- Corrected date format in search API.
|
||||
- Made the public XRDS file work correctly.
|
||||
- Support for a hosted service (status network). Multiple sites can
|
||||
share the same codebase but use different databases.
|
||||
- OEmbed. Links to pages that support OEmbed (http://www.oembed.com/)
|
||||
become popup links, and the media are shown in a special lightbox.
|
||||
- File attachments. Users can attach files of the size and type approved
|
||||
by an administrator, and a shortened link will be included in the
|
||||
notice.
|
||||
- Related notices are organized into conversations, with each reply a
|
||||
branch in a tree. Conversations have pages and are linked to from each
|
||||
notice in the conversation.
|
||||
- User designs. Users can specify colours and backgrounds
|
||||
for their profile pages and other "personal" pages.
|
||||
- Group designs. Group administrators can specify similar designs for
|
||||
group profiles and related pages.
|
||||
- Site designs. Site authors can specify a design (background and
|
||||
colors) for the site.
|
||||
- New themes. Five new themes are added to the base release; these show
|
||||
off the flexibility of Laconica's theming system.
|
||||
- Statistics. Public sites will periodically send usage statistics,
|
||||
configuration options, and dependency information to Laconica dev site.
|
||||
This will help us understand how the software is used and plan future
|
||||
versions of the software.
|
||||
- Additional hooks. The hooks and plugins system introduced in 0.7.x was
|
||||
expanded with additional points of access.
|
||||
- Facebook Connect. A new plugin allows logging in with Facebook Connect
|
||||
(http://developers.facebook.com/connect.php).
|
||||
- A session handler. A new optional session handler class to manage PHP
|
||||
sessions reliably and quickly for large sites.
|
||||
- STOMP queuing. Queue management for offline daemons has been
|
||||
abstracted with three concrete instances. A new interface that should
|
||||
work with STOMP servers like ActiveMQ and RabbitMQ is available, which
|
||||
should make things scale better.
|
||||
- Group block. Group admins can block users from joining or posting to
|
||||
a group.
|
||||
- Group aliases. Groups can be referred to with aliases, additional
|
||||
names. For example, "!yul" and "!montreal" can be the same group.
|
||||
- Bidirectional Twitter bridge. Users can read the tweets their Twitter
|
||||
friends post on Twitter.
|
||||
- Adaptation of WordPress.com Terms of Service (http://en.wordpress.com/tos/)
|
||||
as default TOS for Laconica sites.
|
||||
- Better command-line handling for scripts, including standard options
|
||||
and ability to set hostname and path from the command line.
|
||||
- Many, many bug fixes.
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
@ -198,9 +221,9 @@ especially if you've previously installed PHP/MySQL packages.
|
||||
1. Unpack the tarball you downloaded on your Web server. Usually a
|
||||
command like this will work:
|
||||
|
||||
tar zxf laconica-0.7.4.tar.gz
|
||||
tar zxf laconica-0.8.0.tar.gz
|
||||
|
||||
...which will make a laconica-0.7.4 subdirectory in your current
|
||||
...which will make a laconica-0.8.0 subdirectory in your current
|
||||
directory. (If you don't have shell access on your Web server, you
|
||||
may have to unpack the tarball on your local computer and FTP the
|
||||
files to the server.)
|
||||
@ -208,7 +231,7 @@ especially if you've previously installed PHP/MySQL packages.
|
||||
2. Move the tarball to a directory of your choosing in your Web root
|
||||
directory. Usually something like this will work:
|
||||
|
||||
mv laconica-0.7.4 /var/www/mublog
|
||||
mv laconica-0.8.0 /var/www/mublog
|
||||
|
||||
This will make your Laconica instance available in the mublog path of
|
||||
your server, like "http://example.net/mublog". "microblog" or
|
||||
@ -512,6 +535,11 @@ All the daemons write their process IDs (pids) to /var/run/ by
|
||||
default. This can be useful for starting, stopping, and monitoring the
|
||||
daemons.
|
||||
|
||||
With version 0.8.0, it's now possible to use a STOMP server instead of
|
||||
our kind of hacky home-grown DB-based queue solution. See the "queues"
|
||||
config section below for how to configure to use STOMP. As of this
|
||||
writing, the software has been tested with ActiveMQ (
|
||||
|
||||
Twitter Friends Syncing
|
||||
-----------------------
|
||||
|
||||
@ -702,11 +730,11 @@ However, older installations will have the incorrect storage, and will
|
||||
consequently show up "wrong" in browsers. See below for how to deal
|
||||
with this situation.
|
||||
|
||||
If you've been using Laconica 0.6, 0.5 or lower, or if you've been
|
||||
tracking the "git" version of the software, you will probably want
|
||||
to upgrade and keep your existing data. There is no automated upgrade
|
||||
procedure in Laconica 0.7.4. Try these step-by-step instructions; read
|
||||
to the end first before trying them.
|
||||
If you've been using Laconica 0.7, 0.6, 0.5 or lower, or if you've
|
||||
been tracking the "git" version of the software, you will probably
|
||||
want to upgrade and keep your existing data. There is no automated
|
||||
upgrade procedure in Laconica 0.8.0. Try these step-by-step
|
||||
instructions; read to the end first before trying them.
|
||||
|
||||
0. Download Laconica and set up all the prerequisites as if you were
|
||||
doing a new install.
|
||||
@ -726,20 +754,31 @@ to the end first before trying them.
|
||||
5. Once all writing processes to your site are turned off, make a
|
||||
final backup of the Web directory and database.
|
||||
6. Move your Laconica directory to a backup spot, like "mublog.bak".
|
||||
7. Unpack your Laconica 0.6 tarball and move it to "mublog" or
|
||||
7. Unpack your Laconica 0.8.0 tarball and move it to "mublog" or
|
||||
wherever your code used to be.
|
||||
8. Copy the config.php file and avatar directory from your old
|
||||
directory to your new directory.
|
||||
9. Copy htaccess.sample to .htaccess in the new directory. Change the
|
||||
RewriteBase to use the correct path.
|
||||
10. Rebuild the database. For MySQL, go to your Laconica directory and
|
||||
run the rebuilddb.sh script like this:
|
||||
10. Rebuild the database. NOTE: this step is destructive and cannot be
|
||||
reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't
|
||||
do it without a known-good backup!
|
||||
|
||||
If your database is at version 0.7.4, you can run a special upgrade
|
||||
script:
|
||||
|
||||
mysql -u<rootuser> -p<rootpassword> <database> db/074to080.sql
|
||||
|
||||
Otherwise, go to your Laconica directory and AFTER YOU MAKE A
|
||||
BACKUP run the rebuilddb.sh script like this:
|
||||
|
||||
./scripts/rebuilddb.sh rootuser rootpassword database db/laconica.sql
|
||||
|
||||
Here, rootuser and rootpassword are the username and password for a
|
||||
user who can drop and create databases as well as tables; typically
|
||||
that's _not_ the user Laconica runs as.
|
||||
that's _not_ the user Laconica runs as. Note that rebuilddb.sh drops
|
||||
your database and rebuilds it; if there is an error you have no
|
||||
database. Make sure you have a backup.
|
||||
For PostgreSQL databases there is an equivalent, rebuilddb_psql.sh,
|
||||
which operates slightly differently. Read the documentation in that
|
||||
script before running it.
|
||||
@ -791,6 +830,9 @@ problem.
|
||||
3. When fixup_inboxes is finished, you can set the enabled flag to
|
||||
'true'.
|
||||
|
||||
NOTE: we will drop support for non-inboxed sites in the 0.9.x version
|
||||
of Laconica. It's time to switch now!
|
||||
|
||||
UTF-8 Database
|
||||
--------------
|
||||
|
||||
@ -817,7 +859,7 @@ what to do.
|
||||
Configuration options
|
||||
=====================
|
||||
|
||||
The sole configuration file for Laconica (excepting configurations for
|
||||
The main configuration file for Laconica (excepting configurations for
|
||||
dependency software) is config.php in your Laconica directory. If you
|
||||
edit any other file in the directory, like lib/common.php (where most
|
||||
of the defaults are defined), you will lose your configuration options
|
||||
@ -855,6 +897,8 @@ fancy: whether or not your site uses fancy URLs (see Fancy URLs
|
||||
logfile: full path to a file for Laconica to save logging
|
||||
information to. You may want to use this if you don't have
|
||||
access to syslog.
|
||||
logdebug: whether to log additional debug info like backtraces on
|
||||
hard errors. Default false.
|
||||
locale_path: full path to the directory for locale data. Unless you
|
||||
store all your locale data in one place, you probably
|
||||
don't need to use this.
|
||||
@ -892,8 +936,6 @@ notice: A plain string that will appear on every page. A good place
|
||||
to put introductory information about your service, or info about
|
||||
upgrades and outages, or other community info. Any HTML will
|
||||
be escaped.
|
||||
dupelimit: Time in which it's not OK for the same person to post the
|
||||
same notice; default = 60 seconds.
|
||||
logo: URL of an image file to use as the logo for the site. Overrides
|
||||
the logo in the theme, if any.
|
||||
ssl: Whether to use SSL and https:// URLs for some or all pages.
|
||||
@ -909,6 +951,12 @@ sslserver: use an alternate server name for SSL URLs, like
|
||||
shorturllength: Length of URL at which URLs in a message exceeding 140
|
||||
characters will be sent to the user's chosen
|
||||
shortening service.
|
||||
design: a default design (colors and background) for the site.
|
||||
Sub-items are: backgroundcolor, contentcolor, sidebarcolor,
|
||||
textcolor, linkcolor, backgroundimage, disposition.
|
||||
dupelimit: minimum time allowed for one person to say the same thing
|
||||
twice. Default 60s. Anything lower is considered a user
|
||||
or UI error.
|
||||
|
||||
db
|
||||
--
|
||||
@ -959,6 +1007,10 @@ appname: The name that Laconica uses to log messages. By default it's
|
||||
"laconica", but if you have more than one installation on the
|
||||
server, you may want to change the name for each instance so
|
||||
you can track log messages more easily.
|
||||
priority: level to log at. Currently ignored.
|
||||
facility: what syslog facility to used. Defaults to LOG_USER, only
|
||||
reset if you know what syslog is and have a good reason
|
||||
to change it.
|
||||
|
||||
queue
|
||||
-----
|
||||
@ -968,7 +1020,19 @@ sending out SMS email or XMPP messages, for off-line processing. See
|
||||
'Queues and daemons' above for how to set this up.
|
||||
|
||||
enabled: Whether to uses queues. Defaults to false.
|
||||
|
||||
subsystem: Which kind of queueserver to use. Values include "db" for
|
||||
our hacked-together database queuing (no other server
|
||||
required) and "stomp" for a stomp server.
|
||||
stomp_server: "broker URI" for stomp server. Something like
|
||||
"tcp://hostname:61613". More complicated ones are
|
||||
possible; see your stomp server's documentation for
|
||||
details.
|
||||
queue_basename: a root name to use for queues (stomp only). Typically
|
||||
something like '/queue/sitename/' makes sense.
|
||||
stomp_username: username for connecting to the stomp server; defaults
|
||||
to null.
|
||||
stomp_password: password for connecting to the stomp server; defaults
|
||||
to null.
|
||||
license
|
||||
-------
|
||||
|
||||
@ -1038,6 +1102,8 @@ localonly: If set to true, only messages posted by users of this
|
||||
blacklist: An array of IDs of users to hide from the public stream.
|
||||
Useful if you have someone making excessive Twitterfeed posts
|
||||
to the site, other kinds of automated posts, testing bots, etc.
|
||||
autosource: Sources of notices that are from automatic posters, and thus
|
||||
should be kept off the public timeline. Default empty.
|
||||
|
||||
theme
|
||||
-----
|
||||
@ -1100,6 +1166,15 @@ dropoff: Decay factor for tag listing, in seconds.
|
||||
Defaults to exponential decay over ten days; you can twiddle
|
||||
with it to try and get better results for your site.
|
||||
|
||||
popular
|
||||
-------
|
||||
|
||||
Settings for the "popular" section of the site.
|
||||
|
||||
dropoff: Decay factor for popularity listing, in seconds.
|
||||
Defaults to exponential decay over ten days; you can twiddle
|
||||
with it to try and get better results for your site.
|
||||
|
||||
daemon
|
||||
------
|
||||
|
||||
@ -1153,6 +1228,7 @@ source: The name to use for the source of posts to Twitter. Defaults
|
||||
Twitter <http://twitter.com/help/request_source>, you can use
|
||||
that here instead. Status updates on Twitter will then have
|
||||
links to your site.
|
||||
taguri: base for tag:// URIs. Defaults to site-server + ',2009'.
|
||||
|
||||
inboxes
|
||||
-------
|
||||
@ -1239,7 +1315,7 @@ detection.
|
||||
|
||||
supported: an array of mime types you accept to store and distribute,
|
||||
like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
|
||||
setup your server to properly reckognize the types you want to
|
||||
setup your server to properly recognize the types you want to
|
||||
support.
|
||||
uploads: false to disable uploading files with notices (true by default).
|
||||
filecommand: The required MIME_Type library may need to use the 'file'
|
||||
@ -1260,6 +1336,17 @@ user_quota: total size in bytes a user can store on this server. Each user
|
||||
not exceed the user_quota.
|
||||
monthly_quota: total size permitted in the current month. This is the total
|
||||
size in bytes that a user can upload each month.
|
||||
dir: directory accessible to the Web process where uploads should go.
|
||||
Defaults to the 'file' subdirectory of the install directory, which
|
||||
should be writeable by the Web user.
|
||||
server: server name to use when creating URLs for uploaded files.
|
||||
Defaults to null, meaning to use the default Web server. Using
|
||||
a virtual server here can speed up Web performance.
|
||||
path: URL path, relative to the server, to find files. Defaults to
|
||||
main path + '/file/'.
|
||||
filecommand: command to use for determining the type of a file. May be
|
||||
skipped if fileinfo extension is installed. Defaults to
|
||||
'/usr/bin/file'.
|
||||
|
||||
group
|
||||
-----
|
||||
@ -1300,6 +1387,38 @@ handle: boolean. Whether we should register our own PHP session-handling
|
||||
debug: whether to output debugging info for session storage. Can help
|
||||
with weird session bugs, sometimes. Default false.
|
||||
|
||||
background
|
||||
----------
|
||||
|
||||
Users can upload backgrounds for their pages; this section defines
|
||||
their use.
|
||||
|
||||
server: the server to use for background. Using a separate (even
|
||||
virtual) server for this can speed up load times. Default is
|
||||
null; same as site server.
|
||||
dir: directory to write backgrounds too. Default is '/background/'
|
||||
subdir of install dir.
|
||||
path: path to backgrounds. Default is sub-path of install path; note
|
||||
that you may need to change this if you change site-path too.
|
||||
|
||||
twitterbridge
|
||||
-------------
|
||||
|
||||
A bi-direction bridge to Twitter (http://twitter.com/).
|
||||
|
||||
enabled: default false. If true, will show user's Twitter friends'
|
||||
notices in their inbox and faves pages, only to the user. You
|
||||
must also run the twitterstatusfetcher.php script.
|
||||
|
||||
ping
|
||||
----
|
||||
|
||||
Using the "XML-RPC Ping" method initiated by weblogs.com, the site can
|
||||
notify third-party servers of updates.
|
||||
|
||||
notify: an array of URLs for ping endpoints. Default is the empty
|
||||
array (no notification).
|
||||
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
@ -1396,7 +1515,7 @@ if anyone's been overlooked in error.
|
||||
* Ori Avtalion
|
||||
* Meitar Moscovitz
|
||||
* Ken Sheppardson (Trac server, man-about-town)
|
||||
* Tiago 'gouki' Faria (i18n managerx)
|
||||
* Tiago 'gouki' Faria (i18n manager)
|
||||
* Sean Murphy
|
||||
* Leslie Michael Orchard
|
||||
* Eric Helgeson
|
||||
@ -1405,6 +1524,11 @@ if anyone's been overlooked in error.
|
||||
* Tobias Diekershoff
|
||||
* Dan Moore
|
||||
* Fil
|
||||
* Jeff Mitchell
|
||||
* Brenda Wallace
|
||||
* Jeffery To
|
||||
* Federico Marani
|
||||
* Craig Andrews
|
||||
|
||||
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,
|
||||
|
@ -75,14 +75,14 @@ class ApiAction extends Action
|
||||
}
|
||||
} else {
|
||||
|
||||
# Caller might give us a username even if not required
|
||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||
$user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
|
||||
if ($user) {
|
||||
$this->user = $user;
|
||||
}
|
||||
# Twitter doesn't throw an error if the user isn't found
|
||||
}
|
||||
// Caller might give us a username even if not required
|
||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||
$user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
|
||||
if ($user) {
|
||||
$this->user = $user;
|
||||
}
|
||||
# Twitter doesn't throw an error if the user isn't found
|
||||
}
|
||||
|
||||
$this->process_command();
|
||||
}
|
||||
@ -117,7 +117,7 @@ class ApiAction extends Action
|
||||
}
|
||||
}
|
||||
|
||||
# Whitelist of API methods that don't need authentication
|
||||
// Whitelist of API methods that don't need authentication
|
||||
function requires_auth()
|
||||
{
|
||||
static $noauth = array( 'statuses/public_timeline',
|
||||
@ -127,7 +127,8 @@ class ApiAction extends Action
|
||||
'help/downtime_schedule',
|
||||
'laconica/version',
|
||||
'laconica/config',
|
||||
'laconica/wadl');
|
||||
'laconica/wadl',
|
||||
'groups/timeline');
|
||||
|
||||
static $bareauth = array('statuses/user_timeline',
|
||||
'statuses/friends_timeline',
|
||||
@ -135,28 +136,61 @@ class ApiAction extends Action
|
||||
'statuses/replies',
|
||||
'statuses/mentions',
|
||||
'statuses/followers',
|
||||
'favorites/favorites');
|
||||
'favorites/favorites',
|
||||
'friendships/show');
|
||||
|
||||
$fullname = "$this->api_action/$this->api_method";
|
||||
|
||||
// If the site is "private", all API methods except laconica/config
|
||||
// need authentication
|
||||
|
||||
if (common_config('site', 'private')) {
|
||||
return $fullname != 'laconica/config' || false;
|
||||
}
|
||||
|
||||
// bareauth: only needs auth if without an argument or query param specifying user
|
||||
|
||||
if (in_array($fullname, $bareauth)) {
|
||||
# bareauth: only needs auth if without an argument or query param specifying user
|
||||
if ($this->api_arg || $this->arg('id') || is_numeric($this->arg('user_id')) || $this->arg('screen_name')) {
|
||||
|
||||
// Special case: friendships/show only needs auth if source_id or
|
||||
// source_screen_name is not specified as a param
|
||||
|
||||
if ($fullname == 'friendships/show') {
|
||||
|
||||
$source_id = $this->arg('source_id');
|
||||
$source_screen_name = $this->arg('source_screen_name');
|
||||
|
||||
if (empty($source_id) && empty($source_screen_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if all of these are empty, auth is required
|
||||
|
||||
$id = $this->arg('id');
|
||||
$user_id = $this->arg('user_id');
|
||||
$screen_name = $this->arg('screen_name');
|
||||
|
||||
if (empty($this->api_arg) &&
|
||||
empty($id) &&
|
||||
empty($user_id) &&
|
||||
empty($screen_name)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (in_array($fullname, $noauth)) {
|
||||
# noauth: never needs auth
|
||||
|
||||
// noauth: never needs auth
|
||||
|
||||
return false;
|
||||
} else {
|
||||
# everybody else needs auth
|
||||
|
||||
// everybody else needs auth
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ class FacebookhomeAction extends FacebookAction
|
||||
|
||||
// If this is the first time the user has started the app
|
||||
// prompt for Facebook status update permission
|
||||
if (!$this->facebook->api_client->users_hasAppPermission('status_update')) {
|
||||
if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) {
|
||||
|
||||
if ($this->facebook->api_client->data_getUserPreference(
|
||||
FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') {
|
||||
@ -203,7 +203,7 @@ class FacebookhomeAction extends FacebookAction
|
||||
$api_key = common_config('facebook', 'apikey');
|
||||
|
||||
$auth_url = 'http://www.facebook.com/authorize.php?api_key=' .
|
||||
$api_key . '&v=1.0&ext_perm=status_update&next=' . $next .
|
||||
$api_key . '&v=1.0&ext_perm=publish_stream&next=' . $next .
|
||||
'&next_cancel=' . $next . '&submit=skip';
|
||||
|
||||
$this->elementStart('span', array('class' => 'facebook-button'));
|
||||
|
@ -31,7 +31,7 @@ class FacebookinviteAction extends FacebookAction
|
||||
$this->error = $error;
|
||||
|
||||
if ($this->flink) {
|
||||
if (!$this->facebook->api_client->users_hasAppPermission('status_update') &&
|
||||
if (!$this->facebook->api_client->users_hasAppPermission('publish_stream') &&
|
||||
$this->facebook->api_client->data_getUserPreference(
|
||||
FACEBOOK_PROMPTED_UPDATE_PREF) == 'true') {
|
||||
|
||||
@ -60,7 +60,7 @@ class FacebookinviteAction extends FacebookAction
|
||||
|
||||
// If this is the first time the user has started the app
|
||||
// prompt for Facebook status update permission
|
||||
if (!$this->facebook->api_client->users_hasAppPermission('status_update')) {
|
||||
if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) {
|
||||
|
||||
if ($this->facebook->api_client->data_getUserPreference(
|
||||
FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') {
|
||||
|
@ -78,7 +78,7 @@ class FacebooksettingsAction extends FacebookAction
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->facebook->api_client->users_hasAppPermission('status_update')) {
|
||||
if ($this->facebook->api_client->users_hasAppPermission('publish_stream')) {
|
||||
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
'id' => 'facebook_settings'));
|
||||
@ -131,7 +131,7 @@ class FacebooksettingsAction extends FacebookAction
|
||||
|
||||
$this->elementStart('ul', array('id' => 'fb-permissions-list'));
|
||||
$this->elementStart('li', array('id' => 'fb-permissions-item'));
|
||||
$this->elementStart('fb:prompt-permission', array('perms' => 'status_update',
|
||||
$this->elementStart('fb:prompt-permission', array('perms' => 'publish_stream',
|
||||
'next_fbjs' => 'document.setLocation(\'' . "$this->app_uri/settings.php" . '\')'));
|
||||
$this->element('span', array('class' => 'facebook-button'),
|
||||
sprintf(_('Allow %s to update my Facebook status'), common_config('site', 'name')));
|
||||
|
@ -194,7 +194,7 @@ class FavoritedAction extends Action
|
||||
$qry = 'SELECT notice.*, '.
|
||||
$weightexpr . ' as weight ' .
|
||||
'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
|
||||
'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source ' .
|
||||
'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source,notice.conversation ' .
|
||||
'ORDER BY weight DESC';
|
||||
|
||||
$offset = ($this->page - 1) * NOTICES_PER_PAGE;
|
||||
|
@ -312,36 +312,4 @@ class GroupDesignSettingsAction extends DesignSettingsAction
|
||||
$this->showForm(_('Design preferences saved.'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle input and output a page (overrided)
|
||||
*
|
||||
* @param array $args $_REQUEST arguments
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
if (!common_logged_in()) {
|
||||
$this->clientError(_('Not logged in.'));
|
||||
return;
|
||||
} else if (!common_is_real_login()) {
|
||||
// Cookie theft means that automatic logins can't
|
||||
// change important settings or see private info, and
|
||||
// _all_ our settings are important
|
||||
common_set_returnto($this->selfUrl());
|
||||
$user = common_current_user();
|
||||
if ($user->hasOpenID()) {
|
||||
common_redirect(common_local_url('openidlogin'), 303);
|
||||
} else {
|
||||
common_redirect(common_local_url('login'), 303);
|
||||
}
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$this->handlePost();
|
||||
} else {
|
||||
$this->showForm();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ class NewnoticeAction extends Action
|
||||
|
||||
function isRespectsQuota($user) {
|
||||
$file = new File;
|
||||
$ret = $file->isRespectsQuota($user);
|
||||
$ret = $file->isRespectsQuota($user,$_FILES['attach']['size']);
|
||||
if (true === $ret) return true;
|
||||
$this->clientError($ret);
|
||||
}
|
||||
|
@ -83,14 +83,12 @@ class OthersettingsAction extends AccountSettingsAction
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
'id' => 'form_settings_other',
|
||||
'class' => 'form_settings',
|
||||
'action' =>
|
||||
common_local_url('othersettings')));
|
||||
$this->elementStart('fieldset');
|
||||
$this->element('legend', null, _('URL Auto-shortening'));
|
||||
$this->hidden('token', common_session_token());
|
||||
|
||||
// I18N
|
||||
@ -109,10 +107,14 @@ class OthersettingsAction extends AccountSettingsAction
|
||||
|
||||
$this->elementStart('ul', 'form_data');
|
||||
$this->elementStart('li');
|
||||
$this->dropdown('urlshorteningservice', _('Service'),
|
||||
$this->dropdown('urlshorteningservice', _('Shorten URLs with'),
|
||||
$services, _('Automatic shortening service to use.'),
|
||||
false, $user->urlshorteningservice);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('viewdesigns', _('View profile designs'),
|
||||
$user->viewdesigns, _('Show or hide profile designs.'));
|
||||
$this->elementEnd('li');
|
||||
$this->elementEnd('ul');
|
||||
$this->submit('save', _('Save'));
|
||||
$this->elementEnd('fieldset');
|
||||
@ -145,6 +147,8 @@ class OthersettingsAction extends AccountSettingsAction
|
||||
return;
|
||||
}
|
||||
|
||||
$viewdesigns = $this->boolean('viewdesigns');
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
assert(!is_null($user)); // should already be checked
|
||||
@ -154,6 +158,7 @@ class OthersettingsAction extends AccountSettingsAction
|
||||
$original = clone($user);
|
||||
|
||||
$user->urlshorteningservice = $urlshorteningservice;
|
||||
$user->viewdesigns = $viewdesigns;
|
||||
|
||||
$result = $user->update($original);
|
||||
|
||||
|
@ -317,8 +317,25 @@ class ShowgroupAction extends GroupDesignAction
|
||||
common_local_url('grouprss',
|
||||
array('nickname' => $this->group->nickname));
|
||||
|
||||
return array(new Feed(Feed::RSS1, $url, sprintf(_('Notice feed for %s group'),
|
||||
$this->group->nickname)));
|
||||
return array(new Feed(Feed::RSS1,
|
||||
common_local_url('grouprss',
|
||||
array('nickname' => $this->group->nickname)),
|
||||
sprintf(_('Notice feed for %s group (RSS 1.0)'),
|
||||
$this->group->nickname)),
|
||||
new Feed(Feed::RSS2,
|
||||
common_local_url('api',
|
||||
array('apiaction' => 'groups',
|
||||
'method' => 'timeline',
|
||||
'argument' => $this->group->nickname.'.rss')),
|
||||
sprintf(_('Notice feed for %s group (RSS 2.0)'),
|
||||
$this->group->nickname)),
|
||||
new Feed(Feed::ATOM,
|
||||
common_local_url('api',
|
||||
array('apiaction' => 'groups',
|
||||
'method' => 'timeline',
|
||||
'argument' => $this->group->nickname.'.atom')),
|
||||
sprintf(_('Notice feed for %s group (Atom)'),
|
||||
$this->group->nickname)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
114
actions/twitapigroups.php
Normal file
114
actions/twitapigroups.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Laconica extensions to the Twitter-like API for groups
|
||||
*
|
||||
* 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 Twitter
|
||||
* @package Laconica
|
||||
* @author Craig Andrews
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once INSTALLDIR.'/lib/twitterapi.php';
|
||||
|
||||
/**
|
||||
* Group-specific API methods
|
||||
*
|
||||
* This class handles Laconica group API methods.
|
||||
*
|
||||
* @category Twitter
|
||||
* @package Laconica
|
||||
* @author Craig Andrews
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class TwitapigroupsAction extends TwitterapiAction
|
||||
{
|
||||
|
||||
function timeline($args, $apidata)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
common_debug("in groups api action");
|
||||
|
||||
$this->auth_user = $apidata['user'];
|
||||
$group = $this->get_group($apidata['api_arg'], $apidata);
|
||||
|
||||
if (empty($group)) {
|
||||
$this->clientError('Not Found', 404, $apidata['content-type']);
|
||||
return;
|
||||
}
|
||||
|
||||
$sitename = common_config('site', 'name');
|
||||
$title = sprintf(_("%s timeline"), $group->nickname);
|
||||
$taguribase = common_config('integration', 'taguri');
|
||||
$id = "tag:$taguribase:GroupTimeline:".$group->id;
|
||||
$link = common_local_url('showgroup',
|
||||
array('nickname' => $group->nickname));
|
||||
$subtitle = sprintf(_('Updates from %1$s on %2$s!'),
|
||||
$group->nickname, $sitename);
|
||||
|
||||
$page = (int)$this->arg('page', 1);
|
||||
$count = (int)$this->arg('count', 20);
|
||||
$max_id = (int)$this->arg('max_id', 0);
|
||||
$since_id = (int)$this->arg('since_id', 0);
|
||||
$since = $this->arg('since');
|
||||
|
||||
$notice = $group->getNotices(($page-1)*$count,
|
||||
$count, $since_id, $max_id, $since);
|
||||
|
||||
switch($apidata['content-type']) {
|
||||
case 'xml':
|
||||
$this->show_xml_timeline($notice);
|
||||
break;
|
||||
case 'rss':
|
||||
$this->show_rss_timeline($notice, $title, $link,
|
||||
$subtitle, $suplink);
|
||||
break;
|
||||
case 'atom':
|
||||
if (isset($apidata['api_arg'])) {
|
||||
$selfuri = common_root_url() .
|
||||
'api/statuses/groups/timeline/' .
|
||||
$apidata['api_arg'] . '.atom';
|
||||
} else {
|
||||
$selfuri = common_root_url() .
|
||||
'api/statuses/groups/timeline.atom';
|
||||
}
|
||||
$this->show_atom_timeline($notice, $title, $id, $link,
|
||||
$subtitle, $suplink, $selfuri);
|
||||
break;
|
||||
case 'json':
|
||||
$this->show_json_timeline($notice);
|
||||
break;
|
||||
default:
|
||||
$this->clientError(_('API method not found!'), $code = 404);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -373,9 +373,19 @@ class TwitapistatusesAction extends TwitterapiAction
|
||||
return;
|
||||
}
|
||||
|
||||
// 'id' is an undocumented parameter in Twitter's API. Several
|
||||
// clients make use of it, so we support it too.
|
||||
|
||||
// show.json?id=12345 takes precedence over /show/12345.json
|
||||
|
||||
$this->auth_user = $apidata['user'];
|
||||
$notice_id = $apidata['api_arg'];
|
||||
$notice = Notice::staticGet($notice_id);
|
||||
$notice_id = $this->trimmed('id');
|
||||
|
||||
if (empty($notice_id)) {
|
||||
$notice_id = $apidata['api_arg'];
|
||||
}
|
||||
|
||||
$notice = Notice::staticGet((int)$notice_id);
|
||||
|
||||
if ($notice) {
|
||||
if ($apidata['content-type'] == 'xml') {
|
||||
@ -389,7 +399,6 @@ class TwitapistatusesAction extends TwitterapiAction
|
||||
$this->clientError(_('No status with that ID found.'),
|
||||
404, $apidata['content-type']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function destroy($args, $apidata)
|
||||
|
@ -37,24 +37,24 @@ class TwitapiusersAction extends TwitterapiAction
|
||||
|
||||
$user = null;
|
||||
$email = $this->arg('email');
|
||||
$user_id = $this->arg('user_id');
|
||||
|
||||
// XXX: email field deprecated in Twitter's API
|
||||
|
||||
// XXX: Also: need to add screen_name param
|
||||
|
||||
if ($email) {
|
||||
$user = User::staticGet('email', $email);
|
||||
} elseif ($user_id) {
|
||||
$user = $this->get_user($user_id);
|
||||
} elseif (isset($apidata['api_arg'])) {
|
||||
$user = $this->get_user($apidata['api_arg']);
|
||||
} elseif (isset($apidata['user'])) {
|
||||
$user = $apidata['user'];
|
||||
} else {
|
||||
$user = $this->get_user($apidata['api_arg'], $apidata);
|
||||
}
|
||||
|
||||
if (empty($user)) {
|
||||
$this->client_error(_('Not found.'), 404, $apidata['content-type']);
|
||||
$this->clientError(_('Not found.'), 404, $apidata['content-type']);
|
||||
return;
|
||||
}
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
if (!$profile) {
|
||||
common_server_error(_('User has no profile.'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -122,17 +122,17 @@ class File extends Memcached_DataObject
|
||||
return $x;
|
||||
}
|
||||
|
||||
function isRespectsQuota($user) {
|
||||
if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) {
|
||||
function isRespectsQuota($user,$fileSize) {
|
||||
if ($fileSize > common_config('attachments', 'file_quota')) {
|
||||
return sprintf(_('No file may be larger than %d bytes ' .
|
||||
'and the file you sent was %d bytes. Try to upload a smaller version.'),
|
||||
common_config('attachments', 'file_quota'), $_FILES['attach']['size']);
|
||||
common_config('attachments', 'file_quota'), $fileSize);
|
||||
}
|
||||
|
||||
$query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'";
|
||||
$this->query($query);
|
||||
$this->fetch();
|
||||
$total = $this->total + $_FILES['attach']['size'];
|
||||
$total = $this->total + $fileSize;
|
||||
if ($total > common_config('attachments', 'user_quota')) {
|
||||
return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'));
|
||||
}
|
||||
@ -140,7 +140,7 @@ class File extends Memcached_DataObject
|
||||
$query .= ' month(modified) = month(now()) and year(modified) = year(now())';
|
||||
$this->query($query);
|
||||
$this->fetch();
|
||||
$total = $this->total + $_FILES['attach']['size'];
|
||||
$total = $this->total + $fileSize;
|
||||
if ($total > common_config('attachments', 'monthly_quota')) {
|
||||
return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'));
|
||||
}
|
||||
|
@ -33,41 +33,21 @@ class Memcached_DataObject extends DB_DataObject
|
||||
$k = $keys[0];
|
||||
unset($i);
|
||||
}
|
||||
$i = self::getcached($cls, $k, $v);
|
||||
$i = Memcached_DataObject::getcached($cls, $k, $v);
|
||||
if ($i) {
|
||||
return $i;
|
||||
} else {
|
||||
$i = DB_DataObject::staticGet($cls, $k, $v);
|
||||
if ($i) {
|
||||
$i->encache();
|
||||
} else {
|
||||
self::cachenull($cls, $k, $v);
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
function cachenull($cls, $k, $v)
|
||||
{
|
||||
$c = self::memcache();
|
||||
if (empty($c)) {
|
||||
return;
|
||||
}
|
||||
$c->set(self::cacheKey($cls, $k, $v), null);
|
||||
}
|
||||
|
||||
function multicachenull($cls, $kv)
|
||||
{
|
||||
$c = self::memcache();
|
||||
if (empty($c)) {
|
||||
return;
|
||||
}
|
||||
$c->set(self::multicachekey($cls, $kv), null);
|
||||
}
|
||||
|
||||
function &pkeyGet($cls, $kv)
|
||||
{
|
||||
$i = self::multicache($cls, $kv);
|
||||
$i = Memcached_DataObject::multicache($cls, $kv);
|
||||
if ($i) {
|
||||
return $i;
|
||||
} else {
|
||||
@ -78,7 +58,6 @@ class Memcached_DataObject extends DB_DataObject
|
||||
if ($i->find(true)) {
|
||||
$i->encache();
|
||||
} else {
|
||||
self::multicachenull($cls, $kv);
|
||||
$i = null;
|
||||
}
|
||||
return $i;
|
||||
@ -88,9 +67,6 @@ class Memcached_DataObject extends DB_DataObject
|
||||
function insert()
|
||||
{
|
||||
$result = parent::insert();
|
||||
if ($result) {
|
||||
$this->encache();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
@ -121,11 +97,11 @@ class Memcached_DataObject extends DB_DataObject
|
||||
}
|
||||
|
||||
static function getcached($cls, $k, $v) {
|
||||
$c = self::memcache();
|
||||
$c = Memcached_DataObject::memcache();
|
||||
if (!$c) {
|
||||
return false;
|
||||
} else {
|
||||
return $c->get(self::cacheKey($cls, $k, $v));
|
||||
return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v));
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,23 +168,17 @@ class Memcached_DataObject extends DB_DataObject
|
||||
|
||||
function multicache($cls, $kv)
|
||||
{
|
||||
$c = self::memcache();
|
||||
ksort($kv);
|
||||
$c = Memcached_DataObject::memcache();
|
||||
if (!$c) {
|
||||
return false;
|
||||
} else {
|
||||
return $c->get(self::multicachekey($cls, $kv));
|
||||
$pkeys = implode(',', array_keys($kv));
|
||||
$pvals = implode(',', array_values($kv));
|
||||
return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals));
|
||||
}
|
||||
}
|
||||
|
||||
function multicachekey($cls, $kv)
|
||||
{
|
||||
ksort($kv);
|
||||
$pkeys = implode(',', array_keys($kv));
|
||||
$pvals = implode(',', array_values($kv));
|
||||
|
||||
return self::cacheKey($cls, $pkeys, $pvals);
|
||||
}
|
||||
|
||||
function getSearchEngine($table)
|
||||
{
|
||||
require_once INSTALLDIR.'/lib/search_engines.php';
|
||||
@ -241,7 +211,7 @@ class Memcached_DataObject extends DB_DataObject
|
||||
|
||||
static function cachedQuery($cls, $qry, $expiry=3600)
|
||||
{
|
||||
$c = self::memcache();
|
||||
$c = Memcached_DataObject::memcache();
|
||||
if (!$c) {
|
||||
$inst = new $cls();
|
||||
$inst->query($qry);
|
||||
|
@ -356,6 +356,8 @@ class Notice extends Memcached_DataObject
|
||||
$this->blowTagCache($blowLast);
|
||||
$this->blowGroupCache($blowLast);
|
||||
$this->blowConversationCache($blowLast);
|
||||
$profile = Profile::staticGet($this->profile_id);
|
||||
$profile->blowNoticeCount();
|
||||
}
|
||||
|
||||
function blowConversationCache($blowLast=false)
|
||||
@ -1164,6 +1166,18 @@ class Notice extends Memcached_DataObject
|
||||
}
|
||||
$tag->free();
|
||||
|
||||
# Enclosures
|
||||
$attachments = $this->attachments();
|
||||
if($attachments){
|
||||
foreach($attachments as $attachment){
|
||||
$attributes = array('rel'=>'enclosure','href'=>$attachment->url,'type'=>$attachment->mimetype,'length'=>$attachment->size);
|
||||
if($attachment->title){
|
||||
$attributes['title']=$attachment->title;
|
||||
}
|
||||
$xs->element('link', $attributes, null);
|
||||
}
|
||||
}
|
||||
|
||||
$xs->elementEnd('entry');
|
||||
|
||||
return $xs->getString();
|
||||
@ -1210,7 +1224,7 @@ class Notice extends Memcached_DataObject
|
||||
$window = explode(',', $laststr);
|
||||
$last_id = $window[0];
|
||||
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
|
||||
$last_id, 0, null, $tag)));
|
||||
$last_id, 0, null)));
|
||||
|
||||
$new_window = array_merge($new_ids, $window);
|
||||
|
||||
@ -1225,7 +1239,7 @@ class Notice extends Memcached_DataObject
|
||||
}
|
||||
|
||||
$window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
|
||||
0, 0, null, $tag)));
|
||||
0, 0, null)));
|
||||
|
||||
$windowstr = implode(',', $window);
|
||||
|
||||
|
@ -337,4 +337,132 @@ class Profile extends Memcached_DataObject
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
function subscriptionCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
|
||||
if (!empty($c)) {
|
||||
$cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id));
|
||||
if (is_integer($cnt)) {
|
||||
return (int) $cnt;
|
||||
}
|
||||
}
|
||||
|
||||
$sub = new Subscription();
|
||||
$sub->subscriber = $this->id;
|
||||
|
||||
$cnt = (int) $sub->count('distinct subscribed');
|
||||
|
||||
$cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
|
||||
|
||||
if (!empty($c)) {
|
||||
$c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt);
|
||||
}
|
||||
|
||||
common_debug("subscriptionCount == $cnt");
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
function subscriberCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
if (!empty($c)) {
|
||||
$cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id));
|
||||
if (is_integer($cnt)) {
|
||||
return (int) $cnt;
|
||||
}
|
||||
}
|
||||
|
||||
$sub = new Subscription();
|
||||
$sub->subscribed = $this->id;
|
||||
|
||||
$cnt = (int) $sub->count('distinct subscriber');
|
||||
|
||||
$cnt = ($cnt > 0) ? $cnt - 1 : $cnt;
|
||||
|
||||
if (!empty($c)) {
|
||||
$c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt);
|
||||
}
|
||||
|
||||
common_debug("subscriberCount == $cnt");
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
function faveCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
if (!empty($c)) {
|
||||
$cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id));
|
||||
if (is_integer($cnt)) {
|
||||
return (int) $cnt;
|
||||
}
|
||||
}
|
||||
|
||||
$faves = new Fave();
|
||||
$faves->user_id = $this->id;
|
||||
$cnt = (int) $faves->count('distinct notice_id');
|
||||
|
||||
if (!empty($c)) {
|
||||
$c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt);
|
||||
}
|
||||
|
||||
common_debug("faveCount == $cnt");
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
function noticeCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
|
||||
if (!empty($c)) {
|
||||
$cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id));
|
||||
if (is_integer($cnt)) {
|
||||
return (int) $cnt;
|
||||
}
|
||||
}
|
||||
|
||||
$notices = new Notice();
|
||||
$notices->profile_id = $this->id;
|
||||
$cnt = (int) $notices->count('distinct id');
|
||||
|
||||
if (!empty($c)) {
|
||||
$c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt);
|
||||
}
|
||||
|
||||
common_debug("noticeCount == $cnt");
|
||||
return $cnt;
|
||||
}
|
||||
|
||||
function blowSubscriberCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
if (!empty($c)) {
|
||||
$c->delete(common_cache_key('profile:subscriber_count:'.$this->id));
|
||||
}
|
||||
}
|
||||
|
||||
function blowSubscriptionCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
if (!empty($c)) {
|
||||
$c->delete(common_cache_key('profile:subscription_count:'.$this->id));
|
||||
}
|
||||
}
|
||||
|
||||
function blowFaveCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
if (!empty($c)) {
|
||||
$c->delete(common_cache_key('profile:fave_count:'.$this->id));
|
||||
}
|
||||
}
|
||||
|
||||
function blowNoticeCount()
|
||||
{
|
||||
$c = common_memcache();
|
||||
if (!empty($c)) {
|
||||
$c->delete(common_cache_key('profile:notice_count:'.$this->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,4 +54,9 @@ class Queue_item extends Memcached_DataObject
|
||||
$qi = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
function &pkeyGet($kv)
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('Queue_item', $kv);
|
||||
}
|
||||
}
|
||||
|
@ -494,6 +494,8 @@ class User extends Memcached_DataObject
|
||||
$cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id));
|
||||
$cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
|
||||
}
|
||||
$profile = $this->getProfile();
|
||||
$profile->blowFaveCount();
|
||||
}
|
||||
|
||||
function getSelfTags()
|
||||
|
@ -116,7 +116,9 @@ create table notice (
|
||||
modified timestamp /* comment 'date this record was modified' */,
|
||||
reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) ,
|
||||
is_local integer default 0 /* comment 'notice was generated by a user' */,
|
||||
source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */
|
||||
source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */,
|
||||
conversation integer /*id of root notice in this conversation' */ references notice (id)
|
||||
|
||||
|
||||
/* FULLTEXT(content) */
|
||||
);
|
||||
@ -172,7 +174,7 @@ create table token (
|
||||
tok char(32) not null /* comment 'identifying value' */,
|
||||
secret char(32) not null /* comment 'secret value' */,
|
||||
type integer not null default 0 /* comment 'request or access' */,
|
||||
state integer default 0 /* comment 'for requests; 0 = initial, 1 = authorized, 2 = used' */,
|
||||
state integer default 0 /* comment 'for requests 0 = initial, 1 = authorized, 2 = used' */,
|
||||
|
||||
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
|
||||
modified timestamp /* comment 'date this record was modified' */,
|
||||
@ -346,7 +348,7 @@ create table notice_inbox (
|
||||
user_id integer not null /* comment 'user receiving the message' */ references "user" (id),
|
||||
notice_id integer not null /* comment 'notice received' */ references notice (id),
|
||||
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */,
|
||||
source integer default 1 /* comment 'reason it is in the inbox; 1=subscription' */,
|
||||
source integer default 1 /* comment 'reason it is in the inbox: 1=subscription' */,
|
||||
|
||||
primary key (user_id, notice_id)
|
||||
);
|
||||
@ -436,8 +438,8 @@ create table file (
|
||||
mimetype varchar(50),
|
||||
size integer,
|
||||
title varchar(255),
|
||||
date integer(11),
|
||||
protected integer(1)
|
||||
date integer,
|
||||
protected integer
|
||||
);
|
||||
|
||||
create sequence file_oembed_seq;
|
||||
@ -454,7 +456,7 @@ create table file_oembed (
|
||||
title varchar(255),
|
||||
author_name varchar(50),
|
||||
author_url varchar(255),
|
||||
url varchar(255),
|
||||
url varchar(255)
|
||||
);
|
||||
|
||||
create sequence file_redirection_seq;
|
||||
@ -484,6 +486,18 @@ create table file_to_post (
|
||||
unique(file_id, post_id)
|
||||
);
|
||||
|
||||
create sequence design_seq;
|
||||
create table design (
|
||||
id bigint default nextval('design_seq') /* comment 'design ID'*/,
|
||||
backgroundcolor integer /* comment 'main background color'*/ ,
|
||||
contentcolor integer /*comment 'content area background color'*/ ,
|
||||
sidebarcolor integer /*comment 'sidebar background color'*/ ,
|
||||
textcolor integer /*comment 'text color'*/ ,
|
||||
linkcolor integer /*comment 'link color'*/,
|
||||
backgroundimage varchar(255) /*comment 'background image, if any'*/,
|
||||
disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
/* Textsearch stuff */
|
||||
|
||||
|
300
doc-src/tos
Normal file
300
doc-src/tos
Normal file
@ -0,0 +1,300 @@
|
||||
The gist
|
||||
--------
|
||||
|
||||
We (the folks at [%%site.broughtby%%](%%site.broughtbyurl%%)) run a
|
||||
service called %%site.name%% and would love for you to use it. Our
|
||||
service is designed to give you as much control and ownership over
|
||||
what goes in your notice stream as possible and encourage you to
|
||||
express yourself freely. However, be responsible in what you post. In
|
||||
particular, make sure that none of the prohibited items listed below
|
||||
appear in your notice stream or get linked to from your notice stream (things
|
||||
like spam, viruses, or hate content).
|
||||
|
||||
You can review our [Public Stream](%%action.public%%) to get a sense
|
||||
of the types of notices that are welcome on our service (or not!). If
|
||||
you find a %%site.name%% account that you believe violates our terms
|
||||
of service, please check our [Contact](%%doc.contact%%) documentation.
|
||||
|
||||
(Note: Automattic, Inc., original creators of the below Terms of
|
||||
Service, decided to make them available under a Creative Commons
|
||||
Sharealike license, which means you’re more than welcome to steal it
|
||||
and repurpose it for your own use. Just make sure to replace
|
||||
references to us with ones to you. They’d appreciate a link to
|
||||
[WordPress.com](http://www.wordpress.com/) somewhere on your site.
|
||||
They spent a lot of money and time on the below, and other people
|
||||
shouldn’t need to do the same. (We didn't!))
|
||||
|
||||
Terms of Service
|
||||
----------------
|
||||
|
||||
The following terms and conditions govern all use of the %%site.name%%
|
||||
website and all content, services and products available at or through
|
||||
the website (taken together, the Website). The Website is owned and
|
||||
operated by %%site.broughtby%% (“Operator”). The Website is offered
|
||||
subject to your acceptance without modification of all of the terms
|
||||
and conditions contained herein and all other operating rules,
|
||||
policies (including, without limitation, Operator’s [Privacy
|
||||
Policy](%%doc.privacy%%))
|
||||
and procedures that may be published from time to time on this Site by
|
||||
Operator (collectively, the “Agreement”).
|
||||
|
||||
Please read this Agreement carefully before accessing or using the
|
||||
Website. By accessing or using any part of the web site, you agree to
|
||||
become bound by the terms and conditions of this agreement. If you do
|
||||
not agree to all the terms and conditions of this agreement, then you
|
||||
may not access the Website or use any services. If these terms and
|
||||
conditions are considered an offer by Operator, acceptance is
|
||||
expressly limited to these terms. The Website is available only to
|
||||
individuals who are at least 13 years old.
|
||||
|
||||
<ol>
|
||||
|
||||
<li><strong>Your %%site.name%% Account and Site.</strong> If you
|
||||
create a notice stream on the Website, you are responsible for
|
||||
maintaining the security of your account and notice stream, and you
|
||||
are fully responsible for all activities that occur under the account
|
||||
and any other actions taken in connection with the notice stream. You
|
||||
must not describe or assign keywords to your notice stream in a
|
||||
misleading or unlawful manner, including in a manner intended to trade
|
||||
on the name or reputation of others, and Operator may change or remove
|
||||
any description or keyword that it considers inappropriate or
|
||||
unlawful, or otherwise likely to cause Operator liability. You must
|
||||
immediately notify Operator of any unauthorized uses of your notice
|
||||
stream, your account or any other breaches of security. Operator will
|
||||
not be liable for any acts or omissions by You, including any damages
|
||||
of any kind incurred as a result of such acts or omissions.</li>
|
||||
|
||||
<li><strong>Responsibility of Contributors.</strong> If you operate a
|
||||
notice stream, comment on a notice stream, post material to the
|
||||
Website, post links on the Website, or otherwise make (or allow any
|
||||
third party to make) material available by means of the Website (any
|
||||
such material, “Content”), You are entirely responsible for the
|
||||
content of, and any harm resulting from, that Content. That is the
|
||||
case regardless of whether the Content in question constitutes text,
|
||||
graphics, an audio file, or computer software. By making Content
|
||||
available, you represent and warrant that:
|
||||
|
||||
<ul>
|
||||
|
||||
<li>the downloading, copying and use of the Content will not infringe
|
||||
the proprietary rights, including but not limited to the copyright,
|
||||
patent, trademark or trade secret rights, of any third party;</li>
|
||||
|
||||
<li>if your employer has rights to intellectual property you create,
|
||||
you have either (i) received permission from your employer to post or
|
||||
make available the Content, including but not limited to any software,
|
||||
or (ii) secured from your employer a waiver as to all rights in or to
|
||||
the Content;</li>
|
||||
|
||||
<li>you have fully complied with any third-party licenses
|
||||
relating to the Content, and have done all things necessary to
|
||||
successfully pass through to end users any required terms;</li>
|
||||
|
||||
<li>the Content does not contain or install any viruses, worms, malware,
|
||||
Trojan horses or other harmful or destructive content;</li>
|
||||
|
||||
<li>the Content is not spam, and does not contain unethical or
|
||||
unwanted commercial content designed to drive traffic to third party
|
||||
sites or boost the search engine rankings of third party sites, or to
|
||||
further unlawful acts (such as phishing) or mislead recipients as to
|
||||
the source of the material (such as spoofing);</li>
|
||||
|
||||
<li>if the Content is machine- or randomly-generated, it is for
|
||||
purposes of direct entertainment, information and/or utility for you
|
||||
or other users, and not for spam,</li>
|
||||
|
||||
<li>the Content is not libelous or defamatory (more info on
|
||||
what that means), does not contain threats or incite violence towards
|
||||
individuals or entities, and does not violate the privacy or publicity
|
||||
rights of any third party;</li>
|
||||
|
||||
<li>your notice stream is not getting advertised via unwanted electronic
|
||||
messages such as spam links on newsgroups, email lists, other notice streams
|
||||
and web sites, and similar unsolicited promotional methods;</li>
|
||||
|
||||
<li>your notice stream is not named in a manner that misleads your
|
||||
readers into thinking that you are another person or company. For
|
||||
example, your notice stream’s URL or name is not the name of a person other
|
||||
than yourself or company other than your own; and</li>
|
||||
|
||||
<li>you have, in the case of Content that includes computer code,
|
||||
accurately categorized and/or described the type, nature, uses and
|
||||
effects of the materials, whether requested to do so by Operator or
|
||||
otherwise.</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<p>By submitting Content to Operator for inclusion on your Website, you
|
||||
grant Operator a world-wide, royalty-free, and non-exclusive license
|
||||
to reproduce, modify, adapt and publish the Content solely for the
|
||||
purpose of displaying, distributing and promoting your notice
|
||||
stream.</p>
|
||||
|
||||
<p>By submitting Content to Operator for inclusion on your Website,
|
||||
you grant all readers the right to use, re-use, modify and/or
|
||||
re-distribute the Content under the terms of the <a
|
||||
href="%%license.url%%">%%license.title%%</a>.</p>
|
||||
|
||||
<p>If you delete Content, Operator will use reasonable efforts to remove it from
|
||||
the Website, but you acknowledge that caching or references to the
|
||||
Content may not be made immediately unavailable.</p>
|
||||
|
||||
<p>Without limiting any of those representations or warranties, Operator
|
||||
has the right (though not the obligation) to, in Operator’s sole
|
||||
discretion (i) refuse or remove any content that, in Operator’s
|
||||
reasonable opinion, violates any Operator policy or is in any way
|
||||
harmful or objectionable, or (ii) terminate or deny access to and use
|
||||
of the Website to any individual or entity for any reason, in
|
||||
Operator’s sole discretion.</p>
|
||||
</li>
|
||||
|
||||
<li><strong>Responsibility of Website Visitors.</strong> Operator has not reviewed,
|
||||
and cannot review, all of the material, including computer software,
|
||||
posted to the Website, and cannot therefore be responsible for that
|
||||
material’s content, use or effects. By operating the Website,
|
||||
Operator does not represent or imply that it endorses the material
|
||||
there posted, or that it believes such material to be accurate, useful
|
||||
or non-harmful. You are responsible for taking precautions as
|
||||
necessary to protect yourself and your computer systems from viruses,
|
||||
worms, Trojan horses, and other harmful or destructive content. The
|
||||
Website may contain content that is offensive, indecent, or otherwise
|
||||
objectionable, as well as content containing technical inaccuracies,
|
||||
typographical mistakes, and other errors. The Website may also contain
|
||||
material that violates the privacy or publicity rights, or infringes
|
||||
the intellectual property and other proprietary rights, of third
|
||||
parties, or the downloading, copying or use of which is subject to
|
||||
additional terms and conditions, stated or unstated. Operator
|
||||
disclaims any responsibility for any harm resulting from the use by
|
||||
visitors of the Website, or from any downloading by those visitors of
|
||||
content there posted.</li>
|
||||
|
||||
<li><strong>Content Posted on Other Websites.</strong> We have not reviewed, and
|
||||
cannot review, all of the material, including computer software, made
|
||||
available through the websites and webpages to which %%site.name%%
|
||||
links, and that link to %%site.name%%. Operator does not have any
|
||||
control over those external websites and webpages, and is not
|
||||
responsible for their contents or their use. By linking to a
|
||||
external website or webpage, Operator does not represent or
|
||||
imply that it endorses such website or webpage. You are responsible
|
||||
for taking precautions as necessary to protect yourself and your
|
||||
computer systems from viruses, worms, Trojan horses, and other harmful
|
||||
or destructive content. Operator disclaims any responsibility for
|
||||
any harm resulting from your use of external websites and
|
||||
webpages.</li>
|
||||
|
||||
<li><strong>Copyright Infringement and DMCA Policy.</strong> As Operator asks
|
||||
others to respect its intellectual property rights, it respects the
|
||||
intellectual property rights of others. If you believe that material
|
||||
located on or linked to by %%site.name%% violates your copyright, you
|
||||
are encouraged to notify Operator in accordance with Operator’s
|
||||
Digital Millennium Copyright Act (”DMCA”) Policy. Operator will
|
||||
respond to all such notices, including as required or appropriate by
|
||||
removing the infringing material or disabling all links to the
|
||||
infringing material. In the case of a visitor who may infringe or
|
||||
repeatedly infringes the copyrights or other intellectual property
|
||||
rights of Operator or others, Operator may, in its discretion,
|
||||
terminate or deny access to and use of the Website. In the case of
|
||||
such termination, Operator will have no obligation to provide a
|
||||
refund of any amounts previously paid to Operator.</li>
|
||||
|
||||
<li><strong>Intellectual Property.</strong> This Agreement does not
|
||||
transfer from Operator to you any Operator or third party intellectual
|
||||
property, and all right, title and interest in and to such property
|
||||
will remain (as between the parties) solely with Operator.
|
||||
%%site.name%%, the %%site.name%% logo, and all other trademarks,
|
||||
service marks, graphics and logos used in connection with
|
||||
%%site.name%%, or the Website are trademarks or registered trademarks
|
||||
of Operator or Operator’s licensors. Other trademarks, service marks,
|
||||
graphics and logos used in connection with the Website may be the
|
||||
trademarks of other third parties. Your use of the Website grants you
|
||||
no right or license to reproduce or otherwise use any Operator or
|
||||
third-party trademarks.</li>
|
||||
|
||||
<li><strong>Changes.</strong> Operator reserves the right, at its sole
|
||||
discretion, to modify or replace any part of this Agreement. It is
|
||||
your responsibility to check this Agreement periodically for changes.
|
||||
Your continued use of or access to the Website following the posting
|
||||
of any changes to this Agreement constitutes acceptance of those
|
||||
changes. Operator may also, in the future, offer new services and/or
|
||||
features through the Website (including, the release of new tools and
|
||||
resources). Such new features and/or services shall be subject to the
|
||||
terms and conditions of this Agreement.</li>
|
||||
|
||||
<li><strong>Termination.</strong> Operator may terminate your access
|
||||
to all or any part of the Website at any time, with or without cause,
|
||||
with or without notice, effective immediately. If you wish to
|
||||
terminate this Agreement or your %%site.name%% account (if you have
|
||||
one), you may simply discontinue using the Website. All provisions of
|
||||
this Agreement which by their nature should survive termination shall
|
||||
survive termination, including, without limitation, ownership
|
||||
provisions, warranty disclaimers, indemnity and limitations of
|
||||
liability.</li>
|
||||
|
||||
<li><strong>Disclaimer of Warranties.</strong> The Website is provided
|
||||
“as is”. Operator and its suppliers and licensors hereby disclaim all
|
||||
warranties of any kind, express or implied, including, without
|
||||
limitation, the warranties of merchantability, fitness for a
|
||||
particular purpose and non-infringement. Neither Operator nor its
|
||||
suppliers and licensors, makes any warranty that the Website will be
|
||||
error free or that access thereto will be continuous or uninterrupted.
|
||||
If you’re actually reading this, here’s a treat. You understand that
|
||||
you download from, or otherwise obtain content or services through,
|
||||
the Website at your own discretion and risk.</li>
|
||||
|
||||
<li><strong>Limitation of Liability.</strong> In no event will
|
||||
Operator, or its suppliers or licensors, be liable with respect to any
|
||||
subject matter of this agreement under any contract, negligence,
|
||||
strict liability or other legal or equitable theory for: (i) any
|
||||
special, incidental or consequential damages; (ii) the cost of
|
||||
procurement or substitute products or services; (iii) for interruption
|
||||
of use or loss or corruption of data; or (iv) for any amounts that
|
||||
exceed the fees paid by you to Operator under this agreement during
|
||||
the twelve (12) month period prior to the cause of action. Operator
|
||||
shall have no liability for any failure or delay due to matters beyond
|
||||
their reasonable control. The foregoing shall not apply to the extent
|
||||
prohibited by applicable law.</li>
|
||||
|
||||
<li><strong>General Representation and Warranty.</strong> You
|
||||
represent and warrant that (i) your use of the Website will be in
|
||||
strict accordance with the Operator Privacy Policy, with this
|
||||
Agreement and with all applicable laws and regulations (including
|
||||
without limitation any local laws or regulations in your country,
|
||||
state, city, or other governmental area, regarding online conduct and
|
||||
acceptable content, and including all applicable laws regarding the
|
||||
transmission of technical data exported from the United States or the
|
||||
country in which you reside) and (ii) your use of the Website will not
|
||||
infringe or misappropriate the intellectual property rights of any
|
||||
third party.</li>
|
||||
|
||||
<li><strong>Indemnification.</strong> You agree to indemnify and hold
|
||||
harmless Operator, its contractors, and its licensors, and their
|
||||
respective directors, officers, employees and agents from and against
|
||||
any and all claims and expenses, including attorneys’ fees, arising
|
||||
out of your use of the Website, including but not limited to out of
|
||||
your violation this Agreement.</li>
|
||||
|
||||
<li><strong>Miscellaneous.</strong> This Agreement constitutes the
|
||||
entire agreement between Operator and you concerning the subject
|
||||
matter hereof, and they may only be modified by a written amendment
|
||||
signed by an authorized executive of Operator, or by the posting by
|
||||
Operator of a revised version. If any part of this Agreement is held
|
||||
invalid or unenforceable, that part will be construed to reflect the
|
||||
parties’ original intent, and the remaining portions will remain in
|
||||
full force and effect. A waiver by either party of any term or
|
||||
condition of this Agreement or any breach thereof, in any one
|
||||
instance, will not waive such term or condition or any subsequent
|
||||
breach thereof. You may assign your rights under this Agreement to any
|
||||
party that consents to, and agrees to be bound by, its terms and
|
||||
conditions; Operator may assign its rights under this Agreement
|
||||
without condition. This Agreement will be binding upon and will inure
|
||||
to the benefit of the parties, their successors and permitted
|
||||
assigns.</li> </ol>
|
||||
|
||||
*Originally published by Automattic, Inc. as the [WordPress.com Terms
|
||||
of Service](http://en.wordpress.com/tos/) and made available by them
|
||||
under the [Creative Commons Attribution-ShareAlike 3.0
|
||||
License](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
Modifications to remove reference to "VIP services", rename "blog" to
|
||||
"notice stream", remove the choice-of-venue clause, and add variables
|
||||
specific to instances of this software made by Control Yourself, Inc.
|
||||
and made available under the terms of the same license.*
|
@ -223,6 +223,7 @@ $(document).ready(function(){
|
||||
}
|
||||
$("#notice_data-text").val("");
|
||||
$("#notice_data-attach").val("");
|
||||
$("#notice_in-reply-to").val("");
|
||||
$('#notice_data-attach_selected').remove();
|
||||
counter();
|
||||
}
|
||||
@ -282,7 +283,7 @@ function NoticeAttachments() {
|
||||
},
|
||||
timeout : 0,
|
||||
autoHide : true,
|
||||
css : {'max-width':'502px', 'top':'22.5%', 'left':'32.5%'}
|
||||
css : {'max-width':'542px', 'top':'22.5%', 'left':'32.5%'}
|
||||
};
|
||||
|
||||
$('#content .notice a.attachment').click(function() {
|
||||
|
@ -439,8 +439,6 @@ class Action extends HTMLOutputter // lawsuit
|
||||
$this->menuItem(common_local_url('register'),
|
||||
_('Register'), _('Create an account'), false, 'nav_register');
|
||||
}
|
||||
$this->menuItem(common_local_url('openidlogin'),
|
||||
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
|
||||
$this->menuItem(common_local_url('login'),
|
||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||
}
|
||||
@ -708,6 +706,11 @@ class Action extends HTMLOutputter // lawsuit
|
||||
_('About'));
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'faq')),
|
||||
_('FAQ'));
|
||||
$bb = common_config('site', 'broughtby');
|
||||
if (!empty($bb)) {
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'tos')),
|
||||
_('TOS'));
|
||||
}
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'privacy')),
|
||||
_('Privacy'));
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'source')),
|
||||
@ -769,7 +772,9 @@ class Action extends HTMLOutputter // lawsuit
|
||||
$this->elementStart('p');
|
||||
$this->element('img', array('id' => 'license_cc',
|
||||
'src' => common_config('license', 'image'),
|
||||
'alt' => common_config('license', 'title')));
|
||||
'alt' => common_config('license', 'title'),
|
||||
'width' => '80',
|
||||
'height' => '15'));
|
||||
//TODO: This is dirty: i18n
|
||||
$this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
|
||||
$this->element('a', array('class' => 'license',
|
||||
|
@ -97,18 +97,11 @@ class StatsCommand extends Command
|
||||
{
|
||||
function execute($channel)
|
||||
{
|
||||
$profile = $this->user->getProfile();
|
||||
|
||||
$subs = new Subscription();
|
||||
$subs->subscriber = $this->user->id;
|
||||
$subs_count = (int) $subs->count() - 1;
|
||||
|
||||
$subbed = new Subscription();
|
||||
$subbed->subscribed = $this->user->id;
|
||||
$subbed_count = (int) $subbed->count() - 1;
|
||||
|
||||
$notices = new Notice();
|
||||
$notices->profile_id = $this->user->id;
|
||||
$notice_count = (int) $notices->count();
|
||||
$subs_count = $profile->subscriptionCount();
|
||||
$subbed_count = $profile->subscriberCount();
|
||||
$notice_count = $profile->noticeCount();
|
||||
|
||||
$channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
|
||||
"Subscribers: %2\$s\n".
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
if (!defined('LACONICA')) { exit(1); }
|
||||
|
||||
define('LACONICA_VERSION', '0.8.0dev');
|
||||
define('LACONICA_VERSION', '0.8.0');
|
||||
|
||||
define('AVATAR_PROFILE_SIZE', 96);
|
||||
define('AVATAR_STREAM_SIZE', 48);
|
||||
@ -206,7 +206,7 @@ $config =
|
||||
'inboxes' =>
|
||||
array('enabled' => true), # on by default for new sites
|
||||
'newuser' =>
|
||||
array('subscribe' => null,
|
||||
array('default' => null,
|
||||
'welcome' => null),
|
||||
'snapshot' =>
|
||||
array('run' => 'web',
|
||||
@ -282,6 +282,39 @@ if (function_exists('date_default_timezone_set')) {
|
||||
date_default_timezone_set('UTC');
|
||||
}
|
||||
|
||||
function addPlugin($name, $attrs = null)
|
||||
{
|
||||
$name = ucfirst($name);
|
||||
$pluginclass = "{$name}Plugin";
|
||||
|
||||
if (!class_exists($pluginclass)) {
|
||||
|
||||
$files = array("local/plugins/{$pluginclass}.php",
|
||||
"local/plugins/{$name}/{$pluginclass}.php",
|
||||
"local/{$pluginclass}.php",
|
||||
"local/{$name}/{$pluginclass}.php",
|
||||
"plugins/{$pluginclass}.php",
|
||||
"plugins/{$name}/{$pluginclass}.php");
|
||||
|
||||
foreach ($files as $file) {
|
||||
$fullpath = INSTALLDIR.'/'.$file;
|
||||
if (@file_exists($fullpath)) {
|
||||
include_once($fullpath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$inst = new $pluginclass();
|
||||
|
||||
if (!empty($attrs)) {
|
||||
foreach ($attrs as $aname => $avalue) {
|
||||
$inst->$aname = $avalue;
|
||||
}
|
||||
}
|
||||
return $inst;
|
||||
}
|
||||
|
||||
// From most general to most specific:
|
||||
// server-wide, then vhost-wide, then for a path,
|
||||
// finally for a dir (usually only need one of the last two).
|
||||
|
@ -53,14 +53,19 @@ class CurrentUserDesignAction extends Action
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
|
||||
function showStylesheets()
|
||||
{
|
||||
parent::showStylesheets();
|
||||
|
||||
$design = $this->getDesign();
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($design)) {
|
||||
$design->showCSS($this);
|
||||
if (empty($user) || $user->viewdesigns) {
|
||||
$design = $this->getDesign();
|
||||
|
||||
if (!empty($design)) {
|
||||
$design->showCSS($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,5 +89,4 @@ class CurrentUserDesignAction extends Action
|
||||
return $cur->getDesign();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
166
lib/dbqueuemanager.php
Normal file
166
lib/dbqueuemanager.php
Normal file
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Simple-minded queue manager for storing items in the database
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category QueueManager
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @author Sarven Capadisli <csarven@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class DBQueueManager extends QueueManager
|
||||
{
|
||||
var $qis = array();
|
||||
|
||||
function enqueue($object, $queue)
|
||||
{
|
||||
$notice = $object;
|
||||
|
||||
$qi = new Queue_item();
|
||||
|
||||
$qi->notice_id = $notice->id;
|
||||
$qi->transport = $queue;
|
||||
$qi->created = $notice->created;
|
||||
$result = $qi->insert();
|
||||
|
||||
if (!$result) {
|
||||
common_log_db_error($qi, 'INSERT', __FILE__);
|
||||
throw new ServerException('DB error inserting queue item');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function service($queue, $handler)
|
||||
{
|
||||
while (true) {
|
||||
$this->_log(LOG_DEBUG, 'Checking for notices...');
|
||||
$notice = $this->_nextItem($queue, null);
|
||||
if (empty($notice)) {
|
||||
$this->_log(LOG_DEBUG, 'No notices waiting; idling.');
|
||||
// Nothing in the queue. Do you
|
||||
// have other tasks, like servicing your
|
||||
// XMPP connection, to do?
|
||||
$handler->idle(QUEUE_HANDLER_MISS_IDLE);
|
||||
} else {
|
||||
$this->_log(LOG_INFO, 'Got notice '. $notice->id);
|
||||
// Yay! Got one!
|
||||
if ($handler->handle_notice($notice)) {
|
||||
$this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id);
|
||||
$this->_done($notice, $queue);
|
||||
} else {
|
||||
$this->_log(LOG_INFO, 'Failed to handle notice '. $notice->id);
|
||||
$this->_fail($notice, $queue);
|
||||
}
|
||||
// Chance to e.g. service your XMPP connection
|
||||
$this->_log(LOG_DEBUG, 'Idling after success.');
|
||||
$handler->idle(QUEUE_HANDLER_HIT_IDLE);
|
||||
}
|
||||
// XXX: when do we give up?
|
||||
}
|
||||
}
|
||||
|
||||
function _nextItem($queue, $timeout=null)
|
||||
{
|
||||
$start = time();
|
||||
$result = null;
|
||||
|
||||
do {
|
||||
$qi = Queue_item::top($queue);
|
||||
if (!empty($qi)) {
|
||||
$notice = Notice::staticGet('id', $qi->notice_id);
|
||||
if (!empty($notice)) {
|
||||
$result = $notice;
|
||||
} else {
|
||||
$this->_log(LOG_INFO, 'dequeued non-existent notice ' . $notice->id);
|
||||
$qi->delete();
|
||||
$qi->free();
|
||||
$qi = null;
|
||||
}
|
||||
}
|
||||
} while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function _done($object, $queue)
|
||||
{
|
||||
// XXX: right now, we only handle notices
|
||||
|
||||
$notice = $object;
|
||||
|
||||
$qi = Queue_item::pkeyGet(array('notice_id' => $notice->id,
|
||||
'transport' => $queue));
|
||||
|
||||
if (empty($qi)) {
|
||||
$this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
|
||||
} else {
|
||||
if (empty($qi->claimed)) {
|
||||
$this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '.
|
||||
'for '.$notice->id.', queue '.$queue);
|
||||
}
|
||||
$qi->delete();
|
||||
$qi->free();
|
||||
$qi = null;
|
||||
}
|
||||
|
||||
$this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
|
||||
|
||||
$notice->free();
|
||||
$notice = null;
|
||||
}
|
||||
|
||||
function _fail($object, $queue)
|
||||
{
|
||||
// XXX: right now, we only handle notices
|
||||
|
||||
$notice = $object;
|
||||
|
||||
$qi = Queue_item::pkeyGet(array('notice_id' => $notice->id,
|
||||
'transport' => $queue));
|
||||
|
||||
if (empty($qi)) {
|
||||
$this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue);
|
||||
} else {
|
||||
if (empty($qi->claimed)) {
|
||||
$this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '.
|
||||
'for '.$notice->id.', queue '.$queue);
|
||||
} else {
|
||||
$orig = clone($qi);
|
||||
$qi->claimed = null;
|
||||
$qi->update($orig);
|
||||
$qi = null;
|
||||
}
|
||||
}
|
||||
|
||||
$this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id);
|
||||
|
||||
$notice->free();
|
||||
$notice = null;
|
||||
}
|
||||
|
||||
function _log($level, $msg)
|
||||
{
|
||||
common_log($level, 'DBQueueManager: '.$msg);
|
||||
}
|
||||
}
|
@ -460,16 +460,6 @@ class FacebookAction extends Action
|
||||
}
|
||||
}
|
||||
|
||||
function updateFacebookStatus($notice)
|
||||
{
|
||||
$prefix = $this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $this->fbuid);
|
||||
$content = "$prefix $notice->content";
|
||||
|
||||
if ($this->facebook->api_client->users_hasAppPermission('status_update', $this->fbuid)) {
|
||||
$this->facebook->api_client->users_setStatus($content, $this->fbuid, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
function saveNewNotice()
|
||||
{
|
||||
|
||||
@ -504,7 +494,7 @@ class FacebookAction extends Action
|
||||
$replyto = $this->trimmed('inreplyto');
|
||||
|
||||
$notice = Notice::saveNew($user->id, $content,
|
||||
'Facebook', 1, ($replyto == 'false') ? null : $replyto);
|
||||
'web', 1, ($replyto == 'false') ? null : $replyto);
|
||||
|
||||
if (is_string($notice)) {
|
||||
$this->showPage($notice);
|
||||
@ -514,8 +504,7 @@ class FacebookAction extends Action
|
||||
common_broadcast_notice($notice);
|
||||
|
||||
// Also update the user's Facebook status
|
||||
$this->updateFacebookStatus($notice);
|
||||
$this->updateProfileBox($notice);
|
||||
facebookBroadcastNotice($notice);
|
||||
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,10 @@ function updateProfileBox($facebook, $flink, $notice) {
|
||||
|
||||
function isFacebookBound($notice, $flink) {
|
||||
|
||||
if (empty($flink)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the user does not want to broadcast to Facebook, move along
|
||||
if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
|
||||
common_log(LOG_INFO, "Skipping notice $notice->id " .
|
||||
@ -82,14 +86,18 @@ function isFacebookBound($notice, $flink) {
|
||||
|
||||
// Check to see if the user has given the FB app status update perms
|
||||
$result = $facebook->api_client->
|
||||
users_hasAppPermission('status_update', $fbuid);
|
||||
users_hasAppPermission('publish_stream', $fbuid);
|
||||
|
||||
if ($result != 1) {
|
||||
$result = $facebook->api_client->
|
||||
users_hasAppPermission('status_update', $fbuid);
|
||||
}
|
||||
if ($result != 1) {
|
||||
$user = $flink->getUser();
|
||||
$msg = "Can't send notice $notice->id to Facebook " .
|
||||
$msg = "Not sending notice $notice->id to Facebook " .
|
||||
"because user $user->nickname hasn't given the " .
|
||||
'Facebook app \'status_update\' permission.';
|
||||
common_log(LOG_INFO, $msg);
|
||||
'Facebook app \'status_update\' or \'publish_stream\' permission.';
|
||||
common_debug($msg);
|
||||
$success = false;
|
||||
}
|
||||
|
||||
@ -108,13 +116,16 @@ function facebookBroadcastNotice($notice)
|
||||
{
|
||||
$facebook = getFacebook();
|
||||
$flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
|
||||
$fbuid = $flink->foreign_id;
|
||||
|
||||
if (isFacebookBound($notice, $flink)) {
|
||||
|
||||
$status = null;
|
||||
$fbuid = $flink->foreign_id;
|
||||
|
||||
$user = $flink->getUser();
|
||||
|
||||
// Get the status 'verb' (prefix) the user has set
|
||||
|
||||
try {
|
||||
$prefix = $facebook->api_client->
|
||||
data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
|
||||
@ -122,23 +133,128 @@ function facebookBroadcastNotice($notice)
|
||||
$status = "$prefix $notice->content";
|
||||
|
||||
} catch(FacebookRestClientException $e) {
|
||||
common_log(LOG_ERR, $e->getMessage());
|
||||
return false;
|
||||
common_log(LOG_WARNING, $e->getMessage());
|
||||
common_log(LOG_WARNING,
|
||||
'Unable to get the status verb setting from Facebook ' .
|
||||
"for $user->nickname (user id: $user->id).");
|
||||
}
|
||||
|
||||
// Okay, we're good to go!
|
||||
// Okay, we're good to go, update the FB status
|
||||
|
||||
try {
|
||||
$facebook->api_client->users_setStatus($status, $fbuid, false, true);
|
||||
updateProfileBox($facebook, $flink, $notice);
|
||||
$result = $facebook->api_client->
|
||||
users_hasAppPermission('publish_stream', $fbuid);
|
||||
if($result == 1){
|
||||
// authorized to use the stream api, so use it
|
||||
$fbattachment = null;
|
||||
$attachments = $notice->attachments();
|
||||
if($attachments){
|
||||
$fbattachment=array();
|
||||
$fbattachment['media']=array();
|
||||
//facebook only supports one attachment per item
|
||||
$attachment = $attachments[0];
|
||||
$fbmedia=array();
|
||||
if(strncmp($attachment->mimetype,'image/',strlen('image/'))==0){
|
||||
$fbmedia['type']='image';
|
||||
$fbmedia['src']=$attachment->url;
|
||||
$fbmedia['href']=$attachment->url;
|
||||
$fbattachment['media'][]=$fbmedia;
|
||||
/* Video doesn't seem to work. The notice never makes it to facebook, and no error is reported.
|
||||
}else if(strncmp($attachment->mimetype,'video/',strlen('image/'))==0 || $attachment->mimetype="application/ogg"){
|
||||
$fbmedia['type']='video';
|
||||
$fbmedia['video_src']=$attachment->url;
|
||||
// http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
|
||||
// says that preview_img is required... but we have no value to put in it
|
||||
// $fbmedia['preview_img']=$attachment->url;
|
||||
if($attachment->title){
|
||||
$fbmedia['video_title']=$attachment->title;
|
||||
}
|
||||
$fbmedia['video_type']=$attachment->mimetype;
|
||||
$fbattachment['media'][]=$fbmedia;
|
||||
*/
|
||||
}else if($attachment->mimetype=='audio/mpeg'){
|
||||
$fbmedia['type']='mp3';
|
||||
$fbmedia['src']=$attachment->url;
|
||||
$fbattachment['media'][]=$fbmedia;
|
||||
}else if($attachment->mimetype=='application/x-shockwave-flash'){
|
||||
$fbmedia['type']='flash';
|
||||
// http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29
|
||||
// says that imgsrc is required... but we have no value to put in it
|
||||
// $fbmedia['imgsrc']='';
|
||||
$fbmedia['swfsrc']=$attachment->url;
|
||||
$fbattachment['media'][]=$fbmedia;
|
||||
}else{
|
||||
$fbattachment['name']=($attachment->title?$attachment->title:$attachment->url);
|
||||
$fbattachment['href']=$attachment->url;
|
||||
}
|
||||
}
|
||||
$facebook->api_client->stream_publish($status, $fbattachment, null, null, $fbuid);
|
||||
}else{
|
||||
$facebook->api_client->users_setStatus($status, $fbuid, false, true);
|
||||
}
|
||||
} catch(FacebookRestClientException $e) {
|
||||
common_log(LOG_ERR, $e->getMessage());
|
||||
return false;
|
||||
common_log(LOG_ERR,
|
||||
'Unable to update Facebook status for ' .
|
||||
"$user->nickname (user id: $user->id)!");
|
||||
|
||||
// Should we remove flink if this fails?
|
||||
$code = $e->getCode();
|
||||
|
||||
if ($code >= 200) {
|
||||
|
||||
// 200 The application does not have permission to operate on the passed in uid parameter.
|
||||
// 250 Updating status requires the extended permission status_update or publish_stream.
|
||||
// see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML
|
||||
|
||||
remove_facebook_app($flink);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Now try to update the profile box
|
||||
|
||||
try {
|
||||
updateProfileBox($facebook, $flink, $notice);
|
||||
} catch(FacebookRestClientException $e) {
|
||||
common_log(LOG_WARNING, $e->getMessage());
|
||||
common_log(LOG_WARNING,
|
||||
'Unable to update Facebook profile box for ' .
|
||||
"$user->nickname (user id: $user->id).");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function remove_facebook_app($flink)
|
||||
{
|
||||
|
||||
$user = $flink->getUser();
|
||||
|
||||
common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' .
|
||||
"user $user->nickname (user id: $user->id).");
|
||||
|
||||
$result = $flink->delete();
|
||||
|
||||
if (empty($result)) {
|
||||
common_log(LOG_ERR, 'Could not remove Facebook App ' .
|
||||
"Foreign_link for $user->nickname (user id: $user->id)!");
|
||||
common_log_db_error($flink, 'DELETE', __FILE__);
|
||||
}
|
||||
|
||||
// Notify the user that we are removing their FB app access
|
||||
|
||||
$result = mail_facebook_app_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Facebook app link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -58,10 +58,14 @@ class GroupDesignAction extends Action {
|
||||
{
|
||||
parent::showStylesheets();
|
||||
|
||||
$design = $this->getDesign();
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($design)) {
|
||||
$design->showCSS($this);
|
||||
if (empty($user) || $user->viewdesigns) {
|
||||
$design = $this->getDesign();
|
||||
|
||||
if (!empty($design)) {
|
||||
$design->showCSS($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,12 +80,10 @@ class GroupDesignAction extends Action {
|
||||
|
||||
function getDesign()
|
||||
{
|
||||
|
||||
if (empty($this->group)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->group->getDesign();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -77,6 +77,14 @@ function jabber_daemon_address()
|
||||
return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
|
||||
}
|
||||
|
||||
class Sharing_XMPP extends XMPPHP_XMPP
|
||||
{
|
||||
function getSocket()
|
||||
{
|
||||
return $this->socket;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* connect the configured Jabber account to the configured server
|
||||
*
|
||||
@ -89,7 +97,7 @@ function jabber_connect($resource=null)
|
||||
{
|
||||
static $conn = null;
|
||||
if (!$conn) {
|
||||
$conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ?
|
||||
$conn = new Sharing_XMPP(common_config('xmpp', 'host') ?
|
||||
common_config('xmpp', 'host') :
|
||||
common_config('xmpp', 'server'),
|
||||
common_config('xmpp', 'port'),
|
||||
|
72
lib/mail.php
72
lib/mail.php
@ -625,3 +625,75 @@ function mail_notify_attn($user, $notice)
|
||||
common_init_locale();
|
||||
mail_to_user($user, $subject, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a mail message to notify a user that her Twitter bridge link
|
||||
* has stopped working, and therefore has been removed. This can
|
||||
* happen when the user changes her Twitter password, or otherwise
|
||||
* revokes access.
|
||||
*
|
||||
* @param User $user user whose Twitter bridge link has been removed
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function mail_twitter_bridge_removed($user)
|
||||
{
|
||||
common_init_locale($user->language);
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
$subject = sprintf(_('Your Twitter bridge has been disabled.'));
|
||||
|
||||
$body = sprintf(_("Hi, %1\$s. We're sorry to inform you that your " .
|
||||
'link to Twitter has been disabled. Your Twitter credentials ' .
|
||||
'have either changed (did you recently change your Twitter ' .
|
||||
'password?) or you have otherwise revoked our access to your ' .
|
||||
"Twitter account.\n\n" .
|
||||
'You can re-enable your Twitter bridge by visiting your ' .
|
||||
"Twitter settings page:\n\n\t%2\$s\n\n" .
|
||||
"Regards,\n%3\$s\n"),
|
||||
$profile->getBestName(),
|
||||
common_local_url('twittersettings'),
|
||||
common_config('site', 'name'));
|
||||
|
||||
common_init_locale();
|
||||
return mail_to_user($user, $subject, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a mail message to notify a user that her Facebook Application
|
||||
* access has been removed.
|
||||
*
|
||||
* @param User $user user whose Facebook app link has been removed
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function mail_facebook_app_removed($user)
|
||||
{
|
||||
common_init_locale($user->language);
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
$site_name = common_config('site', 'name');
|
||||
|
||||
$subject = sprintf(
|
||||
_('Your %s Facebook application access has been disabled.',
|
||||
$site_name));
|
||||
|
||||
$body = sprintf(_("Hi, %1\$s. We're sorry to inform you that we are " .
|
||||
'unable to update your Facebook status from %s, and have disabled ' .
|
||||
'the Facebook application for your account. This may be because ' .
|
||||
'you have removed the Facebook application\'s authorization, or ' .
|
||||
'have deleted your Facebook account. You can re-enable the ' .
|
||||
'Facebook application and automatic status updating by ' .
|
||||
"re-installing the %1\$s Facebook application.\n\nRegards,\n\n%1\$s"),
|
||||
$site_name);
|
||||
|
||||
common_init_locale();
|
||||
return mail_to_user($user, $subject, $body);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -61,11 +61,15 @@ class OwnerDesignAction extends Action {
|
||||
{
|
||||
parent::showStylesheets();
|
||||
|
||||
$design = $this->getDesign();
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($design)) {
|
||||
$design->showCSS($this);
|
||||
}
|
||||
if (empty($user) || $user->viewdesigns) {
|
||||
$design = $this->getDesign();
|
||||
|
||||
if (!empty($design)) {
|
||||
$design->showCSS($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,7 @@ function ping_broadcast_notice($notice) {
|
||||
|
||||
$response = xmlrpc_decode($file);
|
||||
|
||||
if (xmlrpc_is_fault($response)) {
|
||||
if (is_array($response) && xmlrpc_is_fault($response)) {
|
||||
common_log(LOG_WARNING,
|
||||
"XML-RPC error for ping ($notify_url, $notice->id) ".
|
||||
"$response[faultString] ($response[faultCode])");
|
||||
|
@ -68,7 +68,7 @@ class PopularNoticeSection extends NoticeSection
|
||||
}
|
||||
$qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' .
|
||||
'notice.rendered,notice.url,notice.created,notice.modified,' .
|
||||
'notice.reply_to,notice.is_local,notice.source ' .
|
||||
'notice.reply_to,notice.is_local,notice.source,notice.conversation ' .
|
||||
'ORDER BY weight DESC';
|
||||
|
||||
$offset = 0;
|
||||
|
@ -163,18 +163,9 @@ class ProfileAction extends OwnerDesignAction
|
||||
|
||||
function showStatistics()
|
||||
{
|
||||
// XXX: WORM cache this
|
||||
$subs = new Subscription();
|
||||
$subs->subscriber = $this->profile->id;
|
||||
$subs_count = (int) $subs->count() - 1;
|
||||
|
||||
$subbed = new Subscription();
|
||||
$subbed->subscribed = $this->profile->id;
|
||||
$subbed_count = (int) $subbed->count() - 1;
|
||||
|
||||
$notices = new Notice();
|
||||
$notices->profile_id = $this->profile->id;
|
||||
$notice_count = (int) $notices->count();
|
||||
$subs_count = $this->profile->subscriptionCount();
|
||||
$subbed_count = $this->profile->subscriberCount();
|
||||
$notice_count = $this->profile->noticeCount();
|
||||
|
||||
$this->elementStart('div', array('id' => 'entity_statistics',
|
||||
'class' => 'section'));
|
||||
@ -199,7 +190,7 @@ class ProfileAction extends OwnerDesignAction
|
||||
array('nickname' => $this->profile->nickname))),
|
||||
_('Subscriptions'));
|
||||
$this->elementEnd('dt');
|
||||
$this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
|
||||
$this->element('dd', null, $subs_count);
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementStart('dl', 'entity_subscribers');
|
||||
@ -208,12 +199,12 @@ class ProfileAction extends OwnerDesignAction
|
||||
array('nickname' => $this->profile->nickname))),
|
||||
_('Subscribers'));
|
||||
$this->elementEnd('dt');
|
||||
$this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
|
||||
$this->element('dd', 'subscribers', $subbed_count);
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementStart('dl', 'entity_notices');
|
||||
$this->element('dt', null, _('Notices'));
|
||||
$this->element('dd', null, (is_int($notice_count)) ? $notice_count : '0');
|
||||
$this->element('dd', null, $notice_count);
|
||||
$this->elementEnd('dl');
|
||||
|
||||
$this->elementEnd('div');
|
||||
|
@ -94,8 +94,8 @@ class ProfileSection extends Section
|
||||
$profile->fullname :
|
||||
$profile->nickname));
|
||||
$this->out->element('span', 'fn nickname', $profile->nickname);
|
||||
$this->out->elementEnd('span');
|
||||
$this->out->elementEnd('a');
|
||||
$this->out->elementEnd('span');
|
||||
$this->out->elementEnd('td');
|
||||
if ($profile->value) {
|
||||
$this->out->element('td', 'value', $profile->value);
|
||||
|
@ -17,14 +17,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
define('CLAIM_TIMEOUT', 1200);
|
||||
|
||||
if (!defined('LACONICA')) { exit(1); }
|
||||
|
||||
require_once(INSTALLDIR.'/lib/daemon.php');
|
||||
require_once(INSTALLDIR.'/classes/Queue_item.php');
|
||||
require_once(INSTALLDIR.'/classes/Notice.php');
|
||||
|
||||
define('CLAIM_TIMEOUT', 1200);
|
||||
define('QUEUE_HANDLER_MISS_IDLE', 10);
|
||||
define('QUEUE_HANDLER_HIT_IDLE', 0);
|
||||
|
||||
class QueueHandler extends Daemon
|
||||
{
|
||||
var $_id = 'generic';
|
||||
@ -38,6 +40,11 @@ class QueueHandler extends Daemon
|
||||
}
|
||||
}
|
||||
|
||||
function timeout()
|
||||
{
|
||||
return 60;
|
||||
}
|
||||
|
||||
function class_name()
|
||||
{
|
||||
return ucfirst($this->transport()) . 'Handler';
|
||||
@ -76,110 +83,21 @@ class QueueHandler extends Daemon
|
||||
return true;
|
||||
}
|
||||
|
||||
function db_dispatch() {
|
||||
do {
|
||||
$qi = Queue_item::top($this->transport());
|
||||
if ($qi) {
|
||||
$this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created));
|
||||
$notice = Notice::staticGet($qi->notice_id);
|
||||
if ($notice) {
|
||||
$this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
|
||||
# XXX: what to do if broadcast fails?
|
||||
$result = $this->handle_notice($notice);
|
||||
if (!$result) {
|
||||
$this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
|
||||
$orig = $qi;
|
||||
$qi->claimed = null;
|
||||
$qi->update($orig);
|
||||
$this->log(LOG_WARNING, 'Abandoned claim for notice ID = ' . $notice->id);
|
||||
continue;
|
||||
}
|
||||
$this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
|
||||
$notice->free();
|
||||
unset($notice);
|
||||
$notice = null;
|
||||
} else {
|
||||
$this->log(LOG_WARNING, 'queue item for notice that does not exist');
|
||||
}
|
||||
$qi->delete();
|
||||
$qi->free();
|
||||
unset($qi);
|
||||
$this->idle(0);
|
||||
} else {
|
||||
$this->clear_old_claims();
|
||||
$this->idle(5);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
function stomp_dispatch() {
|
||||
|
||||
// use an external message queue system via STOMP
|
||||
require_once("Stomp.php");
|
||||
|
||||
$server = common_config('queue','stomp_server');
|
||||
$username = common_config('queue', 'stomp_username');
|
||||
$password = common_config('queue', 'stomp_password');
|
||||
|
||||
$con = new Stomp($server);
|
||||
|
||||
if (!$con->connect($username, $password)) {
|
||||
$this->log(LOG_ERR, 'Failed to connect to queue server');
|
||||
return false;
|
||||
}
|
||||
|
||||
$queue_basename = common_config('queue','queue_basename');
|
||||
// subscribe to the relevant queue (format: basename-transport)
|
||||
$con->subscribe('/queue/'.$queue_basename.'-'.$this->transport());
|
||||
|
||||
do {
|
||||
$frame = $con->readFrame();
|
||||
if ($frame) {
|
||||
$this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($frame->headers['created']));
|
||||
|
||||
// XXX: Now the queue handler receives only the ID of the
|
||||
// notice, and it has to get it from the DB
|
||||
// A massive improvement would be avoid DB query by transmitting
|
||||
// all the notice details via queue server...
|
||||
$notice = Notice::staticGet($frame->body);
|
||||
|
||||
if ($notice) {
|
||||
$this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id);
|
||||
$result = $this->handle_notice($notice);
|
||||
if ($result) {
|
||||
// if the msg has been handled positively, ack it
|
||||
// and the queue server will remove it from the queue
|
||||
$con->ack($frame);
|
||||
$this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id);
|
||||
}
|
||||
else {
|
||||
// no ack
|
||||
$this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id);
|
||||
}
|
||||
$notice->free();
|
||||
unset($notice);
|
||||
$notice = null;
|
||||
} else {
|
||||
$this->log(LOG_WARNING, 'queue item for notice that does not exist');
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
$con->disconnect();
|
||||
}
|
||||
|
||||
function run()
|
||||
{
|
||||
if (!$this->start()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->log(LOG_INFO, 'checking for queued notices');
|
||||
if (common_config('queue','subsystem') == 'stomp') {
|
||||
$this->stomp_dispatch();
|
||||
}
|
||||
else {
|
||||
$this->db_dispatch();
|
||||
}
|
||||
|
||||
$queue = $this->transport();
|
||||
$timeout = $this->timeout();
|
||||
|
||||
$qm = QueueManager::get();
|
||||
|
||||
$qm->service($queue, $this);
|
||||
|
||||
if (!$this->finish()) {
|
||||
return false;
|
||||
}
|
||||
@ -188,24 +106,19 @@ class QueueHandler extends Daemon
|
||||
|
||||
function idle($timeout=0)
|
||||
{
|
||||
if ($timeout>0) {
|
||||
if ($timeout > 0) {
|
||||
sleep($timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function clear_old_claims()
|
||||
{
|
||||
$qi = new Queue_item();
|
||||
$qi->transport = $this->transport();
|
||||
$qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT);
|
||||
$qi->update(DB_DATAOBJECT_WHEREADD_ONLY);
|
||||
$qi->free();
|
||||
unset($qi);
|
||||
}
|
||||
|
||||
function log($level, $msg)
|
||||
{
|
||||
common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg);
|
||||
}
|
||||
|
||||
function getSockets()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
|
74
lib/queuemanager.php
Normal file
74
lib/queuemanager.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Abstract class for queue managers
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category QueueManager
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @author Sarven Capadisli <csarven@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class QueueManager
|
||||
{
|
||||
static $qm = null;
|
||||
|
||||
static function get()
|
||||
{
|
||||
if (empty(self::$qm)) {
|
||||
|
||||
if (Event::handle('StartNewQueueManager', array(&self::$qm))) {
|
||||
|
||||
$enabled = common_config('queue', 'enabled');
|
||||
$type = common_config('queue', 'subsystem');
|
||||
|
||||
if (!$enabled) {
|
||||
// does everything immediately
|
||||
self::$qm = new UnQueueManager();
|
||||
} else {
|
||||
switch ($type) {
|
||||
case 'db':
|
||||
self::$qm = new DBQueueManager();
|
||||
break;
|
||||
case 'stomp':
|
||||
self::$qm = new StompQueueManager();
|
||||
break;
|
||||
default:
|
||||
throw new ServerException("No queue manager class for type '$type'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$qm;
|
||||
}
|
||||
|
||||
function enqueue($object, $queue)
|
||||
{
|
||||
throw ServerException("Unimplemented function 'enqueue' called");
|
||||
}
|
||||
|
||||
function service($queue, $handler)
|
||||
{
|
||||
throw ServerException("Unimplemented function 'service' called");
|
||||
}
|
||||
}
|
@ -261,12 +261,12 @@ class Router
|
||||
$m->connect('api/statuses/:method',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'statuses'),
|
||||
array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|friends|followers|featured)(\.(atom|rss|xml|json))?'));
|
||||
array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?'));
|
||||
|
||||
$m->connect('api/statuses/:method/:argument',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'statuses'),
|
||||
array('method' => '(user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
|
||||
array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
|
||||
|
||||
// users
|
||||
|
||||
@ -394,6 +394,15 @@ class Router
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'laconica'));
|
||||
|
||||
// Groups
|
||||
$m->connect('api/laconica/groups/:method/:argument',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'groups'));
|
||||
|
||||
$m->connect('api/laconica/groups/:method',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'groups'));
|
||||
|
||||
// search
|
||||
$m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
|
||||
$m->connect('api/search.json', array('action' => 'twitapisearchjson'));
|
||||
|
@ -216,6 +216,13 @@ class Rss10Action extends Action
|
||||
$replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
|
||||
$this->element('sioc:reply_of', array('rdf:resource' => $replyurl));
|
||||
}
|
||||
$attachments = $notice->attachments();
|
||||
if($attachments){
|
||||
foreach($attachments as $attachment){
|
||||
$this->element('enc:enclosure', array('rdf:resource'=>$attachment->url,'enc:type'=>$attachment->mimetype,'enc:length'=>$attachment->size), null);
|
||||
}
|
||||
}
|
||||
|
||||
$this->elementEnd('item');
|
||||
$this->creators[$creator_uri] = $profile;
|
||||
}
|
||||
@ -251,6 +258,8 @@ class Rss10Action extends Action
|
||||
'http://creativecommons.org/ns#',
|
||||
'xmlns:content' =>
|
||||
'http://purl.org/rss/1.0/modules/content/',
|
||||
'xmlns:enc' =>
|
||||
'http://purl.oclc.org/net/rss_2.0/enc#',
|
||||
'xmlns:foaf' =>
|
||||
'http://xmlns.com/foaf/0.1/',
|
||||
'xmlns:sioc' =>
|
||||
|
169
lib/stompqueuemanager.php
Normal file
169
lib/stompqueuemanager.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Abstract class for queue managers
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category QueueManager
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @author Sarven Capadisli <csarven@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
require_once 'Stomp.php';
|
||||
|
||||
class LiberalStomp extends Stomp
|
||||
{
|
||||
function getSocket()
|
||||
{
|
||||
return $this->_socket;
|
||||
}
|
||||
}
|
||||
|
||||
class StompQueueManager
|
||||
{
|
||||
var $server = null;
|
||||
var $username = null;
|
||||
var $password = null;
|
||||
var $base = null;
|
||||
var $con = null;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->server = common_config('queue', 'stomp_server');
|
||||
$this->username = common_config('queue', 'stomp_username');
|
||||
$this->password = common_config('queue', 'stomp_password');
|
||||
$this->base = common_config('queue', 'queue_basename');
|
||||
}
|
||||
|
||||
function _connect()
|
||||
{
|
||||
if (empty($this->con)) {
|
||||
$this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'...");
|
||||
$this->con = new LiberalStomp($this->server);
|
||||
|
||||
if ($this->con->connect($this->username, $this->password)) {
|
||||
$this->_log(LOG_INFO, "Connected.");
|
||||
} else {
|
||||
$this->_log(LOG_ERR, 'Failed to connect to queue server');
|
||||
throw new ServerException('Failed to connect to queue server');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enqueue($object, $queue)
|
||||
{
|
||||
$notice = $object;
|
||||
|
||||
$this->_connect();
|
||||
|
||||
// XXX: serialize and send entire notice
|
||||
|
||||
$result = $this->con->send($this->_queueName($queue),
|
||||
$notice->id, // BODY of the message
|
||||
array ('created' => $notice->created));
|
||||
|
||||
if (!$result) {
|
||||
common_log(LOG_ERR, 'Error sending to '.$queue.' queue');
|
||||
return false;
|
||||
}
|
||||
|
||||
common_log(LOG_DEBUG, 'complete remote queueing notice ID = '
|
||||
. $notice->id . ' for ' . $queue);
|
||||
}
|
||||
|
||||
function service($queue, $handler)
|
||||
{
|
||||
$result = null;
|
||||
|
||||
$this->_connect();
|
||||
|
||||
$this->con->setReadTimeout($handler->timeout());
|
||||
|
||||
$this->con->subscribe($this->_queueName($queue));
|
||||
|
||||
while (true) {
|
||||
|
||||
// Wait for something on one of our sockets
|
||||
|
||||
$stompsock = $this->con->getSocket();
|
||||
|
||||
$handsocks = $handler->getSockets();
|
||||
|
||||
$socks = array_merge(array($stompsock), $handsocks);
|
||||
|
||||
$read = $socks;
|
||||
$write = array();
|
||||
$except = array();
|
||||
|
||||
$ready = stream_select($read, $write, $except, $handler->timeout(), 0);
|
||||
|
||||
if ($ready === false) {
|
||||
$this->_log(LOG_ERR, "Error selecting on sockets");
|
||||
} else if ($ready > 0) {
|
||||
if (in_array($stompsock, $read)) {
|
||||
$this->_handleNotice($queue, $handler);
|
||||
}
|
||||
$handler->idle(QUEUE_HANDLER_HIT_IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
$this->con->unsubscribe($this->_queueName($queue));
|
||||
}
|
||||
|
||||
function _handleNotice($queue, $handler)
|
||||
{
|
||||
$frame = $this->con->readFrame();
|
||||
|
||||
if (!empty($frame)) {
|
||||
$notice = Notice::staticGet('id', $frame->body);
|
||||
|
||||
if (empty($notice)) {
|
||||
$this->_log(LOG_WARNING, 'Got ID '. $frame->body .' for non-existent notice in queue '. $queue);
|
||||
$this->con->ack($frame);
|
||||
} else {
|
||||
if ($handler->handle_notice($notice)) {
|
||||
$this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
|
||||
$this->con->ack($frame);
|
||||
} else {
|
||||
$this->_log(LOG_WARNING, 'Failed handling notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue);
|
||||
// FIXME we probably shouldn't have to do
|
||||
// this kind of queue management ourselves
|
||||
$this->con->ack($frame);
|
||||
$this->enqueue($notice, $queue);
|
||||
}
|
||||
unset($notice);
|
||||
}
|
||||
|
||||
unset($frame);
|
||||
}
|
||||
}
|
||||
|
||||
function _queueName($queue)
|
||||
{
|
||||
return common_config('queue', 'queue_basename') . $queue;
|
||||
}
|
||||
|
||||
function _log($level, $msg)
|
||||
{
|
||||
common_log($level, 'StompQueueManager: '.$msg);
|
||||
}
|
||||
}
|
13
lib/subs.php
13
lib/subs.php
@ -44,7 +44,6 @@ function subs_subscribe_user($user, $other_nickname)
|
||||
|
||||
function subs_subscribe_to($user, $other)
|
||||
{
|
||||
|
||||
if ($user->isSubscribed($other)) {
|
||||
return _('Already subscribed!.');
|
||||
}
|
||||
@ -60,12 +59,16 @@ function subs_subscribe_to($user, $other)
|
||||
|
||||
subs_notify($other, $user);
|
||||
|
||||
$cache = common_memcache();
|
||||
$cache = common_memcache();
|
||||
|
||||
if ($cache) {
|
||||
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
|
||||
}
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
$profile->blowSubscriptionsCount();
|
||||
$other->blowSubscribersCount();
|
||||
|
||||
if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
|
||||
if (!$other->subscribeTo($user)) {
|
||||
@ -117,7 +120,6 @@ function subs_unsubscribe_user($user, $other_nickname)
|
||||
|
||||
function subs_unsubscribe_to($user, $other)
|
||||
{
|
||||
|
||||
if (!$user->isSubscribed($other))
|
||||
return _('Not subscribed!.');
|
||||
|
||||
@ -139,6 +141,11 @@ function subs_unsubscribe_to($user, $other)
|
||||
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
|
||||
}
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
||||
$profile->blowSubscriptionsCount();
|
||||
$other->blowSubscribersCount();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -360,14 +360,11 @@ function is_twitter_bound($notice, $flink) {
|
||||
|
||||
function broadcast_twitter($notice)
|
||||
{
|
||||
$success = true;
|
||||
|
||||
$flink = Foreign_link::getByUserID($notice->profile_id,
|
||||
TWITTER_SERVICE);
|
||||
|
||||
// XXX: Not sure WHERE to check whether a notice should go to
|
||||
// Twitter. Should we even put in the queue if it shouldn't? --Zach
|
||||
if (!is_null($flink) && is_twitter_bound($notice, $flink)) {
|
||||
if (is_twitter_bound($notice, $flink)) {
|
||||
|
||||
$fuser = $flink->getForeignUser();
|
||||
$twitter_user = $fuser->nickname;
|
||||
@ -401,33 +398,99 @@ function broadcast_twitter($notice)
|
||||
curl_setopt_array($ch, $options);
|
||||
$data = curl_exec($ch);
|
||||
$errmsg = curl_error($ch);
|
||||
$errno = curl_errno($ch);
|
||||
|
||||
if ($errmsg) {
|
||||
common_debug("cURL error: $errmsg - " .
|
||||
if (!empty($errmsg)) {
|
||||
common_debug("cURL error ($errno): $errmsg - " .
|
||||
"trying to send notice for $twitter_user.",
|
||||
__FILE__);
|
||||
$success = false;
|
||||
|
||||
$user = $flink->getUser();
|
||||
|
||||
if ($errmsg == 'The requested URL returned error: 401') {
|
||||
common_debug(sprintf('User %s (user id: %s) ' .
|
||||
'has bad Twitter credentials!',
|
||||
$user->nickname, $user->id));
|
||||
|
||||
// Bad credentials we need to delete the foreign_link
|
||||
// to Twitter and inform the user.
|
||||
|
||||
remove_twitter_link($flink);
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
// Some other error happened, so we should try to
|
||||
// send again later
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if (!$data) {
|
||||
if (empty($data)) {
|
||||
common_debug("No data returned by Twitter's " .
|
||||
"API trying to send update for $twitter_user",
|
||||
__FILE__);
|
||||
$success = false;
|
||||
}
|
||||
|
||||
// Twitter should return a status
|
||||
$status = json_decode($data);
|
||||
// XXX: Not sure this represents a failure to send, but it
|
||||
// probably does
|
||||
|
||||
if (!$status->id) {
|
||||
common_debug("Unexpected data returned by Twitter " .
|
||||
" API trying to send update for $twitter_user",
|
||||
__FILE__);
|
||||
$success = false;
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// Twitter should return a status
|
||||
$status = json_decode($data);
|
||||
|
||||
if (empty($status)) {
|
||||
common_debug("Unexpected data returned by Twitter " .
|
||||
" API trying to send update for $twitter_user",
|
||||
__FILE__);
|
||||
|
||||
// XXX: Again, this could represent a failure posting
|
||||
// or the Twitter API might just be behaving flakey.
|
||||
// We're treating it as a failure to post.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
return true;
|
||||
}
|
||||
|
||||
function remove_twitter_link($flink)
|
||||
{
|
||||
$user = $flink->getUser();
|
||||
|
||||
common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
|
||||
"user $user->nickname (user id: $user->id).");
|
||||
|
||||
$result = $flink->delete();
|
||||
|
||||
if (empty($result)) {
|
||||
common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
|
||||
"Foreign_link for $user->nickname (user id: $user->id)!");
|
||||
common_log_db_error($flink, 'DELETE', __FILE__);
|
||||
}
|
||||
|
||||
// Notify the user that her Twitter bridge is down
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ class TwitterapiAction extends Action
|
||||
|
||||
$twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
|
||||
$twitter_user['protected'] = false; # not supported by Laconica yet
|
||||
$twitter_user['followers_count'] = $this->count_subscriptions($profile);
|
||||
$twitter_user['followers_count'] = $profile->subscriberCount();
|
||||
|
||||
// To be supported soon...
|
||||
$twitter_user['profile_background_color'] = '';
|
||||
@ -98,17 +98,11 @@ class TwitterapiAction extends Action
|
||||
$twitter_user['profile_sidebar_fill_color'] = '';
|
||||
$twitter_user['profile_sidebar_border_color'] = '';
|
||||
|
||||
$subbed = DB_DataObject::factory('subscription');
|
||||
$subbed->subscriber = $profile->id;
|
||||
$subbed_count = (int) $subbed->count() - 1;
|
||||
$twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
|
||||
$twitter_user['friends_count'] = $profile->subscriptionCount();
|
||||
|
||||
$twitter_user['created_at'] = $this->date_twitter($profile->created);
|
||||
|
||||
$faves = DB_DataObject::factory('fave');
|
||||
$faves->user_id = $user->id;
|
||||
$faves_count = (int) $faves->count();
|
||||
$twitter_user['favourites_count'] = $faves_count; // British spelling!
|
||||
$twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
|
||||
|
||||
// Need to pull up the user for some of this
|
||||
$user = User::staticGet($profile->id);
|
||||
@ -129,11 +123,7 @@ class TwitterapiAction extends Action
|
||||
$twitter_user['profile_background_image_url'] = '';
|
||||
$twitter_user['profile_background_tile'] = false;
|
||||
|
||||
$notices = DB_DataObject::factory('notice');
|
||||
$notices->profile_id = $profile->id;
|
||||
$notice_count = (int) $notices->count();
|
||||
|
||||
$twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
|
||||
$twitter_user['statuses_count'] = $profile->noticeCount();
|
||||
|
||||
// Is the requesting user following this user?
|
||||
$twitter_user['following'] = false;
|
||||
@ -207,7 +197,6 @@ class TwitterapiAction extends Action
|
||||
|
||||
function twitter_rss_entry_array($notice)
|
||||
{
|
||||
|
||||
$profile = $notice->getProfile();
|
||||
$entry = array();
|
||||
|
||||
@ -224,6 +213,19 @@ class TwitterapiAction extends Action
|
||||
$entry['updated'] = $entry['published'];
|
||||
$entry['author'] = $profile->getBestName();
|
||||
|
||||
# Enclosure
|
||||
$attachments = $notice->attachments();
|
||||
if($attachments){
|
||||
$entry['enclosures']=array();
|
||||
foreach($attachments as $attachment){
|
||||
$enclosure=array();
|
||||
$enclosure['url']=$attachment->url;
|
||||
$enclosure['mimetype']=$attachment->mimetype;
|
||||
$enclosure['size']=$attachment->size;
|
||||
$entry['enclosures'][]=$enclosure;
|
||||
}
|
||||
}
|
||||
|
||||
# RSS Item specific
|
||||
$entry['description'] = $entry['content'];
|
||||
$entry['pubDate'] = common_date_rfc2822($notice->created);
|
||||
@ -378,6 +380,13 @@ class TwitterapiAction extends Action
|
||||
$this->element('pubDate', null, $entry['pubDate']);
|
||||
$this->element('guid', null, $entry['guid']);
|
||||
$this->element('link', null, $entry['link']);
|
||||
|
||||
# RSS only supports 1 enclosure per item
|
||||
if($entry['enclosures']){
|
||||
$enclosure = $entry['enclosures'][0];
|
||||
$this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null);
|
||||
}
|
||||
|
||||
$this->elementEnd('item');
|
||||
}
|
||||
|
||||
@ -765,6 +774,34 @@ class TwitterapiAction extends Action
|
||||
}
|
||||
}
|
||||
|
||||
function get_group($id, $apidata=null)
|
||||
{
|
||||
if (empty($id)) {
|
||||
|
||||
if (is_numeric($this->arg('id'))) {
|
||||
return User_group::staticGet($this->arg('id'));
|
||||
} else if ($this->arg('id')) {
|
||||
$nickname = common_canonical_nickname($this->arg('id'));
|
||||
return User_group::staticGet('nickname', $nickname);
|
||||
} else if ($this->arg('group_id')) {
|
||||
// This is to ensure that a non-numeric user_id still
|
||||
// overrides screen_name even if it doesn't get used
|
||||
if (is_numeric($this->arg('group_id'))) {
|
||||
return User_group::staticGet('id', $this->arg('group_id'));
|
||||
}
|
||||
} else if ($this->arg('group_name')) {
|
||||
$nickname = common_canonical_nickname($this->arg('group_name'));
|
||||
return User_group::staticGet('nickname', $nickname);
|
||||
}
|
||||
|
||||
} else if (is_numeric($id)) {
|
||||
return User_group::staticGet($id);
|
||||
} else {
|
||||
$nickname = common_canonical_nickname($id);
|
||||
return User_group::staticGet('nickname', $nickname);
|
||||
}
|
||||
}
|
||||
|
||||
function get_profile($id)
|
||||
{
|
||||
if (is_numeric($id)) {
|
||||
|
85
lib/unqueuemanager.php
Normal file
85
lib/unqueuemanager.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* A queue manager interface for just doing things immediately
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category QueueManager
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @author Sarven Capadisli <csarven@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class UnQueueManager
|
||||
{
|
||||
function enqueue($object, $queue)
|
||||
{
|
||||
$notice = $object;
|
||||
|
||||
switch ($queue)
|
||||
{
|
||||
case 'omb':
|
||||
if ($this->_isLocal($notice)) {
|
||||
require_once(INSTALLDIR.'/lib/omb.php');
|
||||
omb_broadcast_remote_subscribers($notice);
|
||||
}
|
||||
break;
|
||||
case 'public':
|
||||
if ($this->_isLocal($notice)) {
|
||||
require_once(INSTALLDIR.'/lib/jabber.php');
|
||||
jabber_public_notice($notice);
|
||||
}
|
||||
break;
|
||||
case 'twitter':
|
||||
if ($this->_isLocal($notice)) {
|
||||
broadcast_twitter($notice);
|
||||
}
|
||||
break;
|
||||
case 'facebook':
|
||||
if ($this->_isLocal($notice)) {
|
||||
require_once INSTALLDIR . '/lib/facebookutil.php';
|
||||
return facebookBroadcastNotice($notice);
|
||||
}
|
||||
break;
|
||||
case 'ping':
|
||||
if ($this->_isLocal($notice)) {
|
||||
require_once INSTALLDIR . '/lib/ping.php';
|
||||
return ping_broadcast_notice($notice);
|
||||
}
|
||||
case 'sms':
|
||||
require_once(INSTALLDIR.'/lib/mail.php');
|
||||
mail_broadcast_notice_sms($notice);
|
||||
break;
|
||||
case 'jabber':
|
||||
require_once(INSTALLDIR.'/lib/jabber.php');
|
||||
jabber_broadcast_notice($notice);
|
||||
break;
|
||||
default:
|
||||
throw ServerException("UnQueueManager: Unknown queue: $type");
|
||||
}
|
||||
}
|
||||
|
||||
function _isLocal($notice)
|
||||
{
|
||||
return ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
|
||||
$notice->is_local == NOTICE_LOCAL_NONPUBLIC);
|
||||
}
|
||||
}
|
163
lib/util.php
163
lib/util.php
@ -862,165 +862,45 @@ function common_redirect($url, $code=307)
|
||||
|
||||
function common_broadcast_notice($notice, $remote=false)
|
||||
{
|
||||
if (common_config('queue', 'enabled')) {
|
||||
// Do it later!
|
||||
return common_enqueue_notice($notice);
|
||||
} else {
|
||||
return common_real_broadcast($notice, $remote);
|
||||
}
|
||||
return common_enqueue_notice($notice);
|
||||
}
|
||||
|
||||
// Stick the notice on the queue
|
||||
|
||||
function common_enqueue_notice($notice)
|
||||
{
|
||||
$transports = array('omb', 'sms', 'public', 'twitter', 'facebook', 'ping');
|
||||
static $localTransports = array('omb',
|
||||
'twitter',
|
||||
'facebook',
|
||||
'ping');
|
||||
static $allTransports = array('sms');
|
||||
|
||||
if (common_config('xmpp', 'enabled'))
|
||||
{
|
||||
$transports = $allTransports;
|
||||
|
||||
$xmpp = common_config('xmpp', 'enabled');
|
||||
|
||||
if ($xmpp) {
|
||||
$transports[] = 'jabber';
|
||||
}
|
||||
|
||||
if (common_config('queue','subsystem') == 'stomp') {
|
||||
common_enqueue_notice_stomp($notice, $transports);
|
||||
}
|
||||
else {
|
||||
common_enqueue_notice_db($notice, $transports);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function common_enqueue_notice_stomp($notice, $transports)
|
||||
{
|
||||
// use an external message queue system via STOMP
|
||||
require_once("Stomp.php");
|
||||
|
||||
$server = common_config('queue','stomp_server');
|
||||
$username = common_config('queue', 'stomp_username');
|
||||
$password = common_config('queue', 'stomp_password');
|
||||
|
||||
$con = new Stomp($server);
|
||||
|
||||
if (!$con->connect($username, $password)) {
|
||||
common_log(LOG_ERR, 'Failed to connect to queue server');
|
||||
return false;
|
||||
}
|
||||
|
||||
$queue_basename = common_config('queue','queue_basename');
|
||||
|
||||
foreach ($transports as $transport) {
|
||||
$result = $con->send('/queue/'.$queue_basename.'-'.$transport, // QUEUE
|
||||
$notice->id, // BODY of the message
|
||||
array ('created' => $notice->created));
|
||||
if (!$result) {
|
||||
common_log(LOG_ERR, 'Error sending to '.$transport.' queue');
|
||||
return false;
|
||||
}
|
||||
common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' . $notice->id . ' for ' . $transport);
|
||||
}
|
||||
|
||||
//send tags as headers, so they can be used as JMS selectors
|
||||
common_log(LOG_DEBUG, 'searching for tags ' . $notice->id);
|
||||
$tags = array();
|
||||
$tag = new Notice_tag();
|
||||
$tag->notice_id = $notice->id;
|
||||
if ($tag->find()) {
|
||||
while ($tag->fetch()) {
|
||||
common_log(LOG_DEBUG, 'tag found = ' . $tag->tag);
|
||||
array_push($tags,$tag->tag);
|
||||
if ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
|
||||
$notice->is_local == NOTICE_LOCAL_NONPUBLIC) {
|
||||
$transports = array_merge($transports, $localTransports);
|
||||
if ($xmpp) {
|
||||
$transports[] = 'public';
|
||||
}
|
||||
}
|
||||
$tag->free();
|
||||
|
||||
$con->send('/topic/laconica.'.$notice->profile_id,
|
||||
$notice->content,
|
||||
array(
|
||||
'profile_id' => $notice->profile_id,
|
||||
'created' => $notice->created,
|
||||
'tags' => implode($tags,' - ')
|
||||
)
|
||||
);
|
||||
common_log(LOG_DEBUG, 'sent to personal topic ' . $notice->id);
|
||||
$con->send('/topic/laconica.allusers',
|
||||
$notice->content,
|
||||
array(
|
||||
'profile_id' => $notice->profile_id,
|
||||
'created' => $notice->created,
|
||||
'tags' => implode($tags,' - ')
|
||||
)
|
||||
);
|
||||
common_log(LOG_DEBUG, 'sent to catch-all topic ' . $notice->id);
|
||||
$result = true;
|
||||
}
|
||||
$qm = QueueManager::get();
|
||||
|
||||
function common_enqueue_notice_db($notice, $transports)
|
||||
{
|
||||
// in any other case, 'internal'
|
||||
foreach ($transports as $transport) {
|
||||
common_enqueue_notice_transport($notice, $transport);
|
||||
foreach ($transports as $transport)
|
||||
{
|
||||
$qm->enqueue($notice, $transport);
|
||||
}
|
||||
}
|
||||
|
||||
function common_enqueue_notice_transport($notice, $transport)
|
||||
{
|
||||
$qi = new Queue_item();
|
||||
$qi->notice_id = $notice->id;
|
||||
$qi->transport = $transport;
|
||||
$qi->created = $notice->created;
|
||||
$result = $qi->insert();
|
||||
if (!$result) {
|
||||
$last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
|
||||
throw new ServerException('DB error inserting queue item: ' . $last_error->message);
|
||||
}
|
||||
common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
|
||||
return true;
|
||||
}
|
||||
|
||||
function common_real_broadcast($notice, $remote=false)
|
||||
{
|
||||
$success = true;
|
||||
if (!$remote) {
|
||||
// Make sure we have the OMB stuff
|
||||
require_once(INSTALLDIR.'/lib/omb.php');
|
||||
$success = omb_broadcast_remote_subscribers($notice);
|
||||
if (!$success) {
|
||||
common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id);
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
require_once(INSTALLDIR.'/lib/jabber.php');
|
||||
$success = jabber_broadcast_notice($notice);
|
||||
if (!$success) {
|
||||
common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id);
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
require_once(INSTALLDIR.'/lib/mail.php');
|
||||
$success = mail_broadcast_notice_sms($notice);
|
||||
if (!$success) {
|
||||
common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id);
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
$success = jabber_public_notice($notice);
|
||||
if (!$success) {
|
||||
common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id);
|
||||
}
|
||||
}
|
||||
if ($success) {
|
||||
$success = broadcast_twitter($notice);
|
||||
if (!$success) {
|
||||
common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Do a real-time FB broadcast here?
|
||||
|
||||
// XXX: broadcast notices to other IM
|
||||
return $success;
|
||||
}
|
||||
|
||||
function common_broadcast_profile($profile)
|
||||
{
|
||||
// XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
|
||||
@ -1148,6 +1028,9 @@ function common_log_objstring(&$object)
|
||||
if (is_null($object)) {
|
||||
return "null";
|
||||
}
|
||||
if (!($object instanceof DB_DataObject)) {
|
||||
return "(unknown)";
|
||||
}
|
||||
$arr = $object->toArray();
|
||||
$fields = array();
|
||||
foreach ($arr as $k => $v) {
|
||||
|
@ -21,6 +21,8 @@ if (!defined('LACONICA')) { exit(1); }
|
||||
|
||||
require_once(INSTALLDIR.'/lib/queuehandler.php');
|
||||
|
||||
define('PING_INTERVAL', 120);
|
||||
|
||||
/**
|
||||
* Common superclass for all XMPP-using queue handlers. They all need to
|
||||
* service their message queues on idle, and forward any incoming messages
|
||||
@ -30,6 +32,9 @@ require_once(INSTALLDIR.'/lib/queuehandler.php');
|
||||
|
||||
class XmppQueueHandler extends QueueHandler
|
||||
{
|
||||
var $pingid = 0;
|
||||
var $lastping = null;
|
||||
|
||||
function start()
|
||||
{
|
||||
# Low priority; we don't want to receive messages
|
||||
@ -44,6 +49,11 @@ class XmppQueueHandler extends QueueHandler
|
||||
return !is_null($this->conn);
|
||||
}
|
||||
|
||||
function timeout()
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
function handle_reconnect(&$pl)
|
||||
{
|
||||
$this->conn->processUntil('session_start');
|
||||
@ -55,7 +65,13 @@ class XmppQueueHandler extends QueueHandler
|
||||
# Process the queue for as long as needed
|
||||
try {
|
||||
if ($this->conn) {
|
||||
$this->log(LOG_DEBUG, "Servicing the XMPP queue.");
|
||||
$this->conn->processTime($timeout);
|
||||
$now = time();
|
||||
if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) {
|
||||
$this->sendPing();
|
||||
$this->lastping = $now;
|
||||
}
|
||||
}
|
||||
} catch (XMPPHP_Exception $e) {
|
||||
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
|
||||
@ -63,6 +79,22 @@ class XmppQueueHandler extends QueueHandler
|
||||
}
|
||||
}
|
||||
|
||||
function sendPing()
|
||||
{
|
||||
$jid = jabber_daemon_address().'/'.$this->_id.$this->transport();
|
||||
$server = common_config('xmpp', 'server');
|
||||
|
||||
if (!isset($this->pingid)) {
|
||||
$this->pingid = 0;
|
||||
} else {
|
||||
$this->pingid++;
|
||||
}
|
||||
|
||||
$this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
|
||||
|
||||
$this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
|
||||
}
|
||||
|
||||
function forward_message(&$pl)
|
||||
{
|
||||
if ($pl['type'] != 'chat') {
|
||||
@ -91,7 +123,12 @@ class XmppQueueHandler extends QueueHandler
|
||||
if (common_config('xmpp', 'listener')) {
|
||||
return common_config('xmpp', 'listener');
|
||||
} else {
|
||||
return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener';
|
||||
return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon';
|
||||
}
|
||||
}
|
||||
|
||||
function getSockets()
|
||||
{
|
||||
return array($this->conn->getSocket());
|
||||
}
|
||||
}
|
||||
|
@ -313,8 +313,6 @@ class FBConnectPlugin extends Plugin
|
||||
$action->menuItem(common_local_url('register'),
|
||||
_('Register'), _('Create an account'), false, 'nav_register');
|
||||
}
|
||||
$action->menuItem(common_local_url('openidlogin'),
|
||||
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
|
||||
$action->menuItem(common_local_url('login'),
|
||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||
}
|
||||
|
@ -42,11 +42,11 @@ class MailerDaemon
|
||||
|
||||
function handle_message($fname='php://stdin')
|
||||
{
|
||||
list($from, $to, $msg) = $this->parse_message($fname);
|
||||
list($from, $to, $msg, $attachments) = $this->parse_message($fname);
|
||||
if (!$from || !$to || !$msg) {
|
||||
$this->error(null, _('Could not parse message.'));
|
||||
}
|
||||
common_log(LOG_INFO, "Mail from $from to $to: " .substr($msg, 0, 20));
|
||||
common_log(LOG_INFO, "Mail from $from to $to with ".count($attachments) .' attachment(s): ' .substr($msg, 0, 20));
|
||||
$user = $this->user_from($from);
|
||||
if (!$user) {
|
||||
$this->error($from, _('Not a registered user.'));
|
||||
@ -65,7 +65,47 @@ class MailerDaemon
|
||||
return true;
|
||||
}
|
||||
$msg = $this->cleanup_msg($msg);
|
||||
$err = $this->add_notice($user, $msg);
|
||||
$msg = common_shorten_links($msg);
|
||||
if (mb_strlen($msg) > 140) {
|
||||
$this->error($from,_('That\'s too long. '.
|
||||
'Max notice size is 140 chars.'));
|
||||
}
|
||||
$fileRecords = array();
|
||||
foreach($attachments as $attachment){
|
||||
$mimetype = $this->getUploadedFileType($attachment);
|
||||
$stream = stream_get_meta_data($attachment);
|
||||
if (!$this->isRespectsQuota($user,filesize($stream['uri']))) {
|
||||
die('error() should trigger an exception before reaching here.');
|
||||
}
|
||||
$filename = $this->saveFile($user, $attachment,$mimetype);
|
||||
|
||||
fclose($attachment);
|
||||
|
||||
if (empty($filename)) {
|
||||
$this->error($from,_('Couldn\'t save file.'));
|
||||
}
|
||||
|
||||
$fileRecord = $this->storeFile($filename, $mimetype);
|
||||
$fileRecords[] = $fileRecord;
|
||||
$fileurl = common_local_url('attachment',
|
||||
array('attachment' => $fileRecord->id));
|
||||
|
||||
// not sure this is necessary -- Zach
|
||||
$this->maybeAddRedir($fileRecord->id, $fileurl);
|
||||
|
||||
$short_fileurl = common_shorten_url($fileurl);
|
||||
$msg .= ' ' . $short_fileurl;
|
||||
|
||||
if (mb_strlen($msg) > 140) {
|
||||
$this->deleteFile($filename);
|
||||
$this->error($from,_('Max notice size is 140 chars, including attachment URL.'));
|
||||
}
|
||||
|
||||
// Also, not sure this is necessary -- Zach
|
||||
$this->maybeAddRedir($fileRecord->id, $short_fileurl);
|
||||
}
|
||||
|
||||
$err = $this->add_notice($user, $msg, $fileRecords);
|
||||
if (is_string($err)) {
|
||||
$this->error($from, $err);
|
||||
return false;
|
||||
@ -74,6 +114,89 @@ class MailerDaemon
|
||||
}
|
||||
}
|
||||
|
||||
function saveFile($user, $attachment, $mimetype) {
|
||||
|
||||
$filename = File::filename($user->getProfile(), "email", $mimetype);
|
||||
|
||||
$filepath = File::path($filename);
|
||||
|
||||
$stream = stream_get_meta_data($attachment);
|
||||
if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) {
|
||||
return $filename;
|
||||
} else {
|
||||
$this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath));
|
||||
}
|
||||
}
|
||||
|
||||
function storeFile($filename, $mimetype) {
|
||||
|
||||
$file = new File;
|
||||
$file->filename = $filename;
|
||||
|
||||
$file->url = File::url($filename);
|
||||
|
||||
$filepath = File::path($filename);
|
||||
|
||||
$file->size = filesize($filepath);
|
||||
$file->date = time();
|
||||
$file->mimetype = $mimetype;
|
||||
|
||||
$file_id = $file->insert();
|
||||
|
||||
if (!$file_id) {
|
||||
common_log_db_error($file, "INSERT", __FILE__);
|
||||
$this->error(null,_('There was a database error while saving your file. Please try again.'));
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
function maybeAddRedir($file_id, $url)
|
||||
{
|
||||
$file_redir = File_redirection::staticGet('url', $url);
|
||||
|
||||
if (empty($file_redir)) {
|
||||
$file_redir = new File_redirection;
|
||||
$file_redir->url = $url;
|
||||
$file_redir->file_id = $file_id;
|
||||
|
||||
$result = $file_redir->insert();
|
||||
|
||||
if (!$result) {
|
||||
common_log_db_error($file_redir, "INSERT", __FILE__);
|
||||
$this->error(null,_('There was a database error while saving your file. Please try again.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUploadedFileType($fileHandle) {
|
||||
require_once 'MIME/Type.php';
|
||||
|
||||
$cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
|
||||
$cmd = common_config('attachments', 'filecommand');
|
||||
|
||||
$stream = stream_get_meta_data($fileHandle);
|
||||
$filetype = MIME_Type::autoDetect($stream['uri']);
|
||||
if (in_array($filetype, common_config('attachments', 'supported'))) {
|
||||
return $filetype;
|
||||
}
|
||||
$media = MIME_Type::getMedia($filetype);
|
||||
if ('application' !== $media) {
|
||||
$hint = sprintf(_(' Try using another %s format.'), $media);
|
||||
} else {
|
||||
$hint = '';
|
||||
}
|
||||
$this->error(null,sprintf(
|
||||
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
|
||||
}
|
||||
|
||||
function isRespectsQuota($user,$fileSize) {
|
||||
$file = new File;
|
||||
$ret = $file->isRespectsQuota($user,$fileSize);
|
||||
if (true === $ret) return true;
|
||||
$this->error(null,$ret);
|
||||
}
|
||||
|
||||
function error($from, $msg)
|
||||
{
|
||||
file_put_contents("php://stderr", $msg . "\n");
|
||||
@ -133,19 +256,30 @@ class MailerDaemon
|
||||
common_log($level, 'MailDaemon: '.$msg);
|
||||
}
|
||||
|
||||
function add_notice($user, $msg)
|
||||
function add_notice($user, $msg, $fileRecords)
|
||||
{
|
||||
$notice = Notice::saveNew($user->id, $msg, 'mail');
|
||||
if (is_string($notice)) {
|
||||
$this->log(LOG_ERR, $notice);
|
||||
return $notice;
|
||||
}
|
||||
foreach($fileRecords as $fileRecord){
|
||||
$this->attachFile($notice, $fileRecord);
|
||||
}
|
||||
common_broadcast_notice($notice);
|
||||
$this->log(LOG_INFO,
|
||||
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
|
||||
return true;
|
||||
}
|
||||
|
||||
function attachFile($notice, $filerec)
|
||||
{
|
||||
File_to_post::processNew($filerec->id, $notice->id);
|
||||
|
||||
$this->maybeAddRedir($filerec->id,
|
||||
common_local_url('file', array('notice' => $notice->id)));
|
||||
}
|
||||
|
||||
function parse_message($fname)
|
||||
{
|
||||
$contents = file_get_contents($fname);
|
||||
@ -163,12 +297,19 @@ class MailerDaemon
|
||||
|
||||
$type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
|
||||
|
||||
$attachments = array();
|
||||
|
||||
if ($parsed->ctype_primary == 'multipart') {
|
||||
foreach ($parsed->parts as $part) {
|
||||
if ($part->ctype_primary == 'text' &&
|
||||
$part->ctype_secondary == 'plain') {
|
||||
$msg = $part->body;
|
||||
break;
|
||||
}else{
|
||||
if ($part->body) {
|
||||
$attachment = tmpfile();
|
||||
fwrite($attachment, $part->body);
|
||||
$attachments[] = $attachment;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($type == 'text/plain') {
|
||||
@ -176,8 +317,7 @@ class MailerDaemon
|
||||
} else {
|
||||
$this->unsupported_type($type);
|
||||
}
|
||||
|
||||
return array($from, $to, $msg);
|
||||
return array($from, $to, $msg, $attachments);
|
||||
}
|
||||
|
||||
function unsupported_type($type)
|
||||
|
@ -1,66 +1,97 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/*
|
||||
* Laconica - a distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, Control Yourself, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Abort if called from a web server
|
||||
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
|
||||
print "This script must be run from the command line\n";
|
||||
exit();
|
||||
}
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||
define('LACONICA', true);
|
||||
|
||||
require_once(INSTALLDIR . '/lib/common.php');
|
||||
|
||||
// Master Laconica .pot file location (created by update_pot.sh)
|
||||
$laconica_pot = INSTALLDIR . '/locale/laconica.po';
|
||||
|
||||
set_time_limit(60);
|
||||
chdir(dirname(__FILE__) . '/..');
|
||||
|
||||
/* Languages to pull */
|
||||
$languages = array(
|
||||
'da_DK' => 'http://laconi.ca/translate/download.php?file_id=93',
|
||||
'nl_NL' => 'http://laconi.ca/translate/download.php?file_id=97',
|
||||
'en_NZ' => 'http://laconi.ca/translate/download.php?file_id=87',
|
||||
'eo' => 'http://laconi.ca/translate/download.php?file_id=88',
|
||||
'fr_FR' => 'http://laconi.ca/translate/download.php?file_id=99',
|
||||
'de_DE' => 'http://laconi.ca/translate/download.php?file_id=100',
|
||||
'it_IT' => 'http://laconi.ca/translate/download.php?file_id=101',
|
||||
'ko' => 'http://laconi.ca/translate/download.php?file_id=102',
|
||||
'no_NB' => 'http://laconi.ca/translate/download.php?file_id=104',
|
||||
'pt' => 'http://laconi.ca/translate/download.php?file_id=106',
|
||||
'pt_BR' => 'http://laconi.ca/translate/download.php?file_id=107',
|
||||
'ru_RU' => 'http://laconi.ca/translate/download.php?file_id=109',
|
||||
'es' => 'http://laconi.ca/translate/download.php?file_id=110',
|
||||
'tr_TR' => 'http://laconi.ca/translate/download.php?file_id=114',
|
||||
'uk_UA' => 'http://laconi.ca/translate/download.php?file_id=115',
|
||||
'he_IL' => 'http://laconi.ca/translate/download.php?file_id=116',
|
||||
'mk_MK' => 'http://laconi.ca/translate/download.php?file_id=103',
|
||||
'ja_JP' => 'http://laconi.ca/translate/download.php?file_id=117',
|
||||
'cs_CZ' => 'http://laconi.ca/translate/download.php?file_id=96',
|
||||
'ca_ES' => 'http://laconi.ca/translate/download.php?file_id=95',
|
||||
'pl_PL' => 'http://laconi.ca/translate/download.php?file_id=105',
|
||||
'sv_SE' => 'http://laconi.ca/translate/download.php?file_id=128'
|
||||
);
|
||||
$languages = get_all_languages();
|
||||
|
||||
/* Update the languages */
|
||||
foreach ($languages as $code => $file) {
|
||||
|
||||
$lcdir='locale/'.$code;
|
||||
$msgdir=$lcdir.'/LC_MESSAGES';
|
||||
$pofile=$msgdir.'/laconica.po';
|
||||
$mofile=$msgdir.'/laconica.mo';
|
||||
foreach ($languages as $language) {
|
||||
|
||||
/* Check for an existing */
|
||||
if (!is_dir($msgdir)) {
|
||||
mkdir($lcdir);
|
||||
mkdir($msgdir);
|
||||
$existingSHA1 = '';
|
||||
} else {
|
||||
$existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : '';
|
||||
}
|
||||
$code = $language['lang'];
|
||||
$file_url = 'http://laconi.ca/pootle/' . $code .
|
||||
'/laconica/LC_MESSAGES/laconica.po';
|
||||
$lcdir = INSTALLDIR . '/locale/' . $code;
|
||||
$msgdir = "$lcdir/LC_MESSAGES";
|
||||
$pofile = "$msgdir/laconica.po";
|
||||
$mofile = "$msgdir/laconica.mo";
|
||||
|
||||
/* Get the remote one */
|
||||
$newFile = file_get_contents($file);
|
||||
/* Check for an existing */
|
||||
if (!is_dir($msgdir)) {
|
||||
mkdir($lcdir);
|
||||
mkdir($msgdir);
|
||||
$existingSHA1 = '';
|
||||
} else {
|
||||
$existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : '';
|
||||
}
|
||||
|
||||
// Update if the local .po file is different to the one downloaded, or
|
||||
// if the .mo file is not present.
|
||||
if(sha1($newFile)!=$existingSHA1 || !file_exists($mofile)) {
|
||||
echo "Updating ".$code."\n";
|
||||
file_put_contents($pofile, $newFile);
|
||||
$prevdir = getcwd();
|
||||
chdir($msgdir);
|
||||
system('msgmerge -U laconica.po ../../laconica.pot');
|
||||
system('msgfmt -f -o laconica.mo laconica.po');
|
||||
chdir($prevdir);
|
||||
} else {
|
||||
echo "Unchanged - ".$code."\n";
|
||||
}
|
||||
/* Get the remote one */
|
||||
$new_file = curl_get_file($file_url);
|
||||
|
||||
if ($new_file === FALSE) {
|
||||
echo "Couldn't retrieve .po file for $code: $file_url\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update if the local .po file is different to the one downloaded, or
|
||||
// if the .mo file is not present.
|
||||
if (sha1($new_file) != $existingSHA1 || !file_exists($mofile)) {
|
||||
echo "Updating ".$code."\n";
|
||||
file_put_contents($pofile, $new_file);
|
||||
system(sprintf('msgmerge -U %s %s', $pofile, $laconica_pot));
|
||||
system(sprintf('msgfmt -f -o %s %s', $mofile, $pofile));
|
||||
} else {
|
||||
echo "Unchanged - ".$code."\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "Finished\n";
|
||||
|
||||
|
||||
function curl_get_file($url)
|
||||
{
|
||||
$c = curl_init();
|
||||
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($c, CURLOPT_URL, $url);
|
||||
$contents = curl_exec($c);
|
||||
curl_close($c);
|
||||
|
||||
if (!empty($contents)) {
|
||||
return $contents;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -60,7 +60,9 @@ class XMPPDaemon extends Daemon
|
||||
$this->resource = common_config('xmpp', 'resource') . 'daemon';
|
||||
}
|
||||
|
||||
$this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->user}@{$this->server}/{$this->resource}");
|
||||
$this->jid = $this->user.'@'.$this->server.'/'.$this->resource;
|
||||
|
||||
$this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->jid}");
|
||||
}
|
||||
|
||||
function connect()
|
||||
@ -106,10 +108,25 @@ class XMPPDaemon extends Daemon
|
||||
|
||||
$this->log(LOG_DEBUG, "Beginning processing loop.");
|
||||
|
||||
$this->conn->process();
|
||||
while ($this->conn->processTime(60)) {
|
||||
$this->sendPing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sendPing()
|
||||
{
|
||||
if (!isset($this->pingid)) {
|
||||
$this->pingid = 0;
|
||||
} else {
|
||||
$this->pingid++;
|
||||
}
|
||||
|
||||
$this->log(LOG_DEBUG, "Sending ping #{$this->pingid}");
|
||||
|
||||
$this->conn->send("<iq from='{$this->jid}' to='{$this->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
|
||||
}
|
||||
|
||||
function handle_reconnect(&$pl)
|
||||
{
|
||||
$this->log(LOG_DEBUG, "Got reconnection callback.");
|
||||
|
@ -275,7 +275,7 @@ margin-bottom:18px;
|
||||
|
||||
#anon_notice {
|
||||
float:left;
|
||||
width:43.2%;
|
||||
width:42.4%;
|
||||
padding:1.1%;
|
||||
border-radius:7px;
|
||||
-moz-border-radius:7px;
|
||||
@ -396,7 +396,7 @@ margin-bottom:1em;
|
||||
}
|
||||
|
||||
#content {
|
||||
width:64.009%;
|
||||
width:63.311%;
|
||||
min-height:259px;
|
||||
padding:1.795%;
|
||||
float:left;
|
||||
@ -422,7 +422,7 @@ float:left;
|
||||
width:27.917%;
|
||||
min-height:259px;
|
||||
float:left;
|
||||
margin-left:0.5%;
|
||||
margin-left:0.699%;
|
||||
padding:1.795%;
|
||||
border-radius:7px;
|
||||
-moz-border-radius:7px;
|
||||
@ -432,7 +432,7 @@ border-style:solid;
|
||||
}
|
||||
|
||||
#form_notice {
|
||||
width:45.664%;
|
||||
width:45%;
|
||||
float:left;
|
||||
position:relative;
|
||||
line-height:1;
|
||||
@ -471,12 +471,12 @@ cursor:pointer;
|
||||
}
|
||||
#form_notice label[for=notice_data-attach] {
|
||||
text-indent:-9999px;
|
||||
left:394px;
|
||||
left:86%;
|
||||
width:16px;
|
||||
height:16px;
|
||||
}
|
||||
#form_notice #notice_data-attach {
|
||||
left:183px;
|
||||
left:40.6%;
|
||||
padding:0;
|
||||
height:16px;
|
||||
}
|
||||
@ -783,8 +783,8 @@ list-style-type:none;
|
||||
}
|
||||
.notices .notices {
|
||||
margin-top:7px;
|
||||
margin-left:5%;
|
||||
width:95%;
|
||||
margin-left:2%;
|
||||
width:98%;
|
||||
float:left;
|
||||
}
|
||||
|
||||
@ -1020,20 +1020,21 @@ font-weight:bold;
|
||||
padding:0;
|
||||
}
|
||||
#jOverlayContent h1 {
|
||||
max-width:475px;
|
||||
max-width:425px;
|
||||
}
|
||||
#jOverlayContent #content {
|
||||
border-radius:7px;
|
||||
-moz-border-radius:7px;
|
||||
-webkit-border-radius:7px;
|
||||
}
|
||||
#jOverlayContent #content img {
|
||||
max-width:480px;
|
||||
}
|
||||
#jOverlayLoading {
|
||||
top:22.5%;
|
||||
left:40%;
|
||||
}
|
||||
#attachment_view img {
|
||||
max-width:480px;
|
||||
max-height:480px;
|
||||
}
|
||||
#attachment_view #oembed_info {
|
||||
margin-top:11px;
|
||||
}
|
||||
@ -1278,6 +1279,7 @@ margin-bottom:0;
|
||||
|
||||
#form_settings_design #settings_design_background-image img {
|
||||
max-width:480px;
|
||||
max-height:480px;
|
||||
}
|
||||
|
||||
#form_settings_design #settings_design_color .form_data,
|
||||
|
@ -9,7 +9,7 @@ width:78%;
|
||||
#form_notice .form_note + label {
|
||||
position:absolute;
|
||||
top:25px;
|
||||
left:380px;
|
||||
left:83%;
|
||||
text-indent:-9999px;
|
||||
height:16px;
|
||||
width:16px;
|
||||
@ -25,10 +25,6 @@ width:78.5%;
|
||||
#form_notice #notice_data-attach_selected button {
|
||||
padding:0 4px;
|
||||
}
|
||||
#anon_notice {
|
||||
max-width:39%;
|
||||
}
|
||||
|
||||
.notice-options input.submit {
|
||||
font-size:0;
|
||||
margin-top:3px;
|
||||
@ -49,6 +45,6 @@ z-index:1;
|
||||
.notice:hover {
|
||||
z-index:9999;
|
||||
}
|
||||
.notice .thumbnail img {
|
||||
.notice .thumbnail img {
|
||||
z-index:9999;
|
||||
}
|
Loading…
Reference in New Issue
Block a user