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/*
files/*
file/*
_darcs/*
logs/*
config.php
@ -16,3 +17,8 @@ dataobject.ini
.buildpath
.project
.settings
TODO.rym
config-*.php
good-config.php
lac08.log
php.log

125
README
View File

@ -2,8 +2,8 @@
README
------
Laconica 0.7.3 ("You Are The Everything")
7 April 2009
Laconica 0.7.4 ("Can't Get There From Here")
29 May 2009
This is the README file for Laconica, the Open Source microblogging
platform. It includes installation instructions, descriptions of
@ -71,29 +71,29 @@ for additional terms.
New this version
================
This is a minor bug-fix and feature release since version 0.7.2.1,
released Mar 11 2009. Notable changes this version:
This is a minor bug-fix and feature release since version 0.7.3,
released Apr 4 2009. Notable changes this version:
- A plugin to allow a templating language for customization
- A plugin for Piwik Analytics engine
- A bookmarklet for posting a notice about a Web page you're reading
- A welcome notice ('welcomebot') and default subscription for new users
- Support for SSL for some or all pages on the site
- Better handling of empty notice lists on many pages
- Major improvements to the Twitter friend-sync offline processing
- subscribers, subscriptions, groups are listed on the Personal page.
- "Invite" link restored to main menu
- Better memory handling in FOAF output
- Fix for SUP support (FriendFeed)
- Correct and intelligent redirect HTTP status codes
- Fix DB collations for search and sort
- Better H1s and Titles using user full names
- Fixes to make the linkback plugin operational
- Better indication that a notice is being published by Ajax (spinner)
- Better and unified Atom output
- Hiding "register" and "join now" messages when site is closed
- ping, twitter and facebook queuehandlers working better
- Updated RPM spec
- Improved handling of UTF-8 characters. The new code is *not* backwards
compatible by default; see "Upgrading" below for instructions on
converting existing databases to the correct character set.
- Unroll joins for large queries. This greatly enhanced database
performance -- up to 50x for some queries -- at the expense of making
an extra DB hit for some queries.
- Added an optional plugin to use WikiHashtags
(http://hashtags.wikia.com/) for the sidebar on hashtag pages.
- Optimized Twitter friend synchronization.
- Better error handling for Ajax posting of notices, including
HTTP errors and timeouts.
- Experimental Comet plugin -- supports the cometd and the Bayeux
protocol. Using this plugin, you can show "real time" updates on the
public and tag pages. However, server configuration is complex.
- If queues are enabled, update inboxes and memcached off-line. Speeds
up posting considerably.
- Correctly shorten links posted through XMPP.
- <link> elements for pagination, supported by some browsers like Opera.
- Corrected date format in search API.
- Made the public XRDS file work correctly.
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
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
may have to unpack the tarball on your local computer and FTP the
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
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
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
=========
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
tracking the "git" version of the software, you will probably want
to upgrade and keep your existing data. There is no automated upgrade
procedure in Laconica 0.7.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.
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
'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
=====================
@ -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
requests to go to the 'database' (master) server, you'll need
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
------
@ -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
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
===============
@ -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
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
"Notice inboxes" section above.
@ -1265,6 +1330,8 @@ if anyone's been overlooked in error.
* Ken Sedgwick
* Brian Hendrickson
* Tobias Diekershoff
* Dan Moore
* Fil
Thanks also to the developers of our upstream library code and to the
thousands of people who have tried out Identi.ca, installed Laconi.ca,

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,9 @@
* 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');
@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction
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')) .
htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>');
@ -112,30 +104,37 @@ class FacebookinviteAction extends FacebookAction
$multi_params = array('showborder' => 'false');
$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['bypass'] = 'cancel';
$this->element('fb:multi-friend-selector', $multi_params);
$this->elementEnd('fb:request-form');
$this->element('h2', null, sprintf(_('Friends already using %s:'),
common_config('site', 'name')));
$this->elementStart('ul', array('id' => 'facebook-friends'));
if ($exclude_ids) {
foreach ($exclude_ids as $friend) {
$this->elementStart('li');
$this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
$this->element('fb:name', array('uid' => $friend,
'capitalize' => 'true'));
$this->elementEnd('li');
$this->element('h2', null, sprintf(_('Friends already using %s:'),
common_config('site', 'name')));
$this->elementStart('ul', array('id' => 'facebook-friends'));
foreach ($exclude_ids as $friend) {
$this->elementStart('li');
$this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
$this->element('fb:name', array('uid' => $friend,
'capitalize' => 'true'));
$this->elementEnd('li');
}
$this->elementEnd("ul");
}
$this->elementEnd("ul");
}
function title()

View File

@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction
$prefix = $this->trimmed('prefix');
$original = clone($this->flink);
$this->flink->set_flags($noticesync, $replysync, false);
$this->flink->set_flags($noticesync, $replysync, false, false);
$result = $this->flink->update($original);
$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)
{
parent::handle($args);
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
} 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
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->clientError(_('There was a problem with your session token. '.
'Try again, please.'));
return;
}
try {
$this->saveNewNotice();
} 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
*
@ -131,7 +159,6 @@ class NewnoticeAction extends Action
$this->clientError(_('No content!'));
} else {
$content_shortened = common_shorten_links($content);
if (mb_strlen($content_shortened) > 140) {
$this->clientError(_('That\'s too long. '.
'Max notice size is 140 chars.'));
@ -158,17 +185,53 @@ class NewnoticeAction extends Action
$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,
($replyto == 'false') ? null : $replyto);
if (is_string($notice)) {
$this->clientError($notice);
return;
}
if (isset($mimetype)) {
$this->storeFile($notice, $mimetype);
}
$this->saveUrls($notice);
common_broadcast_notice($notice);
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
*
* follow redirects and save all available file information
@ -203,7 +293,7 @@ class NewnoticeAction extends Action
*
* @return void
*/
function saveUrls($notice) {
function saveUrls($notice, $uploaded = null) {
common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id);
}
@ -316,3 +406,4 @@ class NewnoticeAction extends Action
$nli->show();
}
}

View File

@ -122,7 +122,7 @@ class ShownoticeAction extends Action
function lastModified()
{
return max(strtotime($this->notice->created),
return max(strtotime($this->notice->modified),
strtotime($this->profile->modified),
($this->avatar) ? strtotime($this->avatar->modified) : 0);
}
@ -208,10 +208,10 @@ class ShownoticeAction extends Action
function showContent()
{
$this->elementStart('ul', array('class' => 'notices'));
$this->elementStart('ol', array('class' => 'notices xoxo'));
$nli = new NoticeListItem($this->notice, $this);
$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);
}
// 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)
{
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');
$since = $this->arg('since');
$since_id = $this->arg('since_id');
$before_id = $this->arg('before_id');
$max_id = $this->arg('max_id');
$page = $this->arg('page');
@ -74,8 +74,8 @@ class Twitapidirect_messagesAction extends TwitterapiAction
$link = $server . $user->nickname . '/outbox';
}
if ($before_id) {
$message->whereAdd("id < $before_id");
if ($max_id) {
$message->whereAdd("id <= $max_id");
}
if ($since_id) {

View File

@ -45,22 +45,21 @@ class TwitapistatusesAction extends TwitterapiAction
$page = $this->arg('page');
$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) {
$page = 1;
}
if (!$since_id) {
$since_id = 0;
}
if (!$before_id) {
$before_id = 0;
if (!$max_id) {
$max_id = 0;
}
$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) {
@ -97,7 +96,7 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = $this->arg('since_id');
$count = $this->arg('count');
$page = $this->arg('page');
$before_id = $this->arg('before_id');
$max_id = $this->arg('max_id');
if (!$page) {
$page = 1;
@ -111,9 +110,8 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = 0;
}
// NOTE: before_id is an extension to Twitter API -- TB
if (!$before_id) {
$before_id = 0;
if (!$max_id) {
$max_id = 0;
}
$since = strtotime($this->arg('since'));
@ -133,7 +131,7 @@ class TwitapistatusesAction extends TwitterapiAction
$link = common_local_url('all', array('nickname' => $user->nickname));
$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']) {
case 'xml':
@ -184,7 +182,7 @@ class TwitapistatusesAction extends TwitterapiAction
$since = $this->arg('since');
$since_id = $this->arg('since_id');
$page = $this->arg('page');
$before_id = $this->arg('before_id');
$max_id = $this->arg('max_id');
if (!$page) {
$page = 1;
@ -198,9 +196,8 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = 0;
}
// NOTE: before_id is an extensions to Twitter API -- TB
if (!$before_id) {
$before_id = 0;
if (!$max_id) {
$max_id = 0;
}
$since = strtotime($this->arg('since'));
@ -220,7 +217,7 @@ class TwitapistatusesAction extends TwitterapiAction
# 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']) {
case 'xml':
@ -353,7 +350,7 @@ class TwitapistatusesAction extends TwitterapiAction
$count = $this->arg('count');
$page = $this->arg('page');
$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);
$this->auth_user = $apidata['user'];
@ -380,15 +377,14 @@ class TwitapistatusesAction extends TwitterapiAction
$since_id = 0;
}
// NOTE: before_id is an extension to Twitter API -- TB
if (!$before_id) {
$before_id = 0;
if (!$max_id) {
$max_id = 0;
}
$since = strtotime($this->arg('since'));
$notice = $user->getReplies((($page-1)*20),
$count, $since_id, $before_id, $since);
$count, $since_id, $max_id, $since);
$notices = array();
while ($notice->fetch()) {

View File

@ -33,102 +33,53 @@ class TwitapiusersAction extends TwitterapiAction
return;
}
$user = null;
$email = $this->arg('email');
$user_id = $this->arg('user_id');
$user = null;
$email = $this->arg('email');
$user_id = $this->arg('user_id');
if ($email) {
$user = User::staticGet('email', $email);
} elseif ($user_id) {
$user = $this->get_user($user_id);
} elseif (isset($apidata['api_arg'])) {
$user = $this->get_user($apidata['api_arg']);
} elseif (isset($apidata['user'])) {
$user = $apidata['user'];
}
// XXX: email field deprecated in Twitter's API
if (!$user) {
// XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
$this->client_error(_('Not found.'), 404, $apidata['content-type']);
return;
}
// XXX: Also: need to add screen_name param
$profile = $user->getProfile();
if ($email) {
$user = User::staticGet('email', $email);
} elseif ($user_id) {
$user = $this->get_user($user_id);
} elseif (isset($apidata['api_arg'])) {
$user = $this->get_user($apidata['api_arg']);
} elseif (isset($apidata['user'])) {
$user = $apidata['user'];
}
if (!$profile) {
common_server_error(_('User has no profile.'));
return;
}
if (!$user) {
$this->client_error(_('Not found.'), 404, $apidata['content-type']);
return;
}
$twitter_user = $this->twitter_user_array($profile, true);
$profile = $user->getProfile();
// Add in extended user fields offered up by this method
$twitter_user['created_at'] = $this->date_twitter($profile->created);
if (!$profile) {
common_server_error(_('User has no profile.'));
return;
}
$subbed = DB_DataObject::factory('subscription');
$subbed->subscriber = $profile->id;
$subbed_count = (int) $subbed->count() - 1;
$twitter_user = $this->twitter_user_array($profile, true);
$notices = DB_DataObject::factory('notice');
$notices->profile_id = $profile->id;
$notice_count = (int) $notices->count();
if ($apidata['content-type'] == 'xml') {
$this->init_document('xml');
$this->show_twitter_xml_user($twitter_user);
$this->end_document('xml');
} elseif ($apidata['content-type'] == 'json') {
$this->init_document('json');
$this->show_json_objects($twitter_user);
$this->end_document('json');
} else {
$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') {
$this->init_document('xml');
$this->show_twitter_xml_user($twitter_user);
$this->end_document('xml');
} elseif ($apidata['content-type'] == 'json') {
$this->init_document('json');
$this->show_json_objects($twitter_user);
$this->end_document('json');
} else {
// This is in case 'show' was called via /account/verify_credentials
// without a format (xml or json).
// This is in case 'show' was called via /account/verify_credentials
// without a format (xml or json).
header('Content-Type: text/html; charset=utf-8');
print 'Authorized';
}
}
}
}

View File

@ -46,7 +46,7 @@ class Fave extends Memcached_DataObject
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();
@ -59,8 +59,8 @@ class Fave extends Memcached_DataObject
$fav->whereAdd('notice_id > ' . $since_id);
}
if ($before_id != 0) {
$fav->whereAdd('notice_id < ' . $before_id);
if ($max_id != 0) {
$fav->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {

View File

@ -120,4 +120,30 @@ class File extends Memcached_DataObject
File_to_post::processNew($file_id, $notice_id);
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->orderBy('len');
$file->find(true);
if (!empty($file->id)) {
if (!empty($file->url) && (strlen($file->url) < strlen($long_url))) {
return $file->url;
}

View File

@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject
public $__table = 'foreign_link'; // table name
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 $credentials; // varchar(255)
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);
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);
$final = common_shorten_links($content);
if (!$profile) {
common_log(LOG_ERR, '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.');
}
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.');
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->created = common_sql_now();
$notice->content = $content;
$notice->rendered = common_render_content($content, $notice);
$notice->content = $final;
$notice->rendered = common_render_content($final, $notice);
$notice->source = $source;
$notice->uri = $uri;
@ -202,13 +204,9 @@ class Notice extends Memcached_DataObject
$notice->saveReplies();
$notice->saveTags();
$notice->saveGroups();
if (common_config('queue', 'enabled')) {
$notice->addToAuthorInbox();
} else {
$notice->addToInboxes();
}
$notice->addToInboxes();
$notice->saveGroups();
$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
# 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;
}
@ -277,6 +269,18 @@ class Notice extends Memcached_DataObject
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() {
$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);
@ -297,17 +301,6 @@ class Notice extends Memcached_DataObject
$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)
{
$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
# 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')) {
# Skip the cache if this is a since, since_id or before_id qry
if ($since_id > 0 || $before_id > 0 || $since) {
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
# Skip the cache if this is a since, since_id or max_id qry
if ($since_id > 0 || $max_id > 0 || $since) {
return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
} else {
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;
$needWhere = true;
@ -480,7 +473,7 @@ class Notice extends Memcached_DataObject
$qry .= ' notice.id > ' . $since_id;
}
if ($before_id > 0) {
if ($max_id > 0) {
if ($needWhere) {
$qry .= ' WHERE ';
@ -489,7 +482,7 @@ class Notice extends Memcached_DataObject
$qry .= ' AND ';
}
$qry .= ' notice.id < ' . $before_id;
$qry .= ' notice.id <= ' . $max_id;
}
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'),
array(),
'public',
$offset, $limit, $since_id, $before_id, $since);
$offset, $limit, $since_id, $max_id, $since);
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();
@ -681,8 +674,8 @@ class Notice extends Memcached_DataObject
$notice->whereAdd('id > ' . $since_id);
}
if ($before_id != 0) {
$notice->whereAdd('id < ' . $before_id);
if ($max_id != 0) {
$notice->whereAdd('id <= ' . $max_id);
}
if (!is_null($since)) {
@ -726,33 +719,6 @@ class Notice extends Memcached_DataObject
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()
{
$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();
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) {
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);

View File

@ -43,15 +43,15 @@ class Notice_inbox extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###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'),
array($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();
@ -61,8 +61,8 @@ class Notice_inbox extends Memcached_DataObject
$inbox->whereAdd('notice_id > ' . $since_id);
}
if ($before_id != 0) {
$inbox->whereAdd('notice_id < ' . $before_id);
if ($max_id != 0) {
$inbox->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {

View File

@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
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();
@ -59,8 +59,8 @@ class Notice_tag extends Memcached_DataObject
$nt->whereAdd('notice_id > ' . $since_id);
}
if ($before_id != 0) {
$nt->whereAdd('notice_id < ' . $before_id);
if ($max_id != 0) {
$nt->whereAdd('notice_id < ' . $max_id);
}
if (!is_null($since)) {

View File

@ -170,7 +170,7 @@ class Profile extends Memcached_DataObject
$ids = Notice::stream(array($this, '_streamDirect'),
array(),
'profile:notice_ids:' . $this->id,
$offset, $limit, $since_id, $before_id, $since);
$offset, $limit, $since_id, $max_id, $since);
return Notice::getStreamByIds($ids);
}
@ -225,8 +225,8 @@ class Profile extends Memcached_DataObject
$notice->whereAdd('id > ' . $since_id);
}
if ($before_id != 0) {
$notice->whereAdd('id < ' . $before_id);
if ($max_id != 0) {
$notice->whereAdd('id <= ' . $max_id);
}
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 */
###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'),
array($user_id),
'reply:stream:' . $user_id,
$offset, $limit, $since_id, $before_id, $since);
$offset, $limit, $since_id, $max_id, $since);
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->profile_id = $user_id;
@ -40,8 +40,8 @@ class Reply extends Memcached_DataObject
$reply->whereAdd('notice_id > ' . $since_id);
}
if ($before_id != 0) {
$reply->whereAdd('notice_id < ' . $before_id);
if ($max_id != 0) {
$reply->whereAdd('notice_id < ' . $max_id);
}
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)
{
$ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since);
common_debug("Ids = " . implode(',', $ids));
return Notice::getStreamByIds($ids);
}

View File

@ -58,7 +58,7 @@ class User_group extends Memcached_DataObject
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();
@ -71,8 +71,8 @@ class User_group extends Memcached_DataObject
$inbox->whereAdd('notice_id > ' . $since_id);
}
if ($before_id != 0) {
$inbox->whereAdd('notice_id < ' . $before_id);
if ($max_id != 0) {
$inbox->whereAdd('notice_id <= ' . $max_id);
}
if (!is_null($since)) {

View File

@ -215,3 +215,11 @@ $config['sphinx']['port'] = 3312;
// $config['snapshot']['run'] = 'never';
// If you want to report statistics in a cron job instead.
// $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 (
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),
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',

View File

@ -2,13 +2,18 @@ INSERT INTO notice_source
(code, name, url, created)
VALUES
('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()),
('bti','bti','http://gregkh.github.com/bti/', now()),
('cliqset', 'Cliqset', 'http://www.cliqset.com/', 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()),
('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()),
('Facebook','Facebook','http://apps.facebook.com/identica/', 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()),
('Gwibber','Gwibber','http://launchpad.net/gwibber', now()),
('HelloTxt','HelloTxt','http://hellotxt.com/', now()),
@ -28,6 +33,7 @@ VALUES
('pingvine','PingVine','http://pingvine.com/', now()),
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()),
('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),
('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', 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() {
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".');
return;
}
@ -570,7 +570,7 @@ $.fn.clearForm = function() {
$.fn.clearFields = $.fn.clearInputs = function() {
return this.each(function() {
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 = '';
else if (t == 'checkbox' || t == 'radio')
this.checked = false;

View File

@ -17,30 +17,6 @@
*/
$(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
function counter(event){
var maxLength = 140;
@ -227,6 +203,7 @@ $(document).ready(function(){
}
}
$("#notice_data-text").val("");
$("#notice_data-attach").val("");
counter();
}
$("#form_notice").removeClass("processing");
@ -238,6 +215,7 @@ $(document).ready(function(){
$("#form_notice").each(addAjaxHidden);
NoticeHover();
NoticeReply();
NoticeAttachments();
});
@ -276,3 +254,53 @@ function NoticeReplySet(nick,id) {
}
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()
{
$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;
$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) {
$item = $this->newListItem($attachment);
$item->show();
}
$this->out->elementEnd('dd');
$this->out->elementEnd('ul');
$this->out->elementEnd('ol');
$this->out->elementEnd('dl');
return count($att);
@ -266,6 +267,23 @@ class Attachment extends AttachmentListItem
case 'image/jpeg':
$this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
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 {

View File

@ -163,6 +163,42 @@ $config =
array('run' => 'web',
'frequency' => 10000,
'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');
@ -174,6 +210,7 @@ $config['db'] =
'require_prefix' => 'classes/',
'class_prefix' => '',
'mirror' => null,
'utf8' => true,
'db_driver' => 'DB', # XXX: JanRain libs only work with DB
'quote_identifiers' => false,
'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?
require_once('Validate.php');
require_once('markdown.php');
require_once 'Validate.php';
require_once 'markdown.php';
require_once(INSTALLDIR.'/lib/util.php');
require_once(INSTALLDIR.'/lib/action.php');
require_once(INSTALLDIR.'/lib/theme.php');
require_once(INSTALLDIR.'/lib/mail.php');
require_once(INSTALLDIR.'/lib/subs.php');
require_once(INSTALLDIR.'/lib/Shorturl_api.php');
require_once(INSTALLDIR.'/lib/twitter.php');
require_once INSTALLDIR.'/lib/util.php';
require_once INSTALLDIR.'/lib/action.php';
require_once INSTALLDIR.'/lib/theme.php';
require_once INSTALLDIR.'/lib/mail.php';
require_once INSTALLDIR.'/lib/subs.php';
require_once INSTALLDIR.'/lib/Shorturl_api.php';
require_once INSTALLDIR.'/lib/twitter.php';
require_once(INSTALLDIR.'/lib/clientexception.php');
require_once(INSTALLDIR.'/lib/serverexception.php');
require_once INSTALLDIR.'/lib/clientexception.php';
require_once INSTALLDIR.'/lib/serverexception.php';
// XXX: other formats here

View File

@ -98,9 +98,9 @@ class FacebookAction extends Action
// Add a timestamp to the file so Facebook cache wont ignore our changes
$ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
$this->element('link', array('rel' => 'stylesheet',
'type' => 'text/css',
'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
$theme = common_config('site', 'theme');
@ -278,7 +278,7 @@ class FacebookAction extends Action
$this->element('a',
array('href' => common_local_url('register')), _('Register'));
$this->text($loginmsg_part2);
$this->elementEnd('p');
$this->elementEnd('p');
$this->elementEnd('dd');
$this->elementEnd('dl');
@ -317,7 +317,7 @@ class FacebookAction extends Action
$this->elementEnd('ul');
$this->submit('submit', _('Login'));
$this->elementEnd('fieldset');
$this->elementEnd('fieldset');
$this->elementEnd('form');
$this->elementStart('p');
@ -336,65 +336,65 @@ class FacebookAction extends Action
// Need to include inline CSS for styling the Profile box
$app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
$icon_url = $app_props['icon_url'];
$app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
$icon_url = $app_props['icon_url'];
$style = '<style>
.entry-title *,
.entry-content * {
font-size:14px;
font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
}
.entry-title a,
.entry-content a {
color:#002E6E;
}
.entry-title *,
.entry-content * {
font-size:14px;
font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
}
.entry-title a,
.entry-content a {
color:#002E6E;
}
.entry-title .vcard .photo {
float:left;
display:inline;
margin-right:11px;
margin-bottom:11px
margin-right:11px;
margin-bottom:11px
}
.entry-title {
margin-bottom:11px;
}
.entry-title {
margin-bottom:11px;
}
.entry-title p.entry-content {
display:inline;
margin-left:5px;
margin-left:5px;
}
div.entry-content {
clear:both;
}
div.entry-content {
clear:both;
}
div.entry-content dl,
div.entry-content dt,
div.entry-content dd {
display:inline;
text-transform:lowercase;
text-transform:lowercase;
}
div.entry-content dd,
div.entry-content .device dt {
margin-left:0;
margin-right:5px;
div.entry-content .device dt {
margin-left:0;
margin-right:5px;
}
div.entry-content dl.timestamp dt,
div.entry-content dl.response dt {
div.entry-content dl.response dt {
display:none;
}
div.entry-content dd a {
display:inline-block;
}
#facebook_laconica_app {
text-indent:-9999px;
height:16px;
width:16px;
display:block;
background:url('.$icon_url.') no-repeat 0 0;
float:right;
}
#facebook_laconica_app {
text-indent:-9999px;
height:16px;
width:16px;
display:block;
background:url('.$icon_url.') no-repeat 0 0;
float:right;
}
</style>';
$this->xw->openMemory();
@ -646,48 +646,16 @@ class FacebookNoticeListItem extends NoticeListItem
function show()
{
$this->showStart();
$this->showNotice();
$this->showNoticeInfo();
$this->out->elementStart('div', 'entry-title');
$this->showAuthor();
$this->showContent();
$this->out->elementEnd('div');
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
$this->showNoticeSource();
$this->showReplyTo();
$this->out->elementEnd('div');
// XXX: Need to update to show attachements and controls
$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
{
@ -706,28 +674,16 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
/**
* Recipe function for displaying a single notice in the
* Facebook App's Profile
* Facebook App profile notice box
*
* @return void
*/
function show()
{
$this->out->elementStart('div', 'entry-title');
$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->showNotice();
$this->showNoticeInfo();
$this->showAppLink();
}
function showAppLink()

View File

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

View File

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

View File

@ -90,6 +90,7 @@ class NoticeForm extends Form
$this->user = common_current_user();
}
$this->enctype = 'multipart/form-data';
}
/**
@ -142,17 +143,21 @@ class NoticeForm extends Form
'rows' => 4,
'name' => 'status_textarea'),
($this->content) ? $this->content : '');
$this->out->elementStart('dl', 'form_note');
$this->out->element('dt', null, _('Available characters'));
$this->out->element('dd', array('id' => 'notice_text-count'),
'140');
$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) {
$this->out->hidden('notice_return-to', $this->action, 'returnto');
}
$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->element('h2', null, _('Notices'));
$this->out->elementStart('ul', array('class' => 'notices'));
$this->out->elementStart('ol', array('class' => 'notices xoxo'));
$cnt = 0;
@ -100,7 +100,7 @@ class NoticeList extends Widget
$item->show();
}
$this->out->elementEnd('ul');
$this->out->elementEnd('ol');
$this->out->elementEnd('div');
return $cnt;
@ -205,6 +205,7 @@ class NoticeListItem extends Widget
return 'shownotice' !== $this->out->args['action'];
}
/*
function attachmentCount($discriminant = true) {
$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;
@ -212,11 +213,16 @@ class NoticeListItem extends Widget
$file_oembed->fetch();
return intval($file_oembed->c);
}
*/
function showWithAttachment() {
}
function showNoticeInfo()
{
$this->out->elementStart('div', 'entry-content');
$this->showNoticeLink();
// $this->showWithAttachment();
$this->showNoticeSource();
$this->showContext();
$this->out->elementEnd('div');
@ -357,6 +363,10 @@ class NoticeListItem extends Widget
// versions (>> 0.4.x)
$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');
}
@ -387,6 +397,7 @@ class NoticeListItem extends Widget
$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');

View File

@ -52,12 +52,12 @@ class NoticeSection extends Section
{
$notices = $this->getNotices();
$cnt = 0;
$this->out->elementStart('ul', 'notices');
$this->out->elementStart('ol', 'notices xoxo');
while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
$this->showNotice($notices);
}
$this->out->elementEnd('ul');
$this->out->elementEnd('ol');
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)
{
common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
$user = User::staticGet('id', $notice->profile_id);
if (!$user) {
common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
return false;
}
@ -208,8 +204,6 @@ function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
$req->to_postdata(),
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
common_debug('403 result, deleting subscription', __FILE__);
# FIXME: figure out how to delete this
@ -286,14 +280,10 @@ function omb_update_profile($profile, $remote_profile, $subscription)
$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(),
$req->to_postdata(),
array('User-Agent: Laconica/' . LACONICA_VERSION));
common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
if (empty($result) || !$result) {
common_debug("Unable to contact " . $req->get_normalized_http_url());
} else if ($result->status == 403) { # not authorized, don't send again

View File

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

View File

@ -73,21 +73,82 @@ class TwitterapiAction extends Action
function twitter_user_array($profile, $get_notice=false)
{
$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['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);
$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['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) {
$notice = $profile->getCurrentNotice();
@ -612,7 +673,27 @@ class TwitterapiAction extends Action
function get_user($id, $apidata=null)
{
if (!$id) {
return $apidata['user'];
// 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'];
}
} else if (is_numeric($id)) {
return User::staticGet($id);
} 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'
// 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'";
$file = new File;
$file->query($query);
@ -895,6 +900,34 @@ function common_enqueue_notice($notice)
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)
{
$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);
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') {
$token = $this->trimmed('token');
@ -78,7 +99,7 @@ class FBConnectauthAction extends Action
}
$this->createNewUser();
} else if ($this->arg('connect')) {
$this->connectUser();
$this->connectNewUser();
} else {
common_debug(print_r($this->args, true), __FILE__);
$this->showForm(_('Something weird happened.'),
@ -259,7 +280,7 @@ class FBConnectauthAction extends Action
303);
}
function connectUser()
function connectNewUser()
{
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
@ -290,6 +311,23 @@ class FBConnectauthAction extends Action
$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()
{
common_debug("Trying Facebook Login...");

View File

@ -58,8 +58,6 @@ class FBConnectLoginAction extends Action
function showContent() {
$this->elementStart('fieldset');
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'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/FBCLoginGroupNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
/**
* Plugin to enable Facebook Connect
@ -62,27 +63,19 @@ class FBConnectPlugin extends Plugin
$m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
$m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
$m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
$m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
}
// Add in xmlns:fb
function onStartShowHTML($action)
{
$httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
$_SERVER['HTTP_ACCEPT'] : null;
// XXX: allow content negotiation for RDF, RSS, or XRDS
$cp = common_accept_to_prefs($httpaccept);
$sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
$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);
// XXX: Horrible hack to make Safari, FF2, and Chrome work with
// Facebook Connect. These browser cannot use Facebook's
// DOM parsing routines unless the mime type of the page is
// text/html even though Facebook Connect uses XHTML. This is
// A bug in Facebook Connect, and this is a temporary solution
// until they fix their JavaScript libs.
header('Content-Type: text/html');
$action->extraHeaders();
@ -101,20 +94,27 @@ class FBConnectPlugin extends Plugin
return false;
}
function onEndShowLaconicaScripts($action)
{
$action->element('script',
array('type' => 'text/javascript',
'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
' ');
// Note: this script needs to appear in the <body>
function onStartShowHeader($action)
{
$apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect');
$login_url = common_local_url('FBConnectAuth');
$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() {
window.location = "%s";
@ -123,13 +123,23 @@ class FBConnectPlugin extends Plugin
function goto_logout() {
window.location = "%s";
}
</script>', $apikey, $plugin_path, $login_url, $logout_url);
</script>', $apikey,
$login_url, $logout_url);
$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)
{
$action->element('link', array('rel' => 'stylesheet',
@ -143,24 +153,8 @@ class FBConnectPlugin extends Plugin
if ($user) {
$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');
$flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
$flink = Foreign_link::getByUserId($user->id,
FACEBOOK_CONNECT_SERVICE);
$fbuid = 0;
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');
$title = _('Logout from the site');
@ -258,21 +268,32 @@ class FBConnectPlugin extends Plugin
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();
$fbuid = $facebook->get_loggedin_user();
if ($fbuid > 0) {
$facebook->logout(common_local_url('public'));
try {
$fbuid = $facebook->get_loggedin_user();
if ($fbuid > 0) {
$facebook->logout(common_local_url('public'));
}
} catch (Exception $e) {
common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
$e->getMessage());
}
} catch (Exception $e) {
common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
$e->getMessage());
}
return true;
}
}

View File

@ -78,63 +78,73 @@ class FBConnectSettingsAction extends ConnectSettingsAction
function showContent()
{
$user = common_current_user();
$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',
'id' => 'form_settings_facebook',
'class' => 'form_settings',
'action' =>
common_local_url('FBConnectSettings')));
$this->hidden('token', common_session_token());
if (!$flink) {
$this->elementStart('fieldset');
$this->element('p', 'instructions',
_('There is no Facebook user connected to this account.'));
$this->element('legend', null, _('Disconnect my account from Facebook'));
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
if (!$user->password) {
$this->elementStart('p', array('class' => 'form_guide'));
$this->text(_('Disconnecting your Faceboook ' .
'would make it impossible to log in! Please '));
$this->element('a',
array('href' => common_local_url('passwordsettings')),
_('set a password'));
$this->text(_(' first.'));
$this->elementEnd('p');
} else {
$this->submit('disconnect', _('Disconnect'));
}
$this->elementEnd('fieldset');
$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->elementStart('fieldset');
$this->element('legend', null, _('Disconnect my account from Facebook'));
if (!$user->password) {
$this->elementStart('p', array('class' => 'form_guide'));
$this->text(_('Disconnecting your Faceboook ' .
'would make it impossible to log in! Please '));
$this->element('a',
array('href' => common_local_url('passwordsettings')),
_('set a password'));
$this->text(_(' first.'));
$this->elementEnd('p');
} 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->elementEnd('fieldset');
}
$this->elementEnd('form');
}
@ -171,8 +181,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction
try {
// XXX: not sure what exactly to do here
// Clear FB Connect cookies out
$facebook = getFacebook();
$facebook->clear_cookie_state();
@ -182,7 +191,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction
$e->getMessage());
}
$this->showForm(_('Facebook user disconnected.'), true);
$this->showForm(_('You have disconnected from Facebook.'), true);
} else {
$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.
*/
# 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";
@ -51,5 +50,4 @@ echo "ombqueuehandler.php ";
echo "twitterqueuehandler.php ";
echo "facebookqueuehandler.php ";
echo "pingqueuehandler.php ";
echo "inboxqueuehandler.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`
for f in jabberhandler ombhandler publichandler smshandler pinghandler \
xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
memcachehandler inboxhandler twitterstatusfetcher; do
xmppconfirmhandler xmppdaemon twitterhandler facebookhandler; do
FILES="$DIR/$f.*.pid"
for ff in "$FILES" ; do

View File

@ -452,6 +452,21 @@ 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 {
display:none;
}
@ -855,20 +870,6 @@ display:inline-block;
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 {
position:relative;
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 {
float: left;
margin-right: 2em;
@ -1182,6 +1252,7 @@ width:33%;
}
#settings_design_color .form_data label {
float:none;
display:block;
}
#settings_design_color .form_data .swatch {
padding:11px;

View File

@ -30,3 +30,12 @@ margin-right:4px;
.entity_profile {
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;
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 {
display:none;
}

View File

@ -102,6 +102,13 @@ color:#333;
#form_notice.warning #notice_text-count {
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 {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;

View File

@ -82,6 +82,13 @@ color:#333;
#form_notice.warning #notice_text-count {
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 {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
@ -175,6 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
}
/* 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 form input.submit {
background-color:transparent;

View File

@ -82,6 +82,13 @@ color:#333;
#form_notice.warning #notice_text-count {
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 {
background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
@ -175,6 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
}
/* 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 form input.submit {
background-color:transparent;