Merge branch '0.8.x' into testing
Conflicts: actions/twitterauthorization.php lib/oauthclient.php lib/twitter.php lib/twitterapi.php lib/twitteroauthclient.php scripts/twitterstatusfetcher.php
This commit is contained in:
commit
ff87732053
92
README
92
README
@ -553,25 +553,53 @@ our kind of hacky home-grown DB-based queue solution. See the "queues"
|
||||
config section below for how to configure to use STOMP. As of this
|
||||
writing, the software has been tested with ActiveMQ (
|
||||
|
||||
Twitter Friends Syncing
|
||||
-----------------------
|
||||
Twitter Bridge
|
||||
--------------
|
||||
|
||||
As of Laconica 0.6.3, users may set a flag in their settings ("Subscribe
|
||||
to my Twitter friends here" under the Twitter tab) to have Laconica
|
||||
attempt to locate and subscribe to "friends" (people they "follow") on
|
||||
Twitter who also have accounts on your Laconica system, and who have
|
||||
previously set up a link for automatically posting notices to Twitter.
|
||||
* OAuth
|
||||
|
||||
Optionally, there is a script (./scripts/synctwitterfriends.php), meant
|
||||
to be run periodically from a job scheduler (e.g.: cron under Unix), to
|
||||
look for new additions to users' friends lists. Note that the friends
|
||||
syncing only subscribes users to each other, it does not unsubscribe
|
||||
users when they stop following each other on Twitter.
|
||||
As of 0.8.1, OAuth is used to to access protected resources on Twitter
|
||||
instead of HTTP Basic Auth. To use Twitter bridging you will need
|
||||
to register your instance of Laconica as an application on Twitter
|
||||
(http://twitter.com/apps), and update the following variables in your
|
||||
config.php with the consumer key and secret Twitter generates for you:
|
||||
|
||||
Sample cron job:
|
||||
$config['twitter']['consumer_key'] = 'YOURKEY';
|
||||
$config['twitter']['consumer_secret'] = 'YOURSECRET';
|
||||
|
||||
# Update Twitter friends subscriptions every half hour
|
||||
0,30 * * * * /path/to/php /path/to/laconica/scripts/synctwitterfriends.php>&/dev/null
|
||||
When registering your application with Twitter set the type to "Browser"
|
||||
and your Callback URL to:
|
||||
|
||||
http://example.org/mublog/twitter/authorization
|
||||
|
||||
The default access type should be, "Read & Write".
|
||||
|
||||
* Importing statuses from Twitter
|
||||
|
||||
To allow your users to import their friends' Twitter statuses, you will
|
||||
need to enable the bidirectional Twitter bridge in config.php:
|
||||
|
||||
$config['twitterbridge']['enabled'] = true;
|
||||
|
||||
and run the TwitterStatusFetcher daemon (scripts/twitterstatusfetcher.php).
|
||||
Additionally, you will want to set the integration source variable,
|
||||
which will keep notices posted to Twitter via Laconica from looping
|
||||
back. The integration source should be set to the name of your
|
||||
application, exactly as you specified it on the settings page for your
|
||||
Laconica application on Twitter, e.g.:
|
||||
|
||||
$config['integration']['source'] = 'YourApp';
|
||||
|
||||
* Twitter Friends Syncing
|
||||
|
||||
Users may set a flag in their settings ("Subscribe to my Twitter friends
|
||||
here" under the Twitter tab) to have Laconica attempt to locate and
|
||||
subscribe to "friends" (people they "follow") on Twitter who also have
|
||||
accounts on your Laconica system, and who have previously set up a link
|
||||
for automatically posting notices to Twitter.
|
||||
|
||||
As of 0.8.0, this is no longer accomplished via a cron job. Instead you
|
||||
must run the SyncTwitterFriends daemon (scripts/synctwitterfreinds.php).
|
||||
|
||||
Built-in Facebook Application
|
||||
-----------------------------
|
||||
@ -940,6 +968,8 @@ closed: If set to 'true', will disallow registration on your site.
|
||||
the service, *then* set this variable to 'true'.
|
||||
inviteonly: If set to 'true', will only allow registration if the user
|
||||
was invited by an existing user.
|
||||
openidonly: If set to 'true', will only allow registrations and logins
|
||||
through OpenID.
|
||||
private: If set to 'true', anonymous users will be redirected to the
|
||||
'login' page. Also, API methods that normally require no
|
||||
authentication will require it. Note that this does not turn
|
||||
@ -1167,6 +1197,14 @@ For configuring invites.
|
||||
|
||||
enabled: Whether to allow users to send invites. Default true.
|
||||
|
||||
openid
|
||||
------
|
||||
|
||||
For configuring OpenID.
|
||||
|
||||
enabled: Whether to allow users to register and login using OpenID. Default
|
||||
true.
|
||||
|
||||
tag
|
||||
---
|
||||
|
||||
@ -1228,6 +1266,30 @@ enabled: Set to true to enable. Default false.
|
||||
server: a string with the hostname of the sphinx server.
|
||||
port: an integer with the port number of the sphinx server.
|
||||
|
||||
emailpost
|
||||
---------
|
||||
|
||||
For post-by-email.
|
||||
|
||||
enabled: Whether to enable post-by-email. Defaults to true. You will
|
||||
also need to set up maildaemon.php.
|
||||
|
||||
sms
|
||||
---
|
||||
|
||||
For SMS integration.
|
||||
|
||||
enabled: Whether to enable SMS integration. Defaults to true. Queues
|
||||
should also be enabled.
|
||||
|
||||
twitter
|
||||
-------
|
||||
|
||||
For Twitter integration
|
||||
|
||||
enabled: Whether to enable Twitter integration. Defaults to true.
|
||||
Queues should also be enabled.
|
||||
|
||||
integration
|
||||
-----------
|
||||
|
||||
|
@ -25,11 +25,31 @@ require_once INSTALLDIR.'/lib/feedlist.php';
|
||||
|
||||
class AllAction extends ProfileAction
|
||||
{
|
||||
var $notice;
|
||||
|
||||
function isReadOnly($args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
$cur = common_current_user();
|
||||
|
||||
if (!empty($cur) && $cur->id == $this->user->id) {
|
||||
$this->notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
|
||||
} else {
|
||||
$this->notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
|
||||
}
|
||||
|
||||
if($this->page > 1 && $this->notice->N == 0){
|
||||
$this->serverError(_('No such page'),$code=404);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
@ -88,7 +108,9 @@ class AllAction extends ProfileAction
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
|
||||
$this->user->nickname);
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
@ -98,15 +120,7 @@ class AllAction extends ProfileAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$cur = common_current_user();
|
||||
|
||||
if (!empty($cur) && $cur->id == $this->user->id) {
|
||||
$notice = $this->user->noticeInbox(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
|
||||
} else {
|
||||
$notice = $this->user->noticesWithFriends(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
|
||||
}
|
||||
|
||||
$nl = new NoticeList($notice, $this);
|
||||
$nl = new NoticeList($this->notice, $this);
|
||||
|
||||
$cnt = $nl->show();
|
||||
|
||||
|
@ -115,8 +115,8 @@ class AllrssAction extends Rss10Action
|
||||
'link' => common_local_url('all',
|
||||
array('nickname' =>
|
||||
$user->nickname)),
|
||||
'description' => sprintf(_('Feed for friends of %s'),
|
||||
$user->nickname));
|
||||
'description' => sprintf(_('Updates from %1$s and friends on %2$s!'),
|
||||
$user->nickname, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -131,6 +131,8 @@ class ApiAction extends Action
|
||||
'tags/timeline',
|
||||
'oembed/oembed',
|
||||
'groups/show',
|
||||
'groups/timeline',
|
||||
'groups/list_all',
|
||||
'groups/timeline');
|
||||
|
||||
static $bareauth = array('statuses/user_timeline',
|
||||
@ -140,7 +142,8 @@ class ApiAction extends Action
|
||||
'statuses/mentions',
|
||||
'statuses/followers',
|
||||
'favorites/favorites',
|
||||
'friendships/show');
|
||||
'friendships/show',
|
||||
'groups/list_groups');
|
||||
|
||||
$fullname = "$this->api_action/$this->api_method";
|
||||
|
||||
|
@ -103,18 +103,18 @@ class AttachmentAction extends Action
|
||||
$this->element('link',array('rel'=>'alternate',
|
||||
'type'=>'application/json+oembed',
|
||||
'href'=>common_local_url(
|
||||
'api',
|
||||
array('apiaction'=>'oembed','method'=>'oembed.json'),
|
||||
array('url'=>
|
||||
'oembed',
|
||||
array(),
|
||||
array('format'=>'json', 'url'=>
|
||||
common_local_url('attachment',
|
||||
array('attachment' => $this->attachment->id)))),
|
||||
'title'=>'oEmbed'),null);
|
||||
$this->element('link',array('rel'=>'alternate',
|
||||
'type'=>'text/xml+oembed',
|
||||
'href'=>common_local_url(
|
||||
'api',
|
||||
array('apiaction'=>'oembed','method'=>'oembed.xml'),
|
||||
array('url'=>
|
||||
'oembed',
|
||||
array(),
|
||||
array('format'=>'xml','url'=>
|
||||
common_local_url('attachment',
|
||||
array('attachment' => $this->attachment->id)))),
|
||||
'title'=>'oEmbed'),null);
|
||||
|
@ -382,13 +382,7 @@ class AvatarsettingsAction extends AccountSettingsAction
|
||||
function showStylesheets()
|
||||
{
|
||||
parent::showStylesheets();
|
||||
$jcropStyle =
|
||||
common_path('theme/base/css/jquery.Jcrop.css?version='.LACONICA_VERSION);
|
||||
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => $jcropStyle,
|
||||
'media' => 'screen, projection, tv'));
|
||||
$this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,13 +396,8 @@ class AvatarsettingsAction extends AccountSettingsAction
|
||||
parent::showScripts();
|
||||
|
||||
if ($this->mode == 'crop') {
|
||||
$jcropPack = common_path('js/jcrop/jquery.Jcrop.pack.js');
|
||||
$jcropGo = common_path('js/jcrop/jquery.Jcrop.go.js');
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => $jcropPack));
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => $jcropGo));
|
||||
$this->script('js/jcrop/jquery.Jcrop.min.js');
|
||||
$this->script('js/jcrop/jquery.Jcrop.go.js');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,11 @@ class ConfirmaddressAction extends Action
|
||||
parent::handle($args);
|
||||
if (!common_logged_in()) {
|
||||
common_set_returnto($this->selfUrl());
|
||||
common_redirect(common_local_url('login'));
|
||||
if (!common_config('site', 'openidonly')) {
|
||||
common_redirect(common_local_url('login'));
|
||||
} else {
|
||||
common_redirect(common_local_url('openidlogin'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
$code = $this->trimmed('code');
|
||||
|
@ -122,7 +122,7 @@ class EmailsettingsAction extends AccountSettingsAction
|
||||
}
|
||||
$this->elementEnd('fieldset');
|
||||
|
||||
if ($user->email) {
|
||||
if (common_config('emailpost', 'enabled') && $user->email) {
|
||||
$this->elementStart('fieldset', array('id' => 'settings_email_incoming'));
|
||||
$this->element('legend',_('Incoming email'));
|
||||
if ($user->incomingemail) {
|
||||
@ -173,11 +173,13 @@ class EmailsettingsAction extends AccountSettingsAction
|
||||
_('Allow friends to nudge me and send me an email.'),
|
||||
$user->emailnotifynudge);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('emailpost',
|
||||
_('I want to post notices by email.'),
|
||||
$user->emailpost);
|
||||
$this->elementEnd('li');
|
||||
if (common_config('emailpost', 'enabled')) {
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('emailpost',
|
||||
_('I want to post notices by email.'),
|
||||
$user->emailpost);
|
||||
$this->elementEnd('li');
|
||||
}
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('emailmicroid',
|
||||
_('Publish a MicroID for my email address.'),
|
||||
|
@ -153,7 +153,8 @@ class FavoritedAction extends Action
|
||||
$message .= _('Be the first to add a notice to your favorites by clicking the fave button next to any notice you like.');
|
||||
}
|
||||
else {
|
||||
$message .= _('Why not [register an account](%%action.register%%) and be the first to add a notice to your favorites!');
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to add a notice to your favorites!'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
|
@ -111,8 +111,8 @@ class FavoritesrssAction extends Rss10Action
|
||||
'link' => common_local_url('showfavorites',
|
||||
array('nickname' =>
|
||||
$user->nickname)),
|
||||
'description' => sprintf(_('Feed of favorite notices of %s'),
|
||||
$user->nickname));
|
||||
'description' => sprintf(_('Updates favored by %1$s on %2$s!'),
|
||||
$user->nickname, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,9 @@ class FinishopenidloginAction extends Action
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
if (common_is_real_login()) {
|
||||
if (!common_config('openid', 'enabled')) {
|
||||
common_redirect(common_local_url('login'));
|
||||
} else if (common_is_real_login()) {
|
||||
$this->clientError(_('Already logged in.'));
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$token = $this->trimmed('token');
|
||||
@ -217,7 +219,7 @@ class FinishopenidloginAction extends Action
|
||||
|
||||
if (!Validate::string($nickname, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
|
||||
return;
|
||||
}
|
||||
@ -389,7 +391,7 @@ class FinishopenidloginAction extends Action
|
||||
{
|
||||
if (!Validate::string($str, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
return false;
|
||||
}
|
||||
if (!User::allowed_nickname($str)) {
|
||||
|
@ -428,13 +428,7 @@ class GrouplogoAction extends GroupDesignAction
|
||||
function showStylesheets()
|
||||
{
|
||||
parent::showStylesheets();
|
||||
$jcropStyle =
|
||||
common_path('theme/base/css/jquery.Jcrop.css?version='.LACONICA_VERSION);
|
||||
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => $jcropStyle,
|
||||
'media' => 'screen, projection, tv'));
|
||||
$this->cssLink('css/jquery.Jcrop.css','base','screen, projection, tv');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -448,13 +442,8 @@ class GrouplogoAction extends GroupDesignAction
|
||||
parent::showScripts();
|
||||
|
||||
if ($this->mode == 'crop') {
|
||||
$jcropPack = common_path('js/jcrop/jquery.Jcrop.pack.js');
|
||||
$jcropGo = common_path('js/jcrop/jquery.Jcrop.go.js');
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => $jcropPack));
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => $jcropGo));
|
||||
$this->script('js/jcrop/jquery.Jcrop.min.js');
|
||||
$this->script('js/jcrop/jquery.Jcrop.go.js');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,9 +132,10 @@ class groupRssAction extends Rss10Action
|
||||
$c = array('url' => common_local_url('grouprss',
|
||||
array('nickname' =>
|
||||
$group->nickname)),
|
||||
'title' => $group->nickname,
|
||||
'title' => sprintf(_('%s timeline'), $group->nickname),
|
||||
'link' => common_local_url('showgroup', array('nickname' => $group->nickname)),
|
||||
'description' => sprintf(_('Microblog by %s group'), $group->nickname));
|
||||
'description' => sprintf(_('Updates from members of %1$s on %2$s!'),
|
||||
$group->nickname, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,8 @@ class GroupsearchAction extends SearchAction
|
||||
$message = _('If you can\'t find the group you\'re looking for, you can [create it](%%action.newgroup%%) yourself.');
|
||||
}
|
||||
else {
|
||||
$message = _('Why not [register an account](%%action.register%%) and [create the group](%%action.newgroup%%) yourself!');
|
||||
$message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and [create the group](%%%%action.newgroup%%%%) yourself!'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
}
|
||||
$this->elementStart('div', 'guide');
|
||||
$this->raw(common_markup_to_html($message));
|
||||
|
@ -84,6 +84,12 @@ class ImsettingsAction extends ConnectSettingsAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if (!common_config('xmpp', 'enabled')) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_('IM is not available.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
'id' => 'form_settings_im',
|
||||
|
@ -235,7 +235,7 @@ class InviteAction extends CurrentUserDesignAction
|
||||
common_root_url(),
|
||||
$personal,
|
||||
common_local_url('showstream', array('nickname' => $user->nickname)),
|
||||
common_local_url('register', array('code' => $invite->code)));
|
||||
common_local_url((!common_config('site', 'openidonly')) ? 'register' : 'openidlogin', array('code' => $invite->code)));
|
||||
|
||||
mail_send($recipients, $headers, $body);
|
||||
}
|
||||
|
@ -65,6 +65,8 @@ class LoginAction extends Action
|
||||
*
|
||||
* Switches on request method; either shows the form or handles its input.
|
||||
*
|
||||
* Checks if only OpenID is allowed and redirects to openidlogin if so.
|
||||
*
|
||||
* @param array $args $_REQUEST data
|
||||
*
|
||||
* @return void
|
||||
@ -73,7 +75,9 @@ class LoginAction extends Action
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
if (common_is_real_login()) {
|
||||
if (common_config('site', 'openidonly')) {
|
||||
common_redirect(common_local_url('openidlogin'));
|
||||
} else if (common_is_real_login()) {
|
||||
$this->clientError(_('Already logged in.'));
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$this->checkLogin();
|
||||
@ -247,11 +251,15 @@ class LoginAction extends Action
|
||||
return _('For security reasons, please re-enter your ' .
|
||||
'user name and password ' .
|
||||
'before changing your settings.');
|
||||
} else {
|
||||
} else if (common_config('openid', 'enabled')) {
|
||||
return _('Login with your username and password. ' .
|
||||
'Don\'t have a username yet? ' .
|
||||
'[Register](%%action.register%%) a new account, or ' .
|
||||
'try [OpenID](%%action.openidlogin%%). ');
|
||||
} else {
|
||||
return _('Login with your username and password. ' .
|
||||
'Don\'t have a username yet? ' .
|
||||
'[Register](%%action.register%%) a new account.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,8 +91,8 @@ class NewnoticeAction extends Action
|
||||
// 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']));
|
||||
'that much POST data (%s bytes) due to its current configuration.'),
|
||||
$_SERVER['CONTENT_LENGTH']));
|
||||
}
|
||||
parent::handle($args);
|
||||
|
||||
@ -130,7 +130,7 @@ class NewnoticeAction extends Action
|
||||
$hint = '';
|
||||
}
|
||||
$this->clientError(sprintf(
|
||||
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
|
||||
_('%s is not a supported filetype on this server.'), $filetype) . $hint);
|
||||
}
|
||||
|
||||
function isRespectsQuota($user) {
|
||||
@ -190,37 +190,37 @@ class NewnoticeAction extends Action
|
||||
|
||||
if (isset($_FILES['attach']['error'])) {
|
||||
switch ($_FILES['attach']['error']) {
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
// no file uploaded, nothing to do
|
||||
break;
|
||||
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_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_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_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_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_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_CANT_WRITE:
|
||||
$this->clientError(_('Failed to write file to disk.'));
|
||||
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
$this->clientError(_('File upload stopped by extension.'));
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
$this->clientError(_('File upload stopped by extension.'));
|
||||
|
||||
default:
|
||||
die('Should never reach here.');
|
||||
default:
|
||||
die('Should never reach here.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ class NewnoticeAction extends Action
|
||||
$fileRecord = $this->storeFile($filename, $mimetype);
|
||||
|
||||
$fileurl = common_local_url('attachment',
|
||||
array('attachment' => $fileRecord->id));
|
||||
array('attachment' => $fileRecord->id));
|
||||
|
||||
// not sure this is necessary -- Zach
|
||||
$this->maybeAddRedir($fileRecord->id, $fileurl);
|
||||
@ -367,7 +367,7 @@ class NewnoticeAction extends Action
|
||||
File_to_post::processNew($filerec->id, $notice->id);
|
||||
|
||||
$this->maybeAddRedir($filerec->id,
|
||||
common_local_url('file', array('notice' => $notice->id)));
|
||||
common_local_url('file', array('notice' => $notice->id)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +121,9 @@ class NoticesearchAction extends SearchAction
|
||||
$message = sprintf(_('Be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
|
||||
}
|
||||
else {
|
||||
$message = sprintf(_('Why not [register an account](%%%%action.register%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'), urlencode($q));
|
||||
$message = sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to [post on this topic](%%%%action.newnotice%%%%?status_textarea=%s)!'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
|
||||
urlencode($q));
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
|
@ -86,9 +86,10 @@ class NoticesearchrssAction extends Rss10Action
|
||||
{
|
||||
$q = $this->trimmed('q');
|
||||
$c = array('url' => common_local_url('noticesearchrss', array('q' => $q)),
|
||||
'title' => common_config('site', 'name') . sprintf(_(' Search Stream for "%s"'), $q),
|
||||
'title' => sprintf(_('Updates with "%s"'), $q),
|
||||
'link' => common_local_url('noticesearch', array('q' => $q)),
|
||||
'description' => sprintf(_('All updates matching search term "%s"'), $q));
|
||||
'description' => sprintf(_('Updates matching search term "%1$s" on %2$s!'),
|
||||
$q, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -31,8 +31,6 @@ if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once INSTALLDIR.'/lib/twitterapi.php';
|
||||
|
||||
/**
|
||||
* Oembed provider implementation
|
||||
*
|
||||
@ -46,17 +44,13 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class TwitapioembedAction extends TwitterapiAction
|
||||
class OembedAction extends Action
|
||||
{
|
||||
|
||||
function oembed($args, $apidata)
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
common_debug("in oembed api action");
|
||||
|
||||
$this->auth_user = $apidata['user'];
|
||||
|
||||
$url = $args['url'];
|
||||
if( substr(strtolower($url),0,strlen(common_root_url())) == strtolower(common_root_url()) ){
|
||||
$path = substr($url,strlen(common_root_url()));
|
||||
@ -131,8 +125,7 @@ class TwitapioembedAction extends TwitterapiAction
|
||||
default:
|
||||
$this->serverError(_("$path not supported for oembed requests"), 501);
|
||||
}
|
||||
|
||||
switch($apidata['content-type']){
|
||||
switch($args['format']){
|
||||
case 'xml':
|
||||
$this->init_document('xml');
|
||||
$this->elementStart('oembed');
|
||||
@ -151,12 +144,11 @@ class TwitapioembedAction extends TwitterapiAction
|
||||
if($oembed['thumbnail_url']) $this->element('thumbnail_url',null,$oembed['thumbnail_url']);
|
||||
if($oembed['thumbnail_width']) $this->element('thumbnail_width',null,$oembed['thumbnail_width']);
|
||||
if($oembed['thumbnail_height']) $this->element('thumbnail_height',null,$oembed['thumbnail_height']);
|
||||
|
||||
|
||||
$this->elementEnd('oembed');
|
||||
$this->end_document('xml');
|
||||
break;
|
||||
case 'json':
|
||||
case 'json': case '':
|
||||
$this->init_document('json');
|
||||
print(json_encode($oembed));
|
||||
$this->end_document('json');
|
||||
@ -164,10 +156,51 @@ class TwitapioembedAction extends TwitterapiAction
|
||||
default:
|
||||
$this->serverError(_('content type ' . $apidata['content-type'] . ' not supported'), 501);
|
||||
}
|
||||
|
||||
}else{
|
||||
$this->serverError(_('Only ' . common_root_url() . ' urls over plain http please'), 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init_document($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'xml':
|
||||
header('Content-Type: application/xml; charset=utf-8');
|
||||
$this->startXML();
|
||||
break;
|
||||
case 'json':
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Check for JSONP callback
|
||||
$callback = $this->arg('callback');
|
||||
if ($callback) {
|
||||
print $callback . '(';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$this->serverError(_('Not a supported data format.'), 501);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function end_document($type='xml')
|
||||
{
|
||||
switch ($type) {
|
||||
case 'xml':
|
||||
$this->endXML();
|
||||
break;
|
||||
case 'json':
|
||||
// Check for JSONP callback
|
||||
$callback = $this->arg('callback');
|
||||
if ($callback) {
|
||||
print ')';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$this->serverError(_('Not a supported data format.'), 501);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,9 @@ class OpenidloginAction extends Action
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
if (common_is_real_login()) {
|
||||
if (!common_config('openid', 'enabled')) {
|
||||
common_redirect(common_local_url('login'));
|
||||
} else if (common_is_real_login()) {
|
||||
$this->clientError(_('Already logged in.'));
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$openid_url = $this->trimmed('openid_url');
|
||||
|
@ -82,6 +82,12 @@ class OpenidsettingsAction extends AccountSettingsAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if (!common_config('openid', 'enabled')) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_('OpenID is not available.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
|
@ -66,7 +66,7 @@ class OpensearchAction extends Action
|
||||
$type = 'noticesearch';
|
||||
$short_name = _('Notice Search');
|
||||
}
|
||||
header('Content-Type: text/html');
|
||||
header('Content-Type: application/opensearchdescription+xml');
|
||||
$this->startXML();
|
||||
$this->elementStart('OpenSearchDescription', array('xmlns' => 'http://a9.com/-/spec/opensearch/1.1/'));
|
||||
$short_name = common_config('site', 'name').' '.$short_name;
|
||||
|
@ -189,7 +189,7 @@ class ProfilesettingsAction extends AccountSettingsAction
|
||||
// Some validation
|
||||
if (!Validate::string($nickname, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
|
||||
return;
|
||||
} else if (!User::allowed_nickname($nickname)) {
|
||||
|
@ -59,6 +59,7 @@ class PublicAction extends Action
|
||||
*/
|
||||
|
||||
var $page = null;
|
||||
var $notice;
|
||||
|
||||
function isReadOnly($args)
|
||||
{
|
||||
@ -84,6 +85,18 @@ class PublicAction extends Action
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
|
||||
$this->notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1);
|
||||
|
||||
if (!$this->notice) {
|
||||
$this->serverError(_('Could not retrieve public stream.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->page > 1 && $this->notice->N == 0){
|
||||
$this->serverError(_('No such page'),$code=404);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -183,7 +196,8 @@ class PublicAction extends Action
|
||||
}
|
||||
else {
|
||||
if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
$message .= _('Why not [register an account](%%action.register%%) and be the first to post!');
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post!'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,15 +217,7 @@ class PublicAction extends Action
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$notice = Notice::publicStream(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1);
|
||||
|
||||
if (!$notice) {
|
||||
$this->serverError(_('Could not retrieve public stream.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$nl = new NoticeList($notice, $this);
|
||||
$nl = new NoticeList($this->notice, $this);
|
||||
|
||||
$cnt = $nl->show();
|
||||
|
||||
@ -238,9 +244,11 @@ class PublicAction extends Action
|
||||
function showAnonymousMessage()
|
||||
{
|
||||
if (! (common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
$m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool. ' .
|
||||
'[Join now](%%action.register%%) to share notices about yourself with friends, family, and colleagues! ([Read more](%%doc.help%%))');
|
||||
$m = sprintf(_('This is %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool. ' .
|
||||
'[Join now](%%%%action.%s%%%%) to share notices about yourself with friends, family, and colleagues! ' .
|
||||
'([Read more](%%%%doc.help%%%%))'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
} else {
|
||||
$m = _('This is %%site.name%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool.');
|
||||
|
@ -86,9 +86,9 @@ class PublicrssAction extends Rss10Action
|
||||
{
|
||||
$c = array(
|
||||
'url' => common_local_url('publicrss')
|
||||
, 'title' => sprintf(_('%s Public Stream'), common_config('site', 'name'))
|
||||
, 'title' => sprintf(_('%s public timeline'), common_config('site', 'name'))
|
||||
, 'link' => common_local_url('public')
|
||||
, 'description' => sprintf(_('All updates for %s'), common_config('site', 'name')));
|
||||
, 'description' => sprintf(_('%s updates from everyone!'), common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,8 @@ class PublictagcloudAction extends Action
|
||||
$message .= _('Be the first to post one!');
|
||||
}
|
||||
else {
|
||||
$message .= _('Why not [register an account](%%action.register%%) and be the first to post one!');
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and be the first to post one!'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
|
@ -116,6 +116,8 @@ class RegisterAction extends Action
|
||||
*
|
||||
* Checks if registration is closed and shows an error if so.
|
||||
*
|
||||
* Checks if only OpenID is allowed and redirects to openidlogin if so.
|
||||
*
|
||||
* @param array $args $_REQUEST data
|
||||
*
|
||||
* @return void
|
||||
@ -127,6 +129,8 @@ class RegisterAction extends Action
|
||||
|
||||
if (common_config('site', 'closed')) {
|
||||
$this->clientError(_('Registration not allowed.'));
|
||||
} else if (common_config('site', 'openidonly')) {
|
||||
common_redirect(common_local_url('openidlogin'));
|
||||
} else if (common_logged_in()) {
|
||||
$this->clientError(_('Already logged in.'));
|
||||
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
@ -325,14 +329,22 @@ class RegisterAction extends Action
|
||||
} else if ($this->error) {
|
||||
$this->element('p', 'error', $this->error);
|
||||
} else {
|
||||
$instr =
|
||||
common_markup_to_html(_('With this form you can create '.
|
||||
' a new account. ' .
|
||||
'You can then post notices and '.
|
||||
'link up to friends and colleagues. '.
|
||||
'(Have an [OpenID](http://openid.net/)? ' .
|
||||
'Try our [OpenID registration]'.
|
||||
'(%%action.openidlogin%%)!)'));
|
||||
if (common_config('openid', 'enabled')) {
|
||||
$instr =
|
||||
common_markup_to_html(_('With this form you can create '.
|
||||
' a new account. ' .
|
||||
'You can then post notices and '.
|
||||
'link up to friends and colleagues. '.
|
||||
'(Have an [OpenID](http://openid.net/)? ' .
|
||||
'Try our [OpenID registration]'.
|
||||
'(%%action.openidlogin%%)!)'));
|
||||
} else {
|
||||
$instr =
|
||||
common_markup_to_html(_('With this form you can create '.
|
||||
' a new account. ' .
|
||||
'You can then post notices and '.
|
||||
'link up to friends and colleagues.'));
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'instructions');
|
||||
$this->raw($instr);
|
||||
|
@ -71,11 +71,13 @@ class RemotesubscribeAction extends Action
|
||||
if ($this->err) {
|
||||
$this->element('div', 'error', $this->err);
|
||||
} else {
|
||||
$inst = _('To subscribe, you can [login](%%action.login%%),' .
|
||||
' or [register](%%action.register%%) a new ' .
|
||||
' account. If you already have an account ' .
|
||||
' on a [compatible microblogging site](%%doc.openmublog%%), ' .
|
||||
' enter your profile URL below.');
|
||||
$inst = sprintf(_('To subscribe, you can [login](%%%%action.%s%%%%),' .
|
||||
' or [register](%%%%action.%s%%%%) a new ' .
|
||||
' account. If you already have an account ' .
|
||||
' on a [compatible microblogging site](%%doc.openmublog%%), ' .
|
||||
' enter your profile URL below.'),
|
||||
(!common_config('site','openidonly')) ? 'login' : 'openidlogin',
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
$output = common_markup_to_html($inst);
|
||||
$this->elementStart('div', 'instructions');
|
||||
$this->raw($output);
|
||||
|
@ -48,6 +48,7 @@ require_once INSTALLDIR.'/lib/feedlist.php';
|
||||
class RepliesAction extends OwnerDesignAction
|
||||
{
|
||||
var $page = null;
|
||||
var $notice;
|
||||
|
||||
/**
|
||||
* Prepare the object
|
||||
@ -84,6 +85,13 @@ class RepliesAction extends OwnerDesignAction
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
|
||||
$this->notice = $this->user->getReplies(($this->page-1) * NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1);
|
||||
|
||||
if($this->page > 1 && $this->notice->N == 0){
|
||||
$this->serverError(_('No such page'),$code=404);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -159,10 +167,7 @@ class RepliesAction extends OwnerDesignAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$notice = $this->user->getReplies(($this->page-1) * NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1);
|
||||
|
||||
$nl = new NoticeList($notice, $this);
|
||||
$nl = new NoticeList($this->notice, $this);
|
||||
|
||||
$cnt = $nl->show();
|
||||
if (0 === $cnt) {
|
||||
@ -187,7 +192,9 @@ class RepliesAction extends OwnerDesignAction
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
|
||||
$this->user->nickname);
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
|
@ -68,7 +68,8 @@ class RepliesrssAction extends Rss10Action
|
||||
'link' => common_local_url('replies',
|
||||
array('nickname' =>
|
||||
$user->nickname)),
|
||||
'description' => sprintf(_('Feed for replies to %s'), $user->nickname));
|
||||
'description' => sprintf(_('Replies to %1$s on %2$s!'),
|
||||
$user->nickname, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -114,6 +114,29 @@ class ShowfavoritesAction extends OwnerDesignAction
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
|
||||
$cur = common_current_user();
|
||||
|
||||
if (!empty($cur) && $cur->id == $this->user->id) {
|
||||
|
||||
// Show imported/gateway notices as well as local if
|
||||
// the user is looking at his own favorites
|
||||
|
||||
$this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1, true);
|
||||
} else {
|
||||
$this->notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1, false);
|
||||
}
|
||||
|
||||
if (empty($this->notice)) {
|
||||
$this->serverError(_('Could not retrieve favorite notices.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if($this->page > 1 && $this->notice->N == 0){
|
||||
$this->serverError(_('No such page'),$code=404);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -173,7 +196,9 @@ class ShowfavoritesAction extends OwnerDesignAction
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.register%%%%) and then post something interesting they would add to thier favorites :)'), $this->user->nickname);
|
||||
$message = sprintf(_('%s hasn\'t added any notices to his favorites yet. Why not [register an account](%%%%action.%s%%%%) and then post something interesting they would add to their favorites :)'),
|
||||
$this->user->nickname,
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
@ -191,26 +216,7 @@ class ShowfavoritesAction extends OwnerDesignAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$cur = common_current_user();
|
||||
|
||||
if (!empty($cur) && $cur->id == $this->user->id) {
|
||||
|
||||
// Show imported/gateway notices as well as local if
|
||||
// the user is looking at his own favorites
|
||||
|
||||
$notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1, true);
|
||||
} else {
|
||||
$notice = $this->user->favoriteNotices(($this->page-1)*NOTICES_PER_PAGE,
|
||||
NOTICES_PER_PAGE + 1, false);
|
||||
}
|
||||
|
||||
if (empty($notice)) {
|
||||
$this->serverError(_('Could not retrieve favorite notices.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$nl = new NoticeList($notice, $this);
|
||||
$nl = new NoticeList($this->notice, $this);
|
||||
|
||||
$cnt = $nl->show();
|
||||
if (0 == $cnt) {
|
||||
|
@ -130,8 +130,18 @@ class ShowgroupAction extends GroupDesignAction
|
||||
$this->group = User_group::staticGet('nickname', $nickname);
|
||||
|
||||
if (!$this->group) {
|
||||
$this->clientError(_('No such group'), 404);
|
||||
return false;
|
||||
$alias = Group_alias::staticGet('alias', $nickname);
|
||||
if ($alias) {
|
||||
$args = array('id' => $alias->group_id);
|
||||
if ($this->page != 1) {
|
||||
$args['page'] = $this->page;
|
||||
}
|
||||
common_redirect(common_local_url('groupbyid', $args), 301);
|
||||
return false;
|
||||
} else {
|
||||
$this->clientError(_('No such group'), 404);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
@ -440,8 +450,9 @@ class ShowgroupAction extends GroupDesignAction
|
||||
$m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool. Its members share ' .
|
||||
'short messages about their life and interests. '.
|
||||
'[Join now](%%%%action.register%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'),
|
||||
$this->group->nickname);
|
||||
'[Join now](%%%%action.%s%%%%) to become part of this group and many more! ([Read more](%%%%doc.help%%%%))'),
|
||||
$this->group->nickname,
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
} else {
|
||||
$m = sprintf(_('**%s** is a user group on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool. Its members share ' .
|
||||
|
@ -97,8 +97,8 @@ class ShownoticeAction extends OwnerDesignAction
|
||||
|
||||
$this->user = User::staticGet('id', $this->profile->id);
|
||||
|
||||
if (empty($this->user)) {
|
||||
$this->serverError(_('Not a local notice'), 500);
|
||||
if (! $this->notice->is_local) {
|
||||
common_redirect($this->notice->uri);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -190,7 +190,7 @@ class ShownoticeAction extends OwnerDesignAction
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
if ($this->notice->is_local == 0) {
|
||||
if ($this->notice->is_local == Notice::REMOTE_OMB) {
|
||||
if (!empty($this->notice->url)) {
|
||||
common_redirect($this->notice->url, 301);
|
||||
} else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) {
|
||||
@ -278,16 +278,16 @@ class ShownoticeAction extends OwnerDesignAction
|
||||
$this->element('link',array('rel'=>'alternate',
|
||||
'type'=>'application/json+oembed',
|
||||
'href'=>common_local_url(
|
||||
'api',
|
||||
array('apiaction'=>'oembed','method'=>'oembed.json'),
|
||||
array('url'=>$this->notice->uri)),
|
||||
'oembed',
|
||||
array(),
|
||||
array('format'=>'json','url'=>$this->notice->uri)),
|
||||
'title'=>'oEmbed'),null);
|
||||
$this->element('link',array('rel'=>'alternate',
|
||||
'type'=>'text/xml+oembed',
|
||||
'href'=>common_local_url(
|
||||
'api',
|
||||
array('apiaction'=>'oembed','method'=>'oembed.xml'),
|
||||
array('url'=>$this->notice->uri)),
|
||||
'oembed',
|
||||
array(),
|
||||
array('format'=>'xml','url'=>$this->notice->uri)),
|
||||
'title'=>'oEmbed'),null);
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +358,9 @@ class ShowstreamAction extends ProfileAction
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to his or her attention.'), $this->user->nickname);
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.%s%%%%) and then nudge %s or post a notice to his or her attention.'),
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
|
||||
$this->user->nickname);
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
@ -387,8 +389,10 @@ class ShowstreamAction extends ProfileAction
|
||||
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool. ' .
|
||||
'[Join now](%%%%action.register%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
|
||||
$this->user->nickname, $this->user->nickname);
|
||||
'[Join now](%%%%action.%s%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
|
||||
$this->user->nickname,
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin',
|
||||
$this->user->nickname);
|
||||
} else {
|
||||
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [Laconica](http://laconi.ca/) tool. '),
|
||||
|
@ -80,6 +80,12 @@ class SmssettingsAction extends ConnectSettingsAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if (!common_config('sms', 'enabled')) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_('SMS is not available.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
$this->elementStart('form', array('method' => 'post',
|
||||
|
@ -111,7 +111,9 @@ class SubscribersAction extends GalleryAction
|
||||
}
|
||||
}
|
||||
else {
|
||||
$message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.register%%%%) and be the first?'), $this->user->nickname);
|
||||
$message = sprintf(_('%s has no subscribers. Why not [register an account](%%%%action.%s%%%%) and be the first?'),
|
||||
$this->user->nickname,
|
||||
(!common_config('site','openidonly')) ? 'register' : 'openidlogin');
|
||||
}
|
||||
|
||||
$this->elementStart('div', 'guide');
|
||||
|
@ -174,14 +174,26 @@ class SubscriptionsListItem extends SubscriptionListItem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!common_config('xmpp', 'enabled') && !common_config('sms', 'enabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->out->elementStart('form', array('id' => 'subedit-' . $this->profile->id,
|
||||
'method' => 'post',
|
||||
'class' => 'form_subscription_edit',
|
||||
'action' => common_local_url('subedit')));
|
||||
$this->out->hidden('token', common_session_token());
|
||||
$this->out->hidden('profile', $this->profile->id);
|
||||
$this->out->checkbox('jabber', _('Jabber'), $sub->jabber);
|
||||
$this->out->checkbox('sms', _('SMS'), $sub->sms);
|
||||
if (common_config('xmpp', 'enabled')) {
|
||||
$this->out->checkbox('jabber', _('Jabber'), $sub->jabber);
|
||||
} else {
|
||||
$this->out->hidden('jabber', $sub->jabber);
|
||||
}
|
||||
if (common_config('sms', 'enabled')) {
|
||||
$this->out->checkbox('sms', _('SMS'), $sub->sms);
|
||||
} else {
|
||||
$this->out->hidden('sms', $sub->sms);
|
||||
}
|
||||
$this->out->submit('save', _('Save'));
|
||||
$this->out->elementEnd('form');
|
||||
return;
|
||||
|
@ -21,6 +21,9 @@ if (!defined('LACONICA')) { exit(1); }
|
||||
|
||||
class TagAction extends Action
|
||||
{
|
||||
|
||||
var $notice;
|
||||
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
@ -42,6 +45,12 @@ class TagAction extends Action
|
||||
|
||||
common_set_returnto($this->selfUrl());
|
||||
|
||||
$this->notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
|
||||
|
||||
if($this->page > 1 && $this->notice->N == 0){
|
||||
$this->serverError(_('No such page'),$code=404);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -94,9 +103,7 @@ class TagAction extends Action
|
||||
|
||||
function showContent()
|
||||
{
|
||||
$notice = Notice_tag::getStream($this->tag, (($this->page-1)*NOTICES_PER_PAGE), NOTICES_PER_PAGE + 1);
|
||||
|
||||
$nl = new NoticeList($notice, $this);
|
||||
$nl = new NoticeList($this->notice, $this);
|
||||
|
||||
$cnt = $nl->show();
|
||||
|
||||
|
@ -61,7 +61,8 @@ class TagrssAction extends Rss10Action
|
||||
$c = array('url' => common_local_url('tagrss', array('tag' => $tagname)),
|
||||
'title' => $tagname,
|
||||
'link' => common_local_url('tagrss', array('tag' => $tagname)),
|
||||
'description' => sprintf(_('Microblog tagged with %s'), $tagname));
|
||||
'description' => sprintf(_('Updates tagged with %1$s on %2$s!'),
|
||||
$tagname, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,103 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
|
||||
class TwitapigroupsAction extends TwitterapiAction
|
||||
{
|
||||
|
||||
function list_groups($args, $apidata)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
common_debug("in groups api action");
|
||||
|
||||
$this->auth_user = $apidata['user'];
|
||||
$user = $this->get_user($apidata['api_arg'], $apidata);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->clientError('Not Found', 404, $apidata['content-type']);
|
||||
return;
|
||||
}
|
||||
|
||||
$page = (int)$this->arg('page', 1);
|
||||
$count = (int)$this->arg('count', 20);
|
||||
$max_id = (int)$this->arg('max_id', 0);
|
||||
$since_id = (int)$this->arg('since_id', 0);
|
||||
$since = $this->arg('since');
|
||||
$group = $user->getGroups(($page-1)*$count,
|
||||
$count, $since_id, $max_id, $since);
|
||||
|
||||
$sitename = common_config('site', 'name');
|
||||
$title = sprintf(_("%s's groups"), $user->nickname);
|
||||
$taguribase = common_config('integration', 'taguri');
|
||||
$id = "tag:$taguribase:Groups";
|
||||
$link = common_root_url();
|
||||
$subtitle = sprintf(_("groups %s is a member of on %s"), $user->nickname, $sitename);
|
||||
|
||||
switch($apidata['content-type']) {
|
||||
case 'xml':
|
||||
$this->show_xml_groups($group);
|
||||
break;
|
||||
case 'rss':
|
||||
$this->show_rss_groups($group, $title, $link, $subtitle);
|
||||
break;
|
||||
case 'atom':
|
||||
$selfuri = common_root_url() . 'api/laconica/groups/list/' . $user->id . '.atom';
|
||||
$this->show_atom_groups($group, $title, $id, $link,
|
||||
$subtitle, $selfuri);
|
||||
break;
|
||||
case 'json':
|
||||
$this->show_json_groups($group);
|
||||
break;
|
||||
default:
|
||||
$this->clientError(_('API method not found!'), $code = 404);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function list_all($args, $apidata)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
common_debug("in groups api action");
|
||||
|
||||
$page = (int)$this->arg('page', 1);
|
||||
$count = (int)$this->arg('count', 20);
|
||||
$max_id = (int)$this->arg('max_id', 0);
|
||||
$since_id = (int)$this->arg('since_id', 0);
|
||||
$since = $this->arg('since');
|
||||
|
||||
/* TODO:
|
||||
Use the $page, $count, $max_id, $since_id, and $since parameters
|
||||
*/
|
||||
$group = new User_group();
|
||||
$group->orderBy('created DESC');
|
||||
$group->find();
|
||||
|
||||
$sitename = common_config('site', 'name');
|
||||
$title = sprintf(_("%s groups"), $sitename);
|
||||
$taguribase = common_config('integration', 'taguri');
|
||||
$id = "tag:$taguribase:Groups";
|
||||
$link = common_root_url();
|
||||
$subtitle = sprintf(_("groups on %s"), $sitename);
|
||||
|
||||
switch($apidata['content-type']) {
|
||||
case 'xml':
|
||||
$this->show_xml_groups($group);
|
||||
break;
|
||||
case 'rss':
|
||||
$this->show_rss_groups($group, $title, $link, $subtitle);
|
||||
break;
|
||||
case 'atom':
|
||||
$selfuri = common_root_url() . 'api/laconica/groups/list_all.atom';
|
||||
$this->show_atom_groups($group, $title, $id, $link,
|
||||
$subtitle, $selfuri);
|
||||
break;
|
||||
case 'json':
|
||||
$this->show_json_groups($group);
|
||||
break;
|
||||
default:
|
||||
$this->clientError(_('API method not found!'), $code = 404);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function show($args, $apidata)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
@ -449,7 +449,8 @@ class TwitapistatusesAction extends TwitterapiAction
|
||||
function friends($args, $apidata)
|
||||
{
|
||||
parent::handle($args);
|
||||
return $this->subscriptions($apidata, 'subscribed', 'subscriber');
|
||||
$includeStatuses=! (boolean) $args['lite'];
|
||||
return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses);
|
||||
}
|
||||
|
||||
function friendsIDs($args, $apidata)
|
||||
@ -461,7 +462,8 @@ class TwitapistatusesAction extends TwitterapiAction
|
||||
function followers($args, $apidata)
|
||||
{
|
||||
parent::handle($args);
|
||||
return $this->subscriptions($apidata, 'subscriber', 'subscribed');
|
||||
$includeStatuses=! (boolean) $args['lite'];
|
||||
return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses);
|
||||
}
|
||||
|
||||
function followersIDs($args, $apidata)
|
||||
@ -470,7 +472,7 @@ class TwitapistatusesAction extends TwitterapiAction
|
||||
return $this->subscriptions($apidata, 'subscriber', 'subscribed', true);
|
||||
}
|
||||
|
||||
function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false)
|
||||
function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true)
|
||||
{
|
||||
$this->auth_user = $apidata['user'];
|
||||
$user = $this->get_user($apidata['api_arg'], $apidata);
|
||||
@ -526,26 +528,26 @@ class TwitapistatusesAction extends TwitterapiAction
|
||||
if ($onlyIDs) {
|
||||
$this->showIDs($others, $type);
|
||||
} else {
|
||||
$this->show_profiles($others, $type);
|
||||
$this->show_profiles($others, $type, $includeStatuses);
|
||||
}
|
||||
|
||||
$this->end_document($type);
|
||||
}
|
||||
|
||||
function show_profiles($profiles, $type)
|
||||
function show_profiles($profiles, $type, $includeStatuses)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'xml':
|
||||
$this->elementStart('users', array('type' => 'array'));
|
||||
foreach ($profiles as $profile) {
|
||||
$this->show_profile($profile);
|
||||
$this->show_profile($profile,$type,null,$includeStatuses);
|
||||
}
|
||||
$this->elementEnd('users');
|
||||
break;
|
||||
case 'json':
|
||||
$arrays = array();
|
||||
foreach ($profiles as $profile) {
|
||||
$arrays[] = $this->twitter_user_array($profile, true);
|
||||
$arrays[] = $this->twitter_user_array($profile, $includeStatuses);
|
||||
}
|
||||
print json_encode($arrays);
|
||||
break;
|
||||
|
@ -43,6 +43,13 @@ class TwitterauthorizationAction extends Action
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler method
|
||||
*
|
||||
* @param array $args is ignored since it's now passed in in prepare()
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
@ -51,7 +58,7 @@ class TwitterauthorizationAction extends Action
|
||||
$this->clientError(_('Not logged in.'), 403);
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
$user = common_current_user();
|
||||
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
|
||||
|
||||
// If there's already a foreign link record, it means we already
|
||||
@ -66,89 +73,129 @@ class TwitterauthorizationAction extends Action
|
||||
// process
|
||||
|
||||
if (empty($this->oauth_token)) {
|
||||
|
||||
try {
|
||||
|
||||
// Get a new request token and authorize it
|
||||
|
||||
$client = new TwitterOAuthClient();
|
||||
$req_tok = $client->getRequestToken();
|
||||
|
||||
// Sock the request token away in the session temporarily
|
||||
|
||||
$_SESSION['twitter_request_token'] = $req_tok->key;
|
||||
$_SESSION['twitter_request_token_secret'] = $req_tok->key;
|
||||
|
||||
$auth_link = $client->getAuthorizeLink($req_tok);
|
||||
|
||||
} catch (TwitterOAuthClientException $e) {
|
||||
$msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
|
||||
$e->getCode(), $e->getMessage());
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
common_redirect($auth_link);
|
||||
|
||||
$this->authorizeRequestToken();
|
||||
} else {
|
||||
|
||||
// Check to make sure Twitter returned the same request
|
||||
// token we sent them
|
||||
|
||||
if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
|
||||
$_SESSION['twitter_request_token_secret']);
|
||||
|
||||
// Exchange the request token for an access token
|
||||
|
||||
$atok = $client->getAccessToken();
|
||||
|
||||
// Save the access token and Twitter user info
|
||||
|
||||
$client = new TwitterOAuthClient($atok->key, $atok->secret);
|
||||
|
||||
$twitter_user = $client->verify_credentials();
|
||||
|
||||
} catch (OAuthClientException $e) {
|
||||
$msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
|
||||
$e->getCode(), $e->getMessage());
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
$flink = new Foreign_link();
|
||||
|
||||
$flink->user_id = $user->id;
|
||||
$flink->foreign_id = $twitter_user->id;
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
$flink->token = $atok->key;
|
||||
$flink->credentials = $atok->secret;
|
||||
$flink->created = common_sql_now();
|
||||
|
||||
$flink->set_flags(true, false, false, false);
|
||||
|
||||
$flink_id = $flink->insert();
|
||||
|
||||
if (empty($flink_id)) {
|
||||
common_log_db_error($flink, 'INSERT', __FILE__);
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
save_twitter_user($twitter_user->id, $twitter_user->screen_name);
|
||||
|
||||
// clean up the the mess we made in the session
|
||||
|
||||
unset($_SESSION['twitter_request_token']);
|
||||
unset($_SESSION['twitter_request_token_secret']);
|
||||
|
||||
common_redirect(common_local_url('twittersettings'));
|
||||
$this->saveAccessToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks Twitter for a request token, and then redirects to Twitter
|
||||
* to authorize it.
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function authorizeRequestToken()
|
||||
{
|
||||
try {
|
||||
|
||||
// Get a new request token and authorize it
|
||||
|
||||
$client = new TwitterOAuthClient();
|
||||
$req_tok =
|
||||
$client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
|
||||
|
||||
// Sock the request token away in the session temporarily
|
||||
|
||||
$_SESSION['twitter_request_token'] = $req_tok->key;
|
||||
$_SESSION['twitter_request_token_secret'] = $req_tok->secret;
|
||||
|
||||
$auth_link = $client->getAuthorizeLink($req_tok);
|
||||
|
||||
} catch (TwitterOAuthClientException $e) {
|
||||
$msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
|
||||
$e->getCode(), $e->getMessage());
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
common_redirect($auth_link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Twitter returns an authorized request token. Exchanges
|
||||
* it for an access token and stores it.
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function saveAccessToken()
|
||||
{
|
||||
|
||||
// Check to make sure Twitter returned the same request
|
||||
// token we sent them
|
||||
|
||||
if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
|
||||
$_SESSION['twitter_request_token_secret']);
|
||||
|
||||
// Exchange the request token for an access token
|
||||
|
||||
$atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
|
||||
|
||||
// Test the access token and get the user's Twitter info
|
||||
|
||||
$client = new TwitterOAuthClient($atok->key, $atok->secret);
|
||||
$twitter_user = $client->verifyCredentials();
|
||||
|
||||
} catch (OAuthClientException $e) {
|
||||
$msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
|
||||
$e->getCode(), $e->getMessage());
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
// Save the access token and Twitter user info
|
||||
|
||||
$this->saveForeignLink($atok, $twitter_user);
|
||||
|
||||
// Clean up the the mess we made in the session
|
||||
|
||||
unset($_SESSION['twitter_request_token']);
|
||||
unset($_SESSION['twitter_request_token_secret']);
|
||||
|
||||
common_redirect(common_local_url('twittersettings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a Foreign_link between Twitter user and local user,
|
||||
* which includes the access token and secret.
|
||||
*
|
||||
* @param OAuthToken $access_token the access token to save
|
||||
* @param mixed $twitter_user twitter API user object
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function saveForeignLink($access_token, $twitter_user)
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
$flink = new Foreign_link();
|
||||
|
||||
$flink->user_id = $user->id;
|
||||
$flink->foreign_id = $twitter_user->id;
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
|
||||
$creds = TwitterOAuthClient::packToken($access_token);
|
||||
|
||||
$flink->credentials = $creds;
|
||||
$flink->created = common_sql_now();
|
||||
|
||||
// Defaults: noticesync on, everything else off
|
||||
|
||||
$flink->set_flags(true, false, false, false);
|
||||
|
||||
$flink_id = $flink->insert();
|
||||
|
||||
if (empty($flink_id)) {
|
||||
common_log_db_error($flink, 'INSERT', __FILE__);
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
save_twitter_user($twitter_user->id, $twitter_user->screen_name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,12 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
|
||||
function showContent()
|
||||
{
|
||||
if (!common_config('twitter', 'enabled')) {
|
||||
$this->element('div', array('class' => 'error'),
|
||||
_('Twitter is not available.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
$profile = $user->getProfile();
|
||||
|
@ -1,5 +1,16 @@
|
||||
<?php
|
||||
/*
|
||||
/**
|
||||
* Unsubscribe handler
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Action
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @author Robin Millette <millette@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
* Laconica - a distributed open-source microblogging tool
|
||||
* Copyright (C) 2008, 2009, Control Yourself, Inc.
|
||||
*
|
||||
@ -17,6 +28,20 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe handler
|
||||
*
|
||||
* @category Action
|
||||
* @package Laconica
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @author Robin Millette <millette@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
class UnsubscribeAction extends Action
|
||||
{
|
||||
|
||||
@ -31,16 +56,18 @@ class UnsubscribeAction extends Action
|
||||
$user = common_current_user();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||
common_redirect(common_local_url('subscriptions', array('nickname' => $user->nickname)));
|
||||
common_redirect(common_local_url('subscriptions',
|
||||
array('nickname' => $user->nickname)));
|
||||
return;
|
||||
}
|
||||
|
||||
# CSRF protection
|
||||
/* Use a session token for 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.'));
|
||||
$this->clientError(_('There was a problem with your session token. ' .
|
||||
'Try again, please.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -53,7 +80,7 @@ class UnsubscribeAction extends Action
|
||||
|
||||
$other = Profile::staticGet('id', $other_id);
|
||||
|
||||
if (!$other_id) {
|
||||
if (!$other) {
|
||||
$this->clientError(_('No profile with that id.'));
|
||||
return;
|
||||
}
|
||||
@ -76,8 +103,8 @@ class UnsubscribeAction extends Action
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
} else {
|
||||
common_redirect(common_local_url('subscriptions', array('nickname' =>
|
||||
$user->nickname)),
|
||||
common_redirect(common_local_url('subscriptions',
|
||||
array('nickname' => $user->nickname)),
|
||||
303);
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class UpdateprofileAction extends Action
|
||||
$nickname = $req->get_parameter('omb_listenee_nickname');
|
||||
if ($nickname && !Validate::string($nickname, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
$this->clientError(_('Nickname must have only lowercase letters and numbers and no spaces.'));
|
||||
return false;
|
||||
}
|
||||
|
@ -47,7 +47,11 @@ class UserauthorizationAction extends Action
|
||||
# Go log in, and then come back
|
||||
common_set_returnto($_SERVER['REQUEST_URI']);
|
||||
|
||||
common_redirect(common_local_url('login'));
|
||||
if (!common_config('site', 'openidonly')) {
|
||||
common_redirect(common_local_url('login'));
|
||||
} else {
|
||||
common_redirect(common_local_url('openidlogin'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -481,7 +485,7 @@ class UserauthorizationAction extends Action
|
||||
$nickname = $_GET['omb_listenee_nickname'];
|
||||
if (!Validate::string($nickname, array('min_length' => 1,
|
||||
'max_length' => 64,
|
||||
'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) {
|
||||
'format' => NICKNAME_FMT))) {
|
||||
throw new OAuthException('Nickname must have only letters and numbers and no spaces.');
|
||||
}
|
||||
$profile = $_GET['omb_listenee_profile'];
|
||||
|
@ -88,9 +88,10 @@ class UserrssAction extends Rss10Action
|
||||
$c = array('url' => common_local_url('userrss',
|
||||
array('nickname' =>
|
||||
$user->nickname)),
|
||||
'title' => $user->nickname,
|
||||
'title' => sprintf(_('%s timeline'), $user->nickname),
|
||||
'link' => $profile->profileurl,
|
||||
'description' => sprintf(_('Microblog by %s'), $user->nickname));
|
||||
'description' => sprintf(_('Updates from %1$s on %2$s!'),
|
||||
$user->nickname, common_config('site', 'name')));
|
||||
return $c;
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,10 @@ class Design extends Memcached_DataObject
|
||||
'disposition');
|
||||
|
||||
foreach ($attrs as $attr) {
|
||||
$siteDesign->$attr = common_config('design', $attr);
|
||||
$val = common_config('design', $attr);
|
||||
if ($val !== false) {
|
||||
$siteDesign->$attr = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,8 @@ class File extends Memcached_DataObject
|
||||
if (empty($file_redir)) {
|
||||
$redir_data = File_redirection::where($given_url);
|
||||
$redir_url = $redir_data['url'];
|
||||
if ($redir_url === $given_url) {
|
||||
// TODO: max field length
|
||||
if ($redir_url === $given_url || strlen($redir_url) > 255) {
|
||||
$x = File::saveNew($redir_data, $given_url);
|
||||
$file_id = $x->id;
|
||||
} else {
|
||||
|
@ -30,34 +30,38 @@ class Foreign_link extends Memcached_DataObject
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
// XXX: This only returns a 1->1 single obj mapping. Change? Or make
|
||||
// a getForeignUsers() that returns more than one? --Zach
|
||||
static function getByUserID($user_id, $service)
|
||||
{
|
||||
if (empty($user_id) || empty($service)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$flink = new Foreign_link();
|
||||
|
||||
$flink->service = $service;
|
||||
$flink->user_id = $user_id;
|
||||
$flink->limit(1);
|
||||
|
||||
if ($flink->find(true)) {
|
||||
return $flink;
|
||||
}
|
||||
$result = $flink->find(true);
|
||||
|
||||
return empty($result) ? null : $flink;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static function getByForeignID($foreign_id, $service)
|
||||
{
|
||||
$flink = new Foreign_link();
|
||||
$flink->service = $service;
|
||||
$flink->foreign_id = $foreign_id;
|
||||
$flink->limit(1);
|
||||
if (empty($foreign_id) || empty($service)) {
|
||||
return null;
|
||||
} else {
|
||||
$flink = new Foreign_link();
|
||||
$flink->service = $service;
|
||||
$flink->foreign_id = $foreign_id;
|
||||
$flink->limit(1);
|
||||
|
||||
if ($flink->find(true)) {
|
||||
return $flink;
|
||||
$result = $flink->find(true);
|
||||
|
||||
return empty($result) ? null : $flink;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function set_flags($noticesend, $noticerecv, $replysync, $friendsync)
|
||||
@ -67,7 +71,7 @@ class Foreign_link extends Memcached_DataObject
|
||||
} else {
|
||||
$this->noticesync &= ~FOREIGN_NOTICE_SEND;
|
||||
}
|
||||
|
||||
|
||||
if ($noticerecv) {
|
||||
$this->noticesync |= FOREIGN_NOTICE_RECV;
|
||||
} else {
|
||||
|
@ -29,10 +29,6 @@ require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
|
||||
|
||||
define('NOTICE_CACHE_WINDOW', 61);
|
||||
|
||||
define('NOTICE_LOCAL_PUBLIC', 1);
|
||||
define('NOTICE_REMOTE_OMB', 0);
|
||||
define('NOTICE_LOCAL_NONPUBLIC', -1);
|
||||
|
||||
define('MAX_BOXCARS', 128);
|
||||
|
||||
class Notice extends Memcached_DataObject
|
||||
@ -62,7 +58,11 @@ class Notice extends Memcached_DataObject
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
const GATEWAY = -2;
|
||||
/* Notice types */
|
||||
const LOCAL_PUBLIC = 1;
|
||||
const REMOTE_OMB = 0;
|
||||
const LOCAL_NONPUBLIC = -1;
|
||||
const GATEWAY = -2;
|
||||
|
||||
function getProfile()
|
||||
{
|
||||
@ -134,7 +134,7 @@ class Notice extends Memcached_DataObject
|
||||
}
|
||||
|
||||
static function saveNew($profile_id, $content, $source=null,
|
||||
$is_local=1, $reply_to=null, $uri=null, $created=null) {
|
||||
$is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null) {
|
||||
|
||||
$profile = Profile::staticGet($profile_id);
|
||||
|
||||
@ -177,7 +177,7 @@ class Notice extends Memcached_DataObject
|
||||
|
||||
if (($blacklist && in_array($profile_id, $blacklist)) ||
|
||||
($source && $autosource && in_array($source, $autosource))) {
|
||||
$notice->is_local = -1;
|
||||
$notice->is_local = Notice::LOCAL_NONPUBLIC;
|
||||
} else {
|
||||
$notice->is_local = $is_local;
|
||||
}
|
||||
@ -509,7 +509,7 @@ class Notice extends Memcached_DataObject
|
||||
|
||||
function blowPublicCache($blowLast=false)
|
||||
{
|
||||
if ($this->is_local == 1) {
|
||||
if ($this->is_local == Notice::LOCAL_PUBLIC) {
|
||||
$cache = common_memcache();
|
||||
if ($cache) {
|
||||
$cache->delete(common_cache_key('public'));
|
||||
@ -775,10 +775,11 @@ class Notice extends Memcached_DataObject
|
||||
}
|
||||
|
||||
if (common_config('public', 'localonly')) {
|
||||
$notice->whereAdd('is_local = 1');
|
||||
$notice->whereAdd('is_local = ' . Notice::LOCAL_PUBLIC);
|
||||
} else {
|
||||
# -1 == blacklisted
|
||||
$notice->whereAdd('is_local != -1');
|
||||
# -1 == blacklisted, -2 == gateway (i.e. Twitter)
|
||||
$notice->whereAdd('is_local !='. Notice::LOCAL_NONPUBLIC);
|
||||
$notice->whereAdd('is_local !='. Notice::GATEWAY);
|
||||
}
|
||||
|
||||
if ($since_id != 0) {
|
||||
|
@ -297,4 +297,45 @@ class User_group extends Memcached_DataObject
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
function asAtomEntry($namespace=false, $source=false)
|
||||
{
|
||||
$xs = new XMLStringer(true);
|
||||
|
||||
if ($namespace) {
|
||||
$attrs = array('xmlns' => 'http://www.w3.org/2005/Atom',
|
||||
'xmlns:thr' => 'http://purl.org/syndication/thread/1.0');
|
||||
} else {
|
||||
$attrs = array();
|
||||
}
|
||||
|
||||
$xs->elementStart('entry', $attrs);
|
||||
|
||||
if ($source) {
|
||||
$xs->elementStart('source');
|
||||
$xs->element('title', null, $profile->nickname . " - " . common_config('site', 'name'));
|
||||
$xs->element('link', array('href' => $this->permalink()));
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
$xs->elementEnd('source');
|
||||
}
|
||||
|
||||
$xs->element('title', null, $this->nickname);
|
||||
$xs->element('summary', null, $this->description);
|
||||
|
||||
$xs->element('link', array('rel' => 'alternate',
|
||||
'href' => $this->permalink()));
|
||||
|
||||
$xs->element('id', null, $this->permalink());
|
||||
|
||||
$xs->element('published', null, common_date_w3dtf($this->created));
|
||||
$xs->element('updated', null, common_date_w3dtf($this->modified));
|
||||
|
||||
$xs->element('content', array('type' => 'html'), $this->description);
|
||||
|
||||
$xs->elementEnd('entry');
|
||||
|
||||
return $xs->getString();
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,8 @@ $config['site']['path'] = 'laconica';
|
||||
// $config['site']['closed'] = true;
|
||||
// Only allow registration for people invited by another user
|
||||
// $config['site']['inviteonly'] = true;
|
||||
// Only allow registrations and logins through OpenID
|
||||
// $config['site']['openidonly'] = true;
|
||||
// Make the site invisible to non-logged-in users
|
||||
// $config['site']['private'] = true;
|
||||
|
||||
@ -97,6 +99,9 @@ $config['sphinx']['port'] = 3312;
|
||||
// $config['xmpp']['public'][] = 'someindexer@example.net';
|
||||
// $config['xmpp']['debug'] = false;
|
||||
|
||||
// Disable OpenID
|
||||
// $config['openid']['enabled'] = false;
|
||||
|
||||
// Turn off invites
|
||||
// $config['invite']['enabled'] = false;
|
||||
|
||||
@ -164,6 +169,15 @@ $config['sphinx']['port'] = 3312;
|
||||
// $config['memcached']['server'] = 'localhost';
|
||||
// $config['memcached']['port'] = 11211;
|
||||
|
||||
// Disable post-by-email
|
||||
// $config['emailpost']['enabled'] = false;
|
||||
|
||||
// Disable SMS
|
||||
// $config['sms']['enabled'] = false;
|
||||
|
||||
// Disable Twitter integration
|
||||
// $config['twitter']['enabled'] = false;
|
||||
|
||||
// Twitter integration source attribute. Note: default is Laconica
|
||||
// $config['integration']['source'] = 'Laconica';
|
||||
|
||||
@ -173,6 +187,10 @@ $config['sphinx']['port'] = 3312;
|
||||
//
|
||||
// $config['twitterbridge']['enabled'] = true;
|
||||
|
||||
// Twitter OAuth settings
|
||||
// $config['twitter']['consumer_key'] = 'YOURKEY';
|
||||
// $config['twitter']['consumer_secret'] = 'YOURSECRET';
|
||||
|
||||
// Edit throttling. Off by default. If turned on, you can only post 20 notices
|
||||
// every 10 minutes. Admins may want to play with the settings to minimize inconvenience for
|
||||
// real users without getting uncontrollable floods from spammers or runaway bots.
|
||||
@ -243,5 +261,6 @@ $config['sphinx']['port'] = 3312;
|
||||
// $config['attachments']['user_quota'] = 50000000;
|
||||
// $config['attachments']['monthly_quota'] = 15000000;
|
||||
// $config['attachments']['uploads'] = true;
|
||||
// $config['attachments']['path'] = "/file/";
|
||||
|
||||
// $config['oohembed']['endpoint'] = 'http://oohembed.com/oohembed/';
|
||||
|
@ -22,6 +22,7 @@ VALUES
|
||||
('IdentiFox','IdentiFox','http://www.bitbucket.org/uncryptic/identifox/', now()),
|
||||
('identitwitch','IdentiTwitch','http://richfish.org/identitwitch/', now()),
|
||||
('LaTwit','LaTwit','http://latwit.mac65.com/', now()),
|
||||
('LiveTweeter', 'LiveTweeter', 'http://addons.songbirdnest.com/addon/1204', now()),
|
||||
('livetweeter', 'livetweeter', 'http://addons.songbirdnest.com/addon/1204', now()),
|
||||
('maisha', 'Maisha', 'http://maisha.grango.org/', now()),
|
||||
('mbpidgin','mbpidgin','http://code.google.com/p/microblog-purple/', now()),
|
||||
@ -35,6 +36,7 @@ VALUES
|
||||
('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
|
||||
('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
|
||||
('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()),
|
||||
('qwit', 'Qwit', 'http://code.google.com/p/qwit/', 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()),
|
||||
|
30
doc-src/sms
30
doc-src/sms
@ -44,24 +44,24 @@ You can use the following commands with %%site.name%%.
|
||||
* on - turn on notifications
|
||||
* off - turn off notifications
|
||||
* help - show this help
|
||||
* follow <nickname> - subscribe to user
|
||||
* leave <nickname> - unsubscribe from user
|
||||
* d <nickname> <text> - direct message to user
|
||||
* get <nickname> - get last notice from user
|
||||
* whois <nickname> - get profile info on user
|
||||
* fav <nickname> - add user's last notice as a 'fave'
|
||||
* follow <nickname> - subscribe to user
|
||||
* leave <nickname> - unsubscribe from user
|
||||
* d <nickname> <text> - direct message to user
|
||||
* get <nickname> - get last notice from user
|
||||
* whois <nickname> - get profile info on user
|
||||
* fav <nickname> - add user's last notice as a 'fave'
|
||||
* stats - get your stats
|
||||
* stop - same as 'off'
|
||||
* quit - same as 'off'
|
||||
* sub <nickname> - same as 'follow'
|
||||
* unsub <nickname> - same as 'leave'
|
||||
* last <nickname> - same as 'get'
|
||||
* on <nickname> - not yet implemented.
|
||||
* off <nickname> - not yet implemented.
|
||||
* nudge <nickname> - not yet implemented.
|
||||
* invite <phone number> - not yet implemented.
|
||||
* track <word> - not yet implemented.
|
||||
* untrack <word> - not yet implemented.
|
||||
* sub <nickname> - same as 'follow'
|
||||
* unsub <nickname> - same as 'leave'
|
||||
* last <nickname> - same as 'get'
|
||||
* on <nickname> - not yet implemented.
|
||||
* off <nickname> - not yet implemented.
|
||||
* nudge <nickname> - not yet implemented.
|
||||
* invite <phone number> - not yet implemented.
|
||||
* track <word> - not yet implemented.
|
||||
* untrack <word> - not yet implemented.
|
||||
* track off - not yet implemented.
|
||||
* untrack all - not yet implemented.
|
||||
* tracks - not yet implemented.
|
||||
|
3
extlib/php-gettext/AUTHORS
Normal file
3
extlib/php-gettext/AUTHORS
Normal file
@ -0,0 +1,3 @@
|
||||
Danilo Segan <danilo@kvota.net>
|
||||
Nico Kaiser <nico@siriux.net> (contributed most changes between 1.0.2 and 1.0.3, bugfix for 1.0.5)
|
||||
Steven Armstrong <sa@c-area.ch> (gettext.inc, leading to 1.0.6)
|
340
extlib/php-gettext/COPYING
Normal file
340
extlib/php-gettext/COPYING
Normal file
@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
144
extlib/php-gettext/ChangeLog
Normal file
144
extlib/php-gettext/ChangeLog
Normal file
@ -0,0 +1,144 @@
|
||||
2006-02-07 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
* examples/pigs_dropin.php: comment-out bind_textdomain_codeset
|
||||
|
||||
* gettext.inc (T_bind_textdomain_codeset): bind_textdomain_codeset
|
||||
is available only in PHP 4.2.0+ (thanks to Jens A. Tkotz).
|
||||
|
||||
* Makefile: Include gettext.inc in DIST_FILES, VERSION up to
|
||||
1.0.7.
|
||||
|
||||
2006-02-03 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
Added setlocale() emulation as well.
|
||||
|
||||
* examples/pigs_dropin.php: Use T_setlocale() and locale_emulation().
|
||||
* examples/pigs_fallback.php: Use T_setlocale() and locale_emulation().
|
||||
|
||||
* gettext.inc: Added globals $EMULATEGETTEXT and $CURRENTLOCALE.
|
||||
(locale_emulation): Whether emulation is active.
|
||||
(_check_locale): Rewrite.
|
||||
(_setlocale): Added emulated setlocale function.
|
||||
(T_setlocale): Wrapper around _setlocale.
|
||||
(_get_reader): Use variables and _setlocale.
|
||||
|
||||
2006-02-02 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
Fix bug #12192.
|
||||
|
||||
* examples/locale/sr_CS/LC_MESSAGES/messages.po: Correct grammar.
|
||||
* examples/locale/sr_CS/LC_MESSAGES/messages.mo: Rebuild.
|
||||
|
||||
2006-02-02 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
Fix bug #15419.
|
||||
|
||||
* streams.php: Support for PHP 5.1.1 fread() which reads most 8kb.
|
||||
(Fix by Piotr Szotkowski <shot@hot.pl>)
|
||||
|
||||
2006-02-02 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
Merge Steven Armstrong's changes, supporting standard gettext
|
||||
interfaces:
|
||||
|
||||
* examples/*: Restructured examples.
|
||||
* gettext.inc: Added.
|
||||
* AUTHORS: Added Steven.
|
||||
* Makefile (VERSION): Up to 1.0.6.
|
||||
|
||||
2006-01-28 Nico Kaiser <nico@siriux.net>
|
||||
|
||||
* gettext.php (select_string): Fix "true" <-> 1 difference of PHP
|
||||
|
||||
2005-07-29 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
* Makefile (VERSION): Up to 1.0.5.
|
||||
|
||||
2005-07-29 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
Fixes bug #13850.
|
||||
|
||||
* gettext.php (gettext_reader): check $Reader->error as well.
|
||||
|
||||
2005-07-29 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
* Makefile (VERSION): Up to 1.0.4.
|
||||
|
||||
2005-07-29 Danilo Šegan <danilo@gnome.org>
|
||||
|
||||
Fixes bug #13771.
|
||||
|
||||
* gettext.php (gettext_reader->get_plural_forms): Plural forms
|
||||
header extraction regex change. Reported by Edgar Gonzales.
|
||||
|
||||
2005-02-28 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* AUTHORS: Added Nico to the list.
|
||||
|
||||
* Makefile (VERSION): Up to 1.0.3.
|
||||
|
||||
* README: Updated.
|
||||
|
||||
2005-02-28 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* gettext.php: Added pre-loading, code documentation, and many
|
||||
code clean-ups by Nico Kaiser <nico@siriux.net>.
|
||||
|
||||
2005-02-28 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* streams.php (FileReader.read): Handle read($bytes = 0).
|
||||
|
||||
* examples/pigs.php: Prefix gettext function names with T or T_.
|
||||
|
||||
* examples/update: Use the same keywords T_ and T_ngettext.
|
||||
|
||||
* streams.php: Added CachedFileReader.
|
||||
|
||||
2003-11-11 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* gettext.php: Added hashing to find_string.
|
||||
|
||||
2003-11-01 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* Makefile (DIST_FILES): Replaced LICENSE with COPYING.
|
||||
(VERSION): Up to 1.0.2.
|
||||
|
||||
* AUTHORS: Minor edits.
|
||||
|
||||
* README: Minor edits.
|
||||
|
||||
* COPYING: Removed LICENSE, added this file.
|
||||
|
||||
* gettext.php: Added copyright notice and disclaimer.
|
||||
* streams.php: Same.
|
||||
* examples/pigs.php: Same.
|
||||
|
||||
2003-10-23 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* Makefile: Upped version to 1.0.1.
|
||||
|
||||
* gettext.php (gettext_reader): Remove a call to set_total_plurals.
|
||||
(set_total_plurals): Removed unused function for some better days.
|
||||
|
||||
2003-10-23 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* Makefile: Added, version 1.0.0.
|
||||
|
||||
* examples/*: Added an example of usage.
|
||||
|
||||
* README: Described all the crap.
|
||||
|
||||
2003-10-22 Danilo Šegan <dsegan@gmx.net>
|
||||
|
||||
* gettext.php: Plural forms implemented too.
|
||||
|
||||
* streams.php: Added FileReader for direct access to files (no
|
||||
need to keep file in memory).
|
||||
|
||||
* gettext.php: It works, except for plural forms.
|
||||
|
||||
* streams.php: Created abstract class StreamReader.
|
||||
Added StringReader class.
|
||||
|
||||
* gettext.php: Started writing gettext_reader.
|
||||
|
189
extlib/php-gettext/README
Normal file
189
extlib/php-gettext/README
Normal file
@ -0,0 +1,189 @@
|
||||
PHP-gettext 1.0
|
||||
|
||||
Copyright 2003, 2006 -- Danilo "angry with PHP[1]" Segan
|
||||
Licensed under GPLv2 (or any later version, see COPYING)
|
||||
|
||||
[1] PHP is actually cyrillic, and translates roughly to
|
||||
"works-doesn't-work" (UTF-8: Ради-Не-Ради)
|
||||
|
||||
|
||||
Introduction
|
||||
|
||||
How many times did you look for a good translation tool, and
|
||||
found out that gettext is best for the job? Many times.
|
||||
|
||||
How many times did you try to use gettext in PHP, but failed
|
||||
miserably, because either your hosting provider didn't support
|
||||
it, or the server didn't have adequate locale? Many times.
|
||||
|
||||
Well, this is a solution to your needs. It allows using gettext
|
||||
tools for managing translations, yet it doesn't require gettext
|
||||
library at all. It parses generated MO files directly, and thus
|
||||
might be a bit slower than the (maybe provided) gettext library.
|
||||
|
||||
PHP-gettext is a simple reader for GNU gettext MO files. Those
|
||||
are binary containers for translations, produced by GNU msgfmt.
|
||||
|
||||
Why?
|
||||
|
||||
I got used to having gettext work even without gettext
|
||||
library. It's there in my favourite language Python, so I was
|
||||
surprised that I couldn't find it in PHP. I even Googled for it,
|
||||
but to no avail.
|
||||
|
||||
So, I said, what the heck, I'm going to write it for this
|
||||
disguisting language of PHP, because I'm often constrained to it.
|
||||
|
||||
Features
|
||||
|
||||
o Support for simple translations
|
||||
Just define a simple alias for translate() function (suggested
|
||||
use of _() or gettext(); see provided example).
|
||||
|
||||
o Support for ngettext calls (plural forms, see a note under bugs)
|
||||
You may also use plural forms. Translations in MO files need to
|
||||
provide this, and they must also provide "plural-forms" header.
|
||||
Please see 'info gettext' for more details.
|
||||
|
||||
o Support for reading straight files, or strings (!!!)
|
||||
Since I can imagine many different backends for reading in the MO
|
||||
file data, I used imaginary abstract class StreamReader to do all
|
||||
the input (check streams.php). For your convenience, I've already
|
||||
provided two classes for reading files: FileReader and
|
||||
StringReader (CachedFileReader is a combination of the two: it
|
||||
loads entire file contents into a string, and then works on that).
|
||||
See example below for usage. You can for instance use StringReader
|
||||
when you read in data from a database, or you can create your own
|
||||
derivative of StreamReader for anything you like.
|
||||
|
||||
|
||||
Bugs
|
||||
|
||||
Plural-forms field in MO header (translation for empty string,
|
||||
i.e. "") is treated according to PHP syntactic rules (it's
|
||||
eval()ed). Since these should actually follow C syntax, there are
|
||||
some problems.
|
||||
|
||||
For instance, I'm used to using this:
|
||||
Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \
|
||||
n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
|
||||
but it fails with PHP (it sets $plural=2 instead of 0 for $n==1).
|
||||
|
||||
The fix is usually simple, but I'm lazy to go into the details of
|
||||
PHP operator precedence, and maybe try to fix it. In here, I had
|
||||
to put everything after the first ':' in parenthesis:
|
||||
Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : \
|
||||
(n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);
|
||||
That works, and I'm satisfied.
|
||||
|
||||
Besides this one, there are probably a bunch of other bugs, since
|
||||
I hate PHP (did I mention it already? no? strange), and don't
|
||||
know it very well. So, feel free to fix any of those and report
|
||||
them back to me at <danilo@kvota.net>.
|
||||
|
||||
Usage
|
||||
|
||||
Put files streams.php and gettext.php somewhere you can load them
|
||||
from, and require 'em in where you want to use them.
|
||||
|
||||
Then, create one 'stream reader' (a class that provides functions
|
||||
like read(), seekto(), currentpos() and length()) which will
|
||||
provide data for the 'gettext_reader', with eg.
|
||||
$streamer = new FileStream('data.mo');
|
||||
|
||||
Then, use that as a parameter to gettext_reader constructor:
|
||||
$wohoo = new gettext_reader($streamer);
|
||||
|
||||
If you want to disable pre-loading of entire message catalog in
|
||||
memory (if, for example, you have a multi-thousand message catalog
|
||||
which you'll use only occasionally), use "false" for second
|
||||
parameter to gettext_reader constructor:
|
||||
$wohoo = new gettext_reader($streamer, false);
|
||||
|
||||
From now on, you have all the benefits of gettext data at your
|
||||
disposal, so may run:
|
||||
print $wohoo->translate("This is a test");
|
||||
print $wohoo->ngettext("%d bird", "%d birds", $birds);
|
||||
|
||||
You might need to pass parameter "-k" to xgettext to make it
|
||||
extract all the strings. In above example, try with
|
||||
xgettext -ktranslate -kngettext:1,2 file.php
|
||||
what should create messages.po which contains two messages for
|
||||
translation.
|
||||
|
||||
I suggest creating simple aliases for these functions (see
|
||||
example/pigs.php for how do I do it, which means it's probably a
|
||||
bad way).
|
||||
|
||||
|
||||
Usage with gettext.inc (standard gettext interfaces emulation)
|
||||
|
||||
Check example in examples/pig_dropin.php, basically you include
|
||||
gettext.inc and use all the standard gettext interfaces as
|
||||
documented on:
|
||||
|
||||
http://www.php.net/gettext
|
||||
|
||||
The only catch is that you can check return value of setlocale()
|
||||
to see if your locale is system supported or not.
|
||||
|
||||
|
||||
Example
|
||||
|
||||
See in examples/ subdirectory. There are a couple of files.
|
||||
pigs.php is an example, serbian.po is a translation to Serbian
|
||||
language, and serbian.mo is generated with
|
||||
msgfmt -o serbian.mo serbian.po
|
||||
There is also simple "update" script that can be used to generate
|
||||
POT file and to update the translation using msgmerge.
|
||||
|
||||
Interesting TODO:
|
||||
|
||||
o Try to parse "plural-forms" header field, and to follow C syntax
|
||||
rules. This won't be easy.
|
||||
|
||||
Boring TODO:
|
||||
|
||||
o Learn PHP and fix bugs, slowness and other stuff resulting from
|
||||
my lack of knowledge (but *maybe*, it's not my knowledge that is
|
||||
bad, but PHP itself ;-).
|
||||
|
||||
(This is mostly done thanks to Nico Kaiser.)
|
||||
|
||||
o Try to use hash tables in MO files: with pre-loading, would it
|
||||
be useful at all?
|
||||
|
||||
Never-asked-questions:
|
||||
|
||||
o Why did you mark this as version 1.0 when this is the first code
|
||||
release?
|
||||
|
||||
Well, it's quite simple. I consider that the first released thing
|
||||
should be labeled "version 1" (first, right?). Zero is there to
|
||||
indicate that there's zero improvement and/or change compared to
|
||||
"version 1".
|
||||
|
||||
I plan to use version numbers 1.0.* for small bugfixes, and to
|
||||
release 1.1 as "first stable release of version 1".
|
||||
|
||||
This may trick someone that this is actually useful software, but
|
||||
as with any other free software, I take NO RESPONSIBILITY for
|
||||
creating such a masterpiece that will smoke crack, trash your
|
||||
hard disk, and make lasers in your CD device dance to the tune of
|
||||
Mozart's 40th Symphony (there is one like that, right?).
|
||||
|
||||
o Can I...?
|
||||
|
||||
Yes, you can. This is free software (as in freedom, free speech),
|
||||
and you might do whatever you wish with it, provided you do not
|
||||
limit freedom of others (GPL).
|
||||
|
||||
I'm considering licensing this under LGPL, but I *do* want
|
||||
*every* PHP-gettext user to contribute and respect ideas of free
|
||||
software, so don't count on it happening anytime soon.
|
||||
|
||||
I'm sorry that I'm taking away your freedom of taking others'
|
||||
freedom away, but I believe that's neglible as compared to what
|
||||
freedoms you could take away. ;-)
|
||||
|
||||
Uhm, whatever.
|
318
extlib/php-gettext/gettext.inc
Normal file
318
extlib/php-gettext/gettext.inc
Normal file
@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/*
|
||||
Copyright (c) 2005 Steven Armstrong <sa at c-area dot ch>
|
||||
|
||||
Drop in replacement for native gettext.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
/*
|
||||
LC_CTYPE 0
|
||||
LC_NUMERIC 1
|
||||
LC_TIME 2
|
||||
LC_COLLATE 3
|
||||
LC_MONETARY 4
|
||||
LC_MESSAGES 5
|
||||
LC_ALL 6
|
||||
*/
|
||||
|
||||
require('streams.php');
|
||||
require('gettext.php');
|
||||
|
||||
|
||||
// Variables
|
||||
|
||||
global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE;
|
||||
$text_domains = array();
|
||||
$default_domain = 'messages';
|
||||
$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL');
|
||||
$EMULATEGETTEXT = 0;
|
||||
$CURRENTLOCALE = '';
|
||||
|
||||
|
||||
// Utility functions
|
||||
|
||||
/**
|
||||
* Utility function to get a StreamReader for the given text domain.
|
||||
*/
|
||||
function _get_reader($domain=null, $category=5, $enable_cache=true) {
|
||||
global $text_domains, $default_domain, $LC_CATEGORIES;
|
||||
if (!isset($domain)) $domain = $default_domain;
|
||||
if (!isset($text_domains[$domain]->l10n)) {
|
||||
// get the current locale
|
||||
$locale = _setlocale(LC_MESSAGES, 0);
|
||||
$p = isset($text_domains[$domain]->path) ? $text_domains[$domain]->path : './';
|
||||
$path = $p . "$locale/". $LC_CATEGORIES[$category] ."/$domain.mo";
|
||||
if (file_exists($path)) {
|
||||
$input = new FileReader($path);
|
||||
}
|
||||
else {
|
||||
$input = null;
|
||||
}
|
||||
$text_domains[$domain]->l10n = new gettext_reader($input, $enable_cache);
|
||||
}
|
||||
return $text_domains[$domain]->l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are using our emulated gettext API or PHP built-in one.
|
||||
*/
|
||||
function locale_emulation() {
|
||||
global $EMULATEGETTEXT;
|
||||
return $EMULATEGETTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current locale is supported on this system.
|
||||
*/
|
||||
function _check_locale() {
|
||||
global $EMULATEGETTEXT;
|
||||
return !$EMULATEGETTEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the codeset for the given domain.
|
||||
*/
|
||||
function _get_codeset($domain=null) {
|
||||
global $text_domains, $default_domain, $LC_CATEGORIES;
|
||||
if (!isset($domain)) $domain = $default_domain;
|
||||
return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given string to the encoding set by bind_textdomain_codeset.
|
||||
*/
|
||||
function _encode($text) {
|
||||
$source_encoding = mb_detect_encoding($text);
|
||||
$target_encoding = _get_codeset();
|
||||
if ($source_encoding != $target_encoding) {
|
||||
return mb_convert_encoding($text, $target_encoding, $source_encoding);
|
||||
}
|
||||
else {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Custom implementation of the standard gettext related functions
|
||||
|
||||
/**
|
||||
* Sets a requested locale, if needed emulates it.
|
||||
*/
|
||||
function _setlocale($category, $locale) {
|
||||
global $CURRENTLOCALE, $EMULATEGETTEXT;
|
||||
if ($locale === 0) { // use === to differentiate between string "0"
|
||||
if ($CURRENTLOCALE != '')
|
||||
return $CURRENTLOCALE;
|
||||
else
|
||||
// obey LANG variable, maybe extend to support all of LC_* vars
|
||||
// even if we tried to read locale without setting it first
|
||||
return _setlocale($category, $CURRENTLOCALE);
|
||||
} else {
|
||||
$ret = 0;
|
||||
if (function_exists('setlocale')) // I don't know if this ever happens ;)
|
||||
$ret = setlocale($category, $locale);
|
||||
if (($ret and $locale == '') or ($ret == $locale)) {
|
||||
$EMULATEGETTEXT = 0;
|
||||
$CURRENTLOCALE = $ret;
|
||||
} else {
|
||||
if ($locale == '') // emulate variable support
|
||||
$CURRENTLOCALE = getenv('LANG');
|
||||
else
|
||||
$CURRENTLOCALE = $locale;
|
||||
$EMULATEGETTEXT = 1;
|
||||
}
|
||||
return $CURRENTLOCALE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path for a domain.
|
||||
*/
|
||||
function _bindtextdomain($domain, $path) {
|
||||
global $text_domains;
|
||||
// ensure $path ends with a slash
|
||||
if ($path[strlen($path) - 1] != '/') $path .= '/';
|
||||
elseif ($path[strlen($path) - 1] != '\\') $path .= '\\';
|
||||
$text_domains[$domain]->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the character encoding in which the messages from the DOMAIN message catalog will be returned.
|
||||
*/
|
||||
function _bind_textdomain_codeset($domain, $codeset) {
|
||||
global $text_domains;
|
||||
$text_domains[$domain]->codeset = $codeset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default domain.
|
||||
*/
|
||||
function _textdomain($domain) {
|
||||
global $default_domain;
|
||||
$default_domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a message in the current domain.
|
||||
*/
|
||||
function _gettext($msgid) {
|
||||
$l10n = _get_reader();
|
||||
//return $l10n->translate($msgid);
|
||||
return _encode($l10n->translate($msgid));
|
||||
}
|
||||
/**
|
||||
* Alias for gettext.
|
||||
*/
|
||||
function __($msgid) {
|
||||
return _gettext($msgid);
|
||||
}
|
||||
/**
|
||||
* Plural version of gettext.
|
||||
*/
|
||||
function _ngettext($single, $plural, $number) {
|
||||
$l10n = _get_reader();
|
||||
//return $l10n->ngettext($single, $plural, $number);
|
||||
return _encode($l10n->ngettext($single, $plural, $number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the current domain.
|
||||
*/
|
||||
function _dgettext($domain, $msgid) {
|
||||
$l10n = _get_reader($domain);
|
||||
//return $l10n->translate($msgid);
|
||||
return _encode($l10n->translate($msgid));
|
||||
}
|
||||
/**
|
||||
* Plural version of dgettext.
|
||||
*/
|
||||
function _dngettext($domain, $single, $plural, $number) {
|
||||
$l10n = _get_reader($domain);
|
||||
//return $l10n->ngettext($single, $plural, $number);
|
||||
return _encode($l10n->ngettext($single, $plural, $number));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the domain and category for a single lookup.
|
||||
*/
|
||||
function _dcgettext($domain, $msgid, $category) {
|
||||
$l10n = _get_reader($domain, $category);
|
||||
//return $l10n->translate($msgid);
|
||||
return _encode($l10n->translate($msgid));
|
||||
}
|
||||
/**
|
||||
* Plural version of dcgettext.
|
||||
*/
|
||||
function _dcngettext($domain, $single, $plural, $number, $category) {
|
||||
$l10n = _get_reader($domain, $category);
|
||||
//return $l10n->ngettext($single, $plural, $number);
|
||||
return _encode($l10n->ngettext($single, $plural, $number));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Wrappers to use if the standard gettext functions are available, but the current locale is not supported by the system.
|
||||
// Use the standard impl if the current locale is supported, use the custom impl otherwise.
|
||||
|
||||
function T_setlocale($category, $locale) {
|
||||
return _setlocale($category, $locale);
|
||||
}
|
||||
|
||||
function T_bindtextdomain($domain, $path) {
|
||||
if (_check_locale()) return bindtextdomain($domain, $path);
|
||||
else return _bindtextdomain($domain, $path);
|
||||
}
|
||||
function T_bind_textdomain_codeset($domain, $codeset) {
|
||||
// bind_textdomain_codeset is available only in PHP 4.2.0+
|
||||
if (_check_locale() and function_exists('bind_textdomain_codeset')) return bind_textdomain_codeset($domain, $codeset);
|
||||
else return _bind_textdomain_codeset($domain, $codeset);
|
||||
}
|
||||
function T_textdomain($domain) {
|
||||
if (_check_locale()) return textdomain($domain);
|
||||
else return _textdomain($domain);
|
||||
}
|
||||
function T_gettext($msgid) {
|
||||
if (_check_locale()) return gettext($msgid);
|
||||
else return _gettext($msgid);
|
||||
}
|
||||
function T_($msgid) {
|
||||
if (_check_locale()) return _($msgid);
|
||||
return __($msgid);
|
||||
}
|
||||
function T_ngettext($single, $plural, $number) {
|
||||
if (_check_locale()) return ngettext($single, $plural, $number);
|
||||
else return _ngettext($single, $plural, $number);
|
||||
}
|
||||
function T_dgettext($domain, $msgid) {
|
||||
if (_check_locale()) return dgettext($domain, $msgid);
|
||||
else return _dgettext($domain, $msgid);
|
||||
}
|
||||
function T_dngettext($domain, $single, $plural, $number) {
|
||||
if (_check_locale()) return dngettext($domain, $single, $plural, $number);
|
||||
else return _dngettext($domain, $single, $plural, $number);
|
||||
}
|
||||
function T_dcgettext($domain, $msgid, $category) {
|
||||
if (_check_locale()) return dcgettext($domain, $msgid, $category);
|
||||
else return _dcgettext($domain, $msgid, $category);
|
||||
}
|
||||
function T_dcngettext($domain, $single, $plural, $number, $category) {
|
||||
if (_check_locale()) return dcngettext($domain, $single, $plural, $number, $category);
|
||||
else return _dcngettext($domain, $single, $plural, $number, $category);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Wrappers used as a drop in replacement for the standard gettext functions
|
||||
|
||||
if (!function_exists('gettext')) {
|
||||
function bindtextdomain($domain, $path) {
|
||||
return _bindtextdomain($domain, $path);
|
||||
}
|
||||
function bind_textdomain_codeset($domain, $codeset) {
|
||||
return _bind_textdomain_codeset($domain, $codeset);
|
||||
}
|
||||
function textdomain($domain) {
|
||||
return _textdomain($domain);
|
||||
}
|
||||
function gettext($msgid) {
|
||||
return _gettext($msgid);
|
||||
}
|
||||
function _($msgid) {
|
||||
return __($msgid);
|
||||
}
|
||||
function ngettext($single, $plural, $number) {
|
||||
return _ngettext($single, $plural, $number);
|
||||
}
|
||||
function dgettext($domain, $msgid) {
|
||||
return _dgettext($domain, $msgid);
|
||||
}
|
||||
function dngettext($domain, $single, $plural, $number) {
|
||||
return _dngettext($domain, $single, $plural, $number);
|
||||
}
|
||||
function dcgettext($domain, $msgid, $category) {
|
||||
return _dcgettext($domain, $msgid, $category);
|
||||
}
|
||||
function dcngettext($domain, $single, $plural, $number, $category) {
|
||||
return _dcngettext($domain, $single, $plural, $number, $category);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
358
extlib/php-gettext/gettext.php
Normal file
358
extlib/php-gettext/gettext.php
Normal file
@ -0,0 +1,358 @@
|
||||
<?php
|
||||
/*
|
||||
Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
|
||||
Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a simple gettext replacement that works independently from
|
||||
* the system's gettext abilities.
|
||||
* It can read MO files and use them for translating strings.
|
||||
* The files are passed to gettext_reader as a Stream (see streams.php)
|
||||
*
|
||||
* This version has the ability to cache all strings and translations to
|
||||
* speed up the string lookup.
|
||||
* While the cache is enabled by default, it can be switched off with the
|
||||
* second parameter in the constructor (e.g. whenusing very large MO files
|
||||
* that you don't want to keep in memory)
|
||||
*/
|
||||
class gettext_reader {
|
||||
//public:
|
||||
var $error = 0; // public variable that holds error code (0 if no error)
|
||||
|
||||
//private:
|
||||
var $BYTEORDER = 0; // 0: low endian, 1: big endian
|
||||
var $STREAM = NULL;
|
||||
var $short_circuit = false;
|
||||
var $enable_cache = false;
|
||||
var $originals = NULL; // offset of original table
|
||||
var $translations = NULL; // offset of translation table
|
||||
var $pluralheader = NULL; // cache header field for plural forms
|
||||
var $total = 0; // total string count
|
||||
var $table_originals = NULL; // table for original strings (offsets)
|
||||
var $table_translations = NULL; // table for translated strings (offsets)
|
||||
var $cache_translations = NULL; // original -> translation mapping
|
||||
|
||||
|
||||
/* Methods */
|
||||
|
||||
|
||||
/**
|
||||
* Reads a 32bit Integer from the Stream
|
||||
*
|
||||
* @access private
|
||||
* @return Integer from the Stream
|
||||
*/
|
||||
function readint() {
|
||||
if ($this->BYTEORDER == 0) {
|
||||
// low endian
|
||||
return array_shift(unpack('V', $this->STREAM->read(4)));
|
||||
} else {
|
||||
// big endian
|
||||
return array_shift(unpack('N', $this->STREAM->read(4)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an array of Integers from the Stream
|
||||
*
|
||||
* @param int count How many elements should be read
|
||||
* @return Array of Integers
|
||||
*/
|
||||
function readintarray($count) {
|
||||
if ($this->BYTEORDER == 0) {
|
||||
// low endian
|
||||
return unpack('V'.$count, $this->STREAM->read(4 * $count));
|
||||
} else {
|
||||
// big endian
|
||||
return unpack('N'.$count, $this->STREAM->read(4 * $count));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param object Reader the StreamReader object
|
||||
* @param boolean enable_cache Enable or disable caching of strings (default on)
|
||||
*/
|
||||
function gettext_reader($Reader, $enable_cache = true) {
|
||||
// If there isn't a StreamReader, turn on short circuit mode.
|
||||
if (! $Reader || isset($Reader->error) ) {
|
||||
$this->short_circuit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Caching can be turned off
|
||||
$this->enable_cache = $enable_cache;
|
||||
|
||||
// $MAGIC1 = (int)0x950412de; //bug in PHP 5
|
||||
$MAGIC1 = (int) - 1794895138;
|
||||
// $MAGIC2 = (int)0xde120495; //bug
|
||||
$MAGIC2 = (int) - 569244523;
|
||||
|
||||
$this->STREAM = $Reader;
|
||||
$magic = $this->readint();
|
||||
if ($magic == $MAGIC1) {
|
||||
$this->BYTEORDER = 0;
|
||||
} elseif ($magic == $MAGIC2) {
|
||||
$this->BYTEORDER = 1;
|
||||
} else {
|
||||
$this->error = 1; // not MO file
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: Do we care about revision? We should.
|
||||
$revision = $this->readint();
|
||||
|
||||
$this->total = $this->readint();
|
||||
$this->originals = $this->readint();
|
||||
$this->translations = $this->readint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the translation tables from the MO file into the cache
|
||||
* If caching is enabled, also loads all strings into a cache
|
||||
* to speed up translation lookups
|
||||
*
|
||||
* @access private
|
||||
*/
|
||||
function load_tables() {
|
||||
if (is_array($this->cache_translations) &&
|
||||
is_array($this->table_originals) &&
|
||||
is_array($this->table_translations))
|
||||
return;
|
||||
|
||||
/* get original and translations tables */
|
||||
$this->STREAM->seekto($this->originals);
|
||||
$this->table_originals = $this->readintarray($this->total * 2);
|
||||
$this->STREAM->seekto($this->translations);
|
||||
$this->table_translations = $this->readintarray($this->total * 2);
|
||||
|
||||
if ($this->enable_cache) {
|
||||
$this->cache_translations = array ();
|
||||
/* read all strings in the cache */
|
||||
for ($i = 0; $i < $this->total; $i++) {
|
||||
$this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
|
||||
$original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
|
||||
$this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
|
||||
$translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
|
||||
$this->cache_translations[$original] = $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "originals" table
|
||||
*
|
||||
* @access private
|
||||
* @param int num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
function get_original_string($num) {
|
||||
$length = $this->table_originals[$num * 2 + 1];
|
||||
$offset = $this->table_originals[$num * 2 + 2];
|
||||
if (! $length)
|
||||
return '';
|
||||
$this->STREAM->seekto($offset);
|
||||
$data = $this->STREAM->read($length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "translations" table
|
||||
*
|
||||
* @access private
|
||||
* @param int num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
function get_translation_string($num) {
|
||||
$length = $this->table_translations[$num * 2 + 1];
|
||||
$offset = $this->table_translations[$num * 2 + 2];
|
||||
if (! $length)
|
||||
return '';
|
||||
$this->STREAM->seekto($offset);
|
||||
$data = $this->STREAM->read($length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search for string
|
||||
*
|
||||
* @access private
|
||||
* @param string string
|
||||
* @param int start (internally used in recursive function)
|
||||
* @param int end (internally used in recursive function)
|
||||
* @return int string number (offset in originals table)
|
||||
*/
|
||||
function find_string($string, $start = -1, $end = -1) {
|
||||
if (($start == -1) or ($end == -1)) {
|
||||
// find_string is called with only one parameter, set start end end
|
||||
$start = 0;
|
||||
$end = $this->total;
|
||||
}
|
||||
if (abs($start - $end) <= 1) {
|
||||
// We're done, now we either found the string, or it doesn't exist
|
||||
$txt = $this->get_original_string($start);
|
||||
if ($string == $txt)
|
||||
return $start;
|
||||
else
|
||||
return -1;
|
||||
} else if ($start > $end) {
|
||||
// start > end -> turn around and start over
|
||||
return $this->find_string($string, $end, $start);
|
||||
} else {
|
||||
// Divide table in two parts
|
||||
$half = (int)(($start + $end) / 2);
|
||||
$cmp = strcmp($string, $this->get_original_string($half));
|
||||
if ($cmp == 0)
|
||||
// string is exactly in the middle => return it
|
||||
return $half;
|
||||
else if ($cmp < 0)
|
||||
// The string is in the upper half
|
||||
return $this->find_string($string, $start, $half);
|
||||
else
|
||||
// The string is in the lower half
|
||||
return $this->find_string($string, $half, $end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string string to be translated
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
function translate($string) {
|
||||
if ($this->short_circuit)
|
||||
return $string;
|
||||
$this->load_tables();
|
||||
|
||||
if ($this->enable_cache) {
|
||||
// Caching enabled, get translated string from cache
|
||||
if (array_key_exists($string, $this->cache_translations))
|
||||
return $this->cache_translations[$string];
|
||||
else
|
||||
return $string;
|
||||
} else {
|
||||
// Caching not enabled, try to find string
|
||||
$num = $this->find_string($string);
|
||||
if ($num == -1)
|
||||
return $string;
|
||||
else
|
||||
return $this->get_translation_string($num);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible plural forms from MO header
|
||||
*
|
||||
* @access private
|
||||
* @return string plural form header
|
||||
*/
|
||||
function get_plural_forms() {
|
||||
// lets assume message number 0 is header
|
||||
// this is true, right?
|
||||
$this->load_tables();
|
||||
|
||||
// cache header field for plural forms
|
||||
if (! is_string($this->pluralheader)) {
|
||||
if ($this->enable_cache) {
|
||||
$header = $this->cache_translations[""];
|
||||
} else {
|
||||
$header = $this->get_translation_string(0);
|
||||
}
|
||||
if (eregi("plural-forms: ([^\n]*)\n", $header, $regs))
|
||||
$expr = $regs[1];
|
||||
else
|
||||
$expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
|
||||
$this->pluralheader = $expr;
|
||||
}
|
||||
return $this->pluralheader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects which plural form to take
|
||||
*
|
||||
* @access private
|
||||
* @param n count
|
||||
* @return int array index of the right plural form
|
||||
*/
|
||||
function select_string($n) {
|
||||
$string = $this->get_plural_forms();
|
||||
$string = str_replace('nplurals',"\$total",$string);
|
||||
$string = str_replace("n",$n,$string);
|
||||
$string = str_replace('plural',"\$plural",$string);
|
||||
|
||||
$total = 0;
|
||||
$plural = 0;
|
||||
|
||||
eval("$string");
|
||||
if ($plural >= $total) $plural = $total - 1;
|
||||
return $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string single
|
||||
* @param string plural
|
||||
* @param string number
|
||||
* @return translated plural form
|
||||
*/
|
||||
function ngettext($single, $plural, $number) {
|
||||
if ($this->short_circuit) {
|
||||
if ($number != 1)
|
||||
return $plural;
|
||||
else
|
||||
return $single;
|
||||
}
|
||||
|
||||
// find out the appropriate form
|
||||
$select = $this->select_string($number);
|
||||
|
||||
// this should contains all strings separated by NULLs
|
||||
$key = $single.chr(0).$plural;
|
||||
|
||||
|
||||
if ($this->enable_cache) {
|
||||
if (! array_key_exists($key, $this->cache_translations)) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->cache_translations[$key];
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select];
|
||||
}
|
||||
} else {
|
||||
$num = $this->find_string($key);
|
||||
if ($num == -1) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->get_translation_string($num);
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
167
extlib/php-gettext/streams.php
Normal file
167
extlib/php-gettext/streams.php
Normal file
@ -0,0 +1,167 @@
|
||||
<?php
|
||||
/*
|
||||
Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Simple class to wrap file streams, string streams, etc.
|
||||
// seek is essential, and it should be byte stream
|
||||
class StreamReader {
|
||||
// should return a string [FIXME: perhaps return array of bytes?]
|
||||
function read($bytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// should return new position
|
||||
function seekto($position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns current position
|
||||
function currentpos() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// returns length of entire stream (limit for seekto()s)
|
||||
function length() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class StringReader {
|
||||
var $_pos;
|
||||
var $_str;
|
||||
|
||||
function StringReader($str='') {
|
||||
$this->_str = $str;
|
||||
$this->_pos = 0;
|
||||
}
|
||||
|
||||
function read($bytes) {
|
||||
$data = substr($this->_str, $this->_pos, $bytes);
|
||||
$this->_pos += $bytes;
|
||||
if (strlen($this->_str)<$this->_pos)
|
||||
$this->_pos = strlen($this->_str);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function seekto($pos) {
|
||||
$this->_pos = $pos;
|
||||
if (strlen($this->_str)<$this->_pos)
|
||||
$this->_pos = strlen($this->_str);
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function currentpos() {
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function length() {
|
||||
return strlen($this->_str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class FileReader {
|
||||
var $_pos;
|
||||
var $_fd;
|
||||
var $_length;
|
||||
|
||||
function FileReader($filename) {
|
||||
if (file_exists($filename)) {
|
||||
|
||||
$this->_length=filesize($filename);
|
||||
$this->_pos = 0;
|
||||
$this->_fd = fopen($filename,'rb');
|
||||
if (!$this->_fd) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->error = 2; // File doesn't exist
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function read($bytes) {
|
||||
if ($bytes) {
|
||||
fseek($this->_fd, $this->_pos);
|
||||
|
||||
// PHP 5.1.1 does not read more than 8192 bytes in one fread()
|
||||
// the discussions at PHP Bugs suggest it's the intended behaviour
|
||||
$data = '';
|
||||
while ($bytes > 0) {
|
||||
$chunk = fread($this->_fd, $bytes);
|
||||
$data .= $chunk;
|
||||
$bytes -= strlen($chunk);
|
||||
}
|
||||
$this->_pos = ftell($this->_fd);
|
||||
|
||||
return $data;
|
||||
} else return '';
|
||||
}
|
||||
|
||||
function seekto($pos) {
|
||||
fseek($this->_fd, $pos);
|
||||
$this->_pos = ftell($this->_fd);
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function currentpos() {
|
||||
return $this->_pos;
|
||||
}
|
||||
|
||||
function length() {
|
||||
return $this->_length;
|
||||
}
|
||||
|
||||
function close() {
|
||||
fclose($this->_fd);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Preloads entire file in memory first, then creates a StringReader
|
||||
// over it (it assumes knowledge of StringReader internals)
|
||||
class CachedFileReader extends StringReader {
|
||||
function CachedFileReader($filename) {
|
||||
if (file_exists($filename)) {
|
||||
|
||||
$length=filesize($filename);
|
||||
$fd = fopen($filename,'rb');
|
||||
|
||||
if (!$fd) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
return false;
|
||||
}
|
||||
$this->_str = fread($fd, $length);
|
||||
fclose($fd);
|
||||
|
||||
} else {
|
||||
$this->error = 2; // File doesn't exist
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
36
index.php
36
index.php
@ -182,12 +182,36 @@ function main()
|
||||
// If the site is private, and they're not on one of the "public"
|
||||
// parts of the site, redirect to login
|
||||
|
||||
if (!$user && common_config('site', 'private') &&
|
||||
!in_array($action, array('login', 'openidlogin', 'finishopenidlogin',
|
||||
'recoverpassword', 'api', 'doc', 'register')) &&
|
||||
!preg_match('/rss$/', $action)) {
|
||||
common_redirect(common_local_url('login'));
|
||||
return;
|
||||
if (!$user && common_config('site', 'private')) {
|
||||
$public_actions = array('openidlogin', 'finishopenidlogin',
|
||||
'recoverpassword', 'api', 'doc',
|
||||
'opensearch');
|
||||
$login_action = 'openidlogin';
|
||||
if (!common_config('site', 'openidonly')) {
|
||||
$public_actions[] = 'login';
|
||||
$public_actions[] = 'register';
|
||||
$login_action = 'login';
|
||||
}
|
||||
if (!in_array($action, $public_actions) &&
|
||||
!preg_match('/rss$/', $action)) {
|
||||
|
||||
// set returnto
|
||||
$rargs =& common_copy_args($args);
|
||||
unset($rargs['action']);
|
||||
if (common_config('site', 'fancy')) {
|
||||
unset($rargs['p']);
|
||||
}
|
||||
if (array_key_exists('submit', $rargs)) {
|
||||
unset($rargs['submit']);
|
||||
}
|
||||
foreach (array_keys($_COOKIE) as $cookie) {
|
||||
unset($rargs[$cookie]);
|
||||
}
|
||||
common_set_returnto(common_local_url($action, $rargs));
|
||||
|
||||
common_redirect(common_local_url($login_action));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$action_class = ucfirst($action).'Action';
|
||||
|
137
install.php
137
install.php
@ -49,8 +49,7 @@ function checkPrereqs()
|
||||
}
|
||||
|
||||
$reqs = array('gd', 'curl',
|
||||
'xmlwriter', 'mbstring',
|
||||
'gettext');
|
||||
'xmlwriter', 'mbstring');
|
||||
|
||||
foreach ($reqs as $req) {
|
||||
if (!checkExtension($req)) {
|
||||
@ -180,6 +179,9 @@ function handlePost()
|
||||
$password = $_POST['password'];
|
||||
$sitename = $_POST['sitename'];
|
||||
$fancy = !empty($_POST['fancy']);
|
||||
$server = $_SERVER['HTTP_HOST'];
|
||||
$path = substr(dirname($_SERVER['PHP_SELF']), 1);
|
||||
|
||||
?>
|
||||
<dl class="system_notice">
|
||||
<dt>Page notice</dt>
|
||||
@ -219,20 +221,42 @@ function handlePost()
|
||||
}
|
||||
|
||||
switch($dbtype) {
|
||||
case 'mysql': mysql_db_installer($host, $database, $username, $password, $sitename, $fancy);
|
||||
break;
|
||||
case 'pgsql': pgsql_db_installer($host, $database, $username, $password, $sitename, $fancy);
|
||||
break;
|
||||
default:
|
||||
case 'mysql':
|
||||
$db = mysql_db_installer($host, $database, $username, $password);
|
||||
break;
|
||||
case 'pgsql':
|
||||
$db = pgsql_db_installer($host, $database, $username, $password);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
if ($path) $path .= '/';
|
||||
updateStatus("You can visit your <a href='/$path'>new Laconica site</a>.");
|
||||
|
||||
if (!$db) {
|
||||
// database connection failed, do not move on to create config file.
|
||||
return false;
|
||||
}
|
||||
|
||||
updateStatus("Writing config file...");
|
||||
$res = writeConf($sitename, $server, $path, $fancy, $db);
|
||||
|
||||
if (!$res) {
|
||||
updateStatus("Can't write config file.", true);
|
||||
showForm();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO https needs to be considered
|
||||
*/
|
||||
$link = "http://".$server.'/'.$path;
|
||||
|
||||
updateStatus("Laconica has been installed at $link");
|
||||
updateStatus("You can visit your <a href='$link'>new Laconica site</a>.");
|
||||
?>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
function pgsql_db_installer($host, $database, $username, $password, $sitename, $fancy) {
|
||||
function pgsql_db_installer($host, $database, $username, $password) {
|
||||
$connstring = "dbname=$database host=$host user=$username";
|
||||
|
||||
//No password would mean trust authentication used.
|
||||
@ -265,7 +289,7 @@ function pgsql_db_installer($host, $database, $username, $password, $sitename, $
|
||||
if ($res === false) {
|
||||
updateStatus("Can't run database script.", true);
|
||||
showForm();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
foreach (array('sms_carrier' => 'SMS carrier',
|
||||
'notice_source' => 'notice source',
|
||||
@ -276,29 +300,24 @@ function pgsql_db_installer($host, $database, $username, $password, $sitename, $
|
||||
if ($res === false) {
|
||||
updateStatus(sprintf("Can't run %d script.", $name), true);
|
||||
showForm();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pg_query($conn, 'COMMIT');
|
||||
|
||||
updateStatus("Writing config file...");
|
||||
if (empty($password)) {
|
||||
$sqlUrl = "pgsql://$username@$host/$database";
|
||||
}
|
||||
else {
|
||||
$sqlUrl = "pgsql://$username:$password@$host/$database";
|
||||
}
|
||||
$res = writeConf($sitename, $sqlUrl, $fancy, 'pgsql');
|
||||
if (!$res) {
|
||||
updateStatus("Can't write config file.", true);
|
||||
showForm();
|
||||
return;
|
||||
}
|
||||
updateStatus("Done!");
|
||||
|
||||
|
||||
$db = array('type' => 'pgsql', 'database' => $sqlUrl);
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
function mysql_db_installer($host, $database, $username, $password, $sitename, $fancy) {
|
||||
function mysql_db_installer($host, $database, $username, $password) {
|
||||
updateStatus("Starting installation...");
|
||||
updateStatus("Checking database...");
|
||||
|
||||
@ -306,21 +325,21 @@ function mysql_db_installer($host, $database, $username, $password, $sitename, $
|
||||
if (!$conn) {
|
||||
updateStatus("Can't connect to server '$host' as '$username'.", true);
|
||||
showForm();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
updateStatus("Changing to database...");
|
||||
$res = mysql_select_db($database, $conn);
|
||||
if (!$res) {
|
||||
updateStatus("Can't change to database.", true);
|
||||
showForm();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
updateStatus("Running database script...");
|
||||
$res = runDbScript(INSTALLDIR.'/db/laconica.sql', $conn);
|
||||
if ($res === false) {
|
||||
updateStatus("Can't run database script.", true);
|
||||
showForm();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
foreach (array('sms_carrier' => 'SMS carrier',
|
||||
'notice_source' => 'notice source',
|
||||
@ -331,35 +350,44 @@ function mysql_db_installer($host, $database, $username, $password, $sitename, $
|
||||
if ($res === false) {
|
||||
updateStatus(sprintf("Can't run %d script.", $name), true);
|
||||
showForm();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus("Writing config file...");
|
||||
$sqlUrl = "mysqli://$username:$password@$host/$database";
|
||||
$res = writeConf($sitename, $sqlUrl, $fancy);
|
||||
if (!$res) {
|
||||
updateStatus("Can't write config file.", true);
|
||||
showForm();
|
||||
return;
|
||||
}
|
||||
updateStatus("Done!");
|
||||
}
|
||||
function writeConf($sitename, $sqlUrl, $fancy, $type='mysql')
|
||||
$db = array('type' => 'mysql', 'database' => $sqlUrl);
|
||||
return $db;
|
||||
}
|
||||
|
||||
function writeConf($sitename, $server, $path, $fancy, $db)
|
||||
{
|
||||
$res = file_put_contents(INSTALLDIR.'/config.php',
|
||||
"<?php\n".
|
||||
"if (!defined('LACONICA')) { exit(1); }\n\n".
|
||||
"\$config['site']['name'] = \"$sitename\";\n\n".
|
||||
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
|
||||
"\$config['db']['database'] = \"$sqlUrl\";\n\n".
|
||||
($type == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n" .
|
||||
"\$config['db']['type'] = \"$type\";\n\n" : '').
|
||||
"?>");
|
||||
// assemble configuration file in a string
|
||||
$cfg = "<?php\n".
|
||||
"if (!defined('LACONICA')) { exit(1); }\n\n".
|
||||
|
||||
// site name
|
||||
"\$config['site']['name'] = '$sitename';\n\n".
|
||||
|
||||
// site location
|
||||
"\$config['site']['server'] = '$server';\n".
|
||||
"\$config['site']['path'] = '$path'; \n\n".
|
||||
|
||||
// checks if fancy URLs are enabled
|
||||
($fancy ? "\$config['site']['fancy'] = true;\n\n":'').
|
||||
|
||||
// database
|
||||
"\$config['db']['database'] = '{$db['database']}';\n\n".
|
||||
($db['type'] == 'pgsql' ? "\$config['db']['quote_identifiers'] = true;\n\n":'').
|
||||
"\$config['db']['type'] = '{$db['type']}';\n\n".
|
||||
|
||||
"?>";
|
||||
// write configuration file out to install directory
|
||||
$res = file_put_contents(INSTALLDIR.'/config.php', $cfg);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
function runDbScript($filename, $conn, $type='mysql')
|
||||
function runDbScript($filename, $conn, $type = 'mysql')
|
||||
{
|
||||
$sql = trim(file_get_contents($filename));
|
||||
$stmts = explode(';', $sql);
|
||||
@ -368,10 +396,15 @@ function runDbScript($filename, $conn, $type='mysql')
|
||||
if (!mb_strlen($stmt)) {
|
||||
continue;
|
||||
}
|
||||
if ($type == 'mysql') {
|
||||
$res = mysql_query($stmt, $conn);
|
||||
} elseif ($type=='pgsql') {
|
||||
$res = pg_query($conn, $stmt);
|
||||
switch ($type) {
|
||||
case 'mysql':
|
||||
$res = mysql_query($stmt, $conn);
|
||||
break;
|
||||
case 'pgsql':
|
||||
$res = pg_query($conn, $stmt);
|
||||
break;
|
||||
default:
|
||||
updateStatus("runDbScript() error: unknown database type ". $type ." provided.");
|
||||
}
|
||||
if ($res === false) {
|
||||
updateStatus("FAILED SQL: $stmt");
|
||||
@ -383,9 +416,7 @@ function runDbScript($filename, $conn, $type='mysql')
|
||||
|
||||
?>
|
||||
<?php echo"<?"; ?> xml version="1.0" encoding="UTF-8" <?php echo "?>"; ?>
|
||||
<!DOCTYPE html
|
||||
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US">
|
||||
<head>
|
||||
<title>Install Laconica</title>
|
||||
|
163
js/jcrop/jquery.Jcrop.min.js
vendored
Normal file
163
js/jcrop/jquery.Jcrop.min.js
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Jcrop v.0.9.8 (minimized)
|
||||
* (c) 2008 Kelly Hallman and DeepLiquid.com
|
||||
* More information: http://deepliquid.com/content/Jcrop.html
|
||||
* Released under MIT License - this header must remain with code
|
||||
*/
|
||||
|
||||
|
||||
(function($){$.Jcrop=function(obj,opt)
|
||||
{var obj=obj,opt=opt;if(typeof(obj)!=='object')obj=$(obj)[0];if(typeof(opt)!=='object')opt={};if(!('trackDocument'in opt))
|
||||
{opt.trackDocument=$.browser.msie?false:true;if($.browser.msie&&$.browser.version.split('.')[0]=='8')
|
||||
opt.trackDocument=true;}
|
||||
if(!('keySupport'in opt))
|
||||
opt.keySupport=$.browser.msie?false:true;var defaults={trackDocument:false,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:.6,borderOpacity:.4,handleOpacity:.5,handlePad:5,handleSize:9,handleOffset:5,edgeMargin:14,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,boxWidth:0,boxHeight:0,boundary:8,animationDelay:20,swingSpeed:3,allowSelect:true,allowMove:true,allowResize:true,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){}};var options=defaults;setOptions(opt);var $origimg=$(obj);var $img=$origimg.clone().removeAttr('id').css({position:'absolute'});$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);;if(options.addClass)$div.addClass(options.addClass);var $img2=$('<img />').attr('src',$img.attr('src')).css('position','absolute').width(boundx).height(boundy);var $img_holder=$('<div />').width(pct(100)).height(pct(100)).css({zIndex:310,position:'absolute',overflow:'hidden'}).append($img2);var $hdl_holder=$('<div />').width(pct(100)).height(pct(100)).css('zIndex',320);var $sel=$('<div />').css({position:'absolute',zIndex:300}).insertBefore($img).append($img_holder,$hdl_holder);var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var xlimit,ylimit,xmin,ymin;var xscale,yscale,enabled=true;var docOffset=getPos($img),btndown,lastcurs,dimmed,animating,shift_down;var Coords=function()
|
||||
{var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
|
||||
{var pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];};function setCurrent(pos)
|
||||
{var pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];};function getOffset()
|
||||
{return[ox,oy];};function moveOffset(offset)
|
||||
{var ox=offset[0],oy=offset[1];if(0>x1+ox)ox-=ox+x1;if(0>y1+oy)oy-=oy+y1;if(boundy<y2+oy)oy+=boundy-(y2+oy);if(boundx<x2+ox)ox+=boundx-(x2+ox);x1+=ox;x2+=ox;y1+=oy;y2+=oy;};function getCorner(ord)
|
||||
{var c=getFixed();switch(ord)
|
||||
{case'ne':return[c.x2,c.y];case'nw':return[c.x,c.y];case'se':return[c.x2,c.y2];case'sw':return[c.x,c.y2];}};function getFixed()
|
||||
{if(!options.aspectRatio)return getRect();var aspect=options.aspectRatio,min_x=options.minSize[0]/xscale,min_y=options.minSize[1]/yscale,max_x=options.maxSize[0]/xscale,max_y=options.maxSize[1]/yscale,rw=x2-x1,rh=y2-y1,rwa=Math.abs(rw),rha=Math.abs(rh),real_ratio=rwa/rha,xx,yy;if(max_x==0){max_x=boundx*10}
|
||||
if(max_y==0){max_y=boundy*10}
|
||||
if(real_ratio<aspect)
|
||||
{yy=y2;w=rha*aspect;xx=rw<0?x1-w:w+x1;if(xx<0)
|
||||
{xx=0;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}
|
||||
else if(xx>boundx)
|
||||
{xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}
|
||||
else
|
||||
{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0)
|
||||
{yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}
|
||||
else if(yy>boundy)
|
||||
{yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
|
||||
if(xx>x1){if(xx-x1<min_x){xx=x1+min_x;}else if(xx-x1>max_x){xx=x1+max_x;}
|
||||
if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xx<x1){if(x1-xx<min_x){xx=x1-min_x}else if(x1-xx>max_x){xx=x1-max_x;}
|
||||
if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
|
||||
if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
|
||||
if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
|
||||
return last=makeObj(flipCoords(x1,y1,xx,yy));};function rebound(p)
|
||||
{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[p[0],p[1]];};function flipCoords(x1,y1,x2,y2)
|
||||
{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2<x1)
|
||||
{xa=x2;xb=x1;}
|
||||
if(y2<y1)
|
||||
{ya=y2;yb=y1;}
|
||||
return[Math.round(xa),Math.round(ya),Math.round(xb),Math.round(yb)];};function getRect()
|
||||
{var xsize=x2-x1;var ysize=y2-y1;if(xlimit&&(Math.abs(xsize)>xlimit))
|
||||
x2=(xsize>0)?(x1+xlimit):(x1-xlimit);if(ylimit&&(Math.abs(ysize)>ylimit))
|
||||
y2=(ysize>0)?(y1+ylimit):(y1-ylimit);if(ymin&&(Math.abs(ysize)<ymin))
|
||||
y2=(ysize>0)?(y1+ymin):(y1-ymin);if(xmin&&(Math.abs(xsize)<xmin))
|
||||
x2=(xsize>0)?(x1+xmin):(x1-xmin);if(x1<0){x2-=x1;x1-=x1;}
|
||||
if(y1<0){y2-=y1;y1-=y1;}
|
||||
if(x2<0){x1-=x2;x2-=x2;}
|
||||
if(y2<0){y1-=y2;y2-=y2;}
|
||||
if(x2>boundx){var delta=x2-boundx;x1-=delta;x2-=delta;}
|
||||
if(y2>boundy){var delta=y2-boundy;y1-=delta;y2-=delta;}
|
||||
if(x1>boundx){var delta=x1-boundy;y2-=delta;y1-=delta;}
|
||||
if(y1>boundy){var delta=y1-boundy;y2-=delta;y1-=delta;}
|
||||
return makeObj(flipCoords(x1,y1,x2,y2));};function makeObj(a)
|
||||
{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};};return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}();var Selection=function()
|
||||
{var start,end,dragmode,awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;if(options.drawBorders){borders={top:insertBorder('hline').css('top',$.browser.msie?px(-1):px(0)),bottom:insertBorder('hline'),left:insertBorder('vline'),right:insertBorder('vline')};}
|
||||
if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');}
|
||||
options.sideHandles&&createHandles(['n','s','e','w']);options.cornerHandles&&createHandles(['sw','nw','ne','se']);function insertBorder(type)
|
||||
{var jq=$('<div />').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;};function dragDiv(ord,zi)
|
||||
{var jq=$('<div />').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi});$hdl_holder.append(jq);return jq;};function insertHandle(ord)
|
||||
{return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).addClass(cssClass('handle'));};function insertDragbar(ord)
|
||||
{var s=options.handleSize,o=hhs,h=s,w=s,t=o,l=o;switch(ord)
|
||||
{case'n':case's':w=pct(100);break;case'e':case'w':h=pct(100);break;}
|
||||
return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});};function createHandles(li)
|
||||
{for(i in li)handle[li[i]]=insertHandle(li[i]);};function moveHandles(c)
|
||||
{var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;'e'in handle&&handle.e.css({top:px(midvert),left:px(east)})&&handle.w.css({top:px(midvert)})&&handle.s.css({top:px(south),left:px(midhoriz)})&&handle.n.css({left:px(midhoriz)});'ne'in handle&&handle.ne.css({left:px(east)})&&handle.se.css({top:px(south),left:px(east)})&&handle.sw.css({top:px(south)});'b'in handle&&handle.b.css({top:px(south)})&&handle.r.css({left:px(east)});};function moveto(x,y)
|
||||
{$img2.css({top:px(-y),left:px(-x)});$sel.css({top:px(y),left:px(x)});};function resize(w,h)
|
||||
{$sel.width(w).height(h);};function refresh()
|
||||
{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();};function updateVisible()
|
||||
{if(awake)return update();};function update()
|
||||
{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);options.drawBorders&&borders['right'].css({left:px(c.w-1)})&&borders['bottom'].css({top:px(c.h-1)});seehandles&&moveHandles(c);awake||show();options.onChange(unscale(c));};function show()
|
||||
{$sel.show();$img.css('opacity',options.bgOpacity);awake=true;};function release()
|
||||
{disableHandles();$sel.hide();$img.css('opacity',1);awake=false;};function showHandles()
|
||||
{if(seehandles)
|
||||
{moveHandles(Coords.getFixed());$hdl_holder.show();}};function enableHandles()
|
||||
{seehandles=true;if(options.allowResize)
|
||||
{moveHandles(Coords.getFixed());$hdl_holder.show();return true;}};function disableHandles()
|
||||
{seehandles=false;$hdl_holder.hide();};function animMode(v)
|
||||
{(animating=v)?disableHandles():enableHandles();};function done()
|
||||
{animMode(false);refresh();};var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360})
|
||||
$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,done:done};}();var Tracker=function()
|
||||
{var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;if(!trackDoc)
|
||||
{$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
|
||||
function toFront()
|
||||
{$trk.css({zIndex:450});if(trackDoc)
|
||||
{$(document).mousemove(trackMove).mouseup(trackUp);}}
|
||||
function toBack()
|
||||
{$trk.css({zIndex:290});if(trackDoc)
|
||||
{$(document).unbind('mousemove',trackMove).unbind('mouseup',trackUp);}}
|
||||
function trackMove(e)
|
||||
{onMove(mouseAbs(e));};function trackUp(e)
|
||||
{e.preventDefault();e.stopPropagation();if(btndown)
|
||||
{btndown=false;onDone(mouseAbs(e));options.onSelect(unscale(Coords.getFixed()));toBack();onMove=function(){};onDone=function(){};}
|
||||
return false;};function activateHandlers(move,done)
|
||||
{btndown=true;onMove=move;onDone=done;toFront();return false;};function setCursor(t){$trk.css('cursor',t);};$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}();var KeyManager=function()
|
||||
{var $keymgr=$('<input type="radio" />').css({position:'absolute',left:'-30px'}).keypress(parseKey).blur(onBlur),$keywrap=$('<div />').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
|
||||
{if(options.keySupport)
|
||||
{$keymgr.show();$keymgr.focus();}};function onBlur(e)
|
||||
{$keymgr.hide();};function doNudge(e,x,y)
|
||||
{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible();};e.preventDefault();e.stopPropagation();};function parseKey(e)
|
||||
{if(e.ctrlKey)return true;shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode)
|
||||
{case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:Selection.release();break;case 9:return true;}
|
||||
return nothing(e);};if(options.keySupport)$keywrap.insertBefore($img);return{watchKeys:watchKeys};}();function px(n){return''+parseInt(n)+'px';};function pct(n){return''+parseInt(n)+'%';};function cssClass(cl){return options.baseClass+'-'+cl;};function getPos(obj)
|
||||
{var pos=$(obj).offset();return[pos.left,pos.top];};function mouseAbs(e)
|
||||
{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];};function myCursor(type)
|
||||
{if(type!=lastcurs)
|
||||
{Tracker.setCursor(type);lastcurs=type;}};function startDragMode(mode,pos)
|
||||
{docOffset=getPos($img);Tracker.setCursor(mode=='move'?mode:mode+'-resize');if(mode=='move')
|
||||
return Tracker.activateHandlers(createMover(pos),doneSelect);var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);};function dragmodeHandler(mode,f)
|
||||
{return function(pos){if(!options.aspectRatio)switch(mode)
|
||||
{case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}
|
||||
else switch(mode)
|
||||
{case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}
|
||||
Coords.setCurrent(pos);Selection.update();};};function createMover(pos)
|
||||
{var lloc=pos;KeyManager.watchKeys();return function(pos)
|
||||
{Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};};function oppLockCorner(ord)
|
||||
{switch(ord)
|
||||
{case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';};};function createDragger(ord)
|
||||
{return function(e){if(options.disabled)return false;if((ord=='move')&&!options.allowMove)return false;btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};};function presize($obj,w,h)
|
||||
{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0)
|
||||
{nw=w;nh=(w/$obj.width())*$obj.height();}
|
||||
if((nh>h)&&h>0)
|
||||
{nh=h;nw=(h/$obj.height())*$obj.width();}
|
||||
xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);};function unscale(c)
|
||||
{return{x:parseInt(c.x*xscale),y:parseInt(c.y*yscale),x2:parseInt(c.x2*xscale),y2:parseInt(c.y2*yscale),w:parseInt(c.w*xscale),h:parseInt(c.h*yscale)};};function doneSelect(pos)
|
||||
{var c=Coords.getFixed();if(c.w>options.minSelect[0]&&c.h>options.minSelect[1])
|
||||
{Selection.enableHandles();Selection.done();}
|
||||
else
|
||||
{Selection.release();}
|
||||
Tracker.setCursor(options.allowSelect?'crosshair':'default');};function newSelection(e)
|
||||
{if(options.disabled)return false;if(!options.allowSelect)return false;btndown=true;docOffset=getPos($img);Selection.disableHandles();myCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();Selection.update();e.stopPropagation();e.preventDefault();return false;};function selectDrag(pos)
|
||||
{Coords.setCurrent(pos);Selection.update();};function newTracker()
|
||||
{var trk=$('<div></div>').addClass(cssClass('tracker'));$.browser.msie&&trk.css({opacity:0,backgroundColor:'white'});return trk;};function animateTo(a)
|
||||
{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating)return;var animto=Coords.flipCoords(x1,y1,x2,y2);var c=Coords.getFixed();var animat=initcr=[c.x,c.y,c.x2,c.y2];var interv=options.animationDelay;var x=animat[0];var y=animat[1];var x2=animat[2];var y2=animat[3];var ix1=animto[0]-initcr[0];var iy1=animto[1]-initcr[1];var ix2=animto[2]-initcr[2];var iy2=animto[3]-initcr[3];var pcent=0;var velocity=options.swingSpeed;Selection.animMode(true);var animator=function()
|
||||
{return function()
|
||||
{pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent<100)animateStart();else Selection.done();if(pcent>=99.8)pcent=100;setSelectRaw(animat);};}();function animateStart()
|
||||
{window.setTimeout(animator,interv);};animateStart();};function setSelect(rect)
|
||||
{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);};function setSelectRaw(l)
|
||||
{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();};function setOptions(opt)
|
||||
{if(typeof(opt)!='object')opt={};options=$.extend(options,opt);if(typeof(options.onChange)!=='function')
|
||||
options.onChange=function(){};if(typeof(options.onSelect)!=='function')
|
||||
options.onSelect=function(){};};function tellSelect()
|
||||
{return unscale(Coords.getFixed());};function tellScaled()
|
||||
{return Coords.getFixed();};function setOptionsNew(opt)
|
||||
{setOptions(opt);interfaceUpdate();};function disableCrop()
|
||||
{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');};function enableCrop()
|
||||
{options.disabled=false;interfaceUpdate();};function cancelCrop()
|
||||
{Selection.done();Tracker.activateHandlers(null,null);};function destroy()
|
||||
{$div.remove();$origimg.show();};function interfaceUpdate(alt)
|
||||
{options.allowResize?alt?Selection.enableOnly():Selection.enableHandles():Selection.disableHandles();Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');$div.css('backgroundColor',options.bgColor);if('setSelect'in options){setSelect(opt.setSelect);Selection.done();delete(options.setSelect);}
|
||||
if('trueSize'in options){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
|
||||
xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if('outerImage'in options)
|
||||
{$img.attr('src',options.outerImage);delete(options.outerImage);}
|
||||
Selection.refresh();};$hdl_holder.hide();interfaceUpdate(true);var api={animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},release:Selection.release,destroy:destroy};$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options)
|
||||
{function attachWhenDone(from)
|
||||
{var loadsrc=options.useImg||from.src;var img=new Image();img.onload=function(){$.Jcrop(from,options);};img.src=loadsrc;};if(typeof(options)!=='object')options={};this.each(function()
|
||||
{if($(this).data('Jcrop'))
|
||||
{if(options=='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
|
||||
else attachWhenDone(this);});return this;};})(jQuery);
|
File diff suppressed because one or more lines are too long
51
js/util.js
51
js/util.js
@ -17,26 +17,51 @@
|
||||
*/
|
||||
|
||||
$(document).ready(function(){
|
||||
var counterBlackout = false;
|
||||
|
||||
// count character on keyup
|
||||
function counter(event){
|
||||
var maxLength = 140;
|
||||
var currentLength = $("#notice_data-text").val().length;
|
||||
var remaining = maxLength - currentLength;
|
||||
var counter = $("#notice_text-count");
|
||||
counter.text(remaining);
|
||||
|
||||
if (remaining.toString() != counter.text()) {
|
||||
if (!counterBlackout || remaining == 0) {
|
||||
if (counter.text() != String(remaining)) {
|
||||
counter.text(remaining);
|
||||
}
|
||||
|
||||
if (remaining < 0) {
|
||||
$("#form_notice").addClass("warning");
|
||||
} else {
|
||||
$("#form_notice").removeClass("warning");
|
||||
}
|
||||
if (remaining < 0) {
|
||||
$("#form_notice").addClass("warning");
|
||||
} else {
|
||||
$("#form_notice").removeClass("warning");
|
||||
}
|
||||
// Skip updates for the next 500ms.
|
||||
// On slower hardware, updating on every keypress is unpleasant.
|
||||
if (!counterBlackout) {
|
||||
counterBlackout = true;
|
||||
window.setTimeout(clearCounterBlackout, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearCounterBlackout() {
|
||||
// Allow keyup events to poke the counter again
|
||||
counterBlackout = false;
|
||||
// Check if the string changed since we last looked
|
||||
counter(null);
|
||||
}
|
||||
|
||||
function submitonreturn(event) {
|
||||
if (event.keyCode == 13) {
|
||||
if (event.keyCode == 13 || event.keyCode == 10) {
|
||||
// iPhone sends \n not \r for 'return'
|
||||
$("#form_notice").submit();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
$("#notice_data-text").blur();
|
||||
$("body").focus();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -57,6 +82,10 @@ $(document).ready(function(){
|
||||
// XXX: refactor this code
|
||||
|
||||
var favoptions = { dataType: 'xml',
|
||||
beforeSubmit: function(data, target, options) {
|
||||
$(target).addClass('processing');
|
||||
return true;
|
||||
},
|
||||
success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
|
||||
var dis = new_form.id;
|
||||
var fav = dis.replace('disfavor', 'favor');
|
||||
@ -66,6 +95,10 @@ $(document).ready(function(){
|
||||
};
|
||||
|
||||
var disoptions = { dataType: 'xml',
|
||||
beforeSubmit: function(data, target, options) {
|
||||
$(target).addClass('processing');
|
||||
return true;
|
||||
},
|
||||
success: function(xml) { var new_form = document._importNode($('form', xml).get(0), true);
|
||||
var fav = new_form.id;
|
||||
var dis = fav.replace('favor', 'disfavor');
|
||||
@ -255,10 +288,10 @@ function NoticeReply() {
|
||||
function NoticeReplySet(nick,id) {
|
||||
rgx_username = /^[0-9a-zA-Z\-_.]*$/;
|
||||
if (nick.match(rgx_username)) {
|
||||
replyto = "@" + nick + " ";
|
||||
var text = $("#notice_data-text");
|
||||
if (text.length) {
|
||||
text.val(replyto + text.val());
|
||||
replyto = "@" + nick + " ";
|
||||
text.val(replyto + text.val().replace(RegExp(replyto, 'i'), ''));
|
||||
$("#form_notice input#notice_in-reply-to").val(id);
|
||||
if (text.get(0).setSelectionRange) {
|
||||
var len = text.val().length;
|
||||
|
@ -126,6 +126,10 @@ class AccountSettingsNav extends Widget
|
||||
$this->action->elementStart('ul', array('class' => 'nav'));
|
||||
|
||||
foreach ($menu as $menuaction => $menudesc) {
|
||||
if ($menuaction == 'openidsettings' &&
|
||||
!common_config('openid', 'enabled')) {
|
||||
continue;
|
||||
}
|
||||
$this->action->menuItem(common_local_url($menuaction),
|
||||
$menudesc[0],
|
||||
$menudesc[1],
|
||||
|
@ -193,21 +193,12 @@ class Action extends HTMLOutputter // lawsuit
|
||||
if (Event::handle('StartShowStyles', array($this))) {
|
||||
|
||||
if (Event::handle('StartShowLaconicaStyles', array($this))) {
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/display.css', null) . '?version=' . LACONICA_VERSION,
|
||||
'media' => 'screen, projection, tv'));
|
||||
$this->cssLink('css/display.css',null,'screen, projection, tv');
|
||||
if (common_config('site', 'mobile')) {
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/mobile.css', 'base') . '?version=' . LACONICA_VERSION,
|
||||
// TODO: "handheld" CSS for other mobile devices
|
||||
'media' => 'only screen and (max-device-width: 480px)')); // Mobile WebKit
|
||||
// TODO: "handheld" CSS for other mobile devices
|
||||
$this->cssLink('css/mobile.css','base','only screen and (max-device-width: 480px)'); // Mobile WebKit
|
||||
}
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/print.css', 'base') . '?version=' . LACONICA_VERSION,
|
||||
'media' => 'print'));
|
||||
$this->cssLink('css/print.css','base','print');
|
||||
Event::handle('EndShowLaconicaStyles', array($this));
|
||||
}
|
||||
|
||||
@ -253,26 +244,14 @@ class Action extends HTMLOutputter // lawsuit
|
||||
{
|
||||
if (Event::handle('StartShowScripts', array($this))) {
|
||||
if (Event::handle('StartShowJQueryScripts', array($this))) {
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/jquery.min.js')),
|
||||
' ');
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/jquery.form.js')),
|
||||
' ');
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/jquery.joverlay.min.js')),
|
||||
' ');
|
||||
|
||||
$this->script('js/jquery.min.js');
|
||||
$this->script('js/jquery.form.js');
|
||||
$this->script('js/jquery.joverlay.min.js');
|
||||
Event::handle('EndShowJQueryScripts', array($this));
|
||||
}
|
||||
if (Event::handle('StartShowLaconicaScripts', array($this))) {
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/xbImportNode.js')),
|
||||
' ');
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => common_path('js/util.js?version='.LACONICA_VERSION)),
|
||||
' ');
|
||||
$this->script('js/xbImportNode.js');
|
||||
$this->script('js/util.js');
|
||||
// Frame-busting code to avoid clickjacking attacks.
|
||||
$this->element('script', array('type' => 'text/javascript'),
|
||||
'if (window.top !== window.self) { window.top.location.href = window.self.location.href; }');
|
||||
@ -423,6 +402,14 @@ class Action extends HTMLOutputter // lawsuit
|
||||
function showPrimaryNav()
|
||||
{
|
||||
$user = common_current_user();
|
||||
$connect = '';
|
||||
if (common_config('xmpp', 'enabled')) {
|
||||
$connect = 'imsettings';
|
||||
} else if (common_config('sms', 'enabled')) {
|
||||
$connect = 'smssettings';
|
||||
} else if (common_config('twitter', 'enabled')) {
|
||||
$connect = 'twittersettings';
|
||||
}
|
||||
|
||||
$this->elementStart('dl', array('id' => 'site_nav_global_primary'));
|
||||
$this->element('dt', null, _('Primary site navigation'));
|
||||
@ -434,12 +421,9 @@ class Action extends HTMLOutputter // lawsuit
|
||||
_('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
|
||||
$this->menuItem(common_local_url('profilesettings'),
|
||||
_('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
|
||||
if (common_config('xmpp', 'enabled')) {
|
||||
$this->menuItem(common_local_url('imsettings'),
|
||||
_('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
|
||||
} else {
|
||||
$this->menuItem(common_local_url('smssettings'),
|
||||
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
|
||||
if ($connect) {
|
||||
$this->menuItem(common_local_url($connect),
|
||||
_('Connect'), _('Connect to services'), false, 'nav_connect');
|
||||
}
|
||||
if (common_config('invite', 'enabled')) {
|
||||
$this->menuItem(common_local_url('invite'),
|
||||
@ -452,17 +436,24 @@ class Action extends HTMLOutputter // lawsuit
|
||||
_('Logout'), _('Logout from the site'), false, 'nav_logout');
|
||||
}
|
||||
else {
|
||||
if (!common_config('site', 'closed')) {
|
||||
$this->menuItem(common_local_url('register'),
|
||||
_('Register'), _('Create an account'), false, 'nav_register');
|
||||
if (!common_config('site', 'openidonly')) {
|
||||
if (!common_config('site', 'closed')) {
|
||||
$this->menuItem(common_local_url('register'),
|
||||
_('Register'), _('Create an account'), false, 'nav_register');
|
||||
}
|
||||
$this->menuItem(common_local_url('login'),
|
||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||
} else {
|
||||
$this->menuItem(common_local_url('openidlogin'),
|
||||
_('OpenID'), _('Login with OpenID'), false, 'nav_openid');
|
||||
}
|
||||
$this->menuItem(common_local_url('login'),
|
||||
_('Login'), _('Login to the site'), false, 'nav_login');
|
||||
}
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'help')),
|
||||
_('Help'), _('Help me!'), false, 'nav_help');
|
||||
$this->menuItem(common_local_url('peoplesearch'),
|
||||
_('Search'), _('Search for people or text'), false, 'nav_search');
|
||||
if ($user || !common_config('site', 'private')) {
|
||||
$this->menuItem(common_local_url('peoplesearch'),
|
||||
_('Search'), _('Search for people or text'), false, 'nav_search');
|
||||
}
|
||||
Event::handle('EndPrimaryNav', array($this));
|
||||
}
|
||||
$this->elementEnd('ul');
|
||||
|
@ -25,12 +25,14 @@ class ArrayWrapper
|
||||
{
|
||||
var $_items = null;
|
||||
var $_count = 0;
|
||||
var $N = 0;
|
||||
var $_i = -1;
|
||||
|
||||
function __construct($items)
|
||||
{
|
||||
$this->_items = $items;
|
||||
$this->_count = count($this->_items);
|
||||
$this->N = $this->_count;
|
||||
}
|
||||
|
||||
function fetch()
|
||||
@ -76,4 +78,4 @@ class ArrayWrapper
|
||||
$item =& $this->_items[$this->_i];
|
||||
return call_user_func_array(array($item, $name), $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ require_once('PEAR.php');
|
||||
require_once('DB/DataObject.php');
|
||||
require_once('DB/DataObject/Cast.php'); # for dates
|
||||
|
||||
if (!function_exists('gettext')) {
|
||||
require_once("php-gettext/gettext.inc");
|
||||
}
|
||||
require_once(INSTALLDIR.'/lib/language.php');
|
||||
|
||||
// This gets included before the config file, so that admin code and plugins
|
||||
@ -82,7 +85,7 @@ if (isset($server)) {
|
||||
if (isset($path)) {
|
||||
$_path = $path;
|
||||
} else {
|
||||
$_path = array_key_exists('SCRIPT_NAME', $_SERVER) ?
|
||||
$_path = (array_key_exists('SERVER_NAME', $_SERVER) && array_key_exists('SCRIPT_NAME', $_SERVER)) ?
|
||||
_sn_to_path($_SERVER['SCRIPT_NAME']) :
|
||||
null;
|
||||
}
|
||||
@ -109,6 +112,7 @@ $config =
|
||||
'broughtbyurl' => null,
|
||||
'closed' => false,
|
||||
'inviteonly' => false,
|
||||
'openidonly' => false,
|
||||
'private' => false,
|
||||
'ssl' => 'never',
|
||||
'sslserver' => null,
|
||||
@ -169,6 +173,8 @@ $config =
|
||||
'host' => null, # only set if != server
|
||||
'debug' => false, # print extra debug info
|
||||
'public' => array()), # JIDs of users who want to receive the public stream
|
||||
'openid' =>
|
||||
array('enabled' => true),
|
||||
'invite' =>
|
||||
array('enabled' => true),
|
||||
'sphinx' =>
|
||||
@ -183,6 +189,12 @@ $config =
|
||||
array('piddir' => '/var/run',
|
||||
'user' => false,
|
||||
'group' => false),
|
||||
'emailpost' =>
|
||||
array('enabled' => true),
|
||||
'sms' =>
|
||||
array('enabled' => true),
|
||||
'twitter' =>
|
||||
array('enabled' => true),
|
||||
'twitterbridge' =>
|
||||
array('enabled' => false),
|
||||
'integration' =>
|
||||
@ -364,6 +376,12 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
|
||||
$config['db']['ini_'.$_db_name] = INSTALLDIR.'/classes/laconica.ini';
|
||||
}
|
||||
|
||||
// Ignore openidonly if OpenID is disabled
|
||||
|
||||
if (!$config['openid']['enabled']) {
|
||||
$config['site']['openidonly'] = false;
|
||||
}
|
||||
|
||||
// XXX: how many of these could be auto-loaded on use?
|
||||
|
||||
require_once 'Validate.php';
|
||||
|
@ -99,25 +99,27 @@ class ConnectSettingsNav extends Widget
|
||||
function show()
|
||||
{
|
||||
# action => array('prompt', 'title')
|
||||
$menu =
|
||||
array('imsettings' =>
|
||||
array(_('IM'),
|
||||
_('Updates by instant messenger (IM)')),
|
||||
'smssettings' =>
|
||||
array(_('SMS'),
|
||||
_('Updates by SMS')),
|
||||
'twittersettings' =>
|
||||
array(_('Twitter'),
|
||||
_('Twitter integration options')));
|
||||
$menu = array();
|
||||
if (common_config('xmpp', 'enabled')) {
|
||||
$menu['imsettings'] =
|
||||
array(_('IM'),
|
||||
_('Updates by instant messenger (IM)'));
|
||||
}
|
||||
if (common_config('sms', 'enabled')) {
|
||||
$menu['smssettings'] =
|
||||
array(_('SMS'),
|
||||
_('Updates by SMS'));
|
||||
}
|
||||
if (common_config('twitter', 'enabled')) {
|
||||
$menu['twittersettings'] =
|
||||
array(_('Twitter'),
|
||||
_('Twitter integration options'));
|
||||
}
|
||||
|
||||
$action_name = $this->action->trimmed('action');
|
||||
$this->action->elementStart('ul', array('class' => 'nav'));
|
||||
|
||||
foreach ($menu as $menuaction => $menudesc) {
|
||||
if ($menuaction == 'imsettings' &&
|
||||
!common_config('xmpp', 'enabled')) {
|
||||
continue;
|
||||
}
|
||||
$this->action->menuItem(common_local_url($menuaction),
|
||||
$menudesc[0],
|
||||
$menudesc[1],
|
||||
|
@ -311,13 +311,7 @@ class DesignSettingsAction extends AccountSettingsAction
|
||||
function showStylesheets()
|
||||
{
|
||||
parent::showStylesheets();
|
||||
$farbtasticStyle =
|
||||
common_path('theme/base/css/farbtastic.css?version='.LACONICA_VERSION);
|
||||
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => $farbtasticStyle,
|
||||
'media' => 'screen, projection, tv'));
|
||||
$this->cssLink('css/farbtastic.css','base','screen, projection, tv');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -330,13 +324,8 @@ class DesignSettingsAction extends AccountSettingsAction
|
||||
{
|
||||
parent::showScripts();
|
||||
|
||||
$farbtasticPack = common_path('js/farbtastic/farbtastic.js');
|
||||
$userDesignGo = common_path('js/userdesign.go.js');
|
||||
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => $farbtasticPack));
|
||||
$this->element('script', array('type' => 'text/javascript',
|
||||
'src' => $userDesignGo));
|
||||
$this->script('js/farbtastic/farbtastic.js');
|
||||
$this->script('js/farbtastic/farbtastic.go.js');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,7 +72,7 @@ class ErrorAction extends Action
|
||||
$status_string = $this->status[$this->code];
|
||||
header('HTTP/1.1 '.$this->code.' '.$status_string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display content.
|
||||
*
|
||||
@ -97,11 +97,11 @@ class ErrorAction extends Action
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
function showPage()
|
||||
|
||||
function showPage()
|
||||
{
|
||||
parent::showPage();
|
||||
|
||||
|
||||
// We don't want to have any more output after this
|
||||
exit();
|
||||
}
|
||||
|
@ -95,34 +95,13 @@ class FacebookAction extends Action
|
||||
|
||||
function showStylesheets()
|
||||
{
|
||||
// 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));
|
||||
|
||||
$theme = common_config('site', 'theme');
|
||||
|
||||
$ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css');
|
||||
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/display.css', null) . '?ts=' . $ts));
|
||||
|
||||
$ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css');
|
||||
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts));
|
||||
$this->cssLink('css/display.css', 'base');
|
||||
$this->cssLink('css/facebookapp.css', 'base');
|
||||
}
|
||||
|
||||
function showScripts()
|
||||
{
|
||||
// Add a timestamp to the file so Facebook cache wont ignore our changes
|
||||
$ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
|
||||
|
||||
$this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts));
|
||||
$this->script('js/facebookapp.js');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,8 +256,13 @@ class FacebookAction extends Action
|
||||
$this->elementStart('dd');
|
||||
$this->elementStart('p');
|
||||
$this->text(sprintf($loginmsg_part1, common_config('site', 'name')));
|
||||
$this->element('a',
|
||||
array('href' => common_local_url('register')), _('Register'));
|
||||
if (!common_config('site', 'openidonly')) {
|
||||
$this->element('a',
|
||||
array('href' => common_local_url('register')), _('Register'));
|
||||
} else {
|
||||
$this->element('a',
|
||||
array('href' => common_local_url('openidlogin')), _('Register'));
|
||||
}
|
||||
$this->text($loginmsg_part2);
|
||||
$this->elementEnd('p');
|
||||
$this->elementEnd('dd');
|
||||
|
@ -109,10 +109,11 @@ class HTMLOutputter extends XMLOutputter
|
||||
header('Content-Type: '.$type);
|
||||
|
||||
$this->extraHeaders();
|
||||
|
||||
$this->startXML('html',
|
||||
'-//W3C//DTD XHTML 1.0 Strict//EN',
|
||||
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
|
||||
if( ! substr($type,0,strlen('text/html'))=='text/html' ){
|
||||
// Browsers don't like it when <?xml it output for non-xhtml documents
|
||||
$this->xw->startDocument('1.0', 'UTF-8');
|
||||
}
|
||||
$this->xw->writeDTD('html');
|
||||
|
||||
$language = $this->getLanguage();
|
||||
|
||||
@ -338,6 +339,52 @@ class HTMLOutputter extends XMLOutputter
|
||||
'title' => $title));
|
||||
}
|
||||
|
||||
/**
|
||||
* output a script (almost always javascript) tag
|
||||
*
|
||||
* @param string $src relative or absolute script path
|
||||
* @param string $type 'type' attribute value of the tag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function script($src, $type='text/javascript')
|
||||
{
|
||||
$url = parse_url($src);
|
||||
if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
|
||||
{
|
||||
$src = common_path($src) . '?version=' . LACONICA_VERSION;
|
||||
}
|
||||
$this->element('script', array('type' => $type,
|
||||
'src' => $src),
|
||||
' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* output a css link
|
||||
*
|
||||
* @param string $src relative path within the theme directory, or an absolute path
|
||||
* @param string $theme 'theme' that contains the stylesheet
|
||||
* @param string media 'media' attribute of the tag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function cssLink($src,$theme=null,$media=null)
|
||||
{
|
||||
$url = parse_url($src);
|
||||
if( empty($url->scheme) && empty($url->host) && empty($url->query) && empty($url->fragment))
|
||||
{
|
||||
if(file_exists(theme_file($src,$theme))){
|
||||
$src = theme_path($src, $theme) . '?version=' . LACONICA_VERSION;
|
||||
}else{
|
||||
$src = common_path($src);
|
||||
}
|
||||
}
|
||||
$this->element('link', array('rel' => 'stylesheet',
|
||||
'type' => 'text/css',
|
||||
'href' => $src,
|
||||
'media' => $media));
|
||||
}
|
||||
|
||||
/**
|
||||
* output an HTML textarea and associated elements
|
||||
*
|
||||
|
@ -207,7 +207,7 @@ class ResultItem
|
||||
$replier_profile = null;
|
||||
|
||||
if ($this->notice->reply_to) {
|
||||
$reply = Notice::staticGet(intval($notice->reply_to));
|
||||
$reply = Notice::staticGet(intval($this->notice->reply_to));
|
||||
if ($reply) {
|
||||
$replier_profile = $reply->getProfile();
|
||||
}
|
||||
@ -224,7 +224,7 @@ class ResultItem
|
||||
|
||||
$user = User::staticGet('id', $this->profile->id);
|
||||
|
||||
$this->iso_language_code = $this->user->language;
|
||||
$this->iso_language_code = $user->language;
|
||||
|
||||
$this->source = $this->getSourceLink($this->notice->source);
|
||||
|
||||
|
@ -72,14 +72,18 @@ class LoginGroupNav extends Widget
|
||||
// action => array('prompt', 'title')
|
||||
$menu = array();
|
||||
|
||||
$menu['login'] = array(_('Login'),
|
||||
_('Login with a username and password'));
|
||||
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
$menu['register'] = array(_('Register'),
|
||||
_('Sign up for a new account'));
|
||||
if (!common_config('site','openidonly')) {
|
||||
$menu['login'] = array(_('Login'),
|
||||
_('Login with a username and password'));
|
||||
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
$menu['register'] = array(_('Register'),
|
||||
_('Sign up for a new account'));
|
||||
}
|
||||
}
|
||||
if (common_config('openid', 'enabled')) {
|
||||
$menu['openidlogin'] = array(_('OpenID'),
|
||||
_('Login or register with OpenID'));
|
||||
}
|
||||
$menu['openidlogin'] = array(_('OpenID'),
|
||||
_('Login or register with OpenID'));
|
||||
|
||||
$action_name = $this->action->trimmed('action');
|
||||
$this->action->elementStart('ul', array('class' => 'nav'));
|
||||
|
40
lib/mail.php
40
lib/mail.php
@ -596,32 +596,44 @@ function mail_notify_attn($user, $notice)
|
||||
$bestname = $sender->getBestName();
|
||||
|
||||
common_init_locale($user->language);
|
||||
|
||||
|
||||
if ($notice->conversation != $notice->id) {
|
||||
$conversationEmailText = "The full conversation can be read here:\n\n".
|
||||
"\t%5\$s\n\n ";
|
||||
$conversationUrl = common_local_url('conversation',
|
||||
array('id' => $notice->conversation)).'#notice-'.$notice->id;
|
||||
} else {
|
||||
$conversationEmailText = "%5\$s";
|
||||
$conversationUrl = null;
|
||||
}
|
||||
|
||||
$subject = sprintf(_('%s sent a notice to your attention'), $bestname);
|
||||
|
||||
$body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
|
||||
|
||||
$body = sprintf(_("%1\$s just sent a notice to your attention (an '@-reply') on %2\$s.\n\n".
|
||||
"The notice is here:\n\n".
|
||||
"\t%3\$s\n\n" .
|
||||
"It reads:\n\n".
|
||||
"\t%4\$s\n\n" .
|
||||
$conversationEmailText .
|
||||
"You can reply back here:\n\n".
|
||||
"\t%5\$s\n\n" .
|
||||
"\t%6\$s\n\n" .
|
||||
"The list of all @-replies for you here:\n\n" .
|
||||
"%6\$s\n\n" .
|
||||
"%7\$s\n\n" .
|
||||
"Faithfully yours,\n" .
|
||||
"%2\$s\n\n" .
|
||||
"P.S. You can turn off these email notifications here: %7\$s\n"),
|
||||
$bestname,
|
||||
common_config('site', 'name'),
|
||||
"P.S. You can turn off these email notifications here: %8\$s\n"),
|
||||
$bestname,//%1
|
||||
common_config('site', 'name'),//%2
|
||||
common_local_url('shownotice',
|
||||
array('notice' => $notice->id)),
|
||||
$notice->content,
|
||||
array('notice' => $notice->id)),//%3
|
||||
$notice->content,//%4
|
||||
$conversationUrl,//%5
|
||||
common_local_url('newnotice',
|
||||
array('replyto' => $sender->nickname)),
|
||||
array('replyto' => $sender->nickname)),//%6
|
||||
common_local_url('replies',
|
||||
array('nickname' => $user->nickname)),
|
||||
common_local_url('emailsettings'));
|
||||
|
||||
array('nickname' => $user->nickname)),//%7
|
||||
common_local_url('emailsettings'));//%8
|
||||
|
||||
common_init_locale();
|
||||
mail_to_user($user, $subject, $body);
|
||||
}
|
||||
|
@ -350,11 +350,10 @@ class NoticeListItem extends Widget
|
||||
|
||||
function showNoticeLink()
|
||||
{
|
||||
$noticeurl = common_local_url('shownotice',
|
||||
if($this->notice->is_local){
|
||||
$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)) {
|
||||
}else{
|
||||
$noticeurl = $this->notice->uri;
|
||||
}
|
||||
$this->out->elementStart('a', array('rel' => 'bookmark',
|
||||
|
@ -1,54 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Base class for doing OAuth calls as a consumer
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Action
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @copyright 2008 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
require_once('OAuth.php');
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class OAuthClientCurlException extends Exception { }
|
||||
require_once 'OAuth.php';
|
||||
|
||||
/**
|
||||
* Exception wrapper for cURL errors
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class OAuthClientCurlException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for doing OAuth calls as a consumer
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class OAuthClient
|
||||
{
|
||||
var $consumer;
|
||||
var $token;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Can be initialized with just consumer key and secret for requesting new
|
||||
* tokens or with additional request token or access token
|
||||
*
|
||||
* @param string $consumer_key consumer key
|
||||
* @param string $consumer_secret consumer secret
|
||||
* @param string $oauth_token user's token
|
||||
* @param string $oauth_token_secret user's secret
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function __construct($consumer_key, $consumer_secret,
|
||||
$oauth_token = null, $oauth_token_secret = null)
|
||||
{
|
||||
$this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
$this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
|
||||
$this->token = null;
|
||||
$this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
|
||||
$this->token = null;
|
||||
|
||||
if (isset($oauth_token) && isset($oauth_token_secret)) {
|
||||
$this->token = new OAuthToken($oauth_token, $oauth_token_secret);
|
||||
}
|
||||
}
|
||||
|
||||
function getRequestToken()
|
||||
/**
|
||||
* Gets a request token from the given url
|
||||
*
|
||||
* @param string $url OAuth endpoint for grabbing request tokens
|
||||
*
|
||||
* @return OAuthToken $token the request token
|
||||
*/
|
||||
function getRequestToken($url)
|
||||
{
|
||||
$response = $this->oAuthGet(TwitterOAuthClient::$requestTokenURL);
|
||||
$response = $this->oAuthGet($url);
|
||||
parse_str($response);
|
||||
$token = new OAuthToken($oauth_token, $oauth_token_secret);
|
||||
return $token;
|
||||
}
|
||||
|
||||
function getAuthorizeLink($request_token, $oauth_callback = null)
|
||||
/**
|
||||
* Builds a link that can be redirected to in order to
|
||||
* authorize a request token.
|
||||
*
|
||||
* @param string $url endpoint for authorizing request tokens
|
||||
* @param OAuthToken $request_token the request token to be authorized
|
||||
* @param string $oauth_callback optional callback url
|
||||
*
|
||||
* @return string $authorize_url the url to redirect to
|
||||
*/
|
||||
function getAuthorizeLink($url, $request_token, $oauth_callback = null)
|
||||
{
|
||||
$url = TwitterOAuthClient::$authorizeURL . '?oauth_token=' .
|
||||
$authorize_url = $url . '?oauth_token=' .
|
||||
$request_token->key;
|
||||
|
||||
if (isset($oauth_callback)) {
|
||||
$url .= '&oauth_callback=' . urlencode($oauth_callback);
|
||||
$authorize_url .= '&oauth_callback=' . urlencode($oauth_callback);
|
||||
}
|
||||
|
||||
return $url;
|
||||
return $authorize_url;
|
||||
}
|
||||
|
||||
function getAccessToken()
|
||||
/**
|
||||
* Fetches an access token
|
||||
*
|
||||
* @param string $url OAuth endpoint for exchanging authorized request tokens
|
||||
* for access tokens
|
||||
*
|
||||
* @return OAuthToken $token the access token
|
||||
*/
|
||||
function getAccessToken($url)
|
||||
{
|
||||
$response = $this->oAuthPost(TwitterOAuthClient::$accessTokenURL);
|
||||
$response = $this->oAuthPost($url);
|
||||
parse_str($response);
|
||||
$token = new OAuthToken($oauth_token, $oauth_token_secret);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use HTTP GET to make a signed OAuth request
|
||||
*
|
||||
* @param string $url OAuth endpoint
|
||||
*
|
||||
* @return mixed the request
|
||||
*/
|
||||
function oAuthGet($url)
|
||||
{
|
||||
$request = OAuthRequest::from_consumer_and_token($this->consumer,
|
||||
@ -59,6 +157,14 @@ class OAuthClient
|
||||
return $this->httpRequest($request->to_url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use HTTP POST to make a signed OAuth request
|
||||
*
|
||||
* @param string $url OAuth endpoint
|
||||
* @param array $params additional post parameters
|
||||
*
|
||||
* @return mixed the request
|
||||
*/
|
||||
function oAuthPost($url, $params = null)
|
||||
{
|
||||
$request = OAuthRequest::from_consumer_and_token($this->consumer,
|
||||
@ -70,6 +176,14 @@ class OAuthClient
|
||||
$request->to_postdata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a HTTP request using cURL.
|
||||
*
|
||||
* @param string $url Where to make the
|
||||
* @param array $params post parameters
|
||||
*
|
||||
* @return mixed the request
|
||||
*/
|
||||
function httpRequest($url, $params = null)
|
||||
{
|
||||
$options = array(
|
||||
@ -89,7 +203,7 @@ class OAuthClient
|
||||
);
|
||||
|
||||
if (isset($params)) {
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POSTFIELDS] = $params;
|
||||
}
|
||||
|
||||
|
229
lib/parallelizingdaemon.php
Normal file
229
lib/parallelizingdaemon.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Base class for making daemons that can do several tasks in parallel.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Daemon
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
declare(ticks = 1);
|
||||
|
||||
/**
|
||||
* Daemon able to spawn multiple child processes to do work in parallel
|
||||
*
|
||||
* @category Daemon
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class ParallelizingDaemon extends Daemon
|
||||
{
|
||||
private $_children = array();
|
||||
private $_interval = 0; // seconds
|
||||
private $_max_children = 0; // maximum number of children
|
||||
private $_debug = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $id the name/id of this daemon
|
||||
* @param int $interval sleep this long before doing everything again
|
||||
* @param int $max_children maximum number of child processes at a time
|
||||
* @param boolean $debug debug output flag
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
**/
|
||||
|
||||
function __construct($id = null, $interval = 60, $max_children = 2,
|
||||
$debug = null)
|
||||
{
|
||||
parent::__construct(true); // daemonize
|
||||
|
||||
$this->_interval = $interval;
|
||||
$this->_max_children = $max_children;
|
||||
$this->_debug = $debug;
|
||||
|
||||
if (isset($id)) {
|
||||
$this->set_id($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the daemon
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function run()
|
||||
{
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Debugging output enabled.\n";
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
$objects = $this->getObjects();
|
||||
|
||||
foreach ($objects as $o) {
|
||||
|
||||
// Fork a child for each object
|
||||
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1) {
|
||||
die ($this->name() . ' - Couldn\'t fork!');
|
||||
}
|
||||
|
||||
if ($pid) {
|
||||
|
||||
// Parent
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() .
|
||||
" - Forked new child - pid $pid.\n";
|
||||
|
||||
}
|
||||
|
||||
$this->_children[] = $pid;
|
||||
|
||||
} else {
|
||||
|
||||
// Child
|
||||
|
||||
// Do something with each object
|
||||
|
||||
$this->childTask($o);
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
// Remove child from ps list as it finishes
|
||||
|
||||
while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Child $c finished.\n";
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
|
||||
// Wait! We have too many damn kids.
|
||||
|
||||
if (sizeof($this->_children) >= $this->_max_children) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Too many children. Waiting...\n";
|
||||
}
|
||||
|
||||
if (($c = pcntl_wait($status, WUNTRACED)) > 0) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() .
|
||||
" - Finished waiting for child $c.\n";
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all children from the process list before restarting
|
||||
while (($c = pcntl_wait($status, WUNTRACED)) > 0) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Child $c finished.\n";
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
|
||||
// Rest for a bit
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . ' - Waiting ' . $this->_interval .
|
||||
" secs before running again.\n";
|
||||
}
|
||||
|
||||
if ($this->_interval > 0) {
|
||||
sleep($this->_interval);
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child process from the list of children
|
||||
*
|
||||
* @param array &$plist array of processes
|
||||
* @param int $ps process id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function removePs(&$plist, $ps)
|
||||
{
|
||||
for ($i = 0; $i < sizeof($plist); $i++) {
|
||||
if ($plist[$i] == $ps) {
|
||||
unset($plist[$i]);
|
||||
$plist = array_values($plist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of objects to work on in parallel
|
||||
*
|
||||
* @return array An array of objects to work on
|
||||
*/
|
||||
|
||||
function getObjects()
|
||||
{
|
||||
die('Implement ParallelizingDaemon::getObjects().');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something with each object in parallel
|
||||
*
|
||||
* @param mixed $object data to work on
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function childTask($object)
|
||||
{
|
||||
die("Implement ParallelizingDaemon::childTask($object).");
|
||||
}
|
||||
|
||||
}
|
@ -117,15 +117,8 @@ class Router
|
||||
|
||||
$m->connect('main/tagother/:id', array('action' => 'tagother'));
|
||||
|
||||
$m->connect('main/oembed.xml',
|
||||
array('action' => 'api',
|
||||
'method' => 'oembed.xml',
|
||||
'apiaction' => 'oembed'));
|
||||
|
||||
$m->connect('main/oembed.json',
|
||||
array('action' => 'api',
|
||||
'method' => 'oembed.json',
|
||||
'apiaction' => 'oembed'));
|
||||
$m->connect('main/oembed',
|
||||
array('action' => 'oembed'));
|
||||
|
||||
// these take a code
|
||||
|
||||
@ -413,6 +406,28 @@ class Router
|
||||
'apiaction' => 'laconica'));
|
||||
|
||||
// Groups
|
||||
//'list' has to be handled differently, as php will not allow a method to be named 'list'
|
||||
$m->connect('api/laconica/groups/list/:argument',
|
||||
array('action' => 'api',
|
||||
'method' => 'list_groups',
|
||||
'apiaction' => 'groups'));
|
||||
foreach (array('xml', 'json', 'rss', 'atom') as $e) {
|
||||
$m->connect('api/laconica/groups/list.' . $e,
|
||||
array('action' => 'api',
|
||||
'method' => 'list_groups.' . $e,
|
||||
'apiaction' => 'groups'));
|
||||
}
|
||||
|
||||
$m->connect('api/laconica/groups/:method',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'statuses'),
|
||||
array('method' => '(list_all|)(\.(atom|rss|xml|json))?'));
|
||||
|
||||
$m->connect('api/statuses/:method/:argument',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'statuses'),
|
||||
array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)'));
|
||||
|
||||
$m->connect('api/laconica/groups/:method/:argument',
|
||||
array('action' => 'api',
|
||||
'apiaction' => 'groups'));
|
||||
|
@ -120,7 +120,7 @@ class MySQLSearch extends SearchEngine
|
||||
} else if ('identica_notices' === $this->table) {
|
||||
|
||||
// Don't show imported notices
|
||||
$this->target->whereAdd('notice.is_local != ' . NOTICE_GATEWAY);
|
||||
$this->target->whereAdd('notice.is_local != ' . Notice::GATEWAY);
|
||||
|
||||
if (strtolower($q) != $q) {
|
||||
$this->target->whereAdd("( MATCH(content) AGAINST ('" . addslashes($q) .
|
||||
|
395
lib/twitter.php
395
lib/twitter.php
@ -17,83 +17,20 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) { exit(1); }
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
|
||||
|
||||
function get_twitter_data($uri, $screen_name, $password)
|
||||
{
|
||||
|
||||
$options = array(
|
||||
CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => "Laconica",
|
||||
CURLOPT_CONNECTTIMEOUT => 120,
|
||||
CURLOPT_TIMEOUT => 120,
|
||||
# Twitter is strict about accepting invalid "Expect" headers
|
||||
CURLOPT_HTTPHEADER => array('Expect:')
|
||||
);
|
||||
|
||||
$ch = curl_init($uri);
|
||||
curl_setopt_array($ch, $options);
|
||||
$data = curl_exec($ch);
|
||||
$errmsg = curl_error($ch);
|
||||
|
||||
if ($errmsg) {
|
||||
common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
|
||||
__FILE__);
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function twitter_json_data($uri, $screen_name, $password)
|
||||
{
|
||||
$json_data = get_twitter_data($uri, $screen_name, $password);
|
||||
|
||||
if (!$json_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($json_data);
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function twitter_user_info($screen_name, $password)
|
||||
{
|
||||
$uri = "http://twitter.com/users/show/$screen_name.json";
|
||||
return twitter_json_data($uri, $screen_name, $password);
|
||||
}
|
||||
|
||||
function twitter_friends_ids($screen_name, $password)
|
||||
{
|
||||
$uri = "http://twitter.com/friends/ids/$screen_name.json";
|
||||
return twitter_json_data($uri, $screen_name, $password);
|
||||
}
|
||||
|
||||
function update_twitter_user($twitter_id, $screen_name)
|
||||
{
|
||||
$uri = 'http://twitter.com/' . $screen_name;
|
||||
|
||||
$fuser = new Foreign_user();
|
||||
|
||||
$fuser->query('BEGIN');
|
||||
|
||||
// Dropping down to SQL because regular db_object udpate stuff doesn't seem
|
||||
// Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
|
||||
// to work so good with tables that have multiple column primary keys
|
||||
|
||||
// Any time we update the uri for a forein user we have to make sure there
|
||||
@ -102,35 +39,14 @@ function update_twitter_user($twitter_id, $screen_name)
|
||||
$qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
|
||||
$qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
|
||||
|
||||
$result = $fuser->query($qry);
|
||||
|
||||
if ($result) {
|
||||
common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
|
||||
}
|
||||
}
|
||||
$fuser->query($qry);
|
||||
|
||||
// Update the user
|
||||
|
||||
$qry = 'UPDATE foreign_user SET nickname = ';
|
||||
$qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
|
||||
$qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
|
||||
|
||||
$result = $fuser->query($qry);
|
||||
|
||||
if (!$result) {
|
||||
common_log(LOG_WARNING,
|
||||
"Couldn't update foreign_user data for Twitter user: $screen_name");
|
||||
common_log_db_error($fuser, 'UPDATE', __FILE__);
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - ";
|
||||
print common_log_objstring($fuser) . "\n";
|
||||
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
print "DB_DataObject Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$fuser->query('COMMIT');
|
||||
|
||||
$fuser->free();
|
||||
@ -147,23 +63,22 @@ function add_twitter_user($twitter_id, $screen_name)
|
||||
// Clear out any bad old foreign_users with the new user's legit URL
|
||||
// This can happen when users move around or fakester accounts get
|
||||
// repoed, and things like that.
|
||||
|
||||
$luser = new Foreign_user();
|
||||
$luser->uri = $new_uri;
|
||||
$luser->service = TWITTER_SERVICE;
|
||||
$result = $luser->delete();
|
||||
|
||||
if ($result) {
|
||||
if (empty($result)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Removed invalid Twitter user squatting on uri: $new_uri\n";
|
||||
}
|
||||
}
|
||||
|
||||
$luser->free();
|
||||
unset($luser);
|
||||
|
||||
// Otherwise, create a new Twitter user
|
||||
|
||||
$fuser = new Foreign_user();
|
||||
|
||||
$fuser->nickname = $screen_name;
|
||||
@ -173,21 +88,12 @@ function add_twitter_user($twitter_id, $screen_name)
|
||||
$fuser->created = common_sql_now();
|
||||
$result = $fuser->insert();
|
||||
|
||||
if (!$result) {
|
||||
if (empty($result)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
|
||||
common_log_db_error($fuser, 'INSERT', __FILE__);
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
|
||||
print common_log_objstring($fuser) . "\n";
|
||||
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
print "DB_DataObject Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
} else {
|
||||
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Added new Twitter user: $screen_name ($twitter_id).\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -199,23 +105,20 @@ function save_twitter_user($twitter_id, $screen_name)
|
||||
|
||||
// Check to see whether the Twitter user is already in the system,
|
||||
// and update its screen name and uri if so.
|
||||
|
||||
$fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
|
||||
|
||||
if ($fuser) {
|
||||
if (!empty($fuser)) {
|
||||
|
||||
$result = true;
|
||||
|
||||
// Only update if Twitter screen name has changed
|
||||
|
||||
if ($fuser->nickname != $screen_name) {
|
||||
$result = update_twitter_user($twitter_id, $screen_name);
|
||||
|
||||
common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
|
||||
"$fuser->id to $screen_name, was $fuser->nickname");
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print 'Updated nickname (and URI) for Twitter user ' .
|
||||
"$fuser->id to $screen_name, was $fuser->nickname\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -230,119 +133,6 @@ function save_twitter_user($twitter_id, $screen_name)
|
||||
return true;
|
||||
}
|
||||
|
||||
function retreive_twitter_friends($twitter_id, $screen_name, $password)
|
||||
{
|
||||
$friends = array();
|
||||
|
||||
$uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
|
||||
$friends_ids = twitter_friends_ids($screen_name, $password);
|
||||
|
||||
if (!$friends_ids) {
|
||||
return $friends;
|
||||
}
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Twitter 'social graph' ids method says $screen_name has " .
|
||||
count($friends_ids) . " friends.\n";
|
||||
}
|
||||
|
||||
// Calculate how many pages to get...
|
||||
$pages = ceil(count($friends_ids) / 100);
|
||||
|
||||
if ($pages == 0) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - $screen_name seems to have no friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "$screen_name seems to have no friends.\n";
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $pages; $i++) {
|
||||
|
||||
$data = get_twitter_data($uri . $i, $screen_name, $password);
|
||||
|
||||
if (!$data) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't retrieve page $i of $screen_name's friends.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$more_friends = json_decode($data);
|
||||
|
||||
if (!$more_friends) {
|
||||
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - No data for page $i of $screen_name's friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "No data for page $i of $screen_name's friends.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$friends = array_merge($friends, $more_friends);
|
||||
}
|
||||
|
||||
return $friends;
|
||||
}
|
||||
|
||||
function save_twitter_friends($user, $twitter_id, $screen_name, $password)
|
||||
{
|
||||
|
||||
$friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
|
||||
|
||||
if (empty($friends)) {
|
||||
common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't get friends data from Twitter for $screen_name.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
|
||||
$friend_name = $friend->screen_name;
|
||||
$friend_id = (int) $friend->id;
|
||||
|
||||
// Update or create the Foreign_user record
|
||||
if (!save_twitter_user($friend_id, $friend_name)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't save $screen_name's friend, $friend_name.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if there's a related local user
|
||||
$flink = Foreign_link::getByForeignID($friend_id, 1);
|
||||
|
||||
if ($flink) {
|
||||
|
||||
// Get associated user and subscribe her
|
||||
$friend_user = User::staticGet('id', $flink->user_id);
|
||||
if (!empty($friend_user)) {
|
||||
$result = subs_subscribe_to($user, $friend_user);
|
||||
|
||||
if ($result === true) {
|
||||
common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print("Subscribed $friend_user->nickname to $user->nickname.\n");
|
||||
}
|
||||
} else {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "$result ($friend_user->nickname to $user->nickname)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function is_twitter_bound($notice, $flink) {
|
||||
|
||||
// Check to see if notice should go to Twitter
|
||||
@ -351,7 +141,7 @@ function is_twitter_bound($notice, $flink) {
|
||||
// If it's not a Twitter-style reply, or if the user WANTS to send replies.
|
||||
if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
|
||||
($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,7 +151,7 @@ function is_twitter_bound($notice, $flink) {
|
||||
function broadcast_twitter($notice)
|
||||
{
|
||||
$flink = Foreign_link::getByUserID($notice->profile_id,
|
||||
TWITTER_SERVICE);
|
||||
TWITTER_SERVICE);
|
||||
|
||||
if (is_twitter_bound($notice, $flink)) {
|
||||
|
||||
@ -370,62 +160,63 @@ function broadcast_twitter($notice)
|
||||
// XXX: Hack to get around PHP cURL's use of @ being a a meta character
|
||||
$statustxt = preg_replace('/^@/', ' @', $notice->content);
|
||||
|
||||
$client = new TwitterOAuthClient($flink->token, $flink->credentials);
|
||||
$token = TwitterOAuthClient::unpackToken($flink->credentials);
|
||||
|
||||
$client = new TwitterOAuthClient($token->key, $token->secret);
|
||||
|
||||
$status = null;
|
||||
|
||||
try {
|
||||
$status = $client->statuses_update($statustxt);
|
||||
$status = $client->statusesUpdate($statustxt);
|
||||
} catch (OAuthClientCurlException $e) {
|
||||
|
||||
if ($e->getMessage() == 'The requested URL returned error: 401') {
|
||||
if ($e->getMessage() == 'The requested URL returned error: 401') {
|
||||
|
||||
$errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
|
||||
'Twitter OAuth access token.',
|
||||
$user->nickname, $user->id);
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
$errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
|
||||
'Twitter OAuth access token.',
|
||||
$user->nickname, $user->id);
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
// Bad auth token! We need to delete the foreign_link
|
||||
// to Twitter and inform the user.
|
||||
// Bad auth token! We need to delete the foreign_link
|
||||
// to Twitter and inform the user.
|
||||
|
||||
remove_twitter_link($flink);
|
||||
return true;
|
||||
remove_twitter_link($flink);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
} else {
|
||||
|
||||
// Some other error happened, so we should probably
|
||||
// try to send again later.
|
||||
// Some other error happened, so we should probably
|
||||
// try to send again later.
|
||||
|
||||
$errmsg = sprintf('cURL error trying to send notice to Twitter ' .
|
||||
'for user %1$s (user id: %2$s) - ' .
|
||||
'code: %3$s message: $4$s.',
|
||||
$user->nickname, $user->id,
|
||||
$e->getCode(), $e->getMessage());
|
||||
$errmsg = sprintf('cURL error trying to send notice to Twitter ' .
|
||||
'for user %1$s (user id: %2$s) - ' .
|
||||
'code: %3$s message: $4$s.',
|
||||
$user->nickname, $user->id,
|
||||
$e->getCode(), $e->getMessage());
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($status)) {
|
||||
|
||||
// This could represent a failure posting,
|
||||
// or the Twitter API might just be behaving flakey.
|
||||
|
||||
$errmsg = sprint('No data returned by Twitter API when ' .
|
||||
'trying to send update for %1$s (user id %2$s).',
|
||||
$user->nickname, $user->id);
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($status)) {
|
||||
|
||||
// This could represent a failure posting,
|
||||
// or the Twitter API might just be behaving flakey.
|
||||
|
||||
$errmsg = sprint('No data returned by Twitter API when ' .
|
||||
'trying to send update for %1$s (user id %2$s).',
|
||||
$user->nickname, $user->id);
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notice crossed the great divide
|
||||
|
||||
$msg = sprintf('Twitter bridge posted notice %s to Twitter.',
|
||||
$notice->id);
|
||||
common_log(LOG_INFO, $msg);
|
||||
// Notice crossed the great divide
|
||||
|
||||
$msg = sprintf('Twitter bridge posted notice %s to Twitter.',
|
||||
$notice->id);
|
||||
common_log(LOG_INFO, $msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -442,7 +233,7 @@ function remove_twitter_link($flink)
|
||||
|
||||
if (empty($result)) {
|
||||
common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
|
||||
"Foreign_link for $user->nickname (user id: $user->id)!");
|
||||
"Foreign_link for $user->nickname (user id: $user->id)!");
|
||||
common_log_db_error($flink, 'DELETE', __FILE__);
|
||||
}
|
||||
|
||||
@ -450,17 +241,77 @@ function remove_twitter_link($flink)
|
||||
|
||||
if (isset($user->email)) {
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
if (!$result) {
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -188,22 +188,22 @@ class TwitterapiAction extends Action
|
||||
|
||||
// Enclosures
|
||||
$attachments = $notice->attachments();
|
||||
$enclosures = array();
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->isEnclosure()) {
|
||||
$enclosure = array();
|
||||
$enclosure['url'] = $attachment->url;
|
||||
$enclosure['mimetype'] = $attachment->mimetype;
|
||||
$enclosure['size'] = $attachment->size;
|
||||
$enclosures[] = $enclosure;
|
||||
if (!empty($attachments)) {
|
||||
|
||||
$twitter_status['attachments'] = array();
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if ($attachment->isEnclosure()) {
|
||||
$enclosure = array();
|
||||
$enclosure['url'] = $attachment->url;
|
||||
$enclosure['mimetype'] = $attachment->mimetype;
|
||||
$enclosure['size'] = $attachment->size;
|
||||
$twitter_status['attachments'][] = $enclosure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($enclosures)) {
|
||||
$twitter_status['attachments'] = $enclosures;
|
||||
}
|
||||
|
||||
if ($include_user) {
|
||||
# Don't get notice (recursive!)
|
||||
$twitter_user = $this->twitter_user_array($profile, false);
|
||||
@ -233,6 +233,24 @@ class TwitterapiAction extends Action
|
||||
return $twitter_group;
|
||||
}
|
||||
|
||||
function twitter_rss_group_array($group)
|
||||
{
|
||||
$entry = array();
|
||||
$entry['content']=$group->description;
|
||||
$entry['title']=$group->nickname;
|
||||
$entry['link']=$group->permalink();
|
||||
$entry['published']=common_date_iso8601($group->created);
|
||||
$entry['updated']==common_date_iso8601($group->modified);
|
||||
$taguribase = common_config('integration', 'groupuri');
|
||||
$entry['id'] = "group:$groupuribase:$entry[link]";
|
||||
|
||||
$entry['description'] = $entry['content'];
|
||||
$entry['pubDate'] = common_date_rfc2822($group->created);
|
||||
$entry['guid'] = $entry['link'];
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
function twitter_rss_entry_array($notice)
|
||||
{
|
||||
$profile = $notice->getProfile();
|
||||
@ -644,6 +662,65 @@ class TwitterapiAction extends Action
|
||||
|
||||
}
|
||||
|
||||
function show_rss_groups($group, $title, $link, $subtitle)
|
||||
{
|
||||
|
||||
$this->init_document('rss');
|
||||
|
||||
$this->elementStart('channel');
|
||||
$this->element('title', null, $title);
|
||||
$this->element('link', null, $link);
|
||||
$this->element('description', null, $subtitle);
|
||||
$this->element('language', null, 'en-us');
|
||||
$this->element('ttl', null, '40');
|
||||
|
||||
if (is_array($group)) {
|
||||
foreach ($group as $g) {
|
||||
$twitter_group = $this->twitter_rss_group_array($g);
|
||||
$this->show_twitter_rss_item($twitter_group);
|
||||
}
|
||||
} else {
|
||||
while ($group->fetch()) {
|
||||
$twitter_group = $this->twitter_rss_group_array($group);
|
||||
$this->show_twitter_rss_item($twitter_group);
|
||||
}
|
||||
}
|
||||
|
||||
$this->elementEnd('channel');
|
||||
$this->end_twitter_rss();
|
||||
}
|
||||
|
||||
function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null)
|
||||
{
|
||||
|
||||
$this->init_document('atom');
|
||||
|
||||
$this->element('title', null, $title);
|
||||
$this->element('id', null, $id);
|
||||
$this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
|
||||
|
||||
if (!is_null($selfuri)) {
|
||||
$this->element('link', array('href' => $selfuri,
|
||||
'rel' => 'self', 'type' => 'application/atom+xml'), null);
|
||||
}
|
||||
|
||||
$this->element('updated', null, common_date_iso8601('now'));
|
||||
$this->element('subtitle', null, $subtitle);
|
||||
|
||||
if (is_array($group)) {
|
||||
foreach ($group as $g) {
|
||||
$this->raw($g->asAtomEntry());
|
||||
}
|
||||
} else {
|
||||
while ($group->fetch()) {
|
||||
$this->raw($group->asAtomEntry());
|
||||
}
|
||||
}
|
||||
|
||||
$this->end_document('atom');
|
||||
|
||||
}
|
||||
|
||||
function show_json_timeline($notice)
|
||||
{
|
||||
|
||||
@ -668,6 +745,52 @@ class TwitterapiAction extends Action
|
||||
$this->end_document('json');
|
||||
}
|
||||
|
||||
function show_json_groups($group)
|
||||
{
|
||||
|
||||
$this->init_document('json');
|
||||
|
||||
$groups = array();
|
||||
|
||||
if (is_array($group)) {
|
||||
foreach ($group as $g) {
|
||||
$twitter_group = $this->twitter_group_array($g);
|
||||
array_push($groups, $twitter_group);
|
||||
}
|
||||
} else {
|
||||
while ($group->fetch()) {
|
||||
$twitter_group = $this->twitter_group_array($group);
|
||||
array_push($groups, $twitter_group);
|
||||
}
|
||||
}
|
||||
|
||||
$this->show_json_objects($groups);
|
||||
|
||||
$this->end_document('json');
|
||||
}
|
||||
|
||||
function show_xml_groups($group)
|
||||
{
|
||||
|
||||
$this->init_document('xml');
|
||||
$this->elementStart('groups', array('type' => 'array'));
|
||||
|
||||
if (is_array($group)) {
|
||||
foreach ($group as $g) {
|
||||
$twitter_group = $this->twitter_group_array($g);
|
||||
$this->show_twitter_xml_group($twitter_group);
|
||||
}
|
||||
} else {
|
||||
while ($group->fetch()) {
|
||||
$twitter_group = $this->twitter_group_array($group);
|
||||
$this->show_twitter_xml_group($twitter_group);
|
||||
}
|
||||
}
|
||||
|
||||
$this->elementEnd('groups');
|
||||
$this->end_document('xml');
|
||||
}
|
||||
|
||||
function show_single_json_group($group)
|
||||
{
|
||||
$this->init_document('json');
|
||||
@ -844,9 +967,9 @@ class TwitterapiAction extends Action
|
||||
$this->endXML();
|
||||
}
|
||||
|
||||
function show_profile($profile, $content_type='xml', $notice=null)
|
||||
function show_profile($profile, $content_type='xml', $notice=null, $includeStatuses=true)
|
||||
{
|
||||
$profile_array = $this->twitter_user_array($profile, true);
|
||||
$profile_array = $this->twitter_user_array($profile, $includeStatuses);
|
||||
switch ($content_type) {
|
||||
case 'xml':
|
||||
$this->show_twitter_xml_user($profile_array);
|
||||
|
@ -1,11 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Class for doing OAuth calls against Twitter
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @copyright 2008 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for talking to the Twitter API with OAuth.
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class TwitterOAuthClient extends OAuthClient
|
||||
{
|
||||
public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
|
||||
public static $authorizeURL = 'https://twitter.com/oauth/authorize';
|
||||
public static $accessTokenURL = 'https://twitter.com/oauth/access_token';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $oauth_token the user's token
|
||||
* @param string $oauth_token_secret the user's token secret
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function __construct($oauth_token = null, $oauth_token_secret = null)
|
||||
{
|
||||
$consumer_key = common_config('twitter', 'consumer_key');
|
||||
@ -15,39 +64,89 @@ class TwitterOAuthClient extends OAuthClient
|
||||
$oauth_token, $oauth_token_secret);
|
||||
}
|
||||
|
||||
function getAuthorizeLink($request_token) {
|
||||
return parent::getAuthorizeLink($request_token,
|
||||
common_local_url('twitterauthorization'));
|
||||
// XXX: the following two functions are to support the horrible hack
|
||||
// of using the credentils field in Foreign_link to store both
|
||||
// the access token and token secret. This hack should go away with
|
||||
// 0.9, in which we can make DB changes and add a new column for the
|
||||
// token itself.
|
||||
|
||||
static function packToken($token)
|
||||
{
|
||||
return implode(chr(0), array($token->key, $token->secret));
|
||||
}
|
||||
|
||||
function verify_credentials()
|
||||
static function unpackToken($str)
|
||||
{
|
||||
$url = 'https://twitter.com/account/verify_credentials.json';
|
||||
$response = $this->oAuthGet($url);
|
||||
$vals = explode(chr(0), $str);
|
||||
return new OAuthToken($vals[0], $vals[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a link to Twitter's endpoint for authorizing a request token
|
||||
*
|
||||
* @param OAuthToken $request_token token to authorize
|
||||
*
|
||||
* @return the link
|
||||
*/
|
||||
function getAuthorizeLink($request_token)
|
||||
{
|
||||
return parent::getAuthorizeLink(self::$authorizeURL,
|
||||
$request_token,
|
||||
common_local_url('twitterauthorization'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /account/verify_credentials API method
|
||||
*
|
||||
* @return mixed the Twitter user
|
||||
*/
|
||||
function verifyCredentials()
|
||||
{
|
||||
$url = 'https://twitter.com/account/verify_credentials.json';
|
||||
$response = $this->oAuthGet($url);
|
||||
$twitter_user = json_decode($response);
|
||||
return $twitter_user;
|
||||
}
|
||||
|
||||
function statuses_update($status, $in_reply_to_status_id = null)
|
||||
/**
|
||||
* Calls Twitter's /stutuses/update API method
|
||||
*
|
||||
* @param string $status text of the status
|
||||
* @param int $in_reply_to_status_id optional id of the status it's
|
||||
* a reply to
|
||||
*
|
||||
* @return mixed the status
|
||||
*/
|
||||
function statusesUpdate($status, $in_reply_to_status_id = null)
|
||||
{
|
||||
$url = 'https://twitter.com/statuses/update.json';
|
||||
$params = array('status' => $status,
|
||||
$url = 'https://twitter.com/statuses/update.json';
|
||||
$params = array('status' => $status,
|
||||
'in_reply_to_status_id' => $in_reply_to_status_id);
|
||||
$response = $this->oAuthPost($url, $params);
|
||||
$status = json_decode($response);
|
||||
$status = json_decode($response);
|
||||
return $status;
|
||||
}
|
||||
|
||||
function statuses_friends_timeline($since_id = null, $max_id = null,
|
||||
$cnt = null, $page = null) {
|
||||
/**
|
||||
* Calls Twitter's /stutuses/friends_timeline API method
|
||||
*
|
||||
* @param int $since_id show statuses after this id
|
||||
* @param int $max_id show statuses before this id
|
||||
* @param int $cnt number of statuses to show
|
||||
* @param int $page page number
|
||||
*
|
||||
* @return mixed an array of statuses
|
||||
*/
|
||||
function statusesFriendsTimeline($since_id = null, $max_id = null,
|
||||
$cnt = null, $page = null)
|
||||
{
|
||||
|
||||
$url = 'https://twitter.com/statuses/friends_timeline.json';
|
||||
$url = 'https://twitter.com/statuses/friends_timeline.json';
|
||||
$params = array('since_id' => $since_id,
|
||||
'max_id' => $max_id,
|
||||
'count' => $cnt,
|
||||
'page' => $page);
|
||||
$qry = http_build_query($params);
|
||||
$qry = http_build_query($params);
|
||||
|
||||
if (!empty($qry)) {
|
||||
$url .= "?$qry";
|
||||
@ -58,4 +157,64 @@ class TwitterOAuthClient extends OAuthClient
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /stutuses/friends API method
|
||||
*
|
||||
* @param int $id id of the user whom you wish to see friends of
|
||||
* @param int $user_id numerical user id
|
||||
* @param int $screen_name screen name
|
||||
* @param int $page page number
|
||||
*
|
||||
* @return mixed an array of twitter users and their latest status
|
||||
*/
|
||||
function statusesFriends($id = null, $user_id = null, $screen_name = null,
|
||||
$page = null)
|
||||
{
|
||||
$url = "https://twitter.com/statuses/friends.json";
|
||||
|
||||
$params = array('id' => $id,
|
||||
'user_id' => $user_id,
|
||||
'screen_name' => $screen_name,
|
||||
'page' => $page);
|
||||
$qry = http_build_query($params);
|
||||
|
||||
if (!empty($qry)) {
|
||||
$url .= "?$qry";
|
||||
}
|
||||
|
||||
$response = $this->oAuthGet($url);
|
||||
$friends = json_decode($response);
|
||||
return $friends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /stutuses/friends/ids API method
|
||||
*
|
||||
* @param int $id id of the user whom you wish to see friends of
|
||||
* @param int $user_id numerical user id
|
||||
* @param int $screen_name screen name
|
||||
* @param int $page page number
|
||||
*
|
||||
* @return mixed a list of ids, 100 per page
|
||||
*/
|
||||
function friendsIds($id = null, $user_id = null, $screen_name = null,
|
||||
$page = null)
|
||||
{
|
||||
$url = "https://twitter.com/friends/ids.json";
|
||||
|
||||
$params = array('id' => $id,
|
||||
'user_id' => $user_id,
|
||||
'screen_name' => $screen_name,
|
||||
'page' => $page);
|
||||
$qry = http_build_query($params);
|
||||
|
||||
if (!empty($qry)) {
|
||||
$url .= "?$qry";
|
||||
}
|
||||
|
||||
$response = $this->oAuthGet($url);
|
||||
$ids = json_decode($response);
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class UnQueueManager
|
||||
|
||||
function _isLocal($notice)
|
||||
{
|
||||
return ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
|
||||
$notice->is_local == NOTICE_LOCAL_NONPUBLIC);
|
||||
return ($notice->is_local == Notice::LOCAL_PUBLIC ||
|
||||
$notice->is_local == Notice::LOCAL_NONPUBLIC);
|
||||
}
|
||||
}
|
132
lib/util.php
132
lib/util.php
@ -412,87 +412,62 @@ function common_render_text($text)
|
||||
function common_replace_urls_callback($text, $callback, $notice_id = null) {
|
||||
// Start off with a regex
|
||||
$regex = '#'.
|
||||
'(?:'.
|
||||
'(?:^|[\s\(\)\[\]\{\}]+)'.
|
||||
'('.
|
||||
'(?:'.
|
||||
'(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://'.
|
||||
'|'.
|
||||
'(?:mailto|aim|tel|xmpp):'.
|
||||
'(?:'. //Known protocols
|
||||
'(?:'.
|
||||
'(?:https?|ftps?|mms|rtsp|gopher|news|nntp|telnet|wais|file|prospero|webcal|irc)://'.
|
||||
'|'.
|
||||
'(?:mailto|aim|tel|xmpp):'.
|
||||
')[^\s\/]+'.
|
||||
')'.
|
||||
'|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'. //IPv4
|
||||
'|(?:'. //IPv6
|
||||
'(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))'.
|
||||
')|(?:'. //DNS
|
||||
'\S+\.(?:museum|travel|onion|local|[a-z]{2,4})'.
|
||||
')'.
|
||||
')'.
|
||||
'(?:'.
|
||||
'$|(?:'.
|
||||
'/[^\s\(\)\[\]\{\}]*'.
|
||||
')'.
|
||||
')'.
|
||||
'[^.\s]+\.[^\s]+'.
|
||||
'|'.
|
||||
'(?:[^.\s/:]+\.)+'.
|
||||
'(?:museum|travel|[a-z]{2,4})'.
|
||||
'(?:[:/][^\s]*)?'.
|
||||
')'.
|
||||
'#ix';
|
||||
preg_match_all($regex, $text, $matches);
|
||||
return preg_replace_callback($regex, curry(callback_helper,$callback,$notice_id) ,$text);
|
||||
}
|
||||
|
||||
// Then clean up what the regex left behind
|
||||
$offset = 0;
|
||||
foreach($matches[0] as $orig_url) {
|
||||
$url = htmlspecialchars_decode($orig_url);
|
||||
|
||||
// Make sure we didn't pick up an email address
|
||||
if (preg_match('#^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$#i', $url)) continue;
|
||||
|
||||
// Remove surrounding punctuation
|
||||
$url = trim($url, '.?!,;:\'"`([<');
|
||||
|
||||
// Remove surrounding parens and the like
|
||||
preg_match('/[)\]>]+$/', $url, $trailing);
|
||||
if (isset($trailing[0])) {
|
||||
preg_match_all('/[(\[<]/', $url, $opened);
|
||||
preg_match_all('/[)\]>]/', $url, $closed);
|
||||
$unopened = count($closed[0]) - count($opened[0]);
|
||||
|
||||
// Make sure not to take off more closing parens than there are at the end
|
||||
$unopened = ($unopened > mb_strlen($trailing[0])) ? mb_strlen($trailing[0]):$unopened;
|
||||
|
||||
$url = ($unopened > 0) ? mb_substr($url, 0, $unopened * -1):$url;
|
||||
}
|
||||
|
||||
// Remove trailing punctuation again (in case there were some inside parens)
|
||||
$url = rtrim($url, '.?!,;:\'"`');
|
||||
|
||||
// Make sure we didn't capture part of the next sentence
|
||||
preg_match('#((?:[^.\s/]+\.)+)(museum|travel|[a-z]{2,4})#i', $url, $url_parts);
|
||||
|
||||
// Were the parts capitalized any?
|
||||
$last_part = (mb_strtolower($url_parts[2]) !== $url_parts[2]) ? true:false;
|
||||
$prev_part = (mb_strtolower($url_parts[1]) !== $url_parts[1]) ? true:false;
|
||||
|
||||
// If the first part wasn't cap'd but the last part was, we captured too much
|
||||
if ((!$prev_part && $last_part)) {
|
||||
$url = mb_substr($url, 0 , mb_strpos($url, '.'.$url_parts['2'], 0));
|
||||
}
|
||||
|
||||
// Capture the new TLD
|
||||
preg_match('#((?:[^.\s/]+\.)+)(museum|travel|[a-z]{2,4})#i', $url, $url_parts);
|
||||
|
||||
$tlds = array('ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', 'mm', 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye', 'yt', 'yu', 'za', 'zm', 'zw');
|
||||
|
||||
if (!in_array($url_parts[2], $tlds)) continue;
|
||||
|
||||
// Make sure we didn't capture a hash tag
|
||||
if (strpos($url, '#') === 0) continue;
|
||||
|
||||
// Put the url back the way we found it.
|
||||
$url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
|
||||
|
||||
// Call user specified func
|
||||
if (empty($notice_id)) {
|
||||
$modified_url = call_user_func($callback, $url);
|
||||
} else {
|
||||
$modified_url = call_user_func($callback, array($url, $notice_id));
|
||||
}
|
||||
|
||||
// Replace it!
|
||||
$start = mb_strpos($text, $url, $offset);
|
||||
$text = mb_substr($text, 0, $start).$modified_url.mb_substr($text, $start + mb_strlen($url), mb_strlen($text));
|
||||
$offset = $start + mb_strlen($modified_url);
|
||||
function callback_helper($matches, $callback, $notice_id) {
|
||||
$pos = strpos($matches[0],$matches[1]);
|
||||
$left = substr($matches[0],0,$pos);
|
||||
$right = substr($matches[0],$pos+strlen($matches[1]));
|
||||
|
||||
if(empty($notice_id)){
|
||||
$result = call_user_func_array($callback,$matches[1]);
|
||||
}else{
|
||||
$result = call_user_func_array($callback, array($matches[1],$notice_id) );
|
||||
}
|
||||
return $left . $result . $right;
|
||||
}
|
||||
|
||||
return $text;
|
||||
function curry($fn) {
|
||||
//TODO switch to a PHP 5.3 function closure based approach if PHP 5.3 is used
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
$id = uniqid('_partial');
|
||||
$GLOBALS[$id] = array($fn, $args);
|
||||
return create_function(
|
||||
'',
|
||||
'
|
||||
$args = func_get_args();
|
||||
return call_user_func_array(
|
||||
$GLOBALS["'.$id.'"][0],
|
||||
array_merge(
|
||||
$args,
|
||||
$GLOBALS["'.$id.'"][1]));
|
||||
');
|
||||
}
|
||||
|
||||
function common_linkify($url) {
|
||||
@ -500,6 +475,11 @@ function common_linkify($url) {
|
||||
// functions
|
||||
$url = htmlspecialchars_decode($url);
|
||||
|
||||
if(strpos($url, '@')!==false && strpos($url, ':')===false){
|
||||
//url is an email address without the mailto: protocol
|
||||
return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url);
|
||||
}
|
||||
|
||||
$canon = File_redirection::_canonUrl($url);
|
||||
|
||||
$longurl_data = File_redirection::where($url);
|
||||
@ -884,8 +864,8 @@ function common_enqueue_notice($notice)
|
||||
$transports[] = 'jabber';
|
||||
}
|
||||
|
||||
if ($notice->is_local == NOTICE_LOCAL_PUBLIC ||
|
||||
$notice->is_local == NOTICE_LOCAL_NONPUBLIC) {
|
||||
if ($notice->is_local == Notice::LOCAL_PUBLIC ||
|
||||
$notice->is_local == Notice::LOCAL_NONPUBLIC) {
|
||||
$transports = array_merge($transports, $localTransports);
|
||||
if ($xmpp) {
|
||||
$transports[] = 'public';
|
||||
|
38
plugins/Autocomplete/Autocomplete.js
Normal file
38
plugins/Autocomplete/Autocomplete.js
Normal file
@ -0,0 +1,38 @@
|
||||
$(document).ready(function(){
|
||||
$.getJSON($('address .url')[0].href+'/api/statuses/friends.json?user_id=' + current_user['id'] + '&lite=true&callback=?',
|
||||
function(friends){
|
||||
$('#notice_data-text').autocomplete(friends, {
|
||||
multiple: true,
|
||||
multipleSeparator: " ",
|
||||
minChars: 1,
|
||||
formatItem: function(row, i, max){
|
||||
return '@' + row.screen_name + ' (' + row.name + ')';
|
||||
},
|
||||
formatMatch: function(row, i, max){
|
||||
return '@' + row.screen_name;
|
||||
},
|
||||
formatResult: function(row){
|
||||
return '@' + row.screen_name;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
$.getJSON($('address .url')[0].href+'/api/laconica/groups/list.json?user_id=' + current_user['id'] + '&callback=?',
|
||||
function(groups){
|
||||
$('#notice_data-text').autocomplete(groups, {
|
||||
multiple: true,
|
||||
multipleSeparator: " ",
|
||||
minChars: 1,
|
||||
formatItem: function(row, i, max){
|
||||
return '!' + row.nickname + ' (' + row.fullname + ')';
|
||||
},
|
||||
formatMatch: function(row, i, max){
|
||||
return '!' + row.nickname;
|
||||
},
|
||||
formatResult: function(row){
|
||||
return '!' + row.nickname;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
63
plugins/Autocomplete/AutocompletePlugin.php
Normal file
63
plugins/Autocomplete/AutocompletePlugin.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Plugin to enable nickname completion in the enter status box
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package Laconica
|
||||
* @author Craig Andrews <candrews@integralblue.com>
|
||||
* @copyright 2009 Craig Andrews http://candrews.integralblue.com
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class AutocompletePlugin extends Plugin
|
||||
{
|
||||
function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function onEndShowScripts($action){
|
||||
if (common_logged_in()) {
|
||||
$current_user = common_current_user();
|
||||
$js_string = <<<EOT
|
||||
<script type="text/javascript">
|
||||
var current_user = { id: '$current_user->id' };
|
||||
</script>
|
||||
EOT;
|
||||
$action->raw($js_string);
|
||||
$action->script('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.pack.js');
|
||||
$action->script('plugins/Autocomplete/Autocomplete.js');
|
||||
}
|
||||
}
|
||||
|
||||
function onEndShowLaconicaStyles($action)
|
||||
{
|
||||
if (common_logged_in()) {
|
||||
$action->cssLink('plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.css');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
20
plugins/Autocomplete/jquery-autocomplete/changelog.txt
Normal file
20
plugins/Autocomplete/jquery-autocomplete/changelog.txt
Normal file
@ -0,0 +1,20 @@
|
||||
1.0.2
|
||||
-----
|
||||
* Fixed missing semicolon
|
||||
|
||||
1.0.1
|
||||
-----
|
||||
* Fixed element creation (<ul> to <ul/> and <li> to </li>)
|
||||
* Fixed ac_even class (was ac_event)
|
||||
* Fixed bgiframe usage: now its really optional
|
||||
* Removed the blur-on-return workaround, added a less obtrusive one only for Opera
|
||||
* Fixed hold cursor keys: Opera needs keypress, everyone else keydown to scroll through result list when holding cursor key
|
||||
* Updated package to jQuery 1.2.5, removing dimensions
|
||||
* Fixed multiple-mustMatch: Remove only the last term when no match is found
|
||||
* Fixed multiple without mustMatch: Don't select the last active when no match is found (on tab/return)
|
||||
* Fixed multiple cursor position: Put cursor at end of input after selecting a value
|
||||
|
||||
1.0
|
||||
---
|
||||
|
||||
* First release.
|
@ -0,0 +1,48 @@
|
||||
.ac_results {
|
||||
padding: 0px;
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.ac_results ul {
|
||||
width: 100%;
|
||||
list-style-position: outside;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ac_results li {
|
||||
margin: 0px;
|
||||
padding: 2px 5px;
|
||||
cursor: default;
|
||||
display: block;
|
||||
/*
|
||||
if width will be 100% horizontal scrollbar will apear
|
||||
when scroll mode will be used
|
||||
*/
|
||||
/*width: 100%;*/
|
||||
font: menu;
|
||||
font-size: 12px;
|
||||
/*
|
||||
it is very important, if line-height not setted or setted
|
||||
in relative units scroll will be broken in firefox
|
||||
*/
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ac_loading {
|
||||
background: white url('indicator.gif') right center no-repeat;
|
||||
}
|
||||
|
||||
.ac_odd {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.ac_over {
|
||||
background-color: #0A246A;
|
||||
color: white;
|
||||
}
|
759
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.js
Normal file
759
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.js
Normal file
@ -0,0 +1,759 @@
|
||||
/*
|
||||
* Autocomplete - jQuery plugin 1.0.2
|
||||
*
|
||||
* Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js 5747 2008-06-25 18:30:55Z joern.zaefferer $
|
||||
*
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
$.fn.extend({
|
||||
autocomplete: function(urlOrData, options) {
|
||||
var isUrl = typeof urlOrData == "string";
|
||||
options = $.extend({}, $.Autocompleter.defaults, {
|
||||
url: isUrl ? urlOrData : null,
|
||||
data: isUrl ? null : urlOrData,
|
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
|
||||
max: options && !options.scroll ? 10 : 150
|
||||
}, options);
|
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; };
|
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem;
|
||||
|
||||
return this.each(function() {
|
||||
new $.Autocompleter(this, options);
|
||||
});
|
||||
},
|
||||
result: function(handler) {
|
||||
return this.bind("result", handler);
|
||||
},
|
||||
search: function(handler) {
|
||||
return this.trigger("search", [handler]);
|
||||
},
|
||||
flushCache: function() {
|
||||
return this.trigger("flushCache");
|
||||
},
|
||||
setOptions: function(options){
|
||||
return this.trigger("setOptions", [options]);
|
||||
},
|
||||
unautocomplete: function() {
|
||||
return this.trigger("unautocomplete");
|
||||
}
|
||||
});
|
||||
|
||||
$.Autocompleter = function(input, options) {
|
||||
|
||||
var KEY = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
DEL: 46,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ESC: 27,
|
||||
COMMA: 188,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
BACKSPACE: 8
|
||||
};
|
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
|
||||
|
||||
var timeout;
|
||||
var previousValue = "";
|
||||
var cache = $.Autocompleter.Cache(options);
|
||||
var hasFocus = 0;
|
||||
var lastKeyPressCode;
|
||||
var config = {
|
||||
mouseDownOnSelect: false
|
||||
};
|
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
|
||||
|
||||
var blockSubmit;
|
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
|
||||
if (blockSubmit) {
|
||||
blockSubmit = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode;
|
||||
switch(event.keyCode) {
|
||||
|
||||
case KEY.UP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.prev();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.DOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.next();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEUP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageUp();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEDOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageDown();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
|
||||
case KEY.TAB:
|
||||
case KEY.RETURN:
|
||||
if( selectCurrent() ) {
|
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault();
|
||||
blockSubmit = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESC:
|
||||
select.hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(onChange, options.delay);
|
||||
break;
|
||||
}
|
||||
}).focus(function(){
|
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++;
|
||||
}).blur(function() {
|
||||
hasFocus = 0;
|
||||
if (!config.mouseDownOnSelect) {
|
||||
hideResults();
|
||||
}
|
||||
}).click(function() {
|
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) {
|
||||
onChange(0, true);
|
||||
}
|
||||
}).bind("search", function() {
|
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null;
|
||||
function findValueCallback(q, data) {
|
||||
var result;
|
||||
if( data && data.length ) {
|
||||
for (var i=0; i < data.length; i++) {
|
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
|
||||
result = data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( typeof fn == "function" ) fn(result);
|
||||
else $input.trigger("result", result && [result.data, result.value]);
|
||||
}
|
||||
$.each(trimWords($input.val()), function(i, value) {
|
||||
request(value, findValueCallback, findValueCallback);
|
||||
});
|
||||
}).bind("flushCache", function() {
|
||||
cache.flush();
|
||||
}).bind("setOptions", function() {
|
||||
$.extend(options, arguments[1]);
|
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] )
|
||||
cache.populate();
|
||||
}).bind("unautocomplete", function() {
|
||||
select.unbind();
|
||||
$input.unbind();
|
||||
$(input.form).unbind(".autocomplete");
|
||||
});
|
||||
|
||||
|
||||
function selectCurrent() {
|
||||
var selected = select.selected();
|
||||
if( !selected )
|
||||
return false;
|
||||
|
||||
var v = selected.result;
|
||||
previousValue = v;
|
||||
|
||||
if ( options.multiple ) {
|
||||
var words = trimWords($input.val());
|
||||
if ( words.length > 1 ) {
|
||||
v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
|
||||
}
|
||||
v += options.multipleSeparator;
|
||||
}
|
||||
|
||||
$input.val(v);
|
||||
hideResultsNow();
|
||||
$input.trigger("result", [selected.data, selected.value]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function onChange(crap, skipPrevCheck) {
|
||||
if( lastKeyPressCode == KEY.DEL ) {
|
||||
select.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentValue = $input.val();
|
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue )
|
||||
return;
|
||||
|
||||
previousValue = currentValue;
|
||||
|
||||
currentValue = lastWord(currentValue);
|
||||
if ( currentValue.length >= options.minChars) {
|
||||
$input.addClass(options.loadingClass);
|
||||
if (!options.matchCase)
|
||||
currentValue = currentValue.toLowerCase();
|
||||
request(currentValue, receiveData, hideResultsNow);
|
||||
} else {
|
||||
stopLoading();
|
||||
select.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function trimWords(value) {
|
||||
if ( !value ) {
|
||||
return [""];
|
||||
}
|
||||
var words = value.split( options.multipleSeparator );
|
||||
var result = [];
|
||||
$.each(words, function(i, value) {
|
||||
if ( $.trim(value) )
|
||||
result[i] = $.trim(value);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function lastWord(value) {
|
||||
if ( !options.multiple )
|
||||
return value;
|
||||
var words = trimWords(value);
|
||||
return words[words.length - 1];
|
||||
}
|
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){
|
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
|
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
|
||||
}
|
||||
};
|
||||
|
||||
function hideResults() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(hideResultsNow, 200);
|
||||
};
|
||||
|
||||
function hideResultsNow() {
|
||||
var wasVisible = select.visible();
|
||||
select.hide();
|
||||
clearTimeout(timeout);
|
||||
stopLoading();
|
||||
if (options.mustMatch) {
|
||||
// call search and run callback
|
||||
$input.search(
|
||||
function (result){
|
||||
// if no value found, clear the input box
|
||||
if( !result ) {
|
||||
if (options.multiple) {
|
||||
var words = trimWords($input.val()).slice(0, -1);
|
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
||||
}
|
||||
else
|
||||
$input.val( "" );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if (wasVisible)
|
||||
// position cursor at end of input field
|
||||
$.Autocompleter.Selection(input, input.value.length, input.value.length);
|
||||
};
|
||||
|
||||
function receiveData(q, data) {
|
||||
if ( data && data.length && hasFocus ) {
|
||||
stopLoading();
|
||||
select.display(data, q);
|
||||
autoFill(q, data[0].value);
|
||||
select.show();
|
||||
} else {
|
||||
hideResultsNow();
|
||||
}
|
||||
};
|
||||
|
||||
function request(term, success, failure) {
|
||||
if (!options.matchCase)
|
||||
term = term.toLowerCase();
|
||||
var data = cache.load(term);
|
||||
// recieve the cached data
|
||||
if (data && data.length) {
|
||||
success(term, data);
|
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
|
||||
|
||||
var extraParams = {
|
||||
timestamp: +new Date()
|
||||
};
|
||||
$.each(options.extraParams, function(key, param) {
|
||||
extraParams[key] = typeof param == "function" ? param() : param;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort",
|
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name,
|
||||
dataType: options.dataType,
|
||||
url: options.url,
|
||||
data: $.extend({
|
||||
q: lastWord(term),
|
||||
limit: options.max
|
||||
}, extraParams),
|
||||
success: function(data) {
|
||||
var parsed = options.parse && options.parse(data) || parse(data);
|
||||
cache.add(term, parsed);
|
||||
success(term, parsed);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList();
|
||||
failure(term);
|
||||
}
|
||||
};
|
||||
|
||||
function parse(data) {
|
||||
var parsed = [];
|
||||
var rows = data.split("\n");
|
||||
for (var i=0; i < rows.length; i++) {
|
||||
var row = $.trim(rows[i]);
|
||||
if (row) {
|
||||
row = row.split("|");
|
||||
parsed[parsed.length] = {
|
||||
data: row,
|
||||
value: row[0],
|
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
function stopLoading() {
|
||||
$input.removeClass(options.loadingClass);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
$.Autocompleter.defaults = {
|
||||
inputClass: "ac_input",
|
||||
resultsClass: "ac_results",
|
||||
loadingClass: "ac_loading",
|
||||
minChars: 1,
|
||||
delay: 400,
|
||||
matchCase: false,
|
||||
matchSubset: true,
|
||||
matchContains: false,
|
||||
cacheLength: 10,
|
||||
max: 100,
|
||||
mustMatch: false,
|
||||
extraParams: {},
|
||||
selectFirst: true,
|
||||
formatItem: function(row) { return row[0]; },
|
||||
formatMatch: null,
|
||||
autoFill: false,
|
||||
width: 0,
|
||||
multiple: false,
|
||||
multipleSeparator: ", ",
|
||||
highlight: function(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
|
||||
},
|
||||
scroll: true,
|
||||
scrollHeight: 180
|
||||
};
|
||||
|
||||
$.Autocompleter.Cache = function(options) {
|
||||
|
||||
var data = {};
|
||||
var length = 0;
|
||||
|
||||
function matchSubset(s, sub) {
|
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase();
|
||||
var i = s.indexOf(sub);
|
||||
if (i == -1) return false;
|
||||
return i == 0 || options.matchContains;
|
||||
};
|
||||
|
||||
function add(q, value) {
|
||||
if (length > options.cacheLength){
|
||||
flush();
|
||||
}
|
||||
if (!data[q]){
|
||||
length++;
|
||||
}
|
||||
data[q] = value;
|
||||
}
|
||||
|
||||
function populate(){
|
||||
if( !options.data ) return false;
|
||||
// track the matches
|
||||
var stMatchSets = {},
|
||||
nullData = 0;
|
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1;
|
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = [];
|
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
|
||||
var rawValue = options.data[i];
|
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
|
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length);
|
||||
if ( value === false )
|
||||
continue;
|
||||
|
||||
var firstChar = value.charAt(0).toLowerCase();
|
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] )
|
||||
stMatchSets[firstChar] = [];
|
||||
|
||||
// if the match is a string
|
||||
var row = {
|
||||
value: value,
|
||||
data: rawValue,
|
||||
result: options.formatResult && options.formatResult(rawValue) || value
|
||||
};
|
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row);
|
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) {
|
||||
stMatchSets[""].push(row);
|
||||
}
|
||||
};
|
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) {
|
||||
// increase the cache size
|
||||
options.cacheLength++;
|
||||
// add to the cache
|
||||
add(i, value);
|
||||
});
|
||||
}
|
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25);
|
||||
|
||||
function flush(){
|
||||
data = {};
|
||||
length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
flush: flush,
|
||||
add: add,
|
||||
populate: populate,
|
||||
load: function(q) {
|
||||
if (!options.cacheLength || !length)
|
||||
return null;
|
||||
/*
|
||||
* if dealing w/local data and matchContains than we must make sure
|
||||
* to loop through all the data collections looking for matches
|
||||
*/
|
||||
if( !options.url && options.matchContains ){
|
||||
// track all matches
|
||||
var csub = [];
|
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){
|
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){
|
||||
var c = data[k];
|
||||
$.each(c, function(i, x) {
|
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return csub;
|
||||
} else
|
||||
// if the exact item exists, use it
|
||||
if (data[q]){
|
||||
return data[q];
|
||||
} else
|
||||
if (options.matchSubset) {
|
||||
for (var i = q.length - 1; i >= options.minChars; i--) {
|
||||
var c = data[q.substr(0, i)];
|
||||
if (c) {
|
||||
var csub = [];
|
||||
$.each(c, function(i, x) {
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub[csub.length] = x;
|
||||
}
|
||||
});
|
||||
return csub;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) {
|
||||
var CLASSES = {
|
||||
ACTIVE: "ac_over"
|
||||
};
|
||||
|
||||
var listItems,
|
||||
active = -1,
|
||||
data,
|
||||
term = "",
|
||||
needsInit = true,
|
||||
element,
|
||||
list;
|
||||
|
||||
// Create results
|
||||
function init() {
|
||||
if (!needsInit)
|
||||
return;
|
||||
element = $("<div/>")
|
||||
.hide()
|
||||
.addClass(options.resultsClass)
|
||||
.css("position", "absolute")
|
||||
.appendTo(document.body);
|
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) {
|
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
|
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
}
|
||||
}).click(function(event) {
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
select();
|
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus();
|
||||
return false;
|
||||
}).mousedown(function() {
|
||||
config.mouseDownOnSelect = true;
|
||||
}).mouseup(function() {
|
||||
config.mouseDownOnSelect = false;
|
||||
});
|
||||
|
||||
if( options.width > 0 )
|
||||
element.css("width", options.width);
|
||||
|
||||
needsInit = false;
|
||||
}
|
||||
|
||||
function target(event) {
|
||||
var element = event.target;
|
||||
while(element && element.tagName != "LI")
|
||||
element = element.parentNode;
|
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element)
|
||||
return [];
|
||||
return element;
|
||||
}
|
||||
|
||||
function moveSelect(step) {
|
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
|
||||
movePosition(step);
|
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
|
||||
if(options.scroll) {
|
||||
var offset = 0;
|
||||
listItems.slice(0, active).each(function() {
|
||||
offset += this.offsetHeight;
|
||||
});
|
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
|
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
|
||||
} else if(offset < list.scrollTop()) {
|
||||
list.scrollTop(offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function movePosition(step) {
|
||||
active += step;
|
||||
if (active < 0) {
|
||||
active = listItems.size() - 1;
|
||||
} else if (active >= listItems.size()) {
|
||||
active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function limitNumberOfItems(available) {
|
||||
return options.max && options.max < available
|
||||
? options.max
|
||||
: available;
|
||||
}
|
||||
|
||||
function fillList() {
|
||||
list.empty();
|
||||
var max = limitNumberOfItems(data.length);
|
||||
for (var i=0; i < max; i++) {
|
||||
if (!data[i])
|
||||
continue;
|
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
|
||||
if ( formatted === false )
|
||||
continue;
|
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
|
||||
$.data(li, "ac_data", data[i]);
|
||||
}
|
||||
listItems = list.find("li");
|
||||
if ( options.selectFirst ) {
|
||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
|
||||
active = 0;
|
||||
}
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
list.bgiframe();
|
||||
}
|
||||
|
||||
return {
|
||||
display: function(d, q) {
|
||||
init();
|
||||
data = d;
|
||||
term = q;
|
||||
fillList();
|
||||
},
|
||||
next: function() {
|
||||
moveSelect(1);
|
||||
},
|
||||
prev: function() {
|
||||
moveSelect(-1);
|
||||
},
|
||||
pageUp: function() {
|
||||
if (active != 0 && active - 8 < 0) {
|
||||
moveSelect( -active );
|
||||
} else {
|
||||
moveSelect(-8);
|
||||
}
|
||||
},
|
||||
pageDown: function() {
|
||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
|
||||
moveSelect( listItems.size() - 1 - active );
|
||||
} else {
|
||||
moveSelect(8);
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
element && element.hide();
|
||||
listItems && listItems.removeClass(CLASSES.ACTIVE);
|
||||
active = -1;
|
||||
},
|
||||
visible : function() {
|
||||
return element && element.is(":visible");
|
||||
},
|
||||
current: function() {
|
||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
|
||||
},
|
||||
show: function() {
|
||||
var offset = $(input).offset();
|
||||
element.css({
|
||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
|
||||
top: offset.top + input.offsetHeight,
|
||||
left: offset.left
|
||||
}).show();
|
||||
if(options.scroll) {
|
||||
list.scrollTop(0);
|
||||
list.css({
|
||||
maxHeight: options.scrollHeight,
|
||||
overflow: 'auto'
|
||||
});
|
||||
|
||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
|
||||
var listHeight = 0;
|
||||
listItems.each(function() {
|
||||
listHeight += this.offsetHeight;
|
||||
});
|
||||
var scrollbarsVisible = listHeight > options.scrollHeight;
|
||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
|
||||
if (!scrollbarsVisible) {
|
||||
// IE doesn't recalculate width when scrollbar disappears
|
||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
selected: function() {
|
||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
|
||||
return selected && selected.length && $.data(selected[0], "ac_data");
|
||||
},
|
||||
emptyList: function (){
|
||||
list && list.empty();
|
||||
},
|
||||
unbind: function() {
|
||||
element && element.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Selection = function(field, start, end) {
|
||||
if( field.createTextRange ){
|
||||
var selRange = field.createTextRange();
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
} else if( field.setSelectionRange ){
|
||||
field.setSelectionRange(start, end);
|
||||
} else {
|
||||
if( field.selectionStart ){
|
||||
field.selectionStart = start;
|
||||
field.selectionEnd = end;
|
||||
}
|
||||
}
|
||||
field.focus();
|
||||
};
|
||||
|
||||
})(jQuery);
|
15
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.min.js
vendored
Normal file
15
plugins/Autocomplete/jquery-autocomplete/jquery.autocomplete.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
116
plugins/Autocomplete/jquery-autocomplete/lib/jquery.ajaxQueue.js
Normal file
116
plugins/Autocomplete/jquery-autocomplete/lib/jquery.ajaxQueue.js
Normal file
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Ajax Queue Plugin
|
||||
*
|
||||
* Homepage: http://jquery.com/plugins/project/ajaxqueue
|
||||
* Documentation: http://docs.jquery.com/AjaxQueue
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
jQuery.ajaxQueue({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append(html); }
|
||||
});
|
||||
jQuery.ajaxQueue({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append(html); }
|
||||
});
|
||||
jQuery.ajaxSync({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
|
||||
});
|
||||
jQuery.ajaxSync({
|
||||
url: "test.php",
|
||||
success: function(html){ jQuery("ul").append("<b>"+html+"</b>"); }
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<ul style="position: absolute; top: 5px; right: 5px;"></ul>
|
||||
|
||||
*/
|
||||
/*
|
||||
* Queued Ajax requests.
|
||||
* A new Ajax request won't be started until the previous queued
|
||||
* request has finished.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Synced Ajax requests.
|
||||
* The Ajax request will happen as soon as you call this method, but
|
||||
* the callbacks (success/error/complete) won't fire until all previous
|
||||
* synced requests have been completed.
|
||||
*/
|
||||
|
||||
|
||||
(function($) {
|
||||
|
||||
var ajax = $.ajax;
|
||||
|
||||
var pendingRequests = {};
|
||||
|
||||
var synced = [];
|
||||
var syncedData = [];
|
||||
|
||||
$.ajax = function(settings) {
|
||||
// create settings for compatibility with ajaxSetup
|
||||
settings = jQuery.extend(settings, jQuery.extend({}, jQuery.ajaxSettings, settings));
|
||||
|
||||
var port = settings.port;
|
||||
|
||||
switch(settings.mode) {
|
||||
case "abort":
|
||||
if ( pendingRequests[port] ) {
|
||||
pendingRequests[port].abort();
|
||||
}
|
||||
return pendingRequests[port] = ajax.apply(this, arguments);
|
||||
case "queue":
|
||||
var _old = settings.complete;
|
||||
settings.complete = function(){
|
||||
if ( _old )
|
||||
_old.apply( this, arguments );
|
||||
jQuery([ajax]).dequeue("ajax" + port );;
|
||||
};
|
||||
|
||||
jQuery([ ajax ]).queue("ajax" + port, function(){
|
||||
ajax( settings );
|
||||
});
|
||||
return;
|
||||
case "sync":
|
||||
var pos = synced.length;
|
||||
|
||||
synced[ pos ] = {
|
||||
error: settings.error,
|
||||
success: settings.success,
|
||||
complete: settings.complete,
|
||||
done: false
|
||||
};
|
||||
|
||||
syncedData[ pos ] = {
|
||||
error: [],
|
||||
success: [],
|
||||
complete: []
|
||||
};
|
||||
|
||||
settings.error = function(){ syncedData[ pos ].error = arguments; };
|
||||
settings.success = function(){ syncedData[ pos ].success = arguments; };
|
||||
settings.complete = function(){
|
||||
syncedData[ pos ].complete = arguments;
|
||||
synced[ pos ].done = true;
|
||||
|
||||
if ( pos == 0 || !synced[ pos-1 ] )
|
||||
for ( var i = pos; i < synced.length && synced[i].done; i++ ) {
|
||||
if ( synced[i].error ) synced[i].error.apply( jQuery, syncedData[i].error );
|
||||
if ( synced[i].success ) synced[i].success.apply( jQuery, syncedData[i].success );
|
||||
if ( synced[i].complete ) synced[i].complete.apply( jQuery, syncedData[i].complete );
|
||||
|
||||
synced[i] = null;
|
||||
syncedData[i] = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
return ajax.apply(this, arguments);
|
||||
};
|
||||
|
||||
})(jQuery);
|
10
plugins/Autocomplete/jquery-autocomplete/lib/jquery.bgiframe.min.js
vendored
Normal file
10
plugins/Autocomplete/jquery-autocomplete/lib/jquery.bgiframe.min.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net)
|
||||
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*
|
||||
* $LastChangedDate: 2007-07-22 01:45:56 +0200 (Son, 22 Jul 2007) $
|
||||
* $Rev: 2447 $
|
||||
*
|
||||
* Version 2.1.1
|
||||
*/
|
||||
(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&/6.0/.test(navigator.userAgent)){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};})(jQuery);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user