forked from GNUsocial/gnu-social
Merge branch 'testing'
This commit is contained in:
commit
5541565238
208
README
208
README
@ -2,8 +2,8 @@
|
|||||||
README
|
README
|
||||||
------
|
------
|
||||||
|
|
||||||
Laconica 0.7.4 ("Can't Get There From Here")
|
Laconica 0.8.0 ("Shiny Happy People")
|
||||||
29 May 2009
|
8 July 2009
|
||||||
|
|
||||||
This is the README file for Laconica, the Open Source microblogging
|
This is the README file for Laconica, the Open Source microblogging
|
||||||
platform. It includes installation instructions, descriptions of
|
platform. It includes installation instructions, descriptions of
|
||||||
@ -71,29 +71,52 @@ for additional terms.
|
|||||||
New this version
|
New this version
|
||||||
================
|
================
|
||||||
|
|
||||||
This is a minor bug-fix and feature release since version 0.7.3,
|
This is a major feature release since version 0.7.4, released May 31
|
||||||
released Apr 4 2009. Notable changes this version:
|
2009. Notable changes this version:
|
||||||
|
|
||||||
- Improved handling of UTF-8 characters. The new code is *not* backwards
|
- Support for a hosted service (status network). Multiple sites can
|
||||||
compatible by default; see "Upgrading" below for instructions on
|
share the same codebase but use different databases.
|
||||||
converting existing databases to the correct character set.
|
- OEmbed. Links to pages that support OEmbed (http://www.oembed.com/)
|
||||||
- Unroll joins for large queries. This greatly enhanced database
|
become popup links, and the media are shown in a special lightbox.
|
||||||
performance -- up to 50x for some queries -- at the expense of making
|
- File attachments. Users can attach files of the size and type approved
|
||||||
an extra DB hit for some queries.
|
by an administrator, and a shortened link will be included in the
|
||||||
- Added an optional plugin to use WikiHashtags
|
notice.
|
||||||
(http://hashtags.wikia.com/) for the sidebar on hashtag pages.
|
- Related notices are organized into conversations, with each reply a
|
||||||
- Optimized Twitter friend synchronization.
|
branch in a tree. Conversations have pages and are linked to from each
|
||||||
- Better error handling for Ajax posting of notices, including
|
notice in the conversation.
|
||||||
HTTP errors and timeouts.
|
- User designs. Users can specify colours and backgrounds
|
||||||
- Experimental Comet plugin -- supports the cometd and the Bayeux
|
for their profile pages and other "personal" pages.
|
||||||
protocol. Using this plugin, you can show "real time" updates on the
|
- Group designs. Group administrators can specify similar designs for
|
||||||
public and tag pages. However, server configuration is complex.
|
group profiles and related pages.
|
||||||
- If queues are enabled, update inboxes and memcached off-line. Speeds
|
- Site designs. Site authors can specify a design (background and
|
||||||
up posting considerably.
|
colors) for the site.
|
||||||
- Correctly shorten links posted through XMPP.
|
- New themes. Five new themes are added to the base release; these show
|
||||||
- <link> elements for pagination, supported by some browsers like Opera.
|
off the flexibility of Laconica's theming system.
|
||||||
- Corrected date format in search API.
|
- Statistics. Public sites will periodically send usage statistics,
|
||||||
- Made the public XRDS file work correctly.
|
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
|
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
|
1. Unpack the tarball you downloaded on your Web server. Usually a
|
||||||
command like this will work:
|
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
|
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
|
may have to unpack the tarball on your local computer and FTP the
|
||||||
files to the server.)
|
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
|
2. Move the tarball to a directory of your choosing in your Web root
|
||||||
directory. Usually something like this will work:
|
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
|
This will make your Laconica instance available in the mublog path of
|
||||||
your server, like "http://example.net/mublog". "microblog" or
|
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
|
default. This can be useful for starting, stopping, and monitoring the
|
||||||
daemons.
|
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
|
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
|
consequently show up "wrong" in browsers. See below for how to deal
|
||||||
with this situation.
|
with this situation.
|
||||||
|
|
||||||
If you've been using Laconica 0.6, 0.5 or lower, or if you've been
|
If you've been using Laconica 0.7, 0.6, 0.5 or lower, or if you've
|
||||||
tracking the "git" version of the software, you will probably want
|
been tracking the "git" version of the software, you will probably
|
||||||
to upgrade and keep your existing data. There is no automated upgrade
|
want to upgrade and keep your existing data. There is no automated
|
||||||
procedure in Laconica 0.7.4. Try these step-by-step instructions; read
|
upgrade procedure in Laconica 0.8.0. Try these step-by-step
|
||||||
to the end first before trying them.
|
instructions; read to the end first before trying them.
|
||||||
|
|
||||||
0. Download Laconica and set up all the prerequisites as if you were
|
0. Download Laconica and set up all the prerequisites as if you were
|
||||||
doing a new install.
|
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
|
5. Once all writing processes to your site are turned off, make a
|
||||||
final backup of the Web directory and database.
|
final backup of the Web directory and database.
|
||||||
6. Move your Laconica directory to a backup spot, like "mublog.bak".
|
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.
|
wherever your code used to be.
|
||||||
8. Copy the config.php file and avatar directory from your old
|
8. Copy the config.php file and avatar directory from your old
|
||||||
directory to your new directory.
|
directory to your new directory.
|
||||||
9. Copy htaccess.sample to .htaccess in the new directory. Change the
|
9. Copy htaccess.sample to .htaccess in the new directory. Change the
|
||||||
RewriteBase to use the correct path.
|
RewriteBase to use the correct path.
|
||||||
10. Rebuild the database. For MySQL, go to your Laconica directory and
|
10. Rebuild the database. NOTE: this step is destructive and cannot be
|
||||||
run the rebuilddb.sh script like this:
|
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
|
./scripts/rebuilddb.sh rootuser rootpassword database db/laconica.sql
|
||||||
|
|
||||||
Here, rootuser and rootpassword are the username and password for a
|
Here, rootuser and rootpassword are the username and password for a
|
||||||
user who can drop and create databases as well as tables; typically
|
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,
|
For PostgreSQL databases there is an equivalent, rebuilddb_psql.sh,
|
||||||
which operates slightly differently. Read the documentation in that
|
which operates slightly differently. Read the documentation in that
|
||||||
script before running it.
|
script before running it.
|
||||||
@ -791,6 +830,9 @@ problem.
|
|||||||
3. When fixup_inboxes is finished, you can set the enabled flag to
|
3. When fixup_inboxes is finished, you can set the enabled flag to
|
||||||
'true'.
|
'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
|
UTF-8 Database
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@ -817,7 +859,7 @@ what to do.
|
|||||||
Configuration options
|
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
|
dependency software) is config.php in your Laconica directory. If you
|
||||||
edit any other file in the directory, like lib/common.php (where most
|
edit any other file in the directory, like lib/common.php (where most
|
||||||
of the defaults are defined), you will lose your configuration options
|
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
|
logfile: full path to a file for Laconica to save logging
|
||||||
information to. You may want to use this if you don't have
|
information to. You may want to use this if you don't have
|
||||||
access to syslog.
|
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
|
locale_path: full path to the directory for locale data. Unless you
|
||||||
store all your locale data in one place, you probably
|
store all your locale data in one place, you probably
|
||||||
don't need to use this.
|
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
|
to put introductory information about your service, or info about
|
||||||
upgrades and outages, or other community info. Any HTML will
|
upgrades and outages, or other community info. Any HTML will
|
||||||
be escaped.
|
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
|
logo: URL of an image file to use as the logo for the site. Overrides
|
||||||
the logo in the theme, if any.
|
the logo in the theme, if any.
|
||||||
ssl: Whether to use SSL and https:// URLs for some or all pages.
|
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
|
shorturllength: Length of URL at which URLs in a message exceeding 140
|
||||||
characters will be sent to the user's chosen
|
characters will be sent to the user's chosen
|
||||||
shortening service.
|
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
|
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
|
"laconica", but if you have more than one installation on the
|
||||||
server, you may want to change the name for each instance so
|
server, you may want to change the name for each instance so
|
||||||
you can track log messages more easily.
|
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
|
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.
|
'Queues and daemons' above for how to set this up.
|
||||||
|
|
||||||
enabled: Whether to uses queues. Defaults to false.
|
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
|
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.
|
blacklist: An array of IDs of users to hide from the public stream.
|
||||||
Useful if you have someone making excessive Twitterfeed posts
|
Useful if you have someone making excessive Twitterfeed posts
|
||||||
to the site, other kinds of automated posts, testing bots, etc.
|
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
|
theme
|
||||||
-----
|
-----
|
||||||
@ -1100,6 +1166,15 @@ dropoff: Decay factor for tag listing, in seconds.
|
|||||||
Defaults to exponential decay over ten days; you can twiddle
|
Defaults to exponential decay over ten days; you can twiddle
|
||||||
with it to try and get better results for your site.
|
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
|
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
|
Twitter <http://twitter.com/help/request_source>, you can use
|
||||||
that here instead. Status updates on Twitter will then have
|
that here instead. Status updates on Twitter will then have
|
||||||
links to your site.
|
links to your site.
|
||||||
|
taguri: base for tag:// URIs. Defaults to site-server + ',2009'.
|
||||||
|
|
||||||
inboxes
|
inboxes
|
||||||
-------
|
-------
|
||||||
@ -1239,7 +1315,7 @@ detection.
|
|||||||
|
|
||||||
supported: an array of mime types you accept to store and distribute,
|
supported: an array of mime types you accept to store and distribute,
|
||||||
like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
|
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.
|
support.
|
||||||
uploads: false to disable uploading files with notices (true by default).
|
uploads: false to disable uploading files with notices (true by default).
|
||||||
filecommand: The required MIME_Type library may need to use the 'file'
|
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.
|
not exceed the user_quota.
|
||||||
monthly_quota: total size permitted in the current month. This is the total
|
monthly_quota: total size permitted in the current month. This is the total
|
||||||
size in bytes that a user can upload each month.
|
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
|
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
|
debug: whether to output debugging info for session storage. Can help
|
||||||
with weird session bugs, sometimes. Default false.
|
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
|
Troubleshooting
|
||||||
===============
|
===============
|
||||||
|
|
||||||
@ -1396,7 +1515,7 @@ if anyone's been overlooked in error.
|
|||||||
* Ori Avtalion
|
* Ori Avtalion
|
||||||
* Meitar Moscovitz
|
* Meitar Moscovitz
|
||||||
* Ken Sheppardson (Trac server, man-about-town)
|
* Ken Sheppardson (Trac server, man-about-town)
|
||||||
* Tiago 'gouki' Faria (i18n managerx)
|
* Tiago 'gouki' Faria (i18n manager)
|
||||||
* Sean Murphy
|
* Sean Murphy
|
||||||
* Leslie Michael Orchard
|
* Leslie Michael Orchard
|
||||||
* Eric Helgeson
|
* Eric Helgeson
|
||||||
@ -1405,6 +1524,11 @@ if anyone's been overlooked in error.
|
|||||||
* Tobias Diekershoff
|
* Tobias Diekershoff
|
||||||
* Dan Moore
|
* Dan Moore
|
||||||
* Fil
|
* Fil
|
||||||
|
* Jeff Mitchell
|
||||||
|
* Brenda Wallace
|
||||||
|
* Jeffery To
|
||||||
|
* Federico Marani
|
||||||
|
* Craig Andrews
|
||||||
|
|
||||||
Thanks also to the developers of our upstream library code and to the
|
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,
|
thousands of people who have tried out Identi.ca, installed Laconi.ca,
|
||||||
|
@ -75,14 +75,14 @@ class ApiAction extends Action
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
# Caller might give us a username even if not required
|
// Caller might give us a username even if not required
|
||||||
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
if (isset($_SERVER['PHP_AUTH_USER'])) {
|
||||||
$user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
|
$user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']);
|
||||||
if ($user) {
|
if ($user) {
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
}
|
}
|
||||||
# Twitter doesn't throw an error if the user isn't found
|
# Twitter doesn't throw an error if the user isn't found
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->process_command();
|
$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()
|
function requires_auth()
|
||||||
{
|
{
|
||||||
static $noauth = array( 'statuses/public_timeline',
|
static $noauth = array( 'statuses/public_timeline',
|
||||||
@ -127,7 +127,8 @@ class ApiAction extends Action
|
|||||||
'help/downtime_schedule',
|
'help/downtime_schedule',
|
||||||
'laconica/version',
|
'laconica/version',
|
||||||
'laconica/config',
|
'laconica/config',
|
||||||
'laconica/wadl');
|
'laconica/wadl',
|
||||||
|
'groups/timeline');
|
||||||
|
|
||||||
static $bareauth = array('statuses/user_timeline',
|
static $bareauth = array('statuses/user_timeline',
|
||||||
'statuses/friends_timeline',
|
'statuses/friends_timeline',
|
||||||
@ -135,28 +136,61 @@ class ApiAction extends Action
|
|||||||
'statuses/replies',
|
'statuses/replies',
|
||||||
'statuses/mentions',
|
'statuses/mentions',
|
||||||
'statuses/followers',
|
'statuses/followers',
|
||||||
'favorites/favorites');
|
'favorites/favorites',
|
||||||
|
'friendships/show');
|
||||||
|
|
||||||
$fullname = "$this->api_action/$this->api_method";
|
$fullname = "$this->api_action/$this->api_method";
|
||||||
|
|
||||||
// If the site is "private", all API methods except laconica/config
|
// If the site is "private", all API methods except laconica/config
|
||||||
// need authentication
|
// need authentication
|
||||||
|
|
||||||
if (common_config('site', 'private')) {
|
if (common_config('site', 'private')) {
|
||||||
return $fullname != 'laconica/config' || false;
|
return $fullname != 'laconica/config' || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bareauth: only needs auth if without an argument or query param specifying user
|
||||||
|
|
||||||
if (in_array($fullname, $bareauth)) {
|
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;
|
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)) {
|
} else if (in_array($fullname, $noauth)) {
|
||||||
# noauth: never needs auth
|
|
||||||
|
// noauth: never needs auth
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
# everybody else needs auth
|
|
||||||
|
// everybody else needs auth
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class FacebookhomeAction extends FacebookAction
|
|||||||
|
|
||||||
// If this is the first time the user has started the app
|
// If this is the first time the user has started the app
|
||||||
// prompt for Facebook status update permission
|
// 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(
|
if ($this->facebook->api_client->data_getUserPreference(
|
||||||
FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') {
|
FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') {
|
||||||
@ -203,7 +203,7 @@ class FacebookhomeAction extends FacebookAction
|
|||||||
$api_key = common_config('facebook', 'apikey');
|
$api_key = common_config('facebook', 'apikey');
|
||||||
|
|
||||||
$auth_url = 'http://www.facebook.com/authorize.php?api_key=' .
|
$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';
|
'&next_cancel=' . $next . '&submit=skip';
|
||||||
|
|
||||||
$this->elementStart('span', array('class' => 'facebook-button'));
|
$this->elementStart('span', array('class' => 'facebook-button'));
|
||||||
|
@ -31,7 +31,7 @@ class FacebookinviteAction extends FacebookAction
|
|||||||
$this->error = $error;
|
$this->error = $error;
|
||||||
|
|
||||||
if ($this->flink) {
|
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(
|
$this->facebook->api_client->data_getUserPreference(
|
||||||
FACEBOOK_PROMPTED_UPDATE_PREF) == 'true') {
|
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
|
// If this is the first time the user has started the app
|
||||||
// prompt for Facebook status update permission
|
// 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(
|
if ($this->facebook->api_client->data_getUserPreference(
|
||||||
FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') {
|
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',
|
$this->elementStart('form', array('method' => 'post',
|
||||||
'id' => 'facebook_settings'));
|
'id' => 'facebook_settings'));
|
||||||
@ -131,7 +131,7 @@ class FacebooksettingsAction extends FacebookAction
|
|||||||
|
|
||||||
$this->elementStart('ul', array('id' => 'fb-permissions-list'));
|
$this->elementStart('ul', array('id' => 'fb-permissions-list'));
|
||||||
$this->elementStart('li', array('id' => 'fb-permissions-item'));
|
$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" . '\')'));
|
'next_fbjs' => 'document.setLocation(\'' . "$this->app_uri/settings.php" . '\')'));
|
||||||
$this->element('span', array('class' => 'facebook-button'),
|
$this->element('span', array('class' => 'facebook-button'),
|
||||||
sprintf(_('Allow %s to update my Facebook status'), common_config('site', 'name')));
|
sprintf(_('Allow %s to update my Facebook status'), common_config('site', 'name')));
|
||||||
|
@ -194,7 +194,7 @@ class FavoritedAction extends Action
|
|||||||
$qry = 'SELECT notice.*, '.
|
$qry = 'SELECT notice.*, '.
|
||||||
$weightexpr . ' as weight ' .
|
$weightexpr . ' as weight ' .
|
||||||
'FROM notice JOIN fave ON notice.id = fave.notice_id ' .
|
'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';
|
'ORDER BY weight DESC';
|
||||||
|
|
||||||
$offset = ($this->page - 1) * NOTICES_PER_PAGE;
|
$offset = ($this->page - 1) * NOTICES_PER_PAGE;
|
||||||
|
@ -312,36 +312,4 @@ class GroupDesignSettingsAction extends DesignSettingsAction
|
|||||||
$this->showForm(_('Design preferences saved.'), true);
|
$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) {
|
function isRespectsQuota($user) {
|
||||||
$file = new File;
|
$file = new File;
|
||||||
$ret = $file->isRespectsQuota($user);
|
$ret = $file->isRespectsQuota($user,$_FILES['attach']['size']);
|
||||||
if (true === $ret) return true;
|
if (true === $ret) return true;
|
||||||
$this->clientError($ret);
|
$this->clientError($ret);
|
||||||
}
|
}
|
||||||
|
@ -83,14 +83,12 @@ class OthersettingsAction extends AccountSettingsAction
|
|||||||
{
|
{
|
||||||
$user = common_current_user();
|
$user = common_current_user();
|
||||||
|
|
||||||
|
|
||||||
$this->elementStart('form', array('method' => 'post',
|
$this->elementStart('form', array('method' => 'post',
|
||||||
'id' => 'form_settings_other',
|
'id' => 'form_settings_other',
|
||||||
'class' => 'form_settings',
|
'class' => 'form_settings',
|
||||||
'action' =>
|
'action' =>
|
||||||
common_local_url('othersettings')));
|
common_local_url('othersettings')));
|
||||||
$this->elementStart('fieldset');
|
$this->elementStart('fieldset');
|
||||||
$this->element('legend', null, _('URL Auto-shortening'));
|
|
||||||
$this->hidden('token', common_session_token());
|
$this->hidden('token', common_session_token());
|
||||||
|
|
||||||
// I18N
|
// I18N
|
||||||
@ -109,10 +107,14 @@ class OthersettingsAction extends AccountSettingsAction
|
|||||||
|
|
||||||
$this->elementStart('ul', 'form_data');
|
$this->elementStart('ul', 'form_data');
|
||||||
$this->elementStart('li');
|
$this->elementStart('li');
|
||||||
$this->dropdown('urlshorteningservice', _('Service'),
|
$this->dropdown('urlshorteningservice', _('Shorten URLs with'),
|
||||||
$services, _('Automatic shortening service to use.'),
|
$services, _('Automatic shortening service to use.'),
|
||||||
false, $user->urlshorteningservice);
|
false, $user->urlshorteningservice);
|
||||||
$this->elementEnd('li');
|
$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->elementEnd('ul');
|
||||||
$this->submit('save', _('Save'));
|
$this->submit('save', _('Save'));
|
||||||
$this->elementEnd('fieldset');
|
$this->elementEnd('fieldset');
|
||||||
@ -145,6 +147,8 @@ class OthersettingsAction extends AccountSettingsAction
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$viewdesigns = $this->boolean('viewdesigns');
|
||||||
|
|
||||||
$user = common_current_user();
|
$user = common_current_user();
|
||||||
|
|
||||||
assert(!is_null($user)); // should already be checked
|
assert(!is_null($user)); // should already be checked
|
||||||
@ -154,6 +158,7 @@ class OthersettingsAction extends AccountSettingsAction
|
|||||||
$original = clone($user);
|
$original = clone($user);
|
||||||
|
|
||||||
$user->urlshorteningservice = $urlshorteningservice;
|
$user->urlshorteningservice = $urlshorteningservice;
|
||||||
|
$user->viewdesigns = $viewdesigns;
|
||||||
|
|
||||||
$result = $user->update($original);
|
$result = $user->update($original);
|
||||||
|
|
||||||
|
@ -317,8 +317,25 @@ class ShowgroupAction extends GroupDesignAction
|
|||||||
common_local_url('grouprss',
|
common_local_url('grouprss',
|
||||||
array('nickname' => $this->group->nickname));
|
array('nickname' => $this->group->nickname));
|
||||||
|
|
||||||
return array(new Feed(Feed::RSS1, $url, sprintf(_('Notice feed for %s group'),
|
return array(new Feed(Feed::RSS1,
|
||||||
$this->group->nickname)));
|
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,4 +483,4 @@ class GroupAdminSection extends ProfileSection
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
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'];
|
$this->auth_user = $apidata['user'];
|
||||||
$notice_id = $apidata['api_arg'];
|
$notice_id = $this->trimmed('id');
|
||||||
$notice = Notice::staticGet($notice_id);
|
|
||||||
|
if (empty($notice_id)) {
|
||||||
|
$notice_id = $apidata['api_arg'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$notice = Notice::staticGet((int)$notice_id);
|
||||||
|
|
||||||
if ($notice) {
|
if ($notice) {
|
||||||
if ($apidata['content-type'] == 'xml') {
|
if ($apidata['content-type'] == 'xml') {
|
||||||
@ -389,7 +399,6 @@ class TwitapistatusesAction extends TwitterapiAction
|
|||||||
$this->clientError(_('No status with that ID found.'),
|
$this->clientError(_('No status with that ID found.'),
|
||||||
404, $apidata['content-type']);
|
404, $apidata['content-type']);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function destroy($args, $apidata)
|
function destroy($args, $apidata)
|
||||||
|
@ -37,24 +37,24 @@ class TwitapiusersAction extends TwitterapiAction
|
|||||||
|
|
||||||
$user = null;
|
$user = null;
|
||||||
$email = $this->arg('email');
|
$email = $this->arg('email');
|
||||||
$user_id = $this->arg('user_id');
|
|
||||||
|
|
||||||
// XXX: email field deprecated in Twitter's API
|
// XXX: email field deprecated in Twitter's API
|
||||||
|
|
||||||
// XXX: Also: need to add screen_name param
|
|
||||||
|
|
||||||
if ($email) {
|
if ($email) {
|
||||||
$user = User::staticGet('email', $email);
|
$user = User::staticGet('email', $email);
|
||||||
} elseif ($user_id) {
|
} else {
|
||||||
$user = $this->get_user($user_id);
|
$user = $this->get_user($apidata['api_arg'], $apidata);
|
||||||
} elseif (isset($apidata['api_arg'])) {
|
|
||||||
$user = $this->get_user($apidata['api_arg']);
|
|
||||||
} elseif (isset($apidata['user'])) {
|
|
||||||
$user = $apidata['user'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($user)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,17 +122,17 @@ class File extends Memcached_DataObject
|
|||||||
return $x;
|
return $x;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRespectsQuota($user) {
|
function isRespectsQuota($user,$fileSize) {
|
||||||
if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) {
|
if ($fileSize > common_config('attachments', 'file_quota')) {
|
||||||
return sprintf(_('No file may be larger than %d bytes ' .
|
return sprintf(_('No file may be larger than %d bytes ' .
|
||||||
'and the file you sent was %d bytes. Try to upload a smaller version.'),
|
'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'";
|
$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->query($query);
|
||||||
$this->fetch();
|
$this->fetch();
|
||||||
$total = $this->total + $_FILES['attach']['size'];
|
$total = $this->total + $fileSize;
|
||||||
if ($total > common_config('attachments', 'user_quota')) {
|
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'));
|
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())';
|
$query .= ' month(modified) = month(now()) and year(modified) = year(now())';
|
||||||
$this->query($query);
|
$this->query($query);
|
||||||
$this->fetch();
|
$this->fetch();
|
||||||
$total = $this->total + $_FILES['attach']['size'];
|
$total = $this->total + $fileSize;
|
||||||
if ($total > common_config('attachments', 'monthly_quota')) {
|
if ($total > common_config('attachments', 'monthly_quota')) {
|
||||||
return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'));
|
return 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];
|
$k = $keys[0];
|
||||||
unset($i);
|
unset($i);
|
||||||
}
|
}
|
||||||
$i = self::getcached($cls, $k, $v);
|
$i = Memcached_DataObject::getcached($cls, $k, $v);
|
||||||
if ($i) {
|
if ($i) {
|
||||||
return $i;
|
return $i;
|
||||||
} else {
|
} else {
|
||||||
$i = DB_DataObject::staticGet($cls, $k, $v);
|
$i = DB_DataObject::staticGet($cls, $k, $v);
|
||||||
if ($i) {
|
if ($i) {
|
||||||
$i->encache();
|
$i->encache();
|
||||||
} else {
|
|
||||||
self::cachenull($cls, $k, $v);
|
|
||||||
}
|
}
|
||||||
return $i;
|
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)
|
function &pkeyGet($cls, $kv)
|
||||||
{
|
{
|
||||||
$i = self::multicache($cls, $kv);
|
$i = Memcached_DataObject::multicache($cls, $kv);
|
||||||
if ($i) {
|
if ($i) {
|
||||||
return $i;
|
return $i;
|
||||||
} else {
|
} else {
|
||||||
@ -78,7 +58,6 @@ class Memcached_DataObject extends DB_DataObject
|
|||||||
if ($i->find(true)) {
|
if ($i->find(true)) {
|
||||||
$i->encache();
|
$i->encache();
|
||||||
} else {
|
} else {
|
||||||
self::multicachenull($cls, $kv);
|
|
||||||
$i = null;
|
$i = null;
|
||||||
}
|
}
|
||||||
return $i;
|
return $i;
|
||||||
@ -88,9 +67,6 @@ class Memcached_DataObject extends DB_DataObject
|
|||||||
function insert()
|
function insert()
|
||||||
{
|
{
|
||||||
$result = parent::insert();
|
$result = parent::insert();
|
||||||
if ($result) {
|
|
||||||
$this->encache();
|
|
||||||
}
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,11 +97,11 @@ class Memcached_DataObject extends DB_DataObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
static function getcached($cls, $k, $v) {
|
static function getcached($cls, $k, $v) {
|
||||||
$c = self::memcache();
|
$c = Memcached_DataObject::memcache();
|
||||||
if (!$c) {
|
if (!$c) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} 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)
|
function multicache($cls, $kv)
|
||||||
{
|
{
|
||||||
$c = self::memcache();
|
ksort($kv);
|
||||||
|
$c = Memcached_DataObject::memcache();
|
||||||
if (!$c) {
|
if (!$c) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} 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)
|
function getSearchEngine($table)
|
||||||
{
|
{
|
||||||
require_once INSTALLDIR.'/lib/search_engines.php';
|
require_once INSTALLDIR.'/lib/search_engines.php';
|
||||||
@ -241,7 +211,7 @@ class Memcached_DataObject extends DB_DataObject
|
|||||||
|
|
||||||
static function cachedQuery($cls, $qry, $expiry=3600)
|
static function cachedQuery($cls, $qry, $expiry=3600)
|
||||||
{
|
{
|
||||||
$c = self::memcache();
|
$c = Memcached_DataObject::memcache();
|
||||||
if (!$c) {
|
if (!$c) {
|
||||||
$inst = new $cls();
|
$inst = new $cls();
|
||||||
$inst->query($qry);
|
$inst->query($qry);
|
||||||
|
@ -356,6 +356,8 @@ class Notice extends Memcached_DataObject
|
|||||||
$this->blowTagCache($blowLast);
|
$this->blowTagCache($blowLast);
|
||||||
$this->blowGroupCache($blowLast);
|
$this->blowGroupCache($blowLast);
|
||||||
$this->blowConversationCache($blowLast);
|
$this->blowConversationCache($blowLast);
|
||||||
|
$profile = Profile::staticGet($this->profile_id);
|
||||||
|
$profile->blowNoticeCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
function blowConversationCache($blowLast=false)
|
function blowConversationCache($blowLast=false)
|
||||||
@ -1164,6 +1166,18 @@ class Notice extends Memcached_DataObject
|
|||||||
}
|
}
|
||||||
$tag->free();
|
$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');
|
$xs->elementEnd('entry');
|
||||||
|
|
||||||
return $xs->getString();
|
return $xs->getString();
|
||||||
@ -1210,7 +1224,7 @@ class Notice extends Memcached_DataObject
|
|||||||
$window = explode(',', $laststr);
|
$window = explode(',', $laststr);
|
||||||
$last_id = $window[0];
|
$last_id = $window[0];
|
||||||
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
|
$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);
|
$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,
|
$window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
|
||||||
0, 0, null, $tag)));
|
0, 0, null)));
|
||||||
|
|
||||||
$windowstr = implode(',', $window);
|
$windowstr = implode(',', $window);
|
||||||
|
|
||||||
|
@ -337,4 +337,132 @@ class Profile extends Memcached_DataObject
|
|||||||
|
|
||||||
return $profile;
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
||||||
|
|
||||||
class Queue_item extends Memcached_DataObject
|
class Queue_item extends Memcached_DataObject
|
||||||
{
|
{
|
||||||
###START_AUTOCODE
|
###START_AUTOCODE
|
||||||
/* the code below is auto generated do not remove the above tag */
|
/* the code below is auto generated do not remove the above tag */
|
||||||
@ -13,7 +13,7 @@ class Queue_item extends Memcached_DataObject
|
|||||||
public $notice_id; // int(4) primary_key not_null
|
public $notice_id; // int(4) primary_key not_null
|
||||||
public $transport; // varchar(8) primary_key not_null
|
public $transport; // varchar(8) primary_key not_null
|
||||||
public $created; // datetime() not_null
|
public $created; // datetime() not_null
|
||||||
public $claimed; // datetime()
|
public $claimed; // datetime()
|
||||||
|
|
||||||
/* Static get */
|
/* Static get */
|
||||||
function staticGet($k,$v=null)
|
function staticGet($k,$v=null)
|
||||||
@ -24,7 +24,7 @@ class Queue_item extends Memcached_DataObject
|
|||||||
|
|
||||||
function sequenceKey()
|
function sequenceKey()
|
||||||
{ return array(false, false); }
|
{ return array(false, false); }
|
||||||
|
|
||||||
static function top($transport) {
|
static function top($transport) {
|
||||||
|
|
||||||
$qi = new Queue_item();
|
$qi = new Queue_item();
|
||||||
@ -54,4 +54,9 @@ class Queue_item extends Memcached_DataObject
|
|||||||
$qi = null;
|
$qi = null;
|
||||||
return 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));
|
||||||
$cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
|
$cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last'));
|
||||||
}
|
}
|
||||||
|
$profile = $this->getProfile();
|
||||||
|
$profile->blowFaveCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelfTags()
|
function getSelfTags()
|
||||||
|
@ -116,7 +116,9 @@ create table notice (
|
|||||||
modified timestamp /* comment 'date this record was modified' */,
|
modified timestamp /* comment 'date this record was modified' */,
|
||||||
reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) ,
|
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' */,
|
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) */
|
/* FULLTEXT(content) */
|
||||||
);
|
);
|
||||||
@ -172,7 +174,7 @@ create table token (
|
|||||||
tok char(32) not null /* comment 'identifying value' */,
|
tok char(32) not null /* comment 'identifying value' */,
|
||||||
secret char(32) not null /* comment 'secret value' */,
|
secret char(32) not null /* comment 'secret value' */,
|
||||||
type integer not null default 0 /* comment 'request or access' */,
|
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' */,
|
created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */,
|
||||||
modified timestamp /* comment 'date this record was modified' */,
|
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),
|
user_id integer not null /* comment 'user receiving the message' */ references "user" (id),
|
||||||
notice_id integer not null /* comment 'notice received' */ references notice (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' */,
|
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)
|
primary key (user_id, notice_id)
|
||||||
);
|
);
|
||||||
@ -436,8 +438,8 @@ create table file (
|
|||||||
mimetype varchar(50),
|
mimetype varchar(50),
|
||||||
size integer,
|
size integer,
|
||||||
title varchar(255),
|
title varchar(255),
|
||||||
date integer(11),
|
date integer,
|
||||||
protected integer(1)
|
protected integer
|
||||||
);
|
);
|
||||||
|
|
||||||
create sequence file_oembed_seq;
|
create sequence file_oembed_seq;
|
||||||
@ -454,7 +456,7 @@ create table file_oembed (
|
|||||||
title varchar(255),
|
title varchar(255),
|
||||||
author_name varchar(50),
|
author_name varchar(50),
|
||||||
author_url varchar(255),
|
author_url varchar(255),
|
||||||
url varchar(255),
|
url varchar(255)
|
||||||
);
|
);
|
||||||
|
|
||||||
create sequence file_redirection_seq;
|
create sequence file_redirection_seq;
|
||||||
@ -484,6 +486,18 @@ create table file_to_post (
|
|||||||
unique(file_id, post_id)
|
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 */
|
/* 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-text").val("");
|
||||||
$("#notice_data-attach").val("");
|
$("#notice_data-attach").val("");
|
||||||
|
$("#notice_in-reply-to").val("");
|
||||||
$('#notice_data-attach_selected').remove();
|
$('#notice_data-attach_selected').remove();
|
||||||
counter();
|
counter();
|
||||||
}
|
}
|
||||||
@ -282,7 +283,7 @@ function NoticeAttachments() {
|
|||||||
},
|
},
|
||||||
timeout : 0,
|
timeout : 0,
|
||||||
autoHide : true,
|
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() {
|
$('#content .notice a.attachment').click(function() {
|
||||||
|
@ -439,8 +439,6 @@ class Action extends HTMLOutputter // lawsuit
|
|||||||
$this->menuItem(common_local_url('register'),
|
$this->menuItem(common_local_url('register'),
|
||||||
_('Register'), _('Create an account'), false, 'nav_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'),
|
$this->menuItem(common_local_url('login'),
|
||||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||||
}
|
}
|
||||||
@ -708,6 +706,11 @@ class Action extends HTMLOutputter // lawsuit
|
|||||||
_('About'));
|
_('About'));
|
||||||
$this->menuItem(common_local_url('doc', array('title' => 'faq')),
|
$this->menuItem(common_local_url('doc', array('title' => 'faq')),
|
||||||
_('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')),
|
$this->menuItem(common_local_url('doc', array('title' => 'privacy')),
|
||||||
_('Privacy'));
|
_('Privacy'));
|
||||||
$this->menuItem(common_local_url('doc', array('title' => 'source')),
|
$this->menuItem(common_local_url('doc', array('title' => 'source')),
|
||||||
@ -769,7 +772,9 @@ class Action extends HTMLOutputter // lawsuit
|
|||||||
$this->elementStart('p');
|
$this->elementStart('p');
|
||||||
$this->element('img', array('id' => 'license_cc',
|
$this->element('img', array('id' => 'license_cc',
|
||||||
'src' => common_config('license', 'image'),
|
'src' => common_config('license', 'image'),
|
||||||
'alt' => common_config('license', 'title')));
|
'alt' => common_config('license', 'title'),
|
||||||
|
'width' => '80',
|
||||||
|
'height' => '15'));
|
||||||
//TODO: This is dirty: i18n
|
//TODO: This is dirty: i18n
|
||||||
$this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
|
$this->text(_('All '.common_config('site', 'name').' content and data are available under the '));
|
||||||
$this->element('a', array('class' => 'license',
|
$this->element('a', array('class' => 'license',
|
||||||
|
@ -97,18 +97,11 @@ class StatsCommand extends Command
|
|||||||
{
|
{
|
||||||
function execute($channel)
|
function execute($channel)
|
||||||
{
|
{
|
||||||
|
$profile = $this->user->getProfile();
|
||||||
|
|
||||||
$subs = new Subscription();
|
$subs_count = $profile->subscriptionCount();
|
||||||
$subs->subscriber = $this->user->id;
|
$subbed_count = $profile->subscriberCount();
|
||||||
$subs_count = (int) $subs->count() - 1;
|
$notice_count = $profile->noticeCount();
|
||||||
|
|
||||||
$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();
|
|
||||||
|
|
||||||
$channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
|
$channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n".
|
||||||
"Subscribers: %2\$s\n".
|
"Subscribers: %2\$s\n".
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
if (!defined('LACONICA')) { exit(1); }
|
if (!defined('LACONICA')) { exit(1); }
|
||||||
|
|
||||||
define('LACONICA_VERSION', '0.8.0dev');
|
define('LACONICA_VERSION', '0.8.0');
|
||||||
|
|
||||||
define('AVATAR_PROFILE_SIZE', 96);
|
define('AVATAR_PROFILE_SIZE', 96);
|
||||||
define('AVATAR_STREAM_SIZE', 48);
|
define('AVATAR_STREAM_SIZE', 48);
|
||||||
@ -206,7 +206,7 @@ $config =
|
|||||||
'inboxes' =>
|
'inboxes' =>
|
||||||
array('enabled' => true), # on by default for new sites
|
array('enabled' => true), # on by default for new sites
|
||||||
'newuser' =>
|
'newuser' =>
|
||||||
array('subscribe' => null,
|
array('default' => null,
|
||||||
'welcome' => null),
|
'welcome' => null),
|
||||||
'snapshot' =>
|
'snapshot' =>
|
||||||
array('run' => 'web',
|
array('run' => 'web',
|
||||||
@ -282,6 +282,39 @@ if (function_exists('date_default_timezone_set')) {
|
|||||||
date_default_timezone_set('UTC');
|
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:
|
// From most general to most specific:
|
||||||
// server-wide, then vhost-wide, then for a path,
|
// server-wide, then vhost-wide, then for a path,
|
||||||
// finally for a dir (usually only need one of the last two).
|
// finally for a dir (usually only need one of the last two).
|
||||||
|
@ -53,14 +53,19 @@ class CurrentUserDesignAction extends Action
|
|||||||
*
|
*
|
||||||
* @return nothing
|
* @return nothing
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function showStylesheets()
|
function showStylesheets()
|
||||||
{
|
{
|
||||||
parent::showStylesheets();
|
parent::showStylesheets();
|
||||||
|
|
||||||
$design = $this->getDesign();
|
$user = common_current_user();
|
||||||
|
|
||||||
if (!empty($design)) {
|
if (empty($user) || $user->viewdesigns) {
|
||||||
$design->showCSS($this);
|
$design = $this->getDesign();
|
||||||
|
|
||||||
|
if (!empty($design)) {
|
||||||
|
$design->showCSS($this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,5 +89,4 @@ class CurrentUserDesignAction extends Action
|
|||||||
return $cur->getDesign();
|
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()
|
function saveNewNotice()
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -504,7 +494,7 @@ class FacebookAction extends Action
|
|||||||
$replyto = $this->trimmed('inreplyto');
|
$replyto = $this->trimmed('inreplyto');
|
||||||
|
|
||||||
$notice = Notice::saveNew($user->id, $content,
|
$notice = Notice::saveNew($user->id, $content,
|
||||||
'Facebook', 1, ($replyto == 'false') ? null : $replyto);
|
'web', 1, ($replyto == 'false') ? null : $replyto);
|
||||||
|
|
||||||
if (is_string($notice)) {
|
if (is_string($notice)) {
|
||||||
$this->showPage($notice);
|
$this->showPage($notice);
|
||||||
@ -514,8 +504,7 @@ class FacebookAction extends Action
|
|||||||
common_broadcast_notice($notice);
|
common_broadcast_notice($notice);
|
||||||
|
|
||||||
// Also update the user's Facebook status
|
// Also update the user's Facebook status
|
||||||
$this->updateFacebookStatus($notice);
|
facebookBroadcastNotice($notice);
|
||||||
$this->updateProfileBox($notice);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,10 @@ function updateProfileBox($facebook, $flink, $notice) {
|
|||||||
|
|
||||||
function isFacebookBound($notice, $flink) {
|
function isFacebookBound($notice, $flink) {
|
||||||
|
|
||||||
|
if (empty($flink)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If the user does not want to broadcast to Facebook, move along
|
// If the user does not want to broadcast to Facebook, move along
|
||||||
if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
|
if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) {
|
||||||
common_log(LOG_INFO, "Skipping notice $notice->id " .
|
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
|
// Check to see if the user has given the FB app status update perms
|
||||||
$result = $facebook->api_client->
|
$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) {
|
if ($result != 1) {
|
||||||
$user = $flink->getUser();
|
$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 " .
|
"because user $user->nickname hasn't given the " .
|
||||||
'Facebook app \'status_update\' permission.';
|
'Facebook app \'status_update\' or \'publish_stream\' permission.';
|
||||||
common_log(LOG_INFO, $msg);
|
common_debug($msg);
|
||||||
$success = false;
|
$success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,13 +116,16 @@ function facebookBroadcastNotice($notice)
|
|||||||
{
|
{
|
||||||
$facebook = getFacebook();
|
$facebook = getFacebook();
|
||||||
$flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
|
$flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE);
|
||||||
$fbuid = $flink->foreign_id;
|
|
||||||
|
|
||||||
if (isFacebookBound($notice, $flink)) {
|
if (isFacebookBound($notice, $flink)) {
|
||||||
|
|
||||||
$status = null;
|
$status = null;
|
||||||
|
$fbuid = $flink->foreign_id;
|
||||||
|
|
||||||
|
$user = $flink->getUser();
|
||||||
|
|
||||||
// Get the status 'verb' (prefix) the user has set
|
// Get the status 'verb' (prefix) the user has set
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$prefix = $facebook->api_client->
|
$prefix = $facebook->api_client->
|
||||||
data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
|
data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid);
|
||||||
@ -122,23 +133,128 @@ function facebookBroadcastNotice($notice)
|
|||||||
$status = "$prefix $notice->content";
|
$status = "$prefix $notice->content";
|
||||||
|
|
||||||
} catch(FacebookRestClientException $e) {
|
} catch(FacebookRestClientException $e) {
|
||||||
common_log(LOG_ERR, $e->getMessage());
|
common_log(LOG_WARNING, $e->getMessage());
|
||||||
return false;
|
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 {
|
try {
|
||||||
$facebook->api_client->users_setStatus($status, $fbuid, false, true);
|
$result = $facebook->api_client->
|
||||||
updateProfileBox($facebook, $flink, $notice);
|
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) {
|
} catch(FacebookRestClientException $e) {
|
||||||
common_log(LOG_ERR, $e->getMessage());
|
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;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ if (!defined('LACONICA')) {
|
|||||||
/**
|
/**
|
||||||
* Base class for actions that use a group's design
|
* Base class for actions that use a group's design
|
||||||
*
|
*
|
||||||
* Pages related to groups can be themed with a design.
|
* Pages related to groups can be themed with a design.
|
||||||
* This superclass returns that design.
|
* This superclass returns that design.
|
||||||
*
|
*
|
||||||
* @category Action
|
* @category Action
|
||||||
@ -48,7 +48,7 @@ class GroupDesignAction extends Action {
|
|||||||
|
|
||||||
/** The group in question */
|
/** The group in question */
|
||||||
var $group = null;
|
var $group = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the groups's design stylesheet
|
* Show the groups's design stylesheet
|
||||||
*
|
*
|
||||||
@ -58,10 +58,14 @@ class GroupDesignAction extends Action {
|
|||||||
{
|
{
|
||||||
parent::showStylesheets();
|
parent::showStylesheets();
|
||||||
|
|
||||||
$design = $this->getDesign();
|
$user = common_current_user();
|
||||||
|
|
||||||
if (!empty($design)) {
|
if (empty($user) || $user->viewdesigns) {
|
||||||
$design->showCSS($this);
|
$design = $this->getDesign();
|
||||||
|
|
||||||
|
if (!empty($design)) {
|
||||||
|
$design->showCSS($this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,12 +80,10 @@ class GroupDesignAction extends Action {
|
|||||||
|
|
||||||
function getDesign()
|
function getDesign()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (empty($this->group)) {
|
if (empty($this->group)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->group->getDesign();
|
return $this->group->getDesign();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,14 @@ function jabber_daemon_address()
|
|||||||
return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
|
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
|
* connect the configured Jabber account to the configured server
|
||||||
*
|
*
|
||||||
@ -89,7 +97,7 @@ function jabber_connect($resource=null)
|
|||||||
{
|
{
|
||||||
static $conn = null;
|
static $conn = null;
|
||||||
if (!$conn) {
|
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', 'host') :
|
||||||
common_config('xmpp', 'server'),
|
common_config('xmpp', 'server'),
|
||||||
common_config('xmpp', 'port'),
|
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();
|
common_init_locale();
|
||||||
mail_to_user($user, $subject, $body);
|
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();
|
parent::showStylesheets();
|
||||||
|
|
||||||
$design = $this->getDesign();
|
$user = common_current_user();
|
||||||
|
|
||||||
if (!empty($design)) {
|
if (empty($user) || $user->viewdesigns) {
|
||||||
$design->showCSS($this);
|
$design = $this->getDesign();
|
||||||
}
|
|
||||||
|
if (!empty($design)) {
|
||||||
|
$design->showCSS($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,7 +59,7 @@ function ping_broadcast_notice($notice) {
|
|||||||
|
|
||||||
$response = xmlrpc_decode($file);
|
$response = xmlrpc_decode($file);
|
||||||
|
|
||||||
if (xmlrpc_is_fault($response)) {
|
if (is_array($response) && xmlrpc_is_fault($response)) {
|
||||||
common_log(LOG_WARNING,
|
common_log(LOG_WARNING,
|
||||||
"XML-RPC error for ping ($notify_url, $notice->id) ".
|
"XML-RPC error for ping ($notify_url, $notice->id) ".
|
||||||
"$response[faultString] ($response[faultCode])");
|
"$response[faultString] ($response[faultCode])");
|
||||||
|
@ -68,7 +68,7 @@ class PopularNoticeSection extends NoticeSection
|
|||||||
}
|
}
|
||||||
$qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' .
|
$qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' .
|
||||||
'notice.rendered,notice.url,notice.created,notice.modified,' .
|
'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';
|
'ORDER BY weight DESC';
|
||||||
|
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
|
@ -163,18 +163,9 @@ class ProfileAction extends OwnerDesignAction
|
|||||||
|
|
||||||
function showStatistics()
|
function showStatistics()
|
||||||
{
|
{
|
||||||
// XXX: WORM cache this
|
$subs_count = $this->profile->subscriptionCount();
|
||||||
$subs = new Subscription();
|
$subbed_count = $this->profile->subscriberCount();
|
||||||
$subs->subscriber = $this->profile->id;
|
$notice_count = $this->profile->noticeCount();
|
||||||
$subs_count = (int) $subs->count() - 1;
|
|
||||||
|
|
||||||
$subbed = new Subscription();
|
|
||||||
$subbed->subscribed = $this->profile->id;
|
|
||||||
$subbed_count = (int) $subbed->count() - 1;
|
|
||||||
|
|
||||||
$notices = new Notice();
|
|
||||||
$notices->profile_id = $this->profile->id;
|
|
||||||
$notice_count = (int) $notices->count();
|
|
||||||
|
|
||||||
$this->elementStart('div', array('id' => 'entity_statistics',
|
$this->elementStart('div', array('id' => 'entity_statistics',
|
||||||
'class' => 'section'));
|
'class' => 'section'));
|
||||||
@ -199,7 +190,7 @@ class ProfileAction extends OwnerDesignAction
|
|||||||
array('nickname' => $this->profile->nickname))),
|
array('nickname' => $this->profile->nickname))),
|
||||||
_('Subscriptions'));
|
_('Subscriptions'));
|
||||||
$this->elementEnd('dt');
|
$this->elementEnd('dt');
|
||||||
$this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0');
|
$this->element('dd', null, $subs_count);
|
||||||
$this->elementEnd('dl');
|
$this->elementEnd('dl');
|
||||||
|
|
||||||
$this->elementStart('dl', 'entity_subscribers');
|
$this->elementStart('dl', 'entity_subscribers');
|
||||||
@ -208,12 +199,12 @@ class ProfileAction extends OwnerDesignAction
|
|||||||
array('nickname' => $this->profile->nickname))),
|
array('nickname' => $this->profile->nickname))),
|
||||||
_('Subscribers'));
|
_('Subscribers'));
|
||||||
$this->elementEnd('dt');
|
$this->elementEnd('dt');
|
||||||
$this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0');
|
$this->element('dd', 'subscribers', $subbed_count);
|
||||||
$this->elementEnd('dl');
|
$this->elementEnd('dl');
|
||||||
|
|
||||||
$this->elementStart('dl', 'entity_notices');
|
$this->elementStart('dl', 'entity_notices');
|
||||||
$this->element('dt', null, _('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('dl');
|
||||||
|
|
||||||
$this->elementEnd('div');
|
$this->elementEnd('div');
|
||||||
|
@ -94,8 +94,8 @@ class ProfileSection extends Section
|
|||||||
$profile->fullname :
|
$profile->fullname :
|
||||||
$profile->nickname));
|
$profile->nickname));
|
||||||
$this->out->element('span', 'fn nickname', $profile->nickname);
|
$this->out->element('span', 'fn nickname', $profile->nickname);
|
||||||
$this->out->elementEnd('span');
|
|
||||||
$this->out->elementEnd('a');
|
$this->out->elementEnd('a');
|
||||||
|
$this->out->elementEnd('span');
|
||||||
$this->out->elementEnd('td');
|
$this->out->elementEnd('td');
|
||||||
if ($profile->value) {
|
if ($profile->value) {
|
||||||
$this->out->element('td', 'value', $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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
define('CLAIM_TIMEOUT', 1200);
|
|
||||||
|
|
||||||
if (!defined('LACONICA')) { exit(1); }
|
if (!defined('LACONICA')) { exit(1); }
|
||||||
|
|
||||||
require_once(INSTALLDIR.'/lib/daemon.php');
|
require_once(INSTALLDIR.'/lib/daemon.php');
|
||||||
require_once(INSTALLDIR.'/classes/Queue_item.php');
|
require_once(INSTALLDIR.'/classes/Queue_item.php');
|
||||||
require_once(INSTALLDIR.'/classes/Notice.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
|
class QueueHandler extends Daemon
|
||||||
{
|
{
|
||||||
var $_id = 'generic';
|
var $_id = 'generic';
|
||||||
@ -38,6 +40,11 @@ class QueueHandler extends Daemon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeout()
|
||||||
|
{
|
||||||
|
return 60;
|
||||||
|
}
|
||||||
|
|
||||||
function class_name()
|
function class_name()
|
||||||
{
|
{
|
||||||
return ucfirst($this->transport()) . 'Handler';
|
return ucfirst($this->transport()) . 'Handler';
|
||||||
@ -76,110 +83,21 @@ class QueueHandler extends Daemon
|
|||||||
return true;
|
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()
|
function run()
|
||||||
{
|
{
|
||||||
if (!$this->start()) {
|
if (!$this->start()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->log(LOG_INFO, 'checking for queued notices');
|
$this->log(LOG_INFO, 'checking for queued notices');
|
||||||
if (common_config('queue','subsystem') == 'stomp') {
|
|
||||||
$this->stomp_dispatch();
|
$queue = $this->transport();
|
||||||
}
|
$timeout = $this->timeout();
|
||||||
else {
|
|
||||||
$this->db_dispatch();
|
$qm = QueueManager::get();
|
||||||
}
|
|
||||||
|
$qm->service($queue, $this);
|
||||||
|
|
||||||
if (!$this->finish()) {
|
if (!$this->finish()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -188,24 +106,19 @@ class QueueHandler extends Daemon
|
|||||||
|
|
||||||
function idle($timeout=0)
|
function idle($timeout=0)
|
||||||
{
|
{
|
||||||
if ($timeout>0) {
|
if ($timeout > 0) {
|
||||||
sleep($timeout);
|
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)
|
function log($level, $msg)
|
||||||
{
|
{
|
||||||
common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$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',
|
$m->connect('api/statuses/:method',
|
||||||
array('action' => 'api',
|
array('action' => 'api',
|
||||||
'apiaction' => 'statuses'),
|
'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',
|
$m->connect('api/statuses/:method/:argument',
|
||||||
array('action' => 'api',
|
array('action' => 'api',
|
||||||
'apiaction' => 'statuses'),
|
'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
|
// users
|
||||||
|
|
||||||
@ -394,6 +394,15 @@ class Router
|
|||||||
array('action' => 'api',
|
array('action' => 'api',
|
||||||
'apiaction' => 'laconica'));
|
'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
|
// search
|
||||||
$m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
|
$m->connect('api/search.atom', array('action' => 'twitapisearchatom'));
|
||||||
$m->connect('api/search.json', array('action' => 'twitapisearchjson'));
|
$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));
|
$replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to));
|
||||||
$this->element('sioc:reply_of', array('rdf:resource' => $replyurl));
|
$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->elementEnd('item');
|
||||||
$this->creators[$creator_uri] = $profile;
|
$this->creators[$creator_uri] = $profile;
|
||||||
}
|
}
|
||||||
@ -251,6 +258,8 @@ class Rss10Action extends Action
|
|||||||
'http://creativecommons.org/ns#',
|
'http://creativecommons.org/ns#',
|
||||||
'xmlns:content' =>
|
'xmlns:content' =>
|
||||||
'http://purl.org/rss/1.0/modules/content/',
|
'http://purl.org/rss/1.0/modules/content/',
|
||||||
|
'xmlns:enc' =>
|
||||||
|
'http://purl.oclc.org/net/rss_2.0/enc#',
|
||||||
'xmlns:foaf' =>
|
'xmlns:foaf' =>
|
||||||
'http://xmlns.com/foaf/0.1/',
|
'http://xmlns.com/foaf/0.1/',
|
||||||
'xmlns:sioc' =>
|
'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)
|
function subs_subscribe_to($user, $other)
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($user->isSubscribed($other)) {
|
if ($user->isSubscribed($other)) {
|
||||||
return _('Already subscribed!.');
|
return _('Already subscribed!.');
|
||||||
}
|
}
|
||||||
@ -60,12 +59,16 @@ function subs_subscribe_to($user, $other)
|
|||||||
|
|
||||||
subs_notify($other, $user);
|
subs_notify($other, $user);
|
||||||
|
|
||||||
$cache = common_memcache();
|
$cache = common_memcache();
|
||||||
|
|
||||||
if ($cache) {
|
if ($cache) {
|
||||||
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
|
$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->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) {
|
||||||
if (!$other->subscribeTo($user)) {
|
if (!$other->subscribeTo($user)) {
|
||||||
@ -117,7 +120,6 @@ function subs_unsubscribe_user($user, $other_nickname)
|
|||||||
|
|
||||||
function subs_unsubscribe_to($user, $other)
|
function subs_unsubscribe_to($user, $other)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!$user->isSubscribed($other))
|
if (!$user->isSubscribed($other))
|
||||||
return _('Not subscribed!.');
|
return _('Not subscribed!.');
|
||||||
|
|
||||||
@ -139,6 +141,11 @@ function subs_unsubscribe_to($user, $other)
|
|||||||
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
|
$cache->delete(common_cache_key('user:notices_with_friends:' . $user->id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$profile = $user->getProfile();
|
||||||
|
|
||||||
|
$profile->blowSubscriptionsCount();
|
||||||
|
$other->blowSubscribersCount();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,14 +360,11 @@ function is_twitter_bound($notice, $flink) {
|
|||||||
|
|
||||||
function broadcast_twitter($notice)
|
function broadcast_twitter($notice)
|
||||||
{
|
{
|
||||||
$success = true;
|
|
||||||
|
|
||||||
$flink = Foreign_link::getByUserID($notice->profile_id,
|
$flink = Foreign_link::getByUserID($notice->profile_id,
|
||||||
TWITTER_SERVICE);
|
TWITTER_SERVICE);
|
||||||
|
|
||||||
// XXX: Not sure WHERE to check whether a notice should go to
|
if (is_twitter_bound($notice, $flink)) {
|
||||||
// Twitter. Should we even put in the queue if it shouldn't? --Zach
|
|
||||||
if (!is_null($flink) && is_twitter_bound($notice, $flink)) {
|
|
||||||
|
|
||||||
$fuser = $flink->getForeignUser();
|
$fuser = $flink->getForeignUser();
|
||||||
$twitter_user = $fuser->nickname;
|
$twitter_user = $fuser->nickname;
|
||||||
@ -401,33 +398,99 @@ function broadcast_twitter($notice)
|
|||||||
curl_setopt_array($ch, $options);
|
curl_setopt_array($ch, $options);
|
||||||
$data = curl_exec($ch);
|
$data = curl_exec($ch);
|
||||||
$errmsg = curl_error($ch);
|
$errmsg = curl_error($ch);
|
||||||
|
$errno = curl_errno($ch);
|
||||||
|
|
||||||
if ($errmsg) {
|
if (!empty($errmsg)) {
|
||||||
common_debug("cURL error: $errmsg - " .
|
common_debug("cURL error ($errno): $errmsg - " .
|
||||||
"trying to send notice for $twitter_user.",
|
"trying to send notice for $twitter_user.",
|
||||||
__FILE__);
|
__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);
|
curl_close($ch);
|
||||||
|
|
||||||
if (!$data) {
|
if (empty($data)) {
|
||||||
common_debug("No data returned by Twitter's " .
|
common_debug("No data returned by Twitter's " .
|
||||||
"API trying to send update for $twitter_user",
|
"API trying to send update for $twitter_user",
|
||||||
__FILE__);
|
__FILE__);
|
||||||
$success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Twitter should return a status
|
// XXX: Not sure this represents a failure to send, but it
|
||||||
$status = json_decode($data);
|
// probably does
|
||||||
|
|
||||||
if (!$status->id) {
|
return false;
|
||||||
common_debug("Unexpected data returned by Twitter " .
|
|
||||||
" API trying to send update for $twitter_user",
|
} else {
|
||||||
__FILE__);
|
|
||||||
$success = false;
|
// 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['url'] = ($profile->homepage) ? $profile->homepage : null;
|
||||||
$twitter_user['protected'] = false; # not supported by Laconica yet
|
$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...
|
// To be supported soon...
|
||||||
$twitter_user['profile_background_color'] = '';
|
$twitter_user['profile_background_color'] = '';
|
||||||
@ -98,17 +98,11 @@ class TwitterapiAction extends Action
|
|||||||
$twitter_user['profile_sidebar_fill_color'] = '';
|
$twitter_user['profile_sidebar_fill_color'] = '';
|
||||||
$twitter_user['profile_sidebar_border_color'] = '';
|
$twitter_user['profile_sidebar_border_color'] = '';
|
||||||
|
|
||||||
$subbed = DB_DataObject::factory('subscription');
|
$twitter_user['friends_count'] = $profile->subscriptionCount();
|
||||||
$subbed->subscriber = $profile->id;
|
|
||||||
$subbed_count = (int) $subbed->count() - 1;
|
|
||||||
$twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
|
|
||||||
|
|
||||||
$twitter_user['created_at'] = $this->date_twitter($profile->created);
|
$twitter_user['created_at'] = $this->date_twitter($profile->created);
|
||||||
|
|
||||||
$faves = DB_DataObject::factory('fave');
|
$twitter_user['favourites_count'] = $profile->faveCount(); // British spelling!
|
||||||
$faves->user_id = $user->id;
|
|
||||||
$faves_count = (int) $faves->count();
|
|
||||||
$twitter_user['favourites_count'] = $faves_count; // British spelling!
|
|
||||||
|
|
||||||
// Need to pull up the user for some of this
|
// Need to pull up the user for some of this
|
||||||
$user = User::staticGet($profile->id);
|
$user = User::staticGet($profile->id);
|
||||||
@ -129,11 +123,7 @@ class TwitterapiAction extends Action
|
|||||||
$twitter_user['profile_background_image_url'] = '';
|
$twitter_user['profile_background_image_url'] = '';
|
||||||
$twitter_user['profile_background_tile'] = false;
|
$twitter_user['profile_background_tile'] = false;
|
||||||
|
|
||||||
$notices = DB_DataObject::factory('notice');
|
$twitter_user['statuses_count'] = $profile->noticeCount();
|
||||||
$notices->profile_id = $profile->id;
|
|
||||||
$notice_count = (int) $notices->count();
|
|
||||||
|
|
||||||
$twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
|
|
||||||
|
|
||||||
// Is the requesting user following this user?
|
// Is the requesting user following this user?
|
||||||
$twitter_user['following'] = false;
|
$twitter_user['following'] = false;
|
||||||
@ -207,7 +197,6 @@ class TwitterapiAction extends Action
|
|||||||
|
|
||||||
function twitter_rss_entry_array($notice)
|
function twitter_rss_entry_array($notice)
|
||||||
{
|
{
|
||||||
|
|
||||||
$profile = $notice->getProfile();
|
$profile = $notice->getProfile();
|
||||||
$entry = array();
|
$entry = array();
|
||||||
|
|
||||||
@ -224,6 +213,19 @@ class TwitterapiAction extends Action
|
|||||||
$entry['updated'] = $entry['published'];
|
$entry['updated'] = $entry['published'];
|
||||||
$entry['author'] = $profile->getBestName();
|
$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
|
# RSS Item specific
|
||||||
$entry['description'] = $entry['content'];
|
$entry['description'] = $entry['content'];
|
||||||
$entry['pubDate'] = common_date_rfc2822($notice->created);
|
$entry['pubDate'] = common_date_rfc2822($notice->created);
|
||||||
@ -378,6 +380,13 @@ class TwitterapiAction extends Action
|
|||||||
$this->element('pubDate', null, $entry['pubDate']);
|
$this->element('pubDate', null, $entry['pubDate']);
|
||||||
$this->element('guid', null, $entry['guid']);
|
$this->element('guid', null, $entry['guid']);
|
||||||
$this->element('link', null, $entry['link']);
|
$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');
|
$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)
|
function get_profile($id)
|
||||||
{
|
{
|
||||||
if (is_numeric($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)
|
function common_broadcast_notice($notice, $remote=false)
|
||||||
{
|
{
|
||||||
if (common_config('queue', 'enabled')) {
|
return common_enqueue_notice($notice);
|
||||||
// Do it later!
|
|
||||||
return common_enqueue_notice($notice);
|
|
||||||
} else {
|
|
||||||
return common_real_broadcast($notice, $remote);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stick the notice on the queue
|
// Stick the notice on the queue
|
||||||
|
|
||||||
function common_enqueue_notice($notice)
|
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';
|
$transports[] = 'jabber';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (common_config('queue','subsystem') == 'stomp') {
|
if ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
|
||||||
common_enqueue_notice_stomp($notice, $transports);
|
$notice->is_local == NOTICE_LOCAL_NONPUBLIC) {
|
||||||
}
|
$transports = array_merge($transports, $localTransports);
|
||||||
else {
|
if ($xmpp) {
|
||||||
common_enqueue_notice_db($notice, $transports);
|
$transports[] = 'public';
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$tag->free();
|
|
||||||
|
|
||||||
$con->send('/topic/laconica.'.$notice->profile_id,
|
$qm = QueueManager::get();
|
||||||
$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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function common_enqueue_notice_db($notice, $transports)
|
foreach ($transports as $transport)
|
||||||
{
|
{
|
||||||
// in any other case, 'internal'
|
$qm->enqueue($notice, $transport);
|
||||||
foreach ($transports as $transport) {
|
|
||||||
common_enqueue_notice_transport($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;
|
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)
|
function common_broadcast_profile($profile)
|
||||||
{
|
{
|
||||||
// XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ
|
// 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)) {
|
if (is_null($object)) {
|
||||||
return "null";
|
return "null";
|
||||||
}
|
}
|
||||||
|
if (!($object instanceof DB_DataObject)) {
|
||||||
|
return "(unknown)";
|
||||||
|
}
|
||||||
$arr = $object->toArray();
|
$arr = $object->toArray();
|
||||||
$fields = array();
|
$fields = array();
|
||||||
foreach ($arr as $k => $v) {
|
foreach ($arr as $k => $v) {
|
||||||
|
@ -21,6 +21,8 @@ if (!defined('LACONICA')) { exit(1); }
|
|||||||
|
|
||||||
require_once(INSTALLDIR.'/lib/queuehandler.php');
|
require_once(INSTALLDIR.'/lib/queuehandler.php');
|
||||||
|
|
||||||
|
define('PING_INTERVAL', 120);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common superclass for all XMPP-using queue handlers. They all need to
|
* Common superclass for all XMPP-using queue handlers. They all need to
|
||||||
* service their message queues on idle, and forward any incoming messages
|
* 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
|
class XmppQueueHandler extends QueueHandler
|
||||||
{
|
{
|
||||||
|
var $pingid = 0;
|
||||||
|
var $lastping = null;
|
||||||
|
|
||||||
function start()
|
function start()
|
||||||
{
|
{
|
||||||
# Low priority; we don't want to receive messages
|
# Low priority; we don't want to receive messages
|
||||||
@ -44,6 +49,11 @@ class XmppQueueHandler extends QueueHandler
|
|||||||
return !is_null($this->conn);
|
return !is_null($this->conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timeout()
|
||||||
|
{
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
function handle_reconnect(&$pl)
|
function handle_reconnect(&$pl)
|
||||||
{
|
{
|
||||||
$this->conn->processUntil('session_start');
|
$this->conn->processUntil('session_start');
|
||||||
@ -55,7 +65,13 @@ class XmppQueueHandler extends QueueHandler
|
|||||||
# Process the queue for as long as needed
|
# Process the queue for as long as needed
|
||||||
try {
|
try {
|
||||||
if ($this->conn) {
|
if ($this->conn) {
|
||||||
|
$this->log(LOG_DEBUG, "Servicing the XMPP queue.");
|
||||||
$this->conn->processTime($timeout);
|
$this->conn->processTime($timeout);
|
||||||
|
$now = time();
|
||||||
|
if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) {
|
||||||
|
$this->sendPing();
|
||||||
|
$this->lastping = $now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (XMPPHP_Exception $e) {
|
} catch (XMPPHP_Exception $e) {
|
||||||
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
|
$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)
|
function forward_message(&$pl)
|
||||||
{
|
{
|
||||||
if ($pl['type'] != 'chat') {
|
if ($pl['type'] != 'chat') {
|
||||||
@ -91,7 +123,12 @@ class XmppQueueHandler extends QueueHandler
|
|||||||
if (common_config('xmpp', 'listener')) {
|
if (common_config('xmpp', 'listener')) {
|
||||||
return common_config('xmpp', 'listener');
|
return common_config('xmpp', 'listener');
|
||||||
} else {
|
} 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'),
|
$action->menuItem(common_local_url('register'),
|
||||||
_('Register'), _('Create an account'), false, 'nav_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'),
|
$action->menuItem(common_local_url('login'),
|
||||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||||
}
|
}
|
||||||
|
@ -42,11 +42,11 @@ class MailerDaemon
|
|||||||
|
|
||||||
function handle_message($fname='php://stdin')
|
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) {
|
if (!$from || !$to || !$msg) {
|
||||||
$this->error(null, _('Could not parse message.'));
|
$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);
|
$user = $this->user_from($from);
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
$this->error($from, _('Not a registered user.'));
|
$this->error($from, _('Not a registered user.'));
|
||||||
@ -65,7 +65,47 @@ class MailerDaemon
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$msg = $this->cleanup_msg($msg);
|
$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)) {
|
if (is_string($err)) {
|
||||||
$this->error($from, $err);
|
$this->error($from, $err);
|
||||||
return false;
|
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)
|
function error($from, $msg)
|
||||||
{
|
{
|
||||||
file_put_contents("php://stderr", $msg . "\n");
|
file_put_contents("php://stderr", $msg . "\n");
|
||||||
@ -133,19 +256,30 @@ class MailerDaemon
|
|||||||
common_log($level, 'MailDaemon: '.$msg);
|
common_log($level, 'MailDaemon: '.$msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_notice($user, $msg)
|
function add_notice($user, $msg, $fileRecords)
|
||||||
{
|
{
|
||||||
$notice = Notice::saveNew($user->id, $msg, 'mail');
|
$notice = Notice::saveNew($user->id, $msg, 'mail');
|
||||||
if (is_string($notice)) {
|
if (is_string($notice)) {
|
||||||
$this->log(LOG_ERR, $notice);
|
$this->log(LOG_ERR, $notice);
|
||||||
return $notice;
|
return $notice;
|
||||||
}
|
}
|
||||||
|
foreach($fileRecords as $fileRecord){
|
||||||
|
$this->attachFile($notice, $fileRecord);
|
||||||
|
}
|
||||||
common_broadcast_notice($notice);
|
common_broadcast_notice($notice);
|
||||||
$this->log(LOG_INFO,
|
$this->log(LOG_INFO,
|
||||||
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
|
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
|
||||||
return true;
|
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)
|
function parse_message($fname)
|
||||||
{
|
{
|
||||||
$contents = file_get_contents($fname);
|
$contents = file_get_contents($fname);
|
||||||
@ -163,12 +297,19 @@ class MailerDaemon
|
|||||||
|
|
||||||
$type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
|
$type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary;
|
||||||
|
|
||||||
|
$attachments = array();
|
||||||
|
|
||||||
if ($parsed->ctype_primary == 'multipart') {
|
if ($parsed->ctype_primary == 'multipart') {
|
||||||
foreach ($parsed->parts as $part) {
|
foreach ($parsed->parts as $part) {
|
||||||
if ($part->ctype_primary == 'text' &&
|
if ($part->ctype_primary == 'text' &&
|
||||||
$part->ctype_secondary == 'plain') {
|
$part->ctype_secondary == 'plain') {
|
||||||
$msg = $part->body;
|
$msg = $part->body;
|
||||||
break;
|
}else{
|
||||||
|
if ($part->body) {
|
||||||
|
$attachment = tmpfile();
|
||||||
|
fwrite($attachment, $part->body);
|
||||||
|
$attachments[] = $attachment;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if ($type == 'text/plain') {
|
} else if ($type == 'text/plain') {
|
||||||
@ -176,8 +317,7 @@ class MailerDaemon
|
|||||||
} else {
|
} else {
|
||||||
$this->unsupported_type($type);
|
$this->unsupported_type($type);
|
||||||
}
|
}
|
||||||
|
return array($from, $to, $msg, $attachments);
|
||||||
return array($from, $to, $msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function unsupported_type($type)
|
function unsupported_type($type)
|
||||||
|
@ -1,66 +1,97 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
<?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);
|
set_time_limit(60);
|
||||||
chdir(dirname(__FILE__) . '/..');
|
|
||||||
|
|
||||||
/* Languages to pull */
|
/* Languages to pull */
|
||||||
$languages = array(
|
$languages = get_all_languages();
|
||||||
'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'
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Update the languages */
|
/* Update the languages */
|
||||||
foreach ($languages as $code => $file) {
|
|
||||||
|
|
||||||
$lcdir='locale/'.$code;
|
foreach ($languages as $language) {
|
||||||
$msgdir=$lcdir.'/LC_MESSAGES';
|
|
||||||
$pofile=$msgdir.'/laconica.po';
|
|
||||||
$mofile=$msgdir.'/laconica.mo';
|
|
||||||
|
|
||||||
/* Check for an existing */
|
$code = $language['lang'];
|
||||||
if (!is_dir($msgdir)) {
|
$file_url = 'http://laconi.ca/pootle/' . $code .
|
||||||
mkdir($lcdir);
|
'/laconica/LC_MESSAGES/laconica.po';
|
||||||
mkdir($msgdir);
|
$lcdir = INSTALLDIR . '/locale/' . $code;
|
||||||
$existingSHA1 = '';
|
$msgdir = "$lcdir/LC_MESSAGES";
|
||||||
} else {
|
$pofile = "$msgdir/laconica.po";
|
||||||
$existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : '';
|
$mofile = "$msgdir/laconica.mo";
|
||||||
}
|
|
||||||
|
|
||||||
/* Get the remote one */
|
/* Check for an existing */
|
||||||
$newFile = file_get_contents($file);
|
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
|
/* Get the remote one */
|
||||||
// if the .mo file is not present.
|
$new_file = curl_get_file($file_url);
|
||||||
if(sha1($newFile)!=$existingSHA1 || !file_exists($mofile)) {
|
|
||||||
echo "Updating ".$code."\n";
|
if ($new_file === FALSE) {
|
||||||
file_put_contents($pofile, $newFile);
|
echo "Couldn't retrieve .po file for $code: $file_url\n";
|
||||||
$prevdir = getcwd();
|
continue;
|
||||||
chdir($msgdir);
|
}
|
||||||
system('msgmerge -U laconica.po ../../laconica.pot');
|
|
||||||
system('msgfmt -f -o laconica.mo laconica.po');
|
// Update if the local .po file is different to the one downloaded, or
|
||||||
chdir($prevdir);
|
// if the .mo file is not present.
|
||||||
} else {
|
if (sha1($new_file) != $existingSHA1 || !file_exists($mofile)) {
|
||||||
echo "Unchanged - ".$code."\n";
|
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";
|
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->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()
|
function connect()
|
||||||
@ -106,10 +108,25 @@ class XMPPDaemon extends Daemon
|
|||||||
|
|
||||||
$this->log(LOG_DEBUG, "Beginning processing loop.");
|
$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)
|
function handle_reconnect(&$pl)
|
||||||
{
|
{
|
||||||
$this->log(LOG_DEBUG, "Got reconnection callback.");
|
$this->log(LOG_DEBUG, "Got reconnection callback.");
|
||||||
|
@ -275,7 +275,7 @@ margin-bottom:18px;
|
|||||||
|
|
||||||
#anon_notice {
|
#anon_notice {
|
||||||
float:left;
|
float:left;
|
||||||
width:43.2%;
|
width:42.4%;
|
||||||
padding:1.1%;
|
padding:1.1%;
|
||||||
border-radius:7px;
|
border-radius:7px;
|
||||||
-moz-border-radius:7px;
|
-moz-border-radius:7px;
|
||||||
@ -396,7 +396,7 @@ margin-bottom:1em;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
width:64.009%;
|
width:63.311%;
|
||||||
min-height:259px;
|
min-height:259px;
|
||||||
padding:1.795%;
|
padding:1.795%;
|
||||||
float:left;
|
float:left;
|
||||||
@ -422,7 +422,7 @@ float:left;
|
|||||||
width:27.917%;
|
width:27.917%;
|
||||||
min-height:259px;
|
min-height:259px;
|
||||||
float:left;
|
float:left;
|
||||||
margin-left:0.5%;
|
margin-left:0.699%;
|
||||||
padding:1.795%;
|
padding:1.795%;
|
||||||
border-radius:7px;
|
border-radius:7px;
|
||||||
-moz-border-radius:7px;
|
-moz-border-radius:7px;
|
||||||
@ -432,7 +432,7 @@ border-style:solid;
|
|||||||
}
|
}
|
||||||
|
|
||||||
#form_notice {
|
#form_notice {
|
||||||
width:45.664%;
|
width:45%;
|
||||||
float:left;
|
float:left;
|
||||||
position:relative;
|
position:relative;
|
||||||
line-height:1;
|
line-height:1;
|
||||||
@ -471,12 +471,12 @@ cursor:pointer;
|
|||||||
}
|
}
|
||||||
#form_notice label[for=notice_data-attach] {
|
#form_notice label[for=notice_data-attach] {
|
||||||
text-indent:-9999px;
|
text-indent:-9999px;
|
||||||
left:394px;
|
left:86%;
|
||||||
width:16px;
|
width:16px;
|
||||||
height:16px;
|
height:16px;
|
||||||
}
|
}
|
||||||
#form_notice #notice_data-attach {
|
#form_notice #notice_data-attach {
|
||||||
left:183px;
|
left:40.6%;
|
||||||
padding:0;
|
padding:0;
|
||||||
height:16px;
|
height:16px;
|
||||||
}
|
}
|
||||||
@ -783,8 +783,8 @@ list-style-type:none;
|
|||||||
}
|
}
|
||||||
.notices .notices {
|
.notices .notices {
|
||||||
margin-top:7px;
|
margin-top:7px;
|
||||||
margin-left:5%;
|
margin-left:2%;
|
||||||
width:95%;
|
width:98%;
|
||||||
float:left;
|
float:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1020,20 +1020,21 @@ font-weight:bold;
|
|||||||
padding:0;
|
padding:0;
|
||||||
}
|
}
|
||||||
#jOverlayContent h1 {
|
#jOverlayContent h1 {
|
||||||
max-width:475px;
|
max-width:425px;
|
||||||
}
|
}
|
||||||
#jOverlayContent #content {
|
#jOverlayContent #content {
|
||||||
border-radius:7px;
|
border-radius:7px;
|
||||||
-moz-border-radius:7px;
|
-moz-border-radius:7px;
|
||||||
-webkit-border-radius:7px;
|
-webkit-border-radius:7px;
|
||||||
}
|
}
|
||||||
#jOverlayContent #content img {
|
|
||||||
max-width:480px;
|
|
||||||
}
|
|
||||||
#jOverlayLoading {
|
#jOverlayLoading {
|
||||||
top:22.5%;
|
top:22.5%;
|
||||||
left:40%;
|
left:40%;
|
||||||
}
|
}
|
||||||
|
#attachment_view img {
|
||||||
|
max-width:480px;
|
||||||
|
max-height:480px;
|
||||||
|
}
|
||||||
#attachment_view #oembed_info {
|
#attachment_view #oembed_info {
|
||||||
margin-top:11px;
|
margin-top:11px;
|
||||||
}
|
}
|
||||||
@ -1278,6 +1279,7 @@ margin-bottom:0;
|
|||||||
|
|
||||||
#form_settings_design #settings_design_background-image img {
|
#form_settings_design #settings_design_background-image img {
|
||||||
max-width:480px;
|
max-width:480px;
|
||||||
|
max-height:480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#form_settings_design #settings_design_color .form_data,
|
#form_settings_design #settings_design_color .form_data,
|
||||||
|
@ -9,7 +9,7 @@ width:78%;
|
|||||||
#form_notice .form_note + label {
|
#form_notice .form_note + label {
|
||||||
position:absolute;
|
position:absolute;
|
||||||
top:25px;
|
top:25px;
|
||||||
left:380px;
|
left:83%;
|
||||||
text-indent:-9999px;
|
text-indent:-9999px;
|
||||||
height:16px;
|
height:16px;
|
||||||
width:16px;
|
width:16px;
|
||||||
@ -25,10 +25,6 @@ width:78.5%;
|
|||||||
#form_notice #notice_data-attach_selected button {
|
#form_notice #notice_data-attach_selected button {
|
||||||
padding:0 4px;
|
padding:0 4px;
|
||||||
}
|
}
|
||||||
#anon_notice {
|
|
||||||
max-width:39%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-options input.submit {
|
.notice-options input.submit {
|
||||||
font-size:0;
|
font-size:0;
|
||||||
margin-top:3px;
|
margin-top:3px;
|
||||||
@ -49,6 +45,6 @@ z-index:1;
|
|||||||
.notice:hover {
|
.notice:hover {
|
||||||
z-index:9999;
|
z-index:9999;
|
||||||
}
|
}
|
||||||
.notice .thumbnail img {
|
.notice .thumbnail img {
|
||||||
z-index:9999;
|
z-index:9999;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user