Merge branch '0.8.x' into userdesign

Conflicts:
	actions/designsettings.php
This commit is contained in:
Evan Prodromou 2009-06-09 21:51:24 -07:00
commit 4df1ea49ec
66 changed files with 2641 additions and 799 deletions

6
.gitignore vendored
View File

@ -1,5 +1,6 @@
avatar/* avatar/*
files/* files/*
file/*
_darcs/* _darcs/*
logs/* logs/*
config.php config.php
@ -16,3 +17,8 @@ dataobject.ini
.buildpath .buildpath
.project .project
.settings .settings
TODO.rym
config-*.php
good-config.php
lac08.log
php.log

125
README
View File

@ -2,8 +2,8 @@
README README
------ ------
Laconica 0.7.3 ("You Are The Everything") Laconica 0.7.4 ("Can't Get There From Here")
7 April 2009 29 May 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,29 @@ for additional terms.
New this version New this version
================ ================
This is a minor bug-fix and feature release since version 0.7.2.1, This is a minor bug-fix and feature release since version 0.7.3,
released Mar 11 2009. Notable changes this version: released Apr 4 2009. Notable changes this version:
- A plugin to allow a templating language for customization - Improved handling of UTF-8 characters. The new code is *not* backwards
- A plugin for Piwik Analytics engine compatible by default; see "Upgrading" below for instructions on
- A bookmarklet for posting a notice about a Web page you're reading converting existing databases to the correct character set.
- A welcome notice ('welcomebot') and default subscription for new users - Unroll joins for large queries. This greatly enhanced database
- Support for SSL for some or all pages on the site performance -- up to 50x for some queries -- at the expense of making
- Better handling of empty notice lists on many pages an extra DB hit for some queries.
- Major improvements to the Twitter friend-sync offline processing - Added an optional plugin to use WikiHashtags
- subscribers, subscriptions, groups are listed on the Personal page. (http://hashtags.wikia.com/) for the sidebar on hashtag pages.
- "Invite" link restored to main menu - Optimized Twitter friend synchronization.
- Better memory handling in FOAF output - Better error handling for Ajax posting of notices, including
- Fix for SUP support (FriendFeed) HTTP errors and timeouts.
- Correct and intelligent redirect HTTP status codes - Experimental Comet plugin -- supports the cometd and the Bayeux
- Fix DB collations for search and sort protocol. Using this plugin, you can show "real time" updates on the
- Better H1s and Titles using user full names public and tag pages. However, server configuration is complex.
- Fixes to make the linkback plugin operational - If queues are enabled, update inboxes and memcached off-line. Speeds
- Better indication that a notice is being published by Ajax (spinner) up posting considerably.
- Better and unified Atom output - Correctly shorten links posted through XMPP.
- Hiding "register" and "join now" messages when site is closed - <link> elements for pagination, supported by some browsers like Opera.
- ping, twitter and facebook queuehandlers working better - Corrected date format in search API.
- Updated RPM spec - Made the public XRDS file work correctly.
Prerequisites Prerequisites
============= =============
@ -197,9 +197,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.3.tar.gz tar zxf laconica-0.7.4.tar.gz
...which will make a laconica-0.7.3 subdirectory in your current ...which will make a laconica-0.7.4 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.)
@ -207,7 +207,7 @@ especially if you've previously installed PHP/MySQL packages.
2. Move the tarball to a directory of your choosing in your Web root 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.3 /var/www/mublog mv laconica-0.7.4 /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
@ -694,10 +694,17 @@ to users on a remote site. (Or not... it's not well tested.) The
Upgrading Upgrading
========= =========
IMPORTANT NOTE: Laconica 0.7.4 introduced a fix for some
incorrectly-stored international characters ("UTF-8"). For new
installations, it will now store non-ASCII characters correctly.
However, older installations will have the incorrect storage, and will
consequently show up "wrong" in browsers. See below for how to deal
with this situation.
If you've been using Laconica 0.6, 0.5 or lower, or if you've been If you've been using Laconica 0.6, 0.5 or lower, or if you've been
tracking the "git" version of the software, you will probably want tracking the "git" version of the software, you will probably want
to upgrade and keep your existing data. There is no automated upgrade to upgrade and keep your existing data. There is no automated upgrade
procedure in Laconica 0.7.3. Try these step-by-step instructions; read procedure in Laconica 0.7.4. Try these step-by-step instructions; read
to the end first before trying them. 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
@ -783,6 +790,29 @@ 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'.
UTF-8 Database
--------------
Laconica 0.7.4 introduced a fix for some incorrectly-stored
international characters ("UTF-8"). This fix is not
backwards-compatible; installations from before 0.7.4 will show
non-ASCII characters of old notices incorrectly. This section explains
what to do.
0. You can disable the new behaviour by setting the 'db''utf8' config
option to "false". You should only do this until you're ready to
convert your DB to the new format.
1. When you're ready to convert, you can run the fixup_utf8.php script
in the scripts/ subdirectory. If you've had the "new behaviour"
enabled (probably a good idea), you can give the ID of the first
"new" notice as a parameter, and only notices before that one will
be converted. Notices are converted in reverse chronological order,
so the most recent (and visible) ones will be converted first. The
script should work whether or not you have the 'db''utf8' config
option enabled.
2. When you're ready, set $config['db']['utf8'] to true, so that
new notices will be stored correctly.
Configuration options Configuration options
===================== =====================
@ -910,6 +940,10 @@ mirror: you can set this to an array of DSNs, like the above
and adding the slaves to this array. Note that if you want some and adding the slaves to this array. Note that if you want some
requests to go to the 'database' (master) server, you'll need requests to go to the 'database' (master) server, you'll need
to include it in this array, too. to include it in this array, too.
utf8: whether to talk to the database in UTF-8 mode. This is the default
with new installations, but older sites may want to turn it off
until they get their databases fixed up. See "UTF-8 database"
above for details.
syslog syslog
------ ------
@ -1162,6 +1196,37 @@ reporturl: URL to post statistics to. Defaults to Laconica developers'
set 'run' to 'never' than to set this value to something set 'run' to 'never' than to set this value to something
nonsensical. nonsensical.
attachments
-----------
The software lets users upload files with their notices. You can configure
the types of accepted files by mime types and a trio of quota options:
per file, per user (total), per user per month.
We suggest the use of the pecl file_info extension to handle mime type
detection.
supported: an array of mime types you accept to store and distribute,
like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
setup your server to properly reckognize the types you want to
support.
For quotas, be sure you've set the upload_max_filesize and post_max_size
in php.ini to be large enough to handle your upload. In httpd.conf
(if you're using apache), check that the LimitRequestBody directive isn't
set too low (it's optional, so it may not be there at all).
file_quota: maximum size for a single file upload in bytes. A user can send
any amount of notices with attachments as long as each attachment
is smaller than file_quota.
user_quota: total size in bytes a user can store on this server. Each user
can store any number of files as long as their total size does
not exceed the user_quota.
monthly_quota: total size permitted in the current month. This is the total
size in bytes that a user can upload each month.
Troubleshooting Troubleshooting
=============== ===============
@ -1174,7 +1239,7 @@ repository (see below), and you get a compilation error ("unexpected
T_STRING") in the browser, check to see that you don't have any T_STRING") in the browser, check to see that you don't have any
conflicts in your code. conflicts in your code.
If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes" If you upgraded to Laconica 0.7.4 without reading the "Notice inboxes"
section above, and all your users' 'Personal' tabs are empty, read the section above, and all your users' 'Personal' tabs are empty, read the
"Notice inboxes" section above. "Notice inboxes" section above.
@ -1265,6 +1330,8 @@ if anyone's been overlooked in error.
* Ken Sedgwick * Ken Sedgwick
* Brian Hendrickson * Brian Hendrickson
* Tobias Diekershoff * Tobias Diekershoff
* Dan Moore
* Fil
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,

View File

@ -67,6 +67,7 @@ class ApiAction extends Action
$this->process_command(); $this->process_command();
} else { } else {
# basic authentication failed # basic authentication failed
common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname.");
$this->show_basic_auth_error(); $this->show_basic_auth_error();
} }
} }
@ -143,8 +144,8 @@ class ApiAction extends Action
} }
if (in_array($fullname, $bareauth)) { if (in_array($fullname, $bareauth)) {
# bareauth: only needs auth if without an argument # bareauth: only needs auth if without an argument or query param specifying user
if ($this->api_arg) { if ($this->api_arg || $this->arg('id') || is_numeric($this->arg('user_id')) || $this->arg('screen_name')) {
return false; return false;
} else { } else {
return true; return true;

View File

@ -179,14 +179,14 @@ class ConversationTree extends NoticeList
$this->out->elementStart('div', array('id' =>'notices_primary')); $this->out->elementStart('div', array('id' =>'notices_primary'));
$this->out->element('h2', null, _('Notices')); $this->out->element('h2', null, _('Notices'));
$this->out->elementStart('ul', array('class' => 'notices')); $this->out->elementStart('ol', array('class' => 'notices xoxo'));
if (array_key_exists('root', $this->tree)) { if (array_key_exists('root', $this->tree)) {
$rootid = $this->tree['root'][0]; $rootid = $this->tree['root'][0];
$this->showNoticePlus($rootid); $this->showNoticePlus($rootid);
} }
$this->out->elementEnd('ul'); $this->out->elementEnd('ol');
$this->out->elementEnd('div'); $this->out->elementEnd('div');
return $cnt; return $cnt;
@ -215,13 +215,13 @@ class ConversationTree extends NoticeList
if (array_key_exists($id, $this->tree)) { if (array_key_exists($id, $this->tree)) {
$children = $this->tree[$id]; $children = $this->tree[$id];
$this->out->elementStart('ul', array('class' => 'notices')); $this->out->elementStart('ol', array('class' => 'notices'));
foreach ($children as $child) { foreach ($children as $child) {
$this->showNoticePlus($child); $this->showNoticePlus($child);
} }
$this->out->elementEnd('ul'); $this->out->elementEnd('ol');
} }
$this->out->elementEnd('li'); $this->out->elementEnd('li');

View File

@ -68,7 +68,7 @@ class DesignsettingsAction extends AccountSettingsAction
function showContent() function showContent()
{ {
$user = common_current_user(); $user = common_current_user();
$this->elementStart('form', array('method' => 'POST', $this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_design', 'id' => 'form_settings_design',
'class' => 'form_settings', 'class' => 'form_settings',
'action' => 'action' =>
@ -80,7 +80,7 @@ class DesignsettingsAction extends AccountSettingsAction
$this->element('legend', null, _('Change background image')); $this->element('legend', null, _('Change background image'));
$this->elementStart('ul', 'form_data'); $this->elementStart('ul', 'form_data');
$this->elementStart('li'); $this->elementStart('li');
$this->element('label', array('for' => 'design_ background-image_file'), $this->element('label', array('for' => 'design_background-image_file'),
_('Upload file')); _('Upload file'));
$this->element('input', array('name' => 'design_background-image_file', $this->element('input', array('name' => 'design_background-image_file',
'type' => 'file', 'type' => 'file',

View File

@ -115,7 +115,7 @@ class FacebookhomeAction extends FacebookAction
$flink->foreign_id = $this->fbuid; $flink->foreign_id = $this->fbuid;
$flink->service = FACEBOOK_SERVICE; $flink->service = FACEBOOK_SERVICE;
$flink->created = common_sql_now(); $flink->created = common_sql_now();
$flink->set_flags(true, false, false); $flink->set_flags(true, false, false, false);
$flink_id = $flink->insert(); $flink_id = $flink->insert();
@ -138,9 +138,6 @@ class FacebookhomeAction extends FacebookAction
function setDefaults() function setDefaults()
{ {
// A default prefix string for notices
$this->facebook->api_client->data_setUserPreference(
FACEBOOK_NOTICE_PREFIX, 'dented: ');
$this->facebook->api_client->data_setUserPreference( $this->facebook->api_client->data_setUserPreference(
FACEBOOK_PROMPTED_UPDATE_PREF, 'false'); FACEBOOK_PROMPTED_UPDATE_PREF, 'false');
} }

View File

@ -17,7 +17,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
if (!defined('LACONICA')) { exit(1); } if (!defined('LACONICA')) {
exit(1);
}
require_once(INSTALLDIR.'/lib/facebookaction.php'); require_once(INSTALLDIR.'/lib/facebookaction.php');
@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction
function showFormContent() function showFormContent()
{ {
// Get a list of users who are already using the app for exclusion
$exclude_ids = $this->facebook->api_client->friends_getAppUsers();
$exclude_ids_csv = null;
// fbml needs these as a csv string, not an array
if ($exclude_ids) {
$exclude_ids_csv = implode(',', $exclude_ids);
}
$content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) .
htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>'); htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>');
@ -112,17 +104,23 @@ class FacebookinviteAction extends FacebookAction
$multi_params = array('showborder' => 'false'); $multi_params = array('showborder' => 'false');
$multi_params['actiontext'] = $actiontext; $multi_params['actiontext'] = $actiontext;
$multi_params['bypass'] = 'cancel';
if ($exclude_ids_csv) { // Get a list of users who are already using the app for exclusion
$exclude_ids = $this->facebook->api_client->friends_getAppUsers();
$exclude_ids_csv = null;
// fbml needs these as a csv string, not an array
if ($exclude_ids) {
$exclude_ids_csv = implode(',', $exclude_ids);
$multi_params['exclude_ids'] = $exclude_ids_csv; $multi_params['exclude_ids'] = $exclude_ids_csv;
} }
$multi_params['bypass'] = 'cancel';
$this->element('fb:multi-friend-selector', $multi_params); $this->element('fb:multi-friend-selector', $multi_params);
$this->elementEnd('fb:request-form'); $this->elementEnd('fb:request-form');
if ($exclude_ids) {
$this->element('h2', null, sprintf(_('Friends already using %s:'), $this->element('h2', null, sprintf(_('Friends already using %s:'),
common_config('site', 'name'))); common_config('site', 'name')));
$this->elementStart('ul', array('id' => 'facebook-friends')); $this->elementStart('ul', array('id' => 'facebook-friends'));
@ -137,6 +135,7 @@ class FacebookinviteAction extends FacebookAction
$this->elementEnd("ul"); $this->elementEnd("ul");
} }
}
function title() function title()
{ {

View File

@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction
$prefix = $this->trimmed('prefix'); $prefix = $this->trimmed('prefix');
$original = clone($this->flink); $original = clone($this->flink);
$this->flink->set_flags($noticesync, $replysync, false); $this->flink->set_flags($noticesync, $replysync, false, false);
$result = $this->flink->update($original); $result = $this->flink->update($original);
$this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX,

40
actions/file.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, 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/>.
*/
if (!defined('LACONICA')) { exit(1); }
require_once(INSTALLDIR.'/actions/shownotice.php');
class FileAction extends ShowNoticeAction
{
function showPage() {
$source_url = common_local_url('file', array('notice' => $this->notice->id));
$query = "select file_redirection.url as url from file join file_redirection on file.id = file_redirection.file_id where file.url = '$source_url'";
$file = new File_redirection;
$file->query($query);
$file->fetch();
if (empty($file->url)) {
die('nothing attached here');
} else {
header("Location: {$file->url}");
die();
}
}
}

View File

@ -84,20 +84,24 @@ class NewnoticeAction extends Action
function handle($args) function handle($args)
{ {
parent::handle($args);
if (!common_logged_in()) { if (!common_logged_in()) {
$this->clientError(_('Not logged in.')); $this->clientError(_('Not logged in.'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') { } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// check for this before token since all POST and FILES data
// is losts when size is exceeded
if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
$this->clientError(sprintf(_('The server was unable to handle ' .
'that much POST data (%s bytes) due to its current configuration.'),
$_SERVER['CONTENT_LENGTH']));
}
parent::handle($args);
// CSRF protection // CSRF protection
$token = $this->trimmed('token'); $token = $this->trimmed('token');
if (!$token || $token != common_session_token()) { if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. '. $this->clientError(_('There was a problem with your session token. '.
'Try again, please.')); 'Try again, please.'));
return;
} }
try { try {
$this->saveNewNotice(); $this->saveNewNotice();
} catch (Exception $e) { } catch (Exception $e) {
@ -109,6 +113,30 @@ class NewnoticeAction extends Action
} }
} }
function getUploadedFileType() {
require_once 'MIME/Type.php';
$filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
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->clientError(sprintf(
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
}
function isRespectsQuota($user) {
$file = new File;
$ret = $file->isRespectsQuota($user);
if (true === $ret) return true;
$this->clientError($ret);
}
/** /**
* Save a new notice, based on arguments * Save a new notice, based on arguments
* *
@ -131,7 +159,6 @@ class NewnoticeAction extends Action
$this->clientError(_('No content!')); $this->clientError(_('No content!'));
} else { } else {
$content_shortened = common_shorten_links($content); $content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) { if (mb_strlen($content_shortened) > 140) {
$this->clientError(_('That\'s too long. '. $this->clientError(_('That\'s too long. '.
'Max notice size is 140 chars.')); 'Max notice size is 140 chars.'));
@ -158,17 +185,53 @@ class NewnoticeAction extends Action
$replyto = 'false'; $replyto = 'false';
} }
// $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, if (isset($_FILES['attach']['error'])) {
switch ($_FILES['attach']['error']) {
case UPLOAD_ERR_NO_FILE:
// no file uploaded, nothing to do
break;
case UPLOAD_ERR_OK:
$mimetype = $this->getUploadedFileType();
if (!$this->isRespectsQuota($user)) {
die('clientError() should trigger an exception before reaching here.');
}
break;
case UPLOAD_ERR_INI_SIZE:
$this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
case UPLOAD_ERR_FORM_SIZE:
$this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
case UPLOAD_ERR_PARTIAL:
$this->clientError(_('The uploaded file was only partially uploaded.'));
case UPLOAD_ERR_NO_TMP_DIR:
$this->clientError(_('Missing a temporary folder.'));
case UPLOAD_ERR_CANT_WRITE:
$this->clientError(_('Failed to write file to disk.'));
case UPLOAD_ERR_EXTENSION:
$this->clientError(_('File upload stopped by extension.'));
default:
die('Should never reach here.');
}
}
$notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
($replyto == 'false') ? null : $replyto); ($replyto == 'false') ? null : $replyto);
if (is_string($notice)) { if (is_string($notice)) {
$this->clientError($notice); $this->clientError($notice);
return;
} }
if (isset($mimetype)) {
$this->storeFile($notice, $mimetype);
}
$this->saveUrls($notice); $this->saveUrls($notice);
common_broadcast_notice($notice); common_broadcast_notice($notice);
if ($this->boolean('ajax')) { if ($this->boolean('ajax')) {
@ -194,6 +257,33 @@ class NewnoticeAction extends Action
} }
} }
function storeFile($notice, $mimetype) {
$filename = basename($_FILES['attach']['name']);
$destination = "file/{$notice->id}-$filename";
if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) {
$file = new File;
$file->url = common_local_url('file', array('notice' => $notice->id));
$file->size = filesize(INSTALLDIR . "/$destination");
$file->date = time();
$file->mimetype = $mimetype;
if ($file_id = $file->insert()) {
$file_redir = new File_redirection;
$file_redir->url = common_path($destination);
$file_redir->file_id = $file_id;
$file_redir->insert();
$f2p = new File_to_post;
$f2p->file_id = $file_id;
$f2p->post_id = $notice->id;
$f2p->insert();
} else {
$this->clientError(_('There was a database error while saving your file. Please try again.'));
}
} else {
$this->clientError(_('File could not be moved to destination directory.'));
}
}
/** save all urls in the notice to the db /** save all urls in the notice to the db
* *
* follow redirects and save all available file information * follow redirects and save all available file information
@ -203,7 +293,7 @@ class NewnoticeAction extends Action
* *
* @return void * @return void
*/ */
function saveUrls($notice) { function saveUrls($notice, $uploaded = null) {
common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id); common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id);
} }
@ -316,3 +406,4 @@ class NewnoticeAction extends Action
$nli->show(); $nli->show();
} }
} }

View File

@ -122,7 +122,7 @@ class ShownoticeAction extends Action
function lastModified() function lastModified()
{ {
return max(strtotime($this->notice->created), return max(strtotime($this->notice->modified),
strtotime($this->profile->modified), strtotime($this->profile->modified),
($this->avatar) ? strtotime($this->avatar->modified) : 0); ($this->avatar) ? strtotime($this->avatar->modified) : 0);
} }
@ -208,10 +208,10 @@ class ShownoticeAction extends Action
function showContent() function showContent()
{ {
$this->elementStart('ul', array('class' => 'notices')); $this->elementStart('ol', array('class' => 'notices xoxo'));
$nli = new NoticeListItem($this->notice, $this); $nli = new NoticeListItem($this->notice, $this);
$nli->show(); $nli->show();
$this->elementEnd('ul'); $this->elementEnd('ol');
} }
/** /**

View File

@ -98,9 +98,31 @@ class TwitapiaccountAction extends TwitterapiAction
$this->serverError(_('API method under construction.'), $code=501); $this->serverError(_('API method under construction.'), $code=501);
} }
// We don't have a rate limit, but some clients check this method.
// It always returns the same thing: 100 hit left.
function rate_limit_status($args, $apidata) function rate_limit_status($args, $apidata)
{ {
parent::handle($args); parent::handle($args);
$this->serverError(_('API method under construction.'), $code=501);
$type = $apidata['content-type'];
$this->init_document($type);
if ($apidata['content-type'] == 'xml') {
$this->elementStart('hash');
$this->element('remaining-hits', array('type' => 'integer'), 100);
$this->element('hourly-limit', array('type' => 'integer'), 100);
$this->element('reset-time', array('type' => 'datetime'), null);
$this->element('reset_time_in_seconds', array('type' => 'integer'), 0);
$this->elementEnd('hash');
} elseif ($apidata['content-type'] == 'json') {
$out = array('reset_time_in_seconds' => 0,
'remaining_hits' => 100,
'hourly_limit' => 100,
'reset_time' => '');
print json_encode($out);
}
$this->end_document($type);
} }
} }

View File

@ -43,7 +43,7 @@ class Twitapidirect_messagesAction extends TwitterapiAction
$count = $this->arg('count'); $count = $this->arg('count');
$since = $this->arg('since'); $since = $this->arg('since');
$since_id = $this->arg('since_id'); $since_id = $this->arg('since_id');
$before_id = $this->arg('before_id'); $max_id = $this->arg('max_id');
$page = $this->arg('page'); $page = $this->arg('page');
@ -74,8 +74,8 @@ class Twitapidirect_messagesAction extends TwitterapiAction
$link = $server . $user->nickname . '/outbox'; $link = $server . $user->nickname . '/outbox';
} }
if ($before_id) { if ($max_id) {
$message->whereAdd("id < $before_id"); $message->whereAdd("id <= $max_id");
} }
if ($since_id) { if ($since_id) {

View File

@ -45,22 +45,21 @@ class TwitapistatusesAction extends TwitterapiAction
$page = $this->arg('page'); $page = $this->arg('page');
$since_id = $this->arg('since_id'); $since_id = $this->arg('since_id');
$before_id = $this->arg('before_id'); $max_id = $this->arg('max_id');
// NOTE: page, since_id, and before_id are extensions to Twitter API -- TB
if (!$page) { if (!$page) {
$page = 1; $page = 1;
} }
if (!$since_id) { if (!$since_id) {
$since_id = 0; $since_id = 0;
} }
if (!$before_id) { if (!$max_id) {
$before_id = 0; $max_id = 0;
} }
$since = strtotime($this->arg('since')); $since = strtotime($this->arg('since'));
$notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $before_id, $since); $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $max_id, $since);
if ($notice) { if ($notice) {
@ -97,7 +96,7 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = $this->arg('since_id'); $since_id = $this->arg('since_id');
$count = $this->arg('count'); $count = $this->arg('count');
$page = $this->arg('page'); $page = $this->arg('page');
$before_id = $this->arg('before_id'); $max_id = $this->arg('max_id');
if (!$page) { if (!$page) {
$page = 1; $page = 1;
@ -111,9 +110,8 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = 0; $since_id = 0;
} }
// NOTE: before_id is an extension to Twitter API -- TB if (!$max_id) {
if (!$before_id) { $max_id = 0;
$before_id = 0;
} }
$since = strtotime($this->arg('since')); $since = strtotime($this->arg('since'));
@ -133,7 +131,7 @@ class TwitapistatusesAction extends TwitterapiAction
$link = common_local_url('all', array('nickname' => $user->nickname)); $link = common_local_url('all', array('nickname' => $user->nickname));
$subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), $user->nickname, $sitename); $subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), $user->nickname, $sitename);
$notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $before_id, $since); $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $max_id, $since);
switch($apidata['content-type']) { switch($apidata['content-type']) {
case 'xml': case 'xml':
@ -184,7 +182,7 @@ class TwitapistatusesAction extends TwitterapiAction
$since = $this->arg('since'); $since = $this->arg('since');
$since_id = $this->arg('since_id'); $since_id = $this->arg('since_id');
$page = $this->arg('page'); $page = $this->arg('page');
$before_id = $this->arg('before_id'); $max_id = $this->arg('max_id');
if (!$page) { if (!$page) {
$page = 1; $page = 1;
@ -198,9 +196,8 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = 0; $since_id = 0;
} }
// NOTE: before_id is an extensions to Twitter API -- TB if (!$max_id) {
if (!$before_id) { $max_id = 0;
$before_id = 0;
} }
$since = strtotime($this->arg('since')); $since = strtotime($this->arg('since'));
@ -220,7 +217,7 @@ class TwitapistatusesAction extends TwitterapiAction
# XXX: since # XXX: since
$notice = $user->getNotices((($page-1)*20), $count, $since_id, $before_id, $since); $notice = $user->getNotices((($page-1)*20), $count, $since_id, $max_id, $since);
switch($apidata['content-type']) { switch($apidata['content-type']) {
case 'xml': case 'xml':
@ -353,7 +350,7 @@ class TwitapistatusesAction extends TwitterapiAction
$count = $this->arg('count'); $count = $this->arg('count');
$page = $this->arg('page'); $page = $this->arg('page');
$since_id = $this->arg('since_id'); $since_id = $this->arg('since_id');
$before_id = $this->arg('before_id'); $max_id = $this->arg('max_id');
$user = $this->get_user($apidata['api_arg'], $apidata); $user = $this->get_user($apidata['api_arg'], $apidata);
$this->auth_user = $apidata['user']; $this->auth_user = $apidata['user'];
@ -380,15 +377,14 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = 0; $since_id = 0;
} }
// NOTE: before_id is an extension to Twitter API -- TB if (!$max_id) {
if (!$before_id) { $max_id = 0;
$before_id = 0;
} }
$since = strtotime($this->arg('since')); $since = strtotime($this->arg('since'));
$notice = $user->getReplies((($page-1)*20), $notice = $user->getReplies((($page-1)*20),
$count, $since_id, $before_id, $since); $count, $since_id, $max_id, $since);
$notices = array(); $notices = array();
while ($notice->fetch()) { while ($notice->fetch()) {

View File

@ -37,6 +37,10 @@ class TwitapiusersAction extends TwitterapiAction
$email = $this->arg('email'); $email = $this->arg('email');
$user_id = $this->arg('user_id'); $user_id = $this->arg('user_id');
// 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) { } elseif ($user_id) {
@ -48,7 +52,6 @@ class TwitapiusersAction extends TwitterapiAction
} }
if (!$user) { if (!$user) {
// XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
$this->client_error(_('Not found.'), 404, $apidata['content-type']); $this->client_error(_('Not found.'), 404, $apidata['content-type']);
return; return;
} }
@ -62,58 +65,6 @@ class TwitapiusersAction extends TwitterapiAction
$twitter_user = $this->twitter_user_array($profile, true); $twitter_user = $this->twitter_user_array($profile, true);
// Add in extended user fields offered up by this method
$twitter_user['created_at'] = $this->date_twitter($profile->created);
$subbed = DB_DataObject::factory('subscription');
$subbed->subscriber = $profile->id;
$subbed_count = (int) $subbed->count() - 1;
$notices = DB_DataObject::factory('notice');
$notices->profile_id = $profile->id;
$notice_count = (int) $notices->count();
$twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
$twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
// Other fields Twitter sends...
$twitter_user['profile_background_color'] = '';
$twitter_user['profile_background_image_url'] = '';
$twitter_user['profile_text_color'] = '';
$twitter_user['profile_link_color'] = '';
$twitter_user['profile_sidebar_fill_color'] = '';
$twitter_user['profile_sidebar_border_color'] = '';
$twitter_user['profile_background_tile'] = false;
$faves = DB_DataObject::factory('fave');
$faves->user_id = $user->id;
$faves_count = (int) $faves->count();
$twitter_user['favourites_count'] = $faves_count;
$timezone = 'UTC';
if ($user->timezone) {
$timezone = $user->timezone;
}
$t = new DateTime;
$t->setTimezone(new DateTimeZone($timezone));
$twitter_user['utc_offset'] = $t->format('Z');
$twitter_user['time_zone'] = $timezone;
if (isset($apidata['user'])) {
$twitter_user['following'] = $apidata['user']->isSubscribed($profile);
// Notifications on?
$sub = Subscription::pkeyGet(array('subscriber' =>
$apidata['user']->id, 'subscribed' => $profile->id));
if ($sub) {
$twitter_user['notifications'] = ($sub->jabber || $sub->sms);
}
}
if ($apidata['content-type'] == 'xml') { if ($apidata['content-type'] == 'xml') {
$this->init_document('xml'); $this->init_document('xml');
$this->show_twitter_xml_user($twitter_user); $this->show_twitter_xml_user($twitter_user);

View File

@ -46,7 +46,7 @@ class Fave extends Memcached_DataObject
return $ids; return $ids;
} }
function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since) function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
{ {
$fav = new Fave(); $fav = new Fave();
@ -59,8 +59,8 @@ class Fave extends Memcached_DataObject
$fav->whereAdd('notice_id > ' . $since_id); $fav->whereAdd('notice_id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$fav->whereAdd('notice_id < ' . $before_id); $fav->whereAdd('notice_id <= ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {

View File

@ -120,4 +120,30 @@ class File extends Memcached_DataObject
File_to_post::processNew($file_id, $notice_id); File_to_post::processNew($file_id, $notice_id);
return $x; return $x;
} }
function isRespectsQuota($user) {
if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) {
return sprintf(_('No file may be larger than %d bytes ' .
'and the file you sent was %d bytes. Try to upload a smaller version.'),
common_config('attachments', 'file_quota'), $_FILES['attach']['size']);
}
$query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'";
$this->query($query);
$this->fetch();
$total = $this->total + $_FILES['attach']['size'];
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'));
}
$query .= ' month(modified) = month(now()) and year(modified) = year(now())';
$this->query($query);
$this->fetch();
$total = $this->total + $_FILES['attach']['size'];
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 true;
}
} }

View File

@ -133,7 +133,7 @@ class File_redirection extends Memcached_DataObject
$file->limit(1); $file->limit(1);
$file->orderBy('len'); $file->orderBy('len');
$file->find(true); $file->find(true);
if (!empty($file->id)) { if (!empty($file->url) && (strlen($file->url) < strlen($long_url))) {
return $file->url; return $file->url;
} }

View File

@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject
public $__table = 'foreign_link'; // table name public $__table = 'foreign_link'; // table name
public $user_id; // int(4) primary_key not_null public $user_id; // int(4) primary_key not_null
public $foreign_id; // int(4) primary_key not_null public $foreign_id; // bigint(8) primary_key not_null unsigned
public $service; // int(4) primary_key not_null public $service; // int(4) primary_key not_null
public $credentials; // varchar(255) public $credentials; // varchar(255)
public $noticesync; // tinyint(1) not_null default_1 public $noticesync; // tinyint(1) not_null default_1

View File

@ -227,4 +227,28 @@ class Memcached_DataObject extends DB_DataObject
$c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry); $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry);
return new ArrayWrapper($cached); return new ArrayWrapper($cached);
} }
// We overload so that 'SET NAMES "utf8"' is called for
// each connection
function _connect()
{
global $_DB_DATAOBJECT;
$exists = !empty($this->_database_dsn_md5) &&
isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]);
$result = parent::_connect();
if (!$exists) {
$DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
if (common_config('db', 'type') == 'mysql' &&
common_config('db', 'utf8')) {
$conn = $DB->connection;
if ($DB instanceof DB_mysqli) {
mysqli_set_charset($conn, 'utf8');
} else if ($DB instanceof DB_mysql) {
mysql_set_charset('utf8', $conn);
}
}
}
return $result;
}
} }

View File

@ -124,6 +124,8 @@ class Notice extends Memcached_DataObject
$profile = Profile::staticGet($profile_id); $profile = Profile::staticGet($profile_id);
$final = common_shorten_links($content);
if (!$profile) { if (!$profile) {
common_log(LOG_ERR, 'Problem saving notice. Unknown user.'); common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
return _('Problem saving notice. Unknown user.'); return _('Problem saving notice. Unknown user.');
@ -134,7 +136,7 @@ class Notice extends Memcached_DataObject
return _('Too many notices too fast; take a breather and post again in a few minutes.'); return _('Too many notices too fast; take a breather and post again in a few minutes.');
} }
if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $content)) { if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.'); common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.'); return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
} }
@ -165,8 +167,8 @@ class Notice extends Memcached_DataObject
$notice->reply_to = $reply_to; $notice->reply_to = $reply_to;
$notice->created = common_sql_now(); $notice->created = common_sql_now();
$notice->content = $content; $notice->content = $final;
$notice->rendered = common_render_content($content, $notice); $notice->rendered = common_render_content($final, $notice);
$notice->source = $source; $notice->source = $source;
$notice->uri = $uri; $notice->uri = $uri;
@ -202,13 +204,9 @@ class Notice extends Memcached_DataObject
$notice->saveReplies(); $notice->saveReplies();
$notice->saveTags(); $notice->saveTags();
$notice->saveGroups();
if (common_config('queue', 'enabled')) {
$notice->addToAuthorInbox();
} else {
$notice->addToInboxes(); $notice->addToInboxes();
} $notice->saveGroups();
$notice->query('COMMIT'); $notice->query('COMMIT');
@ -218,13 +216,7 @@ class Notice extends Memcached_DataObject
# Clear the cache for subscribed users, so they'll update at next request # Clear the cache for subscribed users, so they'll update at next request
# XXX: someone clever could prepend instead of clearing the cache # XXX: someone clever could prepend instead of clearing the cache
if (common_config('memcached', 'enabled')) {
if (common_config('queue', 'enabled')) {
$notice->blowAuthorCaches();
} else {
$notice->blowCaches(); $notice->blowCaches();
}
}
return $notice; return $notice;
} }
@ -277,6 +269,18 @@ class Notice extends Memcached_DataObject
return true; return true;
} }
function getUploadedAttachment() {
$post = clone $this;
$query = 'select file.url as uploaded from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"';
$post->query($query);
$post->fetch();
$ret = $post->uploaded;
// var_dump($post);
$post->free();
// die();
return $ret;
}
function hasAttachments() { function hasAttachments() {
$post = clone $this; $post = clone $this;
$query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id); $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id);
@ -297,17 +301,6 @@ class Notice extends Memcached_DataObject
$this->blowGroupCache($blowLast); $this->blowGroupCache($blowLast);
} }
function blowAuthorCaches($blowLast=false)
{
// Clear the user's cache
$cache = common_memcache();
if (!empty($cache)) {
$cache->delete(common_cache_key('notice_inbox:by_user:'.$this->profile_id));
}
$this->blowNoticeCache($blowLast);
$this->blowPublicCache($blowLast);
}
function blowGroupCache($blowLast=false) function blowGroupCache($blowLast=false)
{ {
$cache = common_memcache(); $cache = common_memcache();
@ -443,22 +436,22 @@ class Notice extends Memcached_DataObject
# XXX: too many args; we need to move to named params or even a separate # XXX: too many args; we need to move to named params or even a separate
# class for notice streams # class for notice streams
static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=null, $since=null) { static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
if (common_config('memcached', 'enabled')) { if (common_config('memcached', 'enabled')) {
# Skip the cache if this is a since, since_id or before_id qry # Skip the cache if this is a since, since_id or max_id qry
if ($since_id > 0 || $before_id > 0 || $since) { if ($since_id > 0 || $max_id > 0 || $since) {
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
} else { } else {
return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order); return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
} }
} }
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since); return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
} }
static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) { static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
$needAnd = false; $needAnd = false;
$needWhere = true; $needWhere = true;
@ -480,7 +473,7 @@ class Notice extends Memcached_DataObject
$qry .= ' notice.id > ' . $since_id; $qry .= ' notice.id > ' . $since_id;
} }
if ($before_id > 0) { if ($max_id > 0) {
if ($needWhere) { if ($needWhere) {
$qry .= ' WHERE '; $qry .= ' WHERE ';
@ -489,7 +482,7 @@ class Notice extends Memcached_DataObject
$qry .= ' AND '; $qry .= ' AND ';
} }
$qry .= ' notice.id < ' . $before_id; $qry .= ' notice.id <= ' . $max_id;
} }
if ($since) { if ($since) {
@ -647,17 +640,17 @@ class Notice extends Memcached_DataObject
} }
} }
function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null) function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
{ {
$ids = Notice::stream(array('Notice', '_publicStreamDirect'), $ids = Notice::stream(array('Notice', '_publicStreamDirect'),
array(), array(),
'public', 'public',
$offset, $limit, $since_id, $before_id, $since); $offset, $limit, $since_id, $max_id, $since);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null) function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
{ {
$notice = new Notice(); $notice = new Notice();
@ -681,8 +674,8 @@ class Notice extends Memcached_DataObject
$notice->whereAdd('id > ' . $since_id); $notice->whereAdd('id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$notice->whereAdd('id < ' . $before_id); $notice->whereAdd('id <= ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {
@ -726,33 +719,6 @@ class Notice extends Memcached_DataObject
return; return;
} }
function addToAuthorInbox()
{
$enabled = common_config('inboxes', 'enabled');
if ($enabled === true || $enabled === 'transitional') {
$user = User::staticGet('id', $this->profile_id);
if (empty($user)) {
return;
}
$inbox = new Notice_inbox();
$UT = common_config('db','type')=='pgsql'?'"user"':'user';
$qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' .
"SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " .
"FROM $UT " .
"WHERE $UT.id = " . $this->profile_id . ' ' .
'AND NOT EXISTS (SELECT user_id, notice_id ' .
'FROM notice_inbox ' .
"WHERE user_id = " . $this->profile_id . ' '.
'AND notice_id = ' . $this->id . ' )';
if ($enabled === 'transitional') {
$qry .= " AND $UT.inboxed = 1";
}
$inbox->query($qry);
}
return;
}
function saveGroups() function saveGroups()
{ {
$enabled = common_config('inboxes', 'enabled'); $enabled = common_config('inboxes', 'enabled');
@ -1024,15 +990,15 @@ class Notice extends Memcached_DataObject
} }
} }
function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null, $tag=null) function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
{ {
$cache = common_memcache(); $cache = common_memcache();
if (empty($cache) || if (empty($cache) ||
$since_id != 0 || $before_id != 0 || !is_null($since) || $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) { ($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id, return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
$before_id, $since, $tag))); $max_id, $since)));
} }
$idkey = common_cache_key($cachekey); $idkey = common_cache_key($cachekey);

View File

@ -43,15 +43,15 @@ class Notice_inbox extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
function stream($user_id, $offset, $limit, $since_id, $before_id, $since) function stream($user_id, $offset, $limit, $since_id, $max_id, $since)
{ {
return Notice::stream(array('Notice_inbox', '_streamDirect'), return Notice::stream(array('Notice_inbox', '_streamDirect'),
array($user_id), array($user_id),
'notice_inbox:by_user:'.$user_id, 'notice_inbox:by_user:'.$user_id,
$offset, $limit, $since_id, $before_id, $since); $offset, $limit, $since_id, $max_id, $since);
} }
function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since) function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
{ {
$inbox = new Notice_inbox(); $inbox = new Notice_inbox();
@ -61,8 +61,8 @@ class Notice_inbox extends Memcached_DataObject
$inbox->whereAdd('notice_id > ' . $since_id); $inbox->whereAdd('notice_id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$inbox->whereAdd('notice_id < ' . $before_id); $inbox->whereAdd('notice_id <= ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {

View File

@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function _streamDirect($tag, $offset, $limit, $since_id, $before_id, $since) function _streamDirect($tag, $offset, $limit, $since_id, $max_id, $since)
{ {
$nt = new Notice_tag(); $nt = new Notice_tag();
@ -59,8 +59,8 @@ class Notice_tag extends Memcached_DataObject
$nt->whereAdd('notice_id > ' . $since_id); $nt->whereAdd('notice_id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$nt->whereAdd('notice_id < ' . $before_id); $nt->whereAdd('notice_id < ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {

View File

@ -170,7 +170,7 @@ class Profile extends Memcached_DataObject
$ids = Notice::stream(array($this, '_streamDirect'), $ids = Notice::stream(array($this, '_streamDirect'),
array(), array(),
'profile:notice_ids:' . $this->id, 'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $before_id, $since); $offset, $limit, $since_id, $max_id, $since);
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
@ -225,8 +225,8 @@ class Profile extends Memcached_DataObject
$notice->whereAdd('id > ' . $since_id); $notice->whereAdd('id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$notice->whereAdd('id < ' . $before_id); $notice->whereAdd('id <= ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {

View File

@ -22,16 +22,16 @@ class Reply extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
{ {
$ids = Notice::stream(array('Reply', '_streamDirect'), $ids = Notice::stream(array('Reply', '_streamDirect'),
array($user_id), array($user_id),
'reply:stream:' . $user_id, 'reply:stream:' . $user_id,
$offset, $limit, $since_id, $before_id, $since); $offset, $limit, $since_id, $max_id, $since);
return $ids; return $ids;
} }
function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
{ {
$reply = new Reply(); $reply = new Reply();
$reply->profile_id = $user_id; $reply->profile_id = $user_id;
@ -40,8 +40,8 @@ class Reply extends Memcached_DataObject
$reply->whereAdd('notice_id > ' . $since_id); $reply->whereAdd('notice_id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$reply->whereAdd('notice_id < ' . $before_id); $reply->whereAdd('notice_id < ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {

View File

@ -402,7 +402,6 @@ class User extends Memcached_DataObject
function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{ {
$ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since); $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since);
common_debug("Ids = " . implode(',', $ids));
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }

View File

@ -58,7 +58,7 @@ class User_group extends Memcached_DataObject
return Notice::getStreamByIds($ids); return Notice::getStreamByIds($ids);
} }
function _streamDirect($offset, $limit, $since_id, $before_id, $since) function _streamDirect($offset, $limit, $since_id, $max_id, $since)
{ {
$inbox = new Group_inbox(); $inbox = new Group_inbox();
@ -71,8 +71,8 @@ class User_group extends Memcached_DataObject
$inbox->whereAdd('notice_id > ' . $since_id); $inbox->whereAdd('notice_id > ' . $since_id);
} }
if ($before_id != 0) { if ($max_id != 0) {
$inbox->whereAdd('notice_id < ' . $before_id); $inbox->whereAdd('notice_id <= ' . $max_id);
} }
if (!is_null($since)) { if (!is_null($since)) {

View File

@ -215,3 +215,11 @@ $config['sphinx']['port'] = 3312;
// $config['snapshot']['run'] = 'never'; // $config['snapshot']['run'] = 'never';
// If you want to report statistics in a cron job instead. // If you want to report statistics in a cron job instead.
// $config['snapshot']['run'] = 'cron'; // $config['snapshot']['run'] = 'cron';
// Support for file uploads (attachments),
// select supported mimetypes and quotas (in bytes)
// $config['attachments']['supported'] = array('image/png', 'application/ogg');
// $config['attachments']['file_quota'] = 5000000;
// $config['attachments']['user_quota'] = 50000000;
// $config['attachments']['monthly_quota'] = 15000000;

View File

@ -289,7 +289,7 @@ create table foreign_user (
create table foreign_link ( create table foreign_link (
user_id int comment 'link to user on this system, if exists' references user (id), user_id int comment 'link to user on this system, if exists' references user (id),
foreign_id int comment 'link ' references foreign_user(id), foreign_id bigint unsigned comment 'link to user on foreign service, if exists' references foreign_user(id),
service int not null comment 'foreign key to service' references foreign_service(id), service int not null comment 'foreign key to service' references foreign_service(id),
credentials varchar(255) comment 'authc credentials, typically a password', credentials varchar(255) comment 'authc credentials, typically a password',
noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies', noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies',

View File

@ -2,13 +2,18 @@ INSERT INTO notice_source
(code, name, url, created) (code, name, url, created)
VALUES VALUES
('adium', 'Adium', 'http://www.adiumx.com/', now()), ('adium', 'Adium', 'http://www.adiumx.com/', now()),
('Afficheur', 'Afficheur', 'http://afficheur.sourceforge.jp/', now()),
('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()),
('anyio', 'Any.IO', 'http://any.io/', now()),
('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()),
('bti','bti','http://gregkh.github.com/bti/', now()), ('bti','bti','http://gregkh.github.com/bti/', now()),
('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()), ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()),
('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()),
('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()),
('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()),
('Facebook','Facebook','http://apps.facebook.com/identica/', now()), ('Facebook','Facebook','http://apps.facebook.com/identica/', now()),
('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()), ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()),
('get2gnow', 'get2gnow', 'http://uberchicgeekchick.com/?projects=get2gnow', now()),
('gravity', 'Gravity', 'http://mobileways.de/gravity', now()), ('gravity', 'Gravity', 'http://mobileways.de/gravity', now()),
('Gwibber','Gwibber','http://launchpad.net/gwibber', now()), ('Gwibber','Gwibber','http://launchpad.net/gwibber', now()),
('HelloTxt','HelloTxt','http://hellotxt.com/', now()), ('HelloTxt','HelloTxt','http://hellotxt.com/', now()),
@ -28,6 +33,7 @@ VALUES
('pingvine','PingVine','http://pingvine.com/', now()), ('pingvine','PingVine','http://pingvine.com/', now()),
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()), ('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
('posty','Posty','http://spreadingfunkyness.com/posty/', now()), ('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()),
('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()), ('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),
('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()), ('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()),
('rygh.no','rygh.no','http://rygh.no/', now()), ('rygh.no','rygh.no','http://rygh.no/', now()),

523
extlib/MIME/Type.php Normal file
View File

@ -0,0 +1,523 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002, 2008 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Ian Eure <ieure@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Type.php,v 1.6 2009/01/16 11:49:45 cweiske Exp $
require_once 'PEAR.php';
$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
$_fileCmd = 'file';
/**
* Class for working with MIME types
*
* @category MIME
* @package MIME_Type
* @license PHP License 3.0
* @version 1.2.0
* @link http://pear.php.net/package/MIME_Type
* @author Ian Eure <ieure@php.net>
*/
class MIME_Type
{
/**
* The MIME media type
*
* @var string
*/
var $media = '';
/**
* The MIME media sub-type
*
* @var string
*/
var $subType = '';
/**
* Optional MIME parameters
*
* @var array
*/
var $parameters = array();
/**
* List of valid media types.
* A media type is the string in front of the slash.
* The media type of "text/xml" would be "text".
*
* @var array
*/
var $validMediaTypes = array(
'text',
'image',
'audio',
'video',
'application',
'multipart',
'message'
);
/**
* Constructor.
*
* If $type is set, if will be parsed and the appropriate class vars set.
* If not, you get an empty class.
* This is useful, but not quite as useful as parsing a type.
*
* @param string $type MIME type
*
* @return void
*/
function MIME_Type($type = false)
{
if ($type) {
$this->parse($type);
}
}
/**
* Parse a mime-type and set the class variables.
*
* @param string $type MIME type to parse
*
* @return void
*/
function parse($type)
{
$this->media = $this->getMedia($type);
$this->subType = $this->getSubType($type);
$this->parameters = array();
if (MIME_Type::hasParameters($type)) {
require_once 'MIME/Type/Parameter.php';
foreach (MIME_Type::getParameters($type) as $param) {
$param = new MIME_Type_Parameter($param);
$this->parameters[$param->name] = $param;
}
}
}
/**
* Does this type have any parameters?
*
* @param string $type MIME type to check
*
* @return boolean true if $type has parameters, false otherwise
* @static
*/
function hasParameters($type)
{
if (strstr($type, ';')) {
return true;
}
return false;
}
/**
* Get a MIME type's parameters
*
* @param string $type MIME type to get parameters of
*
* @return array $type's parameters
* @static
*/
function getParameters($type)
{
$params = array();
$tmp = explode(';', $type);
for ($i = 1; $i < count($tmp); $i++) {
$params[] = trim($tmp[$i]);
}
return $params;
}
/**
* Strip parameters from a MIME type string.
*
* @param string $type MIME type string
*
* @return string MIME type with parameters removed
* @static
*/
function stripParameters($type)
{
if (strstr($type, ';')) {
return substr($type, 0, strpos($type, ';'));
}
return $type;
}
/**
* Removes comments from a media type, subtype or parameter.
*
* @param string $string String to strip comments from
* @param string &$comment Comment is stored in there.
*
* @return string String without comments
* @static
*/
function stripComments($string, &$comment)
{
if (strpos($string, '(') === false) {
return $string;
}
$inquote = false;
$quoting = false;
$incomment = 0;
$newstring = '';
for ($n = 0; $n < strlen($string); $n++) {
if ($quoting) {
if ($incomment == 0) {
$newstring .= $string[$n];
} else if ($comment !== null) {
$comment .= $string[$n];
}
$quoting = false;
} else if ($string[$n] == '\\') {
$quoting = true;
} else if (!$inquote && $incomment > 0 && $string[$n] == ')') {
$incomment--;
if ($incomment == 0 && $comment !== null) {
$comment .= ' ';
}
} else if (!$inquote && $string[$n] == '(') {
$incomment++;
} else if ($string[$n] == '"') {
if ($inquote) {
$inquote = false;
} else {
$inquote = true;
}
} else if ($incomment == 0) {
$newstring .= $string[$n];
} else if ($comment !== null) {
$comment .= $string[$n];
}
}
if ($comment !== null) {
$comment = trim($comment);
}
return $newstring;
}
/**
* Get a MIME type's media
*
* @note 'media' refers to the portion before the first slash
*
* @param string $type MIME type to get media of
*
* @return string $type's media
* @static
*/
function getMedia($type)
{
$tmp = explode('/', $type);
return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
}
/**
* Get a MIME type's subtype
*
* @param string $type MIME type to get subtype of
*
* @return string $type's subtype, null if invalid mime type
* @static
*/
function getSubType($type)
{
$tmp = explode('/', $type);
if (!isset($tmp[1])) {
return null;
}
$tmp = explode(';', $tmp[1]);
return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
}
/**
* Create a textual MIME type from object values
*
* This function performs the opposite function of parse().
*
* @return string MIME type string
*/
function get()
{
$type = strtolower($this->media . '/' . $this->subType);
if (count($this->parameters)) {
foreach ($this->parameters as $key => $null) {
$type .= '; ' . $this->parameters[$key]->get();
}
}
return $type;
}
/**
* Is this type experimental?
*
* @note Experimental types are denoted by a leading 'x-' in the media or
* subtype, e.g. text/x-vcard or x-world/x-vrml.
*
* @param string $type MIME type to check
*
* @return boolean true if $type is experimental, false otherwise
* @static
*/
function isExperimental($type)
{
if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' ||
substr(MIME_Type::getSubType($type), 0, 2) == 'x-') {
return true;
}
return false;
}
/**
* Is this a vendor MIME type?
*
* @note Vendor types are denoted with a leading 'vnd. in the subtype.
*
* @param string $type MIME type to check
*
* @return boolean true if $type is a vendor type, false otherwise
* @static
*/
function isVendor($type)
{
if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') {
return true;
}
return false;
}
/**
* Is this a wildcard type?
*
* @param string $type MIME type to check
*
* @return boolean true if $type is a wildcard, false otherwise
* @static
*/
function isWildcard($type)
{
if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') {
return true;
}
return false;
}
/**
* Perform a wildcard match on a MIME type
*
* Example:
* MIME_Type::wildcardMatch('image/*', 'image/png')
*
* @param string $card Wildcard to check against
* @param string $type MIME type to check
*
* @return boolean true if there was a match, false otherwise
* @static
*/
function wildcardMatch($card, $type)
{
if (!MIME_Type::isWildcard($card)) {
return false;
}
if ($card == '*/*') {
return true;
}
if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) {
return true;
}
return false;
}
/**
* Add a parameter to this type
*
* @param string $name Attribute name
* @param string $value Attribute value
* @param string $comment Comment for this parameter
*
* @return void
*/
function addParameter($name, $value, $comment = false)
{
$tmp = new MIME_Type_Parameter();
$tmp->name = $name;
$tmp->value = $value;
$tmp->comment = $comment;
$this->parameters[$name] = $tmp;
}
/**
* Remove a parameter from this type
*
* @param string $name Parameter name
*
* @return void
*/
function removeParameter($name)
{
unset($this->parameters[$name]);
}
/**
* Autodetect a file's MIME-type
*
* This function may be called staticly.
*
* @internal Tries to use fileinfo extension at first. If that
* does not work, mime_magic is used. If this is also not available
* or does not succeed, "file" command is tried to be executed with
* System_Command. When that fails, too, then we use our in-built
* extension-to-mimetype-mapping list.
*
* @param string $file Path to the file to get the type of
* @param bool $params Append MIME parameters if true
*
* @return string $file's MIME-type on success, PEAR_Error otherwise
*
* @since 1.0.0beta1
* @static
*/
function autoDetect($file, $params = false)
{
// Sanity checks
if (!file_exists($file)) {
return PEAR::raiseError("File \"$file\" doesn't exist");
}
if (!is_readable($file)) {
return PEAR::raiseError("File \"$file\" is not readable");
}
if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME);
$type = finfo_file($finfo, $file);
finfo_close($finfo);
if ($type !== false && $type !== '') {
return MIME_Type::_handleDetection($type, $params);
}
}
if (function_exists('mime_content_type')) {
$type = mime_content_type($file);
if ($type !== false && $type !== '') {
return MIME_Type::_handleDetection($type, $params);
}
}
@include_once 'System/Command.php';
if (class_exists('System_Command')) {
return MIME_Type::_handleDetection(
MIME_Type::_fileAutoDetect($file),
$params
);
}
require_once 'MIME/Type/Extension.php';
$mte = new MIME_Type_Extension();
return $mte->getMIMEType($file);
}
/**
* Handles a detected MIME type and modifies it if necessary.
*
* @param string $type MIME Type of a file
* @param bool $params Append MIME parameters if true
*
* @return string $file's MIME-type on success, PEAR_Error otherwise
*/
function _handleDetection($type, $params)
{
// _fileAutoDetect() may have returned an error.
if (PEAR::isError($type)) {
return $type;
}
// Don't return an empty string
if (!$type || !strlen($type)) {
return PEAR::raiseError("Sorry, couldn't determine file type.");
}
// Strip parameters if present & requested
if (MIME_Type::hasParameters($type) && !$params) {
$type = MIME_Type::stripParameters($type);
}
return $type;
}
/**
* Autodetect a file's MIME-type with 'file' and System_Command
*
* This function may be called staticly.
*
* @param string $file Path to the file to get the type of
*
* @return string $file's MIME-type
*
* @since 1.0.0beta1
* @static
*/
function _fileAutoDetect($file)
{
$cmd = new System_Command();
// Make sure we have the 'file' command.
$fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd');
if (!$cmd->which($fileCmd)) {
unset($cmd);
return PEAR::raiseError("Can't find file command \"{$fileCmd}\"");
}
$cmd->pushCommand($fileCmd, "-bi " . escapeshellarg($file));
$res = $cmd->execute();
unset($cmd);
return $res;
}
}

View File

@ -0,0 +1,298 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP versions 4 and 5 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2009 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Christian Schmidt <schmidt@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Extension.php,v 1.1 2009/01/16 11:49:45 cweiske Exp $
require_once 'PEAR.php';
/**
* Class for mapping file extensions to MIME types.
*
* @category MIME
* @package MIME_Type
* @author Christian Schmidt <schmidt@php.net>
* @license PHP License 3.0
* @version 1.2.0
* @link http://pear.php.net/package/MIME_Type
*/
class MIME_Type_Extension
{
/**
* Mapping between file extension and MIME type.
*
* @internal The array is sorted alphabetically by value and with primary
* extension first. Be careful about not adding duplicate keys - PHP
* silently ignores duplicates. The following command can be used for
* checking for duplicates:
* grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d
* application/octet-stream is generally used as fallback when no other
* MIME-type can be found, but the array does not contain a lot of such
* unknown extension. One entry exists, though, to allow detection of
* file extension for this MIME-type.
*
* @var array
*/
var $extensionToType = array (
'ez' => 'application/andrew-inset',
'atom' => 'application/atom+xml',
'jar' => 'application/java-archive',
'hqx' => 'application/mac-binhex40',
'cpt' => 'application/mac-compactpro',
'mathml' => 'application/mathml+xml',
'doc' => 'application/msword',
'dat' => 'application/octet-stream',
'oda' => 'application/oda',
'ogg' => 'application/ogg',
'pdf' => 'application/pdf',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
'rdf' => 'application/rdf+xml',
'rss' => 'application/rss+xml',
'smi' => 'application/smil',
'smil' => 'application/smil',
'gram' => 'application/srgs',
'grxml' => 'application/srgs+xml',
'kml' => 'application/vnd.google-earth.kml+xml',
'kmz' => 'application/vnd.google-earth.kmz',
'mif' => 'application/vnd.mif',
'xul' => 'application/vnd.mozilla.xul+xml',
'xls' => 'application/vnd.ms-excel',
'xlb' => 'application/vnd.ms-excel',
'xlt' => 'application/vnd.ms-excel',
'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
'docm' => 'application/vnd.ms-word.document.macroEnabled.12',
'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
'ppt' => 'application/vnd.ms-powerpoint',
'pps' => 'application/vnd.ms-powerpoint',
'odc' => 'application/vnd.oasis.opendocument.chart',
'odb' => 'application/vnd.oasis.opendocument.database',
'odf' => 'application/vnd.oasis.opendocument.formula',
'odg' => 'application/vnd.oasis.opendocument.graphics',
'otg' => 'application/vnd.oasis.opendocument.graphics-template',
'odi' => 'application/vnd.oasis.opendocument.image',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'otp' => 'application/vnd.oasis.opendocument.presentation-template',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
'odt' => 'application/vnd.oasis.opendocument.text',
'odm' => 'application/vnd.oasis.opendocument.text-master',
'ott' => 'application/vnd.oasis.opendocument.text-template',
'oth' => 'application/vnd.oasis.opendocument.text-web',
'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'vsd' => 'application/vnd.visio',
'wbxml' => 'application/vnd.wap.wbxml',
'wmlc' => 'application/vnd.wap.wmlc',
'wmlsc' => 'application/vnd.wap.wmlscriptc',
'vxml' => 'application/voicexml+xml',
'bcpio' => 'application/x-bcpio',
'vcd' => 'application/x-cdlink',
'pgn' => 'application/x-chess-pgn',
'cpio' => 'application/x-cpio',
'csh' => 'application/x-csh',
'dcr' => 'application/x-director',
'dir' => 'application/x-director',
'dxr' => 'application/x-director',
'dvi' => 'application/x-dvi',
'spl' => 'application/x-futuresplash',
'tgz' => 'application/x-gtar',
'gtar' => 'application/x-gtar',
'hdf' => 'application/x-hdf',
'js' => 'application/x-javascript',
'skp' => 'application/x-koan',
'skd' => 'application/x-koan',
'skt' => 'application/x-koan',
'skm' => 'application/x-koan',
'latex' => 'application/x-latex',
'nc' => 'application/x-netcdf',
'cdf' => 'application/x-netcdf',
'sh' => 'application/x-sh',
'shar' => 'application/x-shar',
'swf' => 'application/x-shockwave-flash',
'sit' => 'application/x-stuffit',
'sv4cpio' => 'application/x-sv4cpio',
'sv4crc' => 'application/x-sv4crc',
'tar' => 'application/x-tar',
'tcl' => 'application/x-tcl',
'tex' => 'application/x-tex',
'texinfo' => 'application/x-texinfo',
'texi' => 'application/x-texinfo',
't' => 'application/x-troff',
'tr' => 'application/x-troff',
'roff' => 'application/x-troff',
'man' => 'application/x-troff-man',
'me' => 'application/x-troff-me',
'ms' => 'application/x-troff-ms',
'ustar' => 'application/x-ustar',
'src' => 'application/x-wais-source',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'xslt' => 'application/xslt+xml',
'xml' => 'application/xml',
'xsl' => 'application/xml',
'dtd' => 'application/xml-dtd',
'zip' => 'application/zip',
'au' => 'audio/basic',
'snd' => 'audio/basic',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'kar' => 'audio/midi',
'mpga' => 'audio/mpeg',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'aif' => 'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'aifc' => 'audio/x-aiff',
'm3u' => 'audio/x-mpegurl',
'wma' => 'audio/x-ms-wma',
'wax' => 'audio/x-ms-wax',
'ram' => 'audio/x-pn-realaudio',
'ra' => 'audio/x-pn-realaudio',
'rm' => 'application/vnd.rn-realmedia',
'wav' => 'audio/x-wav',
'pdb' => 'chemical/x-pdb',
'xyz' => 'chemical/x-xyz',
'bmp' => 'image/bmp',
'cgm' => 'image/cgm',
'gif' => 'image/gif',
'ief' => 'image/ief',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'djvu' => 'image/vnd.djvu',
'djv' => 'image/vnd.djvu',
'wbmp' => 'image/vnd.wap.wbmp',
'ras' => 'image/x-cmu-raster',
'ico' => 'image/x-icon',
'pnm' => 'image/x-portable-anymap',
'pbm' => 'image/x-portable-bitmap',
'pgm' => 'image/x-portable-graymap',
'ppm' => 'image/x-portable-pixmap',
'rgb' => 'image/x-rgb',
'xbm' => 'image/x-xbitmap',
'psd' => 'image/x-photoshop',
'xpm' => 'image/x-xpixmap',
'xwd' => 'image/x-xwindowdump',
'eml' => 'message/rfc822',
'igs' => 'model/iges',
'iges' => 'model/iges',
'msh' => 'model/mesh',
'mesh' => 'model/mesh',
'silo' => 'model/mesh',
'wrl' => 'model/vrml',
'vrml' => 'model/vrml',
'ics' => 'text/calendar',
'ifb' => 'text/calendar',
'css' => 'text/css',
'csv' => 'text/csv',
'html' => 'text/html',
'htm' => 'text/html',
'txt' => 'text/plain',
'asc' => 'text/plain',
'rtx' => 'text/richtext',
'rtf' => 'text/rtf',
'sgml' => 'text/sgml',
'sgm' => 'text/sgml',
'tsv' => 'text/tab-separated-values',
'wml' => 'text/vnd.wap.wml',
'wmls' => 'text/vnd.wap.wmlscript',
'etx' => 'text/x-setext',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'mxu' => 'video/vnd.mpegurl',
'm4u' => 'video/vnd.mpegurl',
'flv' => 'video/x-flv',
'asf' => 'video/x-ms-asf',
'asx' => 'video/x-ms-asf',
'wmv' => 'video/x-ms-wmv',
'wm' => 'video/x-ms-wm',
'wmx' => 'video/x-ms-wmx',
'avi' => 'video/x-msvideo',
'ogv' => 'video/ogg',
'movie' => 'video/x-sgi-movie',
'ice' => 'x-conference/x-cooltalk',
);
/**
* Autodetect a file's MIME-type.
*
* @param string $file Path to the file to get the type of
*
* @return string $file's MIME-type on success, PEAR_Error otherwise
*/
function getMIMEType($file)
{
$extension = substr(strrchr($file, '.'), 1);
if ($extension === false) {
return PEAR::raiseError("File has no extension.");
}
if (!isset($this->extensionToType[$extension])) {
return PEAR::raiseError("Sorry, couldn't determine file type.");
}
return $this->extensionToType[$extension];
}
/**
* Return default MIME-type for the specified extension.
*
* @param string $type MIME-type
*
* @return string A file extension without leading period.
*/
function getExtension($type)
{
require_once 'MIME/Type.php';
// Strip parameters and comments.
$type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type);
$extension = array_search($type, $this->extensionToType);
if ($extension === false) {
return PEAR::raiseError("Sorry, couldn't determine extension.");
}
return $extension;
}
}
?>

View File

@ -0,0 +1,163 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Authors: Ian Eure <ieure@php.net> |
// +----------------------------------------------------------------------+
//
// $Id: Parameter.php,v 1.1 2007/03/25 10:10:21 cweiske Exp $
/**
* Class for working with MIME type parameters
*
* @version 1.2.0
* @package MIME_Type
* @author Ian Eure <ieure@php.net>
*/
class MIME_Type_Parameter {
/**
* Parameter name
*
* @var string
*/
var $name;
/**
* Parameter value
*
* @var string
*/
var $value;
/**
* Parameter comment
*
* @var string
*/
var $comment;
/**
* Constructor.
*
* @param string $param MIME parameter to parse, if set.
* @return void
*/
function MIME_Type_Parameter($param = false)
{
if ($param) {
$this->parse($param);
}
}
/**
* Parse a MIME type parameter and set object fields
*
* @param string $param MIME type parameter to parse
* @return void
*/
function parse($param)
{
$comment = '';
$param = MIME_Type::stripComments($param, $comment);
$this->name = $this->getAttribute($param);
$this->value = $this->getValue($param);
$this->comment = $comment;
}
/**
* Get a parameter attribute (e.g. name)
*
* @param string MIME type parameter
* @return string Attribute name
* @static
*/
function getAttribute($param)
{
$tmp = explode('=', $param);
return trim($tmp[0]);
}
/**
* Get a parameter value
*
* @param string $param MIME type parameter
* @return string Value
* @static
*/
function getValue($param)
{
$tmp = explode('=', $param, 2);
$value = $tmp[1];
$value = trim($value);
if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
$value = substr($value, 1, -1);
}
$value = str_replace('\\"', '"', $value);
return $value;
}
/**
* Get a parameter comment
*
* @param string $param MIME type parameter
* @return string Parameter comment
* @see getComment()
* @static
*/
function getComment($param)
{
$cs = strpos($param, '(');
$comment = substr($param, $cs);
return trim($comment, '() ');
}
/**
* Does this parameter have a comment?
*
* @param string $param MIME type parameter
* @return boolean true if $param has a comment, false otherwise
* @static
*/
function hasComment($param)
{
if (strstr($param, '(')) {
return true;
}
return false;
}
/**
* Get a string representation of this parameter
*
* This function performs the oppsite of parse()
*
* @return string String representation of parameter
*/
function get()
{
$val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"';
if ($this->comment) {
$val .= ' (' . $this->comment . ')';
}
return $val;
}
}
?>

View File

@ -157,7 +157,7 @@ $.fn.ajaxSubmit = function(options) {
function fileUpload() { function fileUpload() {
var form = $form[0]; var form = $form[0];
if ($(':input[@name=submit]', form).length) { if ($(':input[name=submit]', form).length) {
alert('Error: Form elements must not be named "submit".'); alert('Error: Form elements must not be named "submit".');
return; return;
} }
@ -570,7 +570,7 @@ $.fn.clearForm = function() {
$.fn.clearFields = $.fn.clearInputs = function() { $.fn.clearFields = $.fn.clearInputs = function() {
return this.each(function() { return this.each(function() {
var t = this.type, tag = this.tagName.toLowerCase(); var t = this.type, tag = this.tagName.toLowerCase();
if (t == 'text' || t == 'password' || tag == 'textarea') if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea')
this.value = ''; this.value = '';
else if (t == 'checkbox' || t == 'radio') else if (t == 'checkbox' || t == 'radio')
this.checked = false; this.checked = false;

View File

@ -17,30 +17,6 @@
*/ */
$(document).ready(function(){ $(document).ready(function(){
$('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; });
$("a.thumbnail").hover(
function() {
var anchor = $(this);
$("a.thumbnail").children('img').remove();
setTimeout(function() {
anchor.closest(".entry-title").addClass('ov');
$.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
anchor.append(data);
});
}, 250);
setTimeout(function() {
anchor.children('img').remove();
anchor.closest(".entry-title").removeClass('ov');
}, 3000);
},
function() {
$(this).children('img').remove();
$(this).closest(".entry-title").removeClass('ov');
}
);
// count character on keyup // count character on keyup
function counter(event){ function counter(event){
var maxLength = 140; var maxLength = 140;
@ -227,6 +203,7 @@ $(document).ready(function(){
} }
} }
$("#notice_data-text").val(""); $("#notice_data-text").val("");
$("#notice_data-attach").val("");
counter(); counter();
} }
$("#form_notice").removeClass("processing"); $("#form_notice").removeClass("processing");
@ -238,6 +215,7 @@ $(document).ready(function(){
$("#form_notice").each(addAjaxHidden); $("#form_notice").each(addAjaxHidden);
NoticeHover(); NoticeHover();
NoticeReply(); NoticeReply();
NoticeAttachments();
}); });
@ -276,3 +254,53 @@ function NoticeReplySet(nick,id) {
} }
return true; return true;
} }
function NoticeAttachments() {
$.fn.jOverlay.options = {
method : 'GET',
data : '',
url : '',
color : '#000',
opacity : '0.6',
zIndex : 99,
center : true,
imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
bgClickToClose : true,
success : function() {
$('#jOverlayContent').append('<button>&#215;</button>');
$('#jOverlayContent button').click($.closeOverlay);
},
timeout : 0
};
$('a.attachment').click(function() {
$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
return false;
});
var t;
$("body:not(#shownotice) a.thumbnail").hover(
function() {
var anchor = $(this);
$("a.thumbnail").children('img').hide();
anchor.closest(".entry-title").addClass('ov');
if (anchor.children('img').length == 0) {
t = setTimeout(function() {
$.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
anchor.append(data);
});
}, 500);
}
else {
anchor.children('img').show();
}
},
function() {
clearTimeout(t);
$("a.thumbnail").children('img').hide();
$(this).closest(".entry-title").removeClass('ov');
}
);
}

View File

@ -79,20 +79,21 @@ class AttachmentList extends Widget
function show() function show()
{ {
$this->out->elementStart('dl', array('id' =>'attachment'));
$this->out->element('dt', null, _('Attachments'));
$this->out->elementStart('dd');
$this->out->elementStart('ul', array('class' => 'attachments'));
$atts = new File; $atts = new File;
$att = $atts->getAttachments($this->notice->id); $att = $atts->getAttachments($this->notice->id);
if (empty($att)) return 0;
$this->out->elementStart('dl', array('id' =>'attachments'));
$this->out->element('dt', null, _('Attachments'));
$this->out->elementStart('dd');
$this->out->elementStart('ol', array('class' => 'attachments'));
foreach ($att as $n=>$attachment) { foreach ($att as $n=>$attachment) {
$item = $this->newListItem($attachment); $item = $this->newListItem($attachment);
$item->show(); $item->show();
} }
$this->out->elementEnd('dd'); $this->out->elementEnd('dd');
$this->out->elementEnd('ul'); $this->out->elementEnd('ol');
$this->out->elementEnd('dl'); $this->out->elementEnd('dl');
return count($att); return count($att);
@ -266,6 +267,23 @@ class Attachment extends AttachmentListItem
case 'image/jpeg': case 'image/jpeg':
$this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt')); $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
break; break;
case 'application/ogg':
case 'audio/x-speex':
case 'video/mpeg':
case 'audio/mpeg':
case 'video/mp4':
case 'video/quicktime':
$arr = array('type' => $this->attachment->mimetype,
'data' => $this->attachment->url,
'width' => 320,
'height' => 240
);
$this->out->elementStart('object', $arr);
$this->out->element('param', array('name' => 'src', 'value' => $this->attachment->url));
$this->out->element('param', array('name' => 'autoStart', 'value' => 1));
$this->out->elementEnd('object');
break;
} }
} }
} else { } else {

View File

@ -163,6 +163,42 @@ $config =
array('run' => 'web', array('run' => 'web',
'frequency' => 10000, 'frequency' => 10000,
'reporturl' => 'http://laconi.ca/stats/report'), 'reporturl' => 'http://laconi.ca/stats/report'),
'attachments' =>
array('supported' => array('image/png',
'image/jpeg',
'image/gif',
'image/svg+xml',
'audio/mpeg',
'audio/x-speex',
'application/ogg',
'application/pdf',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.text-template',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.oasis.opendocument.graphics-template',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.presentation-template',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.spreadsheet-template',
'application/vnd.oasis.opendocument.chart',
'application/vnd.oasis.opendocument.chart-template',
'application/vnd.oasis.opendocument.image',
'application/vnd.oasis.opendocument.image-template',
'application/vnd.oasis.opendocument.formula',
'application/vnd.oasis.opendocument.formula-template',
'application/vnd.oasis.opendocument.text-master',
'application/vnd.oasis.opendocument.text-web',
'application/x-zip',
'application/zip',
'text/plain',
'video/mpeg',
'video/mp4',
'video/quicktime',
'video/mpeg'),
'file_quota' => 5000000,
'user_quota' => 50000000,
'monthly_quota' => 15000000,
),
); );
$config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@ -174,6 +210,7 @@ $config['db'] =
'require_prefix' => 'classes/', 'require_prefix' => 'classes/',
'class_prefix' => '', 'class_prefix' => '',
'mirror' => null, 'mirror' => null,
'utf8' => true,
'db_driver' => 'DB', # XXX: JanRain libs only work with DB 'db_driver' => 'DB', # XXX: JanRain libs only work with DB
'quote_identifiers' => false, 'quote_identifiers' => false,
'type' => 'mysql' ); 'type' => 'mysql' );
@ -223,19 +260,19 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
// XXX: how many of these could be auto-loaded on use? // XXX: how many of these could be auto-loaded on use?
require_once('Validate.php'); require_once 'Validate.php';
require_once('markdown.php'); require_once 'markdown.php';
require_once(INSTALLDIR.'/lib/util.php'); require_once INSTALLDIR.'/lib/util.php';
require_once(INSTALLDIR.'/lib/action.php'); require_once INSTALLDIR.'/lib/action.php';
require_once(INSTALLDIR.'/lib/theme.php'); require_once INSTALLDIR.'/lib/theme.php';
require_once(INSTALLDIR.'/lib/mail.php'); require_once INSTALLDIR.'/lib/mail.php';
require_once(INSTALLDIR.'/lib/subs.php'); require_once INSTALLDIR.'/lib/subs.php';
require_once(INSTALLDIR.'/lib/Shorturl_api.php'); require_once INSTALLDIR.'/lib/Shorturl_api.php';
require_once(INSTALLDIR.'/lib/twitter.php'); require_once INSTALLDIR.'/lib/twitter.php';
require_once(INSTALLDIR.'/lib/clientexception.php'); require_once INSTALLDIR.'/lib/clientexception.php';
require_once(INSTALLDIR.'/lib/serverexception.php'); require_once INSTALLDIR.'/lib/serverexception.php';
// XXX: other formats here // XXX: other formats here

View File

@ -646,48 +646,16 @@ class FacebookNoticeListItem extends NoticeListItem
function show() function show()
{ {
$this->showStart(); $this->showStart();
$this->showNotice();
$this->showNoticeInfo();
$this->out->elementStart('div', 'entry-title'); // XXX: Need to update to show attachements and controls
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
$this->showNoticeSource();
$this->showReplyTo();
$this->out->elementEnd('div');
$this->showEnd(); $this->showEnd();
} }
function showNoticeLink()
{
$noticeurl = common_local_url('shownotice',
array('notice' => $this->notice->id));
// XXX: we need to figure this out better. Is this right?
if (strcmp($this->notice->uri, $noticeurl) != 0 &&
preg_match('/^http/', $this->notice->uri)) {
$noticeurl = $this->notice->uri;
}
$this->out->elementStart('dl', 'timestamp');
$this->out->element('dt', null, _('Published'));
$this->out->elementStart('dd', null);
$this->out->elementStart('a', array('rel' => 'bookmark',
'href' => $noticeurl));
$dt = common_date_iso8601($this->notice->created);
$this->out->element('abbr', array('class' => 'published',
'title' => $dt),
common_date_string($this->notice->created));
$this->out->elementEnd('a');
$this->out->elementEnd('dd');
$this->out->elementEnd('dl');
}
} }
class FacebookProfileBoxNotice extends FacebookNoticeListItem class FacebookProfileBoxNotice extends FacebookNoticeListItem
{ {
@ -706,28 +674,16 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
/** /**
* Recipe function for displaying a single notice in the * Recipe function for displaying a single notice in the
* Facebook App's Profile * Facebook App profile notice box
* *
* @return void * @return void
*/ */
function show() function show()
{ {
$this->showNotice();
$this->out->elementStart('div', 'entry-title'); $this->showNoticeInfo();
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
$this->showNoticeSource();
$this->showReplyTo();
$this->out->elementEnd('div');
$this->showAppLink(); $this->showAppLink();
} }
function showAppLink() function showAppLink()

View File

@ -52,6 +52,8 @@ require_once INSTALLDIR.'/lib/widget.php';
class Form extends Widget class Form extends Widget
{ {
var $enctype = null;
/** /**
* Show the form * Show the form
* *
@ -63,11 +65,15 @@ class Form extends Widget
function show() function show()
{ {
$this->out->elementStart('form', $attributes = array('id' => $this->id(),
array('id' => $this->id(),
'class' => $this->formClass(), 'class' => $this->formClass(),
'method' => 'post', 'method' => 'post',
'action' => $this->action())); 'action' => $this->action());
if (!empty($this->enctype)) {
$attributes['enctype'] = $this->enctype;
}
$this->out->elementStart('form', $attributes);
$this->out->elementStart('fieldset'); $this->out->elementStart('fieldset');
$this->formLegend(); $this->formLegend();
$this->sessionToken(); $this->sessionToken();

View File

@ -335,6 +335,7 @@ function mail_broadcast_notice_sms($notice)
"FROM $UT JOIN subscription " . "FROM $UT JOIN subscription " .
"ON $UT.id = subscription.subscriber " . "ON $UT.id = subscription.subscriber " .
'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' . 'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
'AND subscription.subscribed != subscription.subscriber ' .
"AND $UT.smsemail IS NOT null " . "AND $UT.smsemail IS NOT null " .
"AND $UT.smsnotify = 1 " . "AND $UT.smsnotify = 1 " .
'AND subscription.sms = 1 '); 'AND subscription.sms = 1 ');

View File

@ -90,6 +90,7 @@ class NoticeForm extends Form
$this->user = common_current_user(); $this->user = common_current_user();
} }
$this->enctype = 'multipart/form-data';
} }
/** /**
@ -142,17 +143,21 @@ class NoticeForm extends Form
'rows' => 4, 'rows' => 4,
'name' => 'status_textarea'), 'name' => 'status_textarea'),
($this->content) ? $this->content : ''); ($this->content) ? $this->content : '');
$this->out->elementStart('dl', 'form_note'); $this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters')); $this->out->element('dt', null, _('Available characters'));
$this->out->element('dd', array('id' => 'notice_text-count'), $this->out->element('dd', array('id' => 'notice_text-count'),
'140'); '140');
$this->out->elementEnd('dl'); $this->out->elementEnd('dl');
$this->out->element('label', array('for' => 'notice_data-attach'), _('Attach'));
$this->out->element('input', array('id' => 'notice_data-attach',
'type' => 'file',
'name' => 'attach',
'title' => _('Attach a file')));
if ($this->action) { if ($this->action) {
$this->out->hidden('notice_return-to', $this->action, 'returnto'); $this->out->hidden('notice_return-to', $this->action, 'returnto');
} }
$this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto'); $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto');
$this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
} }
/** /**

View File

@ -85,7 +85,7 @@ class NoticeList extends Widget
{ {
$this->out->elementStart('div', array('id' =>'notices_primary')); $this->out->elementStart('div', array('id' =>'notices_primary'));
$this->out->element('h2', null, _('Notices')); $this->out->element('h2', null, _('Notices'));
$this->out->elementStart('ul', array('class' => 'notices')); $this->out->elementStart('ol', array('class' => 'notices xoxo'));
$cnt = 0; $cnt = 0;
@ -100,7 +100,7 @@ class NoticeList extends Widget
$item->show(); $item->show();
} }
$this->out->elementEnd('ul'); $this->out->elementEnd('ol');
$this->out->elementEnd('div'); $this->out->elementEnd('div');
return $cnt; return $cnt;
@ -205,6 +205,7 @@ class NoticeListItem extends Widget
return 'shownotice' !== $this->out->args['action']; return 'shownotice' !== $this->out->args['action'];
} }
/*
function attachmentCount($discriminant = true) { function attachmentCount($discriminant = true) {
$file_oembed = new File_oembed; $file_oembed = new File_oembed;
$query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id; $query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id;
@ -212,11 +213,16 @@ class NoticeListItem extends Widget
$file_oembed->fetch(); $file_oembed->fetch();
return intval($file_oembed->c); return intval($file_oembed->c);
} }
*/
function showWithAttachment() {
}
function showNoticeInfo() function showNoticeInfo()
{ {
$this->out->elementStart('div', 'entry-content'); $this->out->elementStart('div', 'entry-content');
$this->showNoticeLink(); $this->showNoticeLink();
// $this->showWithAttachment();
$this->showNoticeSource(); $this->showNoticeSource();
$this->showContext(); $this->showContext();
$this->out->elementEnd('div'); $this->out->elementEnd('div');
@ -357,6 +363,10 @@ class NoticeListItem extends Widget
// versions (>> 0.4.x) // versions (>> 0.4.x)
$this->out->raw(common_render_content($this->notice->content, $this->notice)); $this->out->raw(common_render_content($this->notice->content, $this->notice));
} }
$uploaded = $this->notice->getUploadedAttachment();
if ($uploaded) {
$this->out->element('a', array('href' => $uploaded, 'class' => 'attachment'), $uploaded);
}
$this->out->elementEnd('p'); $this->out->elementEnd('p');
} }
@ -387,6 +397,7 @@ class NoticeListItem extends Widget
$this->out->element('abbr', array('class' => 'published', $this->out->element('abbr', array('class' => 'published',
'title' => $dt), 'title' => $dt),
common_date_string($this->notice->created)); common_date_string($this->notice->created));
$this->out->elementEnd('a'); $this->out->elementEnd('a');
$this->out->elementEnd('dd'); $this->out->elementEnd('dd');
$this->out->elementEnd('dl'); $this->out->elementEnd('dl');

View File

@ -52,12 +52,12 @@ class NoticeSection extends Section
{ {
$notices = $this->getNotices(); $notices = $this->getNotices();
$cnt = 0; $cnt = 0;
$this->out->elementStart('ul', 'notices'); $this->out->elementStart('ol', 'notices xoxo');
while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) { while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
$this->showNotice($notices); $this->showNotice($notices);
} }
$this->out->elementEnd('ul'); $this->out->elementEnd('ol');
return ($cnt > NOTICES_PER_SECTION); return ($cnt > NOTICES_PER_SECTION);
} }

View File

@ -159,13 +159,9 @@ function omb_post_notice($notice, $remote_profile, $subscription)
function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret) function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
{ {
common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
$user = User::staticGet('id', $notice->profile_id); $user = User::staticGet('id', $notice->profile_id);
if (!$user) { if (!$user) {
common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
return false; return false;
} }
@ -208,8 +204,6 @@ function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
$req->to_postdata(), $req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION)); array('User-Agent: Laconica/' . LACONICA_VERSION));
common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
if ($result->status == 403) { # not authorized, don't send again if ($result->status == 403) { # not authorized, don't send again
common_debug('403 result, deleting subscription', __FILE__); common_debug('403 result, deleting subscription', __FILE__);
# FIXME: figure out how to delete this # FIXME: figure out how to delete this
@ -286,14 +280,10 @@ function omb_update_profile($profile, $remote_profile, $subscription)
$fetcher = Auth_Yadis_Yadis::getHTTPFetcher(); $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__);
common_debug('postdata = '.$req->to_postdata(), __FILE__);
$result = $fetcher->post($req->get_normalized_http_url(), $result = $fetcher->post($req->get_normalized_http_url(),
$req->to_postdata(), $req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION)); array('User-Agent: Laconica/' . LACONICA_VERSION));
common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
if (empty($result) || !$result) { if (empty($result) || !$result) {
common_debug("Unable to contact " . $req->get_normalized_http_url()); common_debug("Unable to contact " . $req->get_normalized_http_url());
} else if ($result->status == 403) { # not authorized, don't send again } else if ($result->status == 403) { # not authorized, don't send again

View File

@ -164,6 +164,10 @@ class Router
array('action' => 'newnotice'), array('action' => 'newnotice'),
array('replyto' => '[A-Za-z0-9_-]+')); array('replyto' => '[A-Za-z0-9_-]+'));
$m->connect('notice/:notice/file',
array('action' => 'file'),
array('notice' => '[0-9]+'));
$m->connect('notice/:notice', $m->connect('notice/:notice',
array('action' => 'shownotice'), array('action' => 'shownotice'),
array('notice' => '[0-9]+')); array('notice' => '[0-9]+'));

View File

@ -73,21 +73,82 @@ class TwitterapiAction extends Action
function twitter_user_array($profile, $get_notice=false) function twitter_user_array($profile, $get_notice=false)
{ {
$twitter_user = array(); $twitter_user = array();
$twitter_user['name'] = $profile->getBestName();
$twitter_user['followers_count'] = $this->count_subscriptions($profile);
$twitter_user['screen_name'] = $profile->nickname;
$twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
$twitter_user['location'] = ($profile->location) ? $profile->location : null;
$twitter_user['id'] = intval($profile->id); $twitter_user['id'] = intval($profile->id);
$twitter_user['name'] = $profile->getBestName();
$twitter_user['screen_name'] = $profile->nickname;
$twitter_user['location'] = ($profile->location) ? $profile->location : null;
$twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
$avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
$twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
Avatar::defaultImage(AVATAR_STREAM_SIZE);
$twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
$twitter_user['protected'] = false; # not supported by Laconica yet
$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['followers_count'] = $this->count_subscriptions($profile);
// To be supported soon...
$twitter_user['profile_background_color'] = '';
$twitter_user['profile_text_color'] = '';
$twitter_user['profile_link_color'] = '';
$twitter_user['profile_sidebar_fill_color'] = '';
$twitter_user['profile_sidebar_border_color'] = '';
$subbed = DB_DataObject::factory('subscription');
$subbed->subscriber = $profile->id;
$subbed_count = (int) $subbed->count() - 1;
$twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
$twitter_user['created_at'] = $this->date_twitter($profile->created);
$faves = DB_DataObject::factory('fave');
$faves->user_id = $user->id;
$faves_count = (int) $faves->count();
$twitter_user['favourites_count'] = $faves_count; // British spelling!
// Need to pull up the user for some of this
$user = User::staticGet($profile->id);
$timezone = 'UTC';
if ($user->timezone) {
$timezone = $user->timezone;
}
$t = new DateTime;
$t->setTimezone(new DateTimeZone($timezone));
$twitter_user['utc_offset'] = $t->format('Z');
$twitter_user['time_zone'] = $timezone;
// To be supported some day, perhaps
$twitter_user['profile_background_image_url'] = '';
$twitter_user['profile_background_tile'] = false;
$notices = DB_DataObject::factory('notice');
$notices->profile_id = $profile->id;
$notice_count = (int) $notices->count();
$twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
// Is the requesting user following this user?
$twitter_user['following'] = false;
$twitter_user['notifications'] = false;
if (isset($apidata['user'])) {
$twitter_user['following'] = $apidata['user']->isSubscribed($profile);
// Notifications on?
$sub = Subscription::pkeyGet(array('subscriber' =>
$apidata['user']->id, 'subscribed' => $profile->id));
if ($sub) {
$twitter_user['notifications'] = ($sub->jabber || $sub->sms);
}
}
if ($get_notice) { if ($get_notice) {
$notice = $profile->getCurrentNotice(); $notice = $profile->getCurrentNotice();
@ -612,7 +673,27 @@ class TwitterapiAction extends Action
function get_user($id, $apidata=null) function get_user($id, $apidata=null)
{ {
if (!$id) { if (!$id) {
// Twitter supports these other ways of passing the user ID
if (is_numeric($this->arg('id'))) {
return User::staticGet($this->arg('id'));
} else if ($this->arg('id')) {
$nickname = common_canonical_nickname($this->arg('id'));
return User::staticGet('nickname', $nickname);
} else if ($this->arg('user_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('user_id'))) {
return User::staticGet('id', $this->arg('user_id'));
}
} else if ($this->arg('screen_name')) {
$nickname = common_canonical_nickname($this->arg('screen_name'));
return User::staticGet('nickname', $nickname);
} else {
// Fall back to trying the currently authenticated user
return $apidata['user']; return $apidata['user'];
}
} else if (is_numeric($id)) { } else if (is_numeric($id)) {
return User::staticGet($id); return User::staticGet($id);
} else { } else {

View File

@ -499,6 +499,11 @@ function common_linkify($url) {
// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID' // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
// where ID is the id of the attachment for the given URL. // where ID is the id of the attachment for the given URL.
//
// we need a better test telling what can be shown as an attachment
// we're currently picking up oembeds only.
// I think the best option is another file_view table in the db
// and associated dbobject.
$query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'"; $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
$file = new File; $file = new File;
$file->query($query); $file->query($query);
@ -895,6 +900,34 @@ function common_enqueue_notice($notice)
return $result; return $result;
} }
function common_post_inbox_transports()
{
$transports = array('omb', 'sms');
if (common_config('xmpp', 'enabled')) {
$transports = array_merge($transports, array('jabber', 'public'));
}
return $transports;
}
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);
>>>>>>> 0.7.x:lib/util.php
}
common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
return true;
}
function common_real_broadcast($notice, $remote=false) function common_real_broadcast($notice, $remote=false)
{ {
$success = true; $success = true;

View File

@ -0,0 +1,73 @@
<?php
if (!defined('LACONICA')) {
exit(1);
}
/*
* Generates the cross domain communication channel file
* (xd_receiver.html). By generating it we can add some caching
* instructions.
*
* See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
*/
class FBC_XDReceiverAction extends Action
{
/**
* Do we need to write to the database?
*
* @return boolean true
*/
function isReadonly()
{
return true;
}
/**
* Handle a request
*
* @param array $args Arguments from $_REQUEST
*
* @return void
*/
function handle($args)
{
// Parent handling, including cache check
parent::handle($args);
$this->showPage();
}
function showPage()
{
// cache the xd_receiver
header('Cache-Control: max-age=225065900');
header('Expires:');
header('Pragma:');
$this->startXML('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
$language = $this->getLanguage();
$this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xml:lang' => $language,
'lang' => $language));
$this->elementStart('head');
$this->element('title', null, 'cross domain receiver page');
$this->element('script',
array('src' =>
'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js',
'type' => 'text/javascript'), '');
$this->elementEnd('head');
$this->elementStart('body');
$this->elementEnd('body');
$this->elementEnd('html');
}
}

View File

@ -62,7 +62,28 @@ class FBConnectauthAction extends Action
parent::handle($args); parent::handle($args);
if (common_is_real_login()) { if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
// User is already logged in. Does she already have a linked Facebook acct?
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
if ($flink) {
// User already has a linked Facebook account and shouldn't be here
common_debug('There is already a local user (' . $flink->user_id .
') linked with this Facebook (' . $this->fbuid . ').');
// We don't want these cookies
getFacebook()->clear_cookie_state();
$this->clientError(_('There is already a local user linked with this Facebook.'));
} else {
// User came from the Facebook connect settings tab, and
// probably just wants to link/relink their Facebook account
$this->connectUser();
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') { } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token'); $token = $this->trimmed('token');
@ -78,7 +99,7 @@ class FBConnectauthAction extends Action
} }
$this->createNewUser(); $this->createNewUser();
} else if ($this->arg('connect')) { } else if ($this->arg('connect')) {
$this->connectUser(); $this->connectNewUser();
} else { } else {
common_debug(print_r($this->args, true), __FILE__); common_debug(print_r($this->args, true), __FILE__);
$this->showForm(_('Something weird happened.'), $this->showForm(_('Something weird happened.'),
@ -259,7 +280,7 @@ class FBConnectauthAction extends Action
303); 303);
} }
function connectUser() function connectNewUser()
{ {
$nickname = $this->trimmed('nickname'); $nickname = $this->trimmed('nickname');
$password = $this->trimmed('password'); $password = $this->trimmed('password');
@ -290,6 +311,23 @@ class FBConnectauthAction extends Action
$this->goHome($user->nickname); $this->goHome($user->nickname);
} }
function connectUser()
{
$user = common_current_user();
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug("Connected Facebook user $this->fbuid to local user $user->id");
// Return to Facebook connection settings tab
common_redirect(common_local_url('FBConnectSettings'), 303);
}
function tryLogin() function tryLogin()
{ {
common_debug("Trying Facebook Login..."); common_debug("Trying Facebook Login...");

View File

@ -58,8 +58,6 @@ class FBConnectLoginAction extends Action
function showContent() { function showContent() {
$this->elementStart('fieldset'); $this->elementStart('fieldset');
$this->element('fb:login-button', array('onlogin' => 'goto_login()', $this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long')); 'length' => 'long'));

View File

@ -39,6 +39,7 @@ require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php'; require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php'; require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php'; require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
/** /**
* Plugin to enable Facebook Connect * Plugin to enable Facebook Connect
@ -62,27 +63,19 @@ class FBConnectPlugin extends Plugin
$m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
$m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
$m->connect('settings/facebook', array('action' => 'FBConnectSettings')); $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
$m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
} }
// Add in xmlns:fb // Add in xmlns:fb
function onStartShowHTML($action) function onStartShowHTML($action)
{ {
$httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? // XXX: Horrible hack to make Safari, FF2, and Chrome work with
$_SERVER['HTTP_ACCEPT'] : null; // Facebook Connect. These browser cannot use Facebook's
// DOM parsing routines unless the mime type of the page is
// XXX: allow content negotiation for RDF, RSS, or XRDS // text/html even though Facebook Connect uses XHTML. This is
// A bug in Facebook Connect, and this is a temporary solution
$cp = common_accept_to_prefs($httpaccept); // until they fix their JavaScript libs.
$sp = common_accept_to_prefs(PAGE_TYPE_PREFS); header('Content-Type: text/html');
$type = common_negotiate_type($cp, $sp);
if (!$type) {
throw new ClientException(_('This page is not available in a '.
'media type you accept'), 406);
}
header('Content-Type: '.$type);
$action->extraHeaders(); $action->extraHeaders();
@ -101,20 +94,27 @@ class FBConnectPlugin extends Plugin
return false; return false;
} }
function onEndShowLaconicaScripts($action) // Note: this script needs to appear in the <body>
{
$action->element('script',
array('type' => 'text/javascript',
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
' ');
function onStartShowHeader($action)
{
$apikey = common_config('facebook', 'apikey'); $apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect'); $plugin_path = common_path('plugins/FBConnect');
$login_url = common_local_url('FBConnectAuth'); $login_url = common_local_url('FBConnectAuth');
$logout_url = common_local_url('logout'); $logout_url = common_local_url('logout');
$html = sprintf('<script type="text/javascript">FB.init("%s", "%s/xd_receiver.htm"); // XXX: Facebook says we don't need this FB_RequireFeatures(),
// but we actually do, for IE and Safari. Gar.
$html = sprintf('<script type="text/javascript">
window.onload = function () {
FB_RequireFeatures(
["XFBML"],
function() {
FB.Facebook.init("%s", "../xd_receiver.html");
}
); }
function goto_login() { function goto_login() {
window.location = "%s"; window.location = "%s";
@ -123,13 +123,23 @@ class FBConnectPlugin extends Plugin
function goto_logout() { function goto_logout() {
window.location = "%s"; window.location = "%s";
} }
</script>', $apikey,
</script>', $apikey, $plugin_path, $login_url, $logout_url); $login_url, $logout_url);
$action->raw($html); $action->raw($html);
} }
// Note: this script needs to appear as close as possible to </body>
function onEndShowFooter($action)
{
$action->element('script',
array('type' => 'text/javascript',
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
'');
}
function onEndShowLaconicaStyles($action) function onEndShowLaconicaStyles($action)
{ {
$action->element('link', array('rel' => 'stylesheet', $action->element('link', array('rel' => 'stylesheet',
@ -143,24 +153,8 @@ class FBConnectPlugin extends Plugin
if ($user) { if ($user) {
$action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), $flink = Foreign_link::getByUserId($user->id,
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); FACEBOOK_CONNECT_SERVICE);
$action->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$action->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
} else {
$action->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
$action->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
$flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
$fbuid = 0; $fbuid = 0;
if ($flink) { if ($flink) {
@ -195,9 +189,25 @@ class FBConnectPlugin extends Plugin
} }
} }
// Need to override the Logout link to make it do FB stuff $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
$action->menuItem(common_local_url('profilesettings'),
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
if (common_config('xmpp', 'enabled')) {
$action->menuItem(common_local_url('imsettings'),
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
} else {
$action->menuItem(common_local_url('smssettings'),
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
$action->menuItem(common_local_url('invite'),
_('Invite'),
sprintf(_('Invite friends and colleagues to join you on %s'),
common_config('site', 'name')),
false, 'nav_invitecontact');
if ($fbuid > 0) { // Need to override the Logout link to make it do FB stuff
if ($flink && $fbuid > 0) {
$logout_url = common_local_url('logout'); $logout_url = common_local_url('logout');
$title = _('Logout from the site'); $title = _('Logout from the site');
@ -258,11 +268,19 @@ class FBConnectPlugin extends Plugin
return true; return true;
} }
function onEndLogout($action) function onStartLogout($action)
{ {
try { $user = common_current_user();
$flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
$action->logout();
if ($flink) {
$facebook = getFacebook(); $facebook = getFacebook();
try {
$fbuid = $facebook->get_loggedin_user(); $fbuid = $facebook->get_loggedin_user();
if ($fbuid > 0) { if ($fbuid > 0) {
@ -275,4 +293,7 @@ class FBConnectPlugin extends Plugin
} }
} }
return true;
}
} }

View File

@ -78,41 +78,40 @@ class FBConnectSettingsAction extends ConnectSettingsAction
function showContent() function showContent()
{ {
$user = common_current_user(); $user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
if (!$flink) {
$this->element('p', 'form_note',
_('There is no Facebook user connected to this account.'));
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
return;
}
$this->element('p', 'form_note',
_('Connected Facebook user:'));
$this->elementStart('p', array('class' => 'facebook-user-display'));
$this->elementStart('fb:profile-pic',
array('uid' => $flink->foreign_id,
'size' => 'square',
'linked' => 'true',
'facebook-logo' => 'true'));
$this->elementEnd('fb:profile-pic');
$this->elementStart('fb:name', array('uid' => $flink->foreign_id));
$this->elementEnd('fb:name');
$this->elementEnd('p');
$this->elementStart('form', array('method' => 'post', $this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_facebook', 'id' => 'form_settings_facebook',
'class' => 'form_settings', 'class' => 'form_settings',
'action' => 'action' =>
common_local_url('FBConnectSettings'))); common_local_url('FBConnectSettings')));
if (!$flink) {
$this->element('p', 'instructions',
_('There is no Facebook user connected to this account.'));
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
} else {
$this->element('p', 'form_note',
_('Connected Facebook user'));
$this->elementStart('p', array('class' => 'facebook-user-display'));
$this->elementStart('fb:profile-pic',
array('uid' => $flink->foreign_id,
'size' => 'small',
'linked' => 'true',
'facebook-logo' => 'true'));
$this->elementEnd('fb:profile-pic');
$this->elementStart('fb:name', array('uid' => $flink->foreign_id,
'useyou' => 'false'));
$this->elementEnd('fb:name');
$this->elementEnd('p');
$this->hidden('token', common_session_token()); $this->hidden('token', common_session_token());
$this->elementStart('fieldset'); $this->elementStart('fieldset');
@ -131,10 +130,21 @@ class FBConnectSettingsAction extends ConnectSettingsAction
$this->text(_(' first.')); $this->text(_(' first.'));
$this->elementEnd('p'); $this->elementEnd('p');
} else { } else {
$note = 'Keep your %s account but disconnect from Facebook. ' .
'You\'ll use your %s password to log in.';
$site = common_config('site', 'name');
$this->element('p', 'instructions',
sprintf($note, $site, $site));
$this->submit('disconnect', _('Disconnect')); $this->submit('disconnect', _('Disconnect'));
} }
$this->elementEnd('fieldset'); $this->elementEnd('fieldset');
}
$this->elementEnd('form'); $this->elementEnd('form');
} }
@ -171,8 +181,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction
try { try {
// XXX: not sure what exactly to do here // Clear FB Connect cookies out
$facebook = getFacebook(); $facebook = getFacebook();
$facebook->clear_cookie_state(); $facebook->clear_cookie_state();
@ -182,7 +191,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction
$e->getMessage()); $e->getMessage());
} }
$this->showForm(_('Facebook user disconnected.'), true); $this->showForm(_('You have disconnected from Facebook.'), true);
} else { } else {
$this->showForm(_('Not sure what you\'re trying to do.')); $this->showForm(_('Not sure what you\'re trying to do.'));

View File

@ -1,10 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>cross domain receiver page</title>
</head>
<body>
<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js" type="text/javascript"></script>
</body>
</html>

368
scripts/fixup_utf8.php Normal file
View File

@ -0,0 +1,368 @@
#!/usr/bin/env php
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 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(1);
}
ini_set("max_execution_time", "0");
ini_set("max_input_time", "0");
set_time_limit(0);
mb_internal_encoding('UTF-8');
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('LACONICA', true);
require_once(INSTALLDIR . '/lib/common.php');
require_once('DB.php');
class UTF8FixerUpper
{
var $dbl = null;
var $dbu = null;
var $args = array();
function __construct($args)
{
$this->args = $args;
if (array_key_exists('max_date', $args)) {
$this->max_date = strftime('%Y-%m-%d %H:%M:%S', strtotime($args['max_date']));
} else {
$this->max_date = strftime('%Y-%m-%d %H:%M:%S', time());
}
$this->dbl = $this->doConnect('latin1');
if (empty($this->dbl)) {
return;
}
$this->dbu = $this->doConnect('utf8');
if (empty($this->dbu)) {
return;
}
}
function doConnect($charset)
{
$db = DB::connect(common_config('db', 'database'),
array('persistent' => false));
if (PEAR::isError($db)) {
echo "ERROR: " . $db->getMessage() . "\n";
return NULL;
}
$conn = $db->connection;
$succ = mysqli_set_charset($conn, $charset);
if (!$succ) {
echo "ERROR: couldn't set charset\n";
$db->disconnect();
return NULL;
}
$result = $db->autoCommit(true);
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
$db->disconnect();
return NULL;
}
return $db;
}
function fixup()
{
$this->fixupNotices($this->args['max_notice'],
$this->args['min_notice']);
$this->fixupProfiles();
$this->fixupGroups();
$this->fixupMessages();
}
function fixupNotices($max_id, $min_id) {
// Do a separate DB connection
$sth = $this->dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
if (PEAR::isError($sth)) {
echo "ERROR: " . $sth->getMessage() . "\n";
return;
}
$sql = 'SELECT id, content, rendered FROM notice ' .
'WHERE LENGTH(content) != CHAR_LENGTH(content) '.
'AND modified < "'.$this->max_date.'" ';
if (!empty($max_id)) {
$sql .= ' AND id <= ' . $max_id;
}
if (!empty($min_id)) {
$sql .= ' AND id >= ' . $min_id;
}
$sql .= ' ORDER BY id DESC';
$rn = $this->dbl->query($sql);
if (PEAR::isError($rn)) {
echo "ERROR: " . $rn->getMessage() . "\n";
return;
}
echo "Number of rows: " . $rn->numRows() . "\n";
$notice = array();
while (DB_OK == $rn->fetchInto($notice)) {
$id = ($notice[0])+0;
$content = bin2hex($notice[1]);
$rendered = bin2hex($notice[2]);
echo "$id...";
$result =& $this->dbu->execute($sth, array($content, $rendered, $id));
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
continue;
}
$cnt = $this->dbu->affectedRows();
if ($cnt != 1) {
echo "ERROR: 0 rows affected\n";
continue;
}
$notice = Notice::staticGet('id', $id);
$notice->decache();
$notice->free();
echo "OK\n";
}
}
function fixupProfiles()
{
// Do a separate DB connection
$sth = $this->dbu->prepare("UPDATE profile SET ".
"fullname = UNHEX(?),".
"location = UNHEX(?), ".
"bio = UNHEX(?) ".
"WHERE id = ?");
if (PEAR::isError($sth)) {
echo "ERROR: " . $sth->getMessage() . "\n";
return;
}
$sql = 'SELECT id, fullname, location, bio FROM profile ' .
'WHERE (LENGTH(fullname) != CHAR_LENGTH(fullname) '.
'OR LENGTH(location) != CHAR_LENGTH(location) '.
'OR LENGTH(bio) != CHAR_LENGTH(bio)) '.
'AND modified < "'.$this->max_date.'" '.
' ORDER BY modified DESC';
$rn = $this->dbl->query($sql);
if (PEAR::isError($rn)) {
echo "ERROR: " . $rn->getMessage() . "\n";
return;
}
echo "Number of rows: " . $rn->numRows() . "\n";
$profile = array();
while (DB_OK == $rn->fetchInto($profile)) {
$id = ($profile[0])+0;
$fullname = bin2hex($profile[1]);
$location = bin2hex($profile[2]);
$bio = bin2hex($profile[3]);
echo "$id...";
$result =& $this->dbu->execute($sth, array($fullname, $location, $bio, $id));
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
continue;
}
$cnt = $this->dbu->affectedRows();
if ($cnt != 1) {
echo "ERROR: 0 rows affected\n";
continue;
}
$profile = Profile::staticGet('id', $id);
$profile->decache();
$profile->free();
echo "OK\n";
}
}
function fixupGroups()
{
// Do a separate DB connection
$sth = $this->dbu->prepare("UPDATE user_group SET ".
"fullname = UNHEX(?),".
"location = UNHEX(?), ".
"description = UNHEX(?) ".
"WHERE id = ?");
if (PEAR::isError($sth)) {
echo "ERROR: " . $sth->getMessage() . "\n";
return;
}
$sql = 'SELECT id, fullname, location, description FROM user_group ' .
'WHERE LENGTH(fullname) != CHAR_LENGTH(fullname) '.
'OR LENGTH(location) != CHAR_LENGTH(location) '.
'OR LENGTH(description) != CHAR_LENGTH(description) ';
'AND modified < "'.$this->max_date.'" '.
'ORDER BY modified DESC';
$rn = $this->dbl->query($sql);
if (PEAR::isError($rn)) {
echo "ERROR: " . $rn->getMessage() . "\n";
return;
}
echo "Number of rows: " . $rn->numRows() . "\n";
$user_group = array();
while (DB_OK == $rn->fetchInto($user_group)) {
$id = ($user_group[0])+0;
$fullname = bin2hex($user_group[1]);
$location = bin2hex($user_group[2]);
$description = bin2hex($user_group[3]);
echo "$id...";
$result =& $this->dbu->execute($sth, array($fullname, $location, $description, $id));
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
continue;
}
$cnt = $this->dbu->affectedRows();
if ($cnt != 1) {
echo "ERROR: 0 rows affected\n";
continue;
}
$user_group = User_group::staticGet('id', $id);
$user_group->decache();
$user_group->free();
echo "OK\n";
}
}
function fixupMessages() {
// Do a separate DB connection
$sth = $this->dbu->prepare("UPDATE message SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
if (PEAR::isError($sth)) {
echo "ERROR: " . $sth->getMessage() . "\n";
return;
}
$sql = 'SELECT id, content, rendered FROM message ' .
'WHERE LENGTH(content) != CHAR_LENGTH(content) '.
'AND modified < "'.$this->max_date.'" '.
'ORDER BY id DESC';
$rn = $this->dbl->query($sql);
if (PEAR::isError($rn)) {
echo "ERROR: " . $rn->getMessage() . "\n";
return;
}
echo "Number of rows: " . $rn->numRows() . "\n";
$message = array();
while (DB_OK == $rn->fetchInto($message)) {
$id = ($message[0])+0;
$content = bin2hex($message[1]);
$rendered = bin2hex($message[2]);
echo "$id...";
$result =& $this->dbu->execute($sth, array($content, $rendered, $id));
if (PEAR::isError($result)) {
echo "ERROR: " . $result->getMessage() . "\n";
continue;
}
$cnt = $this->dbu->affectedRows();
if ($cnt != 1) {
echo "ERROR: 0 rows affected\n";
continue;
}
$message = Message::staticGet('id', $id);
$message->decache();
$message->free();
echo "OK\n";
}
}
}
$max_date = ($argc > 1) ? $argv[1] : null;
$max_id = ($argc > 2) ? $argv[2] : null;
$min_id = ($argc > 3) ? $argv[3] : null;
$fixer = new UTF8FixerUpper(array('max_date' => $max_date,
'max_notice' => $max_id,
'min_notice' => $min_id));
$fixer->fixup();

View File

@ -25,7 +25,6 @@
* daemon names. * daemon names.
*/ */
# Abort if called from a web server # Abort if called from a web server
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n"; print "This script must be run from the command line\n";
@ -51,5 +50,4 @@ echo "ombqueuehandler.php ";
echo "twitterqueuehandler.php "; echo "twitterqueuehandler.php ";
echo "facebookqueuehandler.php "; echo "facebookqueuehandler.php ";
echo "pingqueuehandler.php "; echo "pingqueuehandler.php ";
echo "inboxqueuehandler.php ";
echo "smsqueuehandler.php "; echo "smsqueuehandler.php ";

View File

@ -1,69 +0,0 @@
#!/usr/bin/env php
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008,2009 Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Abort if called from a web server
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit();
}
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('LACONICA', true);
require_once(INSTALLDIR . '/lib/common.php');
require_once(INSTALLDIR . '/lib/queuehandler.php');
set_error_handler('common_error_handler');
class InboxQueueHandler extends QueueHandler
{
function transport()
{
return 'inbox';
}
function start() {
$this->log(LOG_INFO, "INITIALIZE");
return true;
}
function handle_notice($notice)
{
$this->log(LOG_INFO, "Distributing notice to inboxes for $notice->id");
$notice->addToInboxes();
$notice->blowSubsCache();
return true;
}
function finish() {
}
}
ini_set("max_execution_time", "0");
ini_set("max_input_time", "0");
set_time_limit(0);
mb_internal_encoding('UTF-8');
$id = ($argc > 1) ? $argv[1] : null;
$handler = new InboxQueueHandler($id);
$handler->runOnce();

View File

@ -1,70 +0,0 @@
#!/usr/bin/env php
<?php
/*
* Laconica - a distributed open-source microblogging tool
* Copyright (C) 2008,2009 Control Yourself, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Abort if called from a web server
if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
print "This script must be run from the command line\n";
exit();
}
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
define('LACONICA', true);
require_once(INSTALLDIR . '/lib/common.php');
require_once(INSTALLDIR . '/lib/queuehandler.php');
set_error_handler('common_error_handler');
class MemcachedQueueHandler extends QueueHandler
{
function transport()
{
return 'memcache';
}
function start() {
$this->log(LOG_INFO, "INITIALIZE");
return true;
}
function handle_notice($notice)
{
// XXX: fork here
$this->log(LOG_INFO, "Blowing memcached for $notice->id");
$notice->blowCaches();
return true;
}
function finish() {
}
}
ini_set("max_execution_time", "0");
ini_set("max_input_time", "0");
set_time_limit(0);
mb_internal_encoding('UTF-8');
$id = ($argc > 1) ? $argv[1] : null;
$handler = new MemcachedQueueHandler($id);
$handler->runOnce();

View File

@ -24,8 +24,7 @@ SDIR=`dirname $0`
DIR=`php $SDIR/getpiddir.php` DIR=`php $SDIR/getpiddir.php`
for f in jabberhandler ombhandler publichandler smshandler pinghandler \ for f in jabberhandler ombhandler publichandler smshandler pinghandler \
xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \ xmppconfirmhandler xmppdaemon twitterhandler facebookhandler; do
memcachehandler inboxhandler twitterstatusfetcher; do
FILES="$DIR/$f.*.pid" FILES="$DIR/$f.*.pid"
for ff in "$FILES" ; do for ff in "$FILES" ; do

View File

@ -452,6 +452,21 @@ float:left;
font-size:1.3em; font-size:1.3em;
margin-bottom:7px; margin-bottom:7px;
} }
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
}
#form_notice label[for=notice_data-attach],
#form_notice #notice_data-attach {
position:absolute;
top:25px;
right:49px;
width:16px;
height:16px;
cursor:pointer;
}
#form_notice #notice_data-attach {
text-indent:-279px;
}
#form_notice #notice_submit label { #form_notice #notice_submit label {
display:none; display:none;
} }
@ -855,20 +870,6 @@ display:inline-block;
text-transform:lowercase; text-transform:lowercase;
} }
.notice .attachment {
position:relative;
}
.notice .attachment img {
position:absolute;
top:18px;
left:0;
z-index:99;
}
#shownotice .notice .attachment img {
position:static;
}
.notice-options { .notice-options {
position:relative; position:relative;
font-size:0.95em; font-size:0.95em;
@ -936,6 +937,75 @@ padding:0;
} }
.notice .attachment {
position:relative;
padding-left:16px;
}
#attachments .attachment {
padding-left:0;
}
.notice .attachment img {
position:absolute;
top:18px;
left:0;
z-index:99;
}
#shownotice .notice .attachment img {
position:static;
}
#attachments {
clear:both;
float:left;
width:100%;
margin-top:18px;
}
#attachments dt {
font-weight:bold;
font-size:1.3em;
margin-bottom:4px;
}
#attachments ol li {
margin-bottom:18px;
list-style-type:decimal;
float:left;
clear:both;
}
#jOverlayContent,
#jOverlayContent #content,
#jOverlayContent #content_inner {
width: auto !important;
margin-bottom:0;
}
#jOverlayContent #content {
padding:11px;
min-height:auto;
}
#jOverlayContent .external span {
display:block;
margin-bottom:11px;
}
#jOverlayContent button {
position:absolute;
top:0;
right:0;
width:29px;
height:29px;
text-align:center;
font-weight:bold;
padding:0;
}
#jOverlayContent h1 {
max-width:475px;
}
#jOverlayContent #content {
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
}
#usergroups #new_group { #usergroups #new_group {
float: left; float: left;
margin-right: 2em; margin-right: 2em;
@ -1182,6 +1252,7 @@ width:33%;
} }
#settings_design_color .form_data label { #settings_design_color .form_data label {
float:none; float:none;
display:block;
} }
#settings_design_color .form_data .swatch { #settings_design_color .form_data .swatch {
padding:11px; padding:11px;

View File

@ -30,3 +30,12 @@ margin-right:4px;
.entity_profile { .entity_profile {
width:64%; width:64%;
} }
.notice {
z-index:1;
}
.notice:hover {
z-index:9999;
}
.notice .thumbnail img {
z-index:9999;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@ -446,6 +446,27 @@ float:left;
font-size:1.3em; font-size:1.3em;
margin-bottom:7px; margin-bottom:7px;
} }
#form_notice label {
display:block;
float:left;
font-size:1.3em;
margin-bottom:7px;
}
#form_notice label[for=notice_data-attach] {
text-indent:-9999px;
}
#form_notice label[for=notice_data-attach],
#form_notice #notice_data-attach {
position:absolute;
top:25px;
right:49px;
width:16px;
height:16px;
cursor:pointer;
}
#form_notice #notice_data-attach {
text-indent:-279px;
}
#form_notice #notice_submit label { #form_notice #notice_submit label {
display:none; display:none;
} }

View File

@ -102,6 +102,13 @@ color:#333;
#form_notice.warning #notice_text-count { #form_notice.warning #notice_text-count {
color:#000; color:#000;
} }
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit { #form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait; cursor:wait;

View File

@ -82,6 +82,13 @@ color:#333;
#form_notice.warning #notice_text-count { #form_notice.warning #notice_text-count {
color:#000; color:#000;
} }
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit { #form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait; cursor:wait;
@ -175,6 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
} }
/* NOTICES */ /* NOTICES */
.notice .attachment {
background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
}
#attachments .attachment {
background:none;
}
.notice-options .notice_reply a, .notice-options .notice_reply a,
.notice-options form input.submit { .notice-options form input.submit {
background-color:transparent; background-color:transparent;

View File

@ -82,6 +82,13 @@ color:#333;
#form_notice.warning #notice_text-count { #form_notice.warning #notice_text-count {
color:#000; color:#000;
} }
#form_notice label[for=notice_data-attach] {
background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
}
#form_notice #notice_data-attach {
opacity:0;
}
#form_notice.processing #notice_action-submit { #form_notice.processing #notice_action-submit {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait; cursor:wait;
@ -175,6 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
} }
/* NOTICES */ /* NOTICES */
.notice .attachment {
background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
}
#attachments .attachment {
background:none;
}
.notice-options .notice_reply a, .notice-options .notice_reply a,
.notice-options form input.submit { .notice-options form input.submit {
background-color:transparent; background-color:transparent;