diff options
author | Evan Prodromou <evan@controlyourself.ca> | 2009-06-10 04:46:20 +0000 |
---|---|---|
committer | Evan Prodromou <evan@controlyourself.ca> | 2009-06-10 04:46:20 +0000 |
commit | 4110838cb44c9e2e54f8ff8d722fdcbc5666fafe (patch) | |
tree | 51335c05e93194217c90a46b21e5477bba47c378 | |
parent | 003c63e587128c6d095386c563c2615c2ac245c2 (diff) | |
parent | 207750e75774c823328889cb4102aad6a0b12281 (diff) |
Merge branch '0.8.x' of git://gitorious.org/laconica/dev into dev/0.8.x
85 files changed, 4284 insertions, 1786 deletions
diff --git a/.gitignore b/.gitignore index da6947bfd..3418d8ee5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ avatar/* files/* +file/* _darcs/* logs/* config.php @@ -16,3 +17,8 @@ dataobject.ini .buildpath .project .settings +TODO.rym +config-*.php +good-config.php +lac08.log +php.log @@ -694,6 +694,13 @@ to users on a remote site. (Or not... it's not well tested.) The Upgrading ========= +IMPORTANT NOTE: Laconica 0.7.4 introduced a fix for some +incorrectly-stored international characters ("UTF-8"). For new +installations, it will now store non-ASCII characters correctly. +However, older installations will have the incorrect storage, and will +consequently show up "wrong" in browsers. See below for how to deal +with this situation. + If you've been using Laconica 0.6, 0.5 or lower, or if you've been tracking the "git" version of the software, you will probably want to upgrade and keep your existing data. There is no automated upgrade @@ -783,6 +790,29 @@ problem. 3. When fixup_inboxes is finished, you can set the enabled flag to 'true'. +UTF-8 Database +-------------- + +Laconica 0.7.4 introduced a fix for some incorrectly-stored +international characters ("UTF-8"). This fix is not +backwards-compatible; installations from before 0.7.4 will show +non-ASCII characters of old notices incorrectly. This section explains +what to do. + +0. You can disable the new behaviour by setting the 'db''utf8' config + option to "false". You should only do this until you're ready to + convert your DB to the new format. +1. When you're ready to convert, you can run the fixup_utf8.php script + in the scripts/ subdirectory. If you've had the "new behaviour" + enabled (probably a good idea), you can give the ID of the first + "new" notice as a parameter, and only notices before that one will + be converted. Notices are converted in reverse chronological order, + so the most recent (and visible) ones will be converted first. The + script should work whether or not you have the 'db''utf8' config + option enabled. +2. When you're ready, set $config['db']['utf8'] to true, so that + new notices will be stored correctly. + Configuration options ===================== @@ -910,6 +940,10 @@ mirror: you can set this to an array of DSNs, like the above and adding the slaves to this array. Note that if you want some requests to go to the 'database' (master) server, you'll need to include it in this array, too. +utf8: whether to talk to the database in UTF-8 mode. This is the default + with new installations, but older sites may want to turn it off + until they get their databases fixed up. See "UTF-8 database" + above for details. syslog ------ @@ -1136,6 +1170,63 @@ welcome: nickname of a user account that sends welcome messages to new If either of these special user accounts are specified, the users should be created before the configuration is updated. +snapshot +-------- + +The software will, by default, send statistical snapshots about the +local installation to a stats server on the laconi.ca Web site. This +data is used by the developers to prioritize development decisions. No +identifying data about users or organizations is collected. The data +is available to the public for review. Participating in this survey +helps Laconica developers take your needs into account when updating +the software. + +run: string indicating when to run the statistics. Values can be 'web' + (run occasionally at Web time), 'cron' (run from a cron script), + or 'never' (don't ever run). If you set it to 'cron', remember to + schedule the script to run on a regular basis. +frequency: if run value is 'web', how often to report statistics. + Measured in Web hits; depends on how active your site is. + Default is 10000 -- that is, one report every 10000 Web hits, + on average. +reporturl: URL to post statistics to. Defaults to Laconica developers' + report system, but if they go evil or disappear you may + need to update this to another value. Note: if you + don't want to report stats, it's much better to + set 'run' to 'never' than to set this value to something + nonsensical. + + +attachments +----------- + +The software lets users upload files with their notices. You can configure +the types of accepted files by mime types and a trio of quota options: +per file, per user (total), per user per month. + +We suggest the use of the pecl file_info extension to handle mime type +detection. + +supported: an array of mime types you accept to store and distribute, + like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you + setup your server to properly reckognize the types you want to + support. + +For quotas, be sure you've set the upload_max_filesize and post_max_size +in php.ini to be large enough to handle your upload. In httpd.conf +(if you're using apache), check that the LimitRequestBody directive isn't +set too low (it's optional, so it may not be there at all). + +file_quota: maximum size for a single file upload in bytes. A user can send + any amount of notices with attachments as long as each attachment + is smaller than file_quota. +user_quota: total size in bytes a user can store on this server. Each user + can store any number of files as long as their total size does + not exceed the user_quota. +monthly_quota: total size permitted in the current month. This is the total + size in bytes that a user can upload each month. + + Troubleshooting =============== diff --git a/actions/api.php b/actions/api.php index 8762b4bcd..b25ba99f3 100644 --- a/actions/api.php +++ b/actions/api.php @@ -67,6 +67,7 @@ class ApiAction extends Action $this->process_command(); } else { # basic authentication failed + common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname."); $this->show_basic_auth_error(); } } diff --git a/actions/attachment.php b/actions/attachment.php index b9187ff08..16ee723d9 100644 --- a/actions/attachment.php +++ b/actions/attachment.php @@ -31,8 +31,6 @@ if (!defined('LACONICA')) { exit(1); } -//require_once INSTALLDIR.'/lib/personalgroupnav.php'; -//require_once INSTALLDIR.'/lib/feedlist.php'; require_once INSTALLDIR.'/lib/attachmentlist.php'; /** @@ -67,11 +65,11 @@ class AttachmentAction extends Action { parent::prepare($args); - $id = $this->arg('attachment'); - - $this->attachment = File::staticGet($id); + if ($id = $this->trimmed('attachment')) { + $this->attachment = File::staticGet($id); + } - if (!$this->attachment) { + if (empty($this->attachment)) { $this->clientError(_('No such attachment.'), 404); return false; } @@ -178,10 +176,8 @@ class AttachmentAction extends Action function showContent() { - $this->elementStart('ul', array('class' => 'attachments')); $ali = new Attachment($this->attachment, $this); $cnt = $ali->show(); - $this->elementEnd('ul'); } /** diff --git a/actions/attachment_ajax.php b/actions/attachment_ajax.php index 1620b27dd..3d83393c5 100644 --- a/actions/attachment_ajax.php +++ b/actions/attachment_ajax.php @@ -46,26 +46,6 @@ require_once INSTALLDIR.'/actions/attachment.php'; class Attachment_ajaxAction extends AttachmentAction { /** - * Load attributes based on database arguments - * - * Loads all the DB stuff - * - * @param array $args $_REQUEST array - * - * @return success flag - */ - - function prepare($args) - { - parent::prepare($args); - if (!$this->attachment) { - $this->clientError(_('No such attachment.'), 404); - return false; - } - return true; - } - - /** * Show page, a template method. * * @return nothing @@ -95,8 +75,6 @@ class Attachment_ajaxAction extends AttachmentAction $this->elementEnd('div'); } - - /** * Last-modified date for page * diff --git a/actions/attachments_ajax.php b/actions/attachment_thumbnail.php index 402d8b5e7..b4070e747 100644 --- a/actions/attachments_ajax.php +++ b/actions/attachment_thumbnail.php @@ -31,9 +31,7 @@ if (!defined('LACONICA')) { exit(1); } -//require_once INSTALLDIR.'/lib/personalgroupnav.php'; -//require_once INSTALLDIR.'/lib/feedlist.php'; -require_once INSTALLDIR.'/actions/attachments.php'; +require_once INSTALLDIR.'/actions/attachment.php'; /** * Show notice attachments @@ -45,39 +43,8 @@ require_once INSTALLDIR.'/actions/attachments.php'; * @link http://laconi.ca/ */ -class Attachments_ajaxAction extends AttachmentsAction +class Attachment_thumbnailAction extends AttachmentAction { - function showContent() - { - } - - /** - * Fill the content area of the page - * - * Shows a single notice list item. - * - * @return void - */ - - function showContentBlock() - { - $al = new AttachmentList($this->notice, $this); - $cnt = $al->show(); - } - - /** - * Extra <head> content - * - * We show the microid(s) for the author, if any. - * - * @return void - */ - - function extraHead() - { - } - - /** * Show page, a template method. * @@ -100,16 +67,52 @@ class Attachments_ajaxAction extends AttachmentsAction */ function showCore() { - $this->elementStart('div', array('id' => 'core')); - if (Event::handle('StartShowContentBlock', array($this))) { - $this->showContentBlock(); - Event::handle('EndShowContentBlock', array($this)); + $file_thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id); + if (empty($file_thumbnail->url)) { + return; } - $this->elementEnd('div'); + $this->element('img', array('src' => $file_thumbnail->url, 'alt' => 'Thumbnail')); } + /** + * Last-modified date for page + * + * When was the content of this page last modified? Based on notice, + * profile, avatar. + * + * @return int last-modified date as unix timestamp + */ +/* + function lastModified() + { + return max(strtotime($this->notice->created), + strtotime($this->profile->modified), + ($this->avatar) ? strtotime($this->avatar->modified) : 0); + } +*/ - - + /** + * An entity tag for this page + * + * Shows the ETag for the page, based on the notice ID and timestamps + * for the notice, profile, and avatar. It's weak, since we change + * the date text "one hour ago", etc. + * + * @return string etag + */ +/* + function etag() + { + $avtime = ($this->avatar) ? + strtotime($this->avatar->modified) : 0; + + return 'W/"' . implode(':', array($this->arg('action'), + common_language(), + $this->notice->id, + strtotime($this->notice->created), + strtotime($this->profile->modified), + $avtime)) . '"'; + } +*/ } diff --git a/actions/attachments.php b/actions/attachments.php deleted file mode 100644 index 6b31c839d..000000000 --- a/actions/attachments.php +++ /dev/null @@ -1,292 +0,0 @@ -<?php -/** - * Laconica, the distributed open-source microblogging tool - * - * Show notice attachments - * - * 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 Personal - * @package Laconica - * @author Evan Prodromou <evan@controlyourself.ca> - * @copyright 2008-2009 Control Yourself, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ - */ - -if (!defined('LACONICA')) { - exit(1); -} - -//require_once INSTALLDIR.'/lib/personalgroupnav.php'; -//require_once INSTALLDIR.'/lib/feedlist.php'; -require_once INSTALLDIR.'/lib/attachmentlist.php'; - -/** - * Show notice attachments - * - * @category Personal - * @package Laconica - * @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 AttachmentsAction extends Action -{ - /** - * Notice object to show - */ - - var $notice = null; - - /** - * Profile of the notice object - */ - - var $profile = null; - - /** - * Avatar of the profile of the notice object - */ - - var $avatar = null; - - /** - * Is this action read-only? - * - * @return boolean true - */ - - function isReadOnly($args) - { - return true; - } - - /** - * Last-modified date for page - * - * When was the content of this page last modified? Based on notice, - * profile, avatar. - * - * @return int last-modified date as unix timestamp - */ - - function lastModified() - { - return max(strtotime($this->notice->created), - strtotime($this->profile->modified), - ($this->avatar) ? strtotime($this->avatar->modified) : 0); - } - - /** - * An entity tag for this page - * - * Shows the ETag for the page, based on the notice ID and timestamps - * for the notice, profile, and avatar. It's weak, since we change - * the date text "one hour ago", etc. - * - * @return string etag - */ - - function etag() - { - $avtime = ($this->avatar) ? - strtotime($this->avatar->modified) : 0; - - return 'W/"' . implode(':', array($this->arg('action'), - common_language(), - $this->notice->id, - strtotime($this->notice->created), - strtotime($this->profile->modified), - $avtime)) . '"'; - } - - /** - * Title of the page - * - * @return string title of the page - */ - - function title() - { - return sprintf(_('%1$s\'s status on %2$s'), - $this->profile->nickname, - common_exact_date($this->notice->created)); - } - - - /** - * Load attributes based on database arguments - * - * Loads all the DB stuff - * - * @param array $args $_REQUEST array - * - * @return success flag - */ - - function prepare($args) - { - parent::prepare($args); - - $id = $this->arg('notice'); - - $this->notice = Notice::staticGet($id); - - if (!$this->notice) { - $this->clientError(_('No such notice.'), 404); - return false; - } - - -/* -// STOP if there are no attachments -// maybe even redirect if there's a single one -// RYM FIXME TODO - $this->clientError(_('No such attachment.'), 404); - return false; - -*/ - - - - - $this->profile = $this->notice->getProfile(); - - if (!$this->profile) { - $this->serverError(_('Notice has no profile'), 500); - return false; - } - - $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); - return true; - } - - - - /** - * Handle input - * - * Only handles get, so just show the page. - * - * @param array $args $_REQUEST data (unused) - * - * @return void - */ - - function handle($args) - { - parent::handle($args); - - if ($this->notice->is_local == 0) { - if (!empty($this->notice->url)) { - common_redirect($this->notice->url, 301); - } else if (!empty($this->notice->uri) && preg_match('/^https?:/', $this->notice->uri)) { - common_redirect($this->notice->uri, 301); - } - } else { - $f2p = new File_to_post; - $f2p->post_id = $this->notice->id; - $file = new File; - $file->joinAdd($f2p); - $file->selectAdd(); - $file->selectAdd('file.id as id'); - $count = $file->find(true); - if (!$count) return; - if (1 === $count) { - common_redirect(common_local_url('attachment', array('attachment' => $file->id)), 301); - } else { - $this->showPage(); - } - } - } - - /** - * Don't show local navigation - * - * @return void - */ - - function showLocalNavBlock() - { - } - - /** - * Fill the content area of the page - * - * Shows a single notice list item. - * - * @return void - */ - - function showContent() - { - $al = new AttachmentList($this->notice, $this); - $cnt = $al->show(); - } - - /** - * Don't show page notice - * - * @return void - */ - - function showPageNoticeBlock() - { - } - - /** - * Don't show aside - * - * @return void - */ - - function showAside() { - } - - /** - * Extra <head> content - * - * We show the microid(s) for the author, if any. - * - * @return void - */ - - function extraHead() - { - $user = User::staticGet($this->profile->id); - - if (!$user) { - return; - } - - if ($user->emailmicroid && $user->email && $this->notice->uri) { - $id = new Microid('mailto:'. $user->email, - $this->notice->uri); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } - - if ($user->jabbermicroid && $user->jabber && $this->notice->uri) { - $id = new Microid('xmpp:', $user->jabber, - $this->notice->uri); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } - } -} - diff --git a/actions/conversation.php b/actions/conversation.php index 05cfb76e3..0d7cb9a87 100644 --- a/actions/conversation.php +++ b/actions/conversation.php @@ -11,7 +11,7 @@ * @link http://laconi.ca/ * * Laconica - a distributed open-source microblogging tool - * Copyright (C) 2008, Controlez-Vous, Inc. + * Copyright (C) 2009, Control Yourself, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -31,7 +31,7 @@ if (!defined('LACONICA')) { exit(1); } -require_once(INSTALLDIR.'/lib/noticelist.php'); +require_once INSTALLDIR.'/lib/noticelist.php'; /** * Conversation tree in the browser @@ -42,9 +42,10 @@ require_once(INSTALLDIR.'/lib/noticelist.php'); * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://laconi.ca/ */ + class ConversationAction extends Action { - var $id = null; + var $id = null; var $page = null; /** @@ -69,24 +70,47 @@ class ConversationAction extends Action return true; } + /** + * Handle the action + * + * @param array $args Web and URL arguments + * + * @return void + */ + function handle($args) { parent::handle($args); $this->showPage(); } + /** + * Returns the page title + * + * @return string page title + */ + function title() { return _("Conversation"); } + /** + * Show content. + * + * Display a hierarchical unordered list in the content area. + * Uses ConversationTree to do most of the heavy lifting. + * + * @return void + */ + function showContent() { // FIXME this needs to be a tree, not a list $qry = 'SELECT * FROM notice WHERE conversation = %s '; - $offset = ($this->page-1)*NOTICES_PER_PAGE; + $offset = ($this->page-1) * NOTICES_PER_PAGE; $limit = NOTICES_PER_PAGE + 1; $txt = sprintf($qry, $this->id); @@ -95,9 +119,9 @@ class ConversationAction extends Action 'notice:conversation:'.$this->id, $offset, $limit); - $nl = new NoticeList($notices, $this); + $ct = new ConversationTree($notices, $this); - $cnt = $nl->show(); + $cnt = $ct->show(); $this->pagination($this->page > 1, $cnt > NOTICES_PER_PAGE, $this->page, 'conversation', array('id' => $this->id)); @@ -105,3 +129,170 @@ class ConversationAction extends Action } +/** + * Conversation tree + * + * The widget class for displaying a hierarchical list of notices. + * + * @category Widget + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ + +class ConversationTree extends NoticeList +{ + var $tree = null; + var $table = null; + + /** + * Show the tree of notices + * + * @return void + */ + + function show() + { + $cnt = 0; + + $this->tree = array(); + $this->table = array(); + + while ($this->notice->fetch()) { + + $cnt++; + + $id = $this->notice->id; + $notice = clone($this->notice); + + $this->table[$id] = $notice; + + if (is_null($notice->reply_to)) { + $this->tree['root'] = array($notice->id); + } else if (array_key_exists($notice->reply_to, $this->tree)) { + $this->tree[$notice->reply_to][] = $notice->id; + } else { + $this->tree[$notice->reply_to] = array($notice->id); + } + } + + $this->out->elementStart('div', array('id' =>'notices_primary')); + $this->out->element('h2', null, _('Notices')); + $this->out->elementStart('ol', array('class' => 'notices xoxo')); + + if (array_key_exists('root', $this->tree)) { + $rootid = $this->tree['root'][0]; + $this->showNoticePlus($rootid); + } + + $this->out->elementEnd('ol'); + $this->out->elementEnd('div'); + + return $cnt; + } + + /** + * Shows a notice plus its list of children. + * + * @param integer $id ID of the notice to show + * + * @return void + */ + + function showNoticePlus($id) + { + $notice = $this->table[$id]; + + // We take responsibility for doing the li + + $this->out->elementStart('li', array('class' => 'hentry notice', + 'id' => 'notice-' . $this->notice->id)); + + $item = $this->newListItem($notice); + $item->show(); + + if (array_key_exists($id, $this->tree)) { + $children = $this->tree[$id]; + + $this->out->elementStart('ol', array('class' => 'notices')); + + foreach ($children as $child) { + $this->showNoticePlus($child); + } + + $this->out->elementEnd('ol'); + } + + $this->out->elementEnd('li'); + } + + /** + * Override parent class to return our preferred item. + * + * @param Notice $notice Notice to display + * + * @return NoticeListItem a list item to show + */ + + function newListItem($notice) + { + return new ConversationTreeItem($notice, $this->out); + } +} + +/** + * Conversation tree list item + * + * Special class of NoticeListItem for use inside conversation trees. + * + * @category Widget + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 + * @link http://laconi.ca/ + */ + +class ConversationTreeItem extends NoticeListItem +{ + /** + * start a single notice. + * + * The default creates the <li>; we skip, since the ConversationTree + * takes care of that. + * + * @return void + */ + + function showStart() + { + return; + } + + /** + * finish the notice + * + * The default closes the <li>; we skip, since the ConversationTree + * takes care of that. + * + * @return void + */ + + function showEnd() + { + return; + } + + /** + * show link to notice conversation page + * + * Since we're only used on the conversation page, we skip this + * + * @return void + */ + + function showContext() + { + return; + } +} diff --git a/actions/deletenotice.php b/actions/deletenotice.php index 6c350b33a..e733f9650 100644 --- a/actions/deletenotice.php +++ b/actions/deletenotice.php @@ -112,8 +112,8 @@ class DeletenoticeAction extends DeleteAction $this->hidden('token', common_session_token()); $this->hidden('notice', $this->trimmed('notice')); $this->element('p', null, _('Are you sure you want to delete this notice?')); - $this->submit('form_action-yes', _('Yes'), 'submit form_action-primary', 'yes'); - $this->submit('form_action-no', _('No'), 'submit form_action-secondary', 'no'); + $this->submit('form_action-no', _('No'), 'submit form_action-primary', 'no', _("Do not delete this notice")); + $this->submit('form_action-yes', _('Yes'), 'submit form_action-secondary', 'yes', _('Delete this notice')); $this->elementEnd('fieldset'); $this->elementEnd('form'); } diff --git a/actions/designsettings.php b/actions/designsettings.php index a85b36a25..5774b8537 100644 --- a/actions/designsettings.php +++ b/actions/designsettings.php @@ -70,7 +70,7 @@ class DesignsettingsAction extends AccountSettingsAction function showContent() { $user = common_current_user(); - $this->elementStart('form', array('method' => 'POST', + $this->elementStart('form', array('method' => 'post', 'id' => 'form_settings_design', 'class' => 'form_settings', 'action' => @@ -82,7 +82,7 @@ class DesignsettingsAction extends AccountSettingsAction $this->element('legend', null, _('Change background image')); $this->elementStart('ul', 'form_data'); $this->elementStart('li'); - $this->element('label', array('for' => 'design_ background-image_file'), + $this->element('label', array('for' => 'design_background-image_file'), _('Upload file')); $this->element('input', array('name' => 'design_background-image_file', 'type' => 'file', @@ -140,10 +140,12 @@ class DesignsettingsAction extends AccountSettingsAction $this->elementEnd('ul'); $this->elementEnd('fieldset'); - $this->submit('save', _('Save')); - $this->element('input', array('type' => 'reset', + $this->element('input', array('id' => 'settings_design_reset', + 'type' => 'reset', 'value' => 'Reset', - 'class' => 'form_action-secondary')); + 'class' => 'submit form_action-primary', + 'title' => _('Reset back to default'))); + $this->submit('save', _('Save'), 'submit form_action-secondary', 'save', _('Save design')); /*TODO: Check submitted form values: json_encode(form values) diff --git a/actions/facebookhome.php b/actions/facebookhome.php index 5946e6c98..00b35ef68 100644 --- a/actions/facebookhome.php +++ b/actions/facebookhome.php @@ -115,7 +115,7 @@ class FacebookhomeAction extends FacebookAction $flink->foreign_id = $this->fbuid; $flink->service = FACEBOOK_SERVICE; $flink->created = common_sql_now(); - $flink->set_flags(true, false, false); + $flink->set_flags(true, false, false, false); $flink_id = $flink->insert(); @@ -138,9 +138,6 @@ class FacebookhomeAction extends FacebookAction function setDefaults() { - // A default prefix string for notices - $this->facebook->api_client->data_setUserPreference( - FACEBOOK_NOTICE_PREFIX, 'dented: '); $this->facebook->api_client->data_setUserPreference( FACEBOOK_PROMPTED_UPDATE_PREF, 'false'); } diff --git a/actions/facebookinvite.php b/actions/facebookinvite.php index 1302064ad..2207580f7 100644 --- a/actions/facebookinvite.php +++ b/actions/facebookinvite.php @@ -17,7 +17,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('LACONICA')) { exit(1); } +if (!defined('LACONICA')) { + exit(1); +} require_once(INSTALLDIR.'/lib/facebookaction.php'); @@ -67,7 +69,7 @@ class FacebookinviteAction extends FacebookAction function showSuccessContent() { - $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), + $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), common_config('site', 'name'))); $this->element('p', null, _('Invitations have been sent to the following users:')); @@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction function showFormContent() { - - // Get a list of users who are already using the app for exclusion - $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); - $exclude_ids_csv = null; - - // fbml needs these as a csv string, not an array - if ($exclude_ids) { - $exclude_ids_csv = implode(',', $exclude_ids); - } - $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) . htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>'); @@ -109,36 +101,43 @@ class FacebookinviteAction extends FacebookAction 'content' => $content)); $this->hidden('invite', 'true'); $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name')); - - $multi_params = array('showborder' => 'false'); + + $multi_params = array('showborder' => 'false'); $multi_params['actiontext'] = $actiontext; - - if ($exclude_ids_csv) { + $multi_params['bypass'] = 'cancel'; + + // Get a list of users who are already using the app for exclusion + $exclude_ids = $this->facebook->api_client->friends_getAppUsers(); + $exclude_ids_csv = null; + + // fbml needs these as a csv string, not an array + if ($exclude_ids) { + $exclude_ids_csv = implode(',', $exclude_ids); $multi_params['exclude_ids'] = $exclude_ids_csv; } - $multi_params['bypass'] = 'cancel'; - $this->element('fb:multi-friend-selector', $multi_params); - $this->elementEnd('fb:request-form'); - $this->element('h2', null, sprintf(_('Friends already using %s:'), - common_config('site', 'name'))); - $this->elementStart('ul', array('id' => 'facebook-friends')); - - foreach ($exclude_ids as $friend) { - $this->elementStart('li'); - $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); - $this->element('fb:name', array('uid' => $friend, - 'capitalize' => 'true')); - $this->elementEnd('li'); - } + if ($exclude_ids) { - $this->elementEnd("ul"); + $this->element('h2', null, sprintf(_('Friends already using %s:'), + common_config('site', 'name'))); + $this->elementStart('ul', array('id' => 'facebook-friends')); + + foreach ($exclude_ids as $friend) { + $this->elementStart('li'); + $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square')); + $this->element('fb:name', array('uid' => $friend, + 'capitalize' => 'true')); + $this->elementEnd('li'); + } + + $this->elementEnd("ul"); + } } - - function title() + + function title() { return sprintf(_('Send invitations')); } diff --git a/actions/facebooksettings.php b/actions/facebooksettings.php index 236460c1c..227e12316 100644 --- a/actions/facebooksettings.php +++ b/actions/facebooksettings.php @@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction $prefix = $this->trimmed('prefix'); $original = clone($this->flink); - $this->flink->set_flags($noticesync, $replysync, false); + $this->flink->set_flags($noticesync, $replysync, false, false); $result = $this->flink->update($original); $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX, diff --git a/actions/file.php b/actions/file.php new file mode 100644 index 000000000..1179dbe9a --- /dev/null +++ b/actions/file.php @@ -0,0 +1,40 @@ +<?php +/* + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2008, Controlez-Vous, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +if (!defined('LACONICA')) { exit(1); } + +require_once(INSTALLDIR.'/actions/shownotice.php'); + +class FileAction extends ShowNoticeAction +{ + function showPage() { + $source_url = common_local_url('file', array('notice' => $this->notice->id)); + $query = "select file_redirection.url as url from file join file_redirection on file.id = file_redirection.file_id where file.url = '$source_url'"; + $file = new File_redirection; + $file->query($query); + $file->fetch(); + if (empty($file->url)) { + die('nothing attached here'); + } else { + header("Location: {$file->url}"); + die(); + } + } +} + diff --git a/actions/newnotice.php b/actions/newnotice.php index ae0ff9636..02976a2ae 100644 --- a/actions/newnotice.php +++ b/actions/newnotice.php @@ -84,20 +84,24 @@ class NewnoticeAction extends Action function handle($args) { - parent::handle($args); - if (!common_logged_in()) { $this->clientError(_('Not logged in.')); } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { + // check for this before token since all POST and FILES data + // is losts when size is exceeded + if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) { + $this->clientError(sprintf(_('The server was unable to handle ' . + 'that much POST data (%s bytes) due to its current configuration.'), + $_SERVER['CONTENT_LENGTH'])); + } + parent::handle($args); // CSRF protection $token = $this->trimmed('token'); if (!$token || $token != common_session_token()) { $this->clientError(_('There was a problem with your session token. '. 'Try again, please.')); - return; } - try { $this->saveNewNotice(); } catch (Exception $e) { @@ -109,6 +113,30 @@ class NewnoticeAction extends Action } } + function getUploadedFileType() { + require_once 'MIME/Type.php'; + + $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']); + if (in_array($filetype, common_config('attachments', 'supported'))) { + return $filetype; + } + $media = MIME_Type::getMedia($filetype); + if ('application' !== $media) { + $hint = sprintf(_(' Try using another %s format.'), $media); + } else { + $hint = ''; + } + $this->clientError(sprintf( + _('%s is not a supported filetype on this server.'), $filetype) . $hint); + } + + function isRespectsQuota($user) { + $file = new File; + $ret = $file->isRespectsQuota($user); + if (true === $ret) return true; + $this->clientError($ret); + } + /** * Save a new notice, based on arguments * @@ -131,7 +159,6 @@ class NewnoticeAction extends Action $this->clientError(_('No content!')); } else { $content_shortened = common_shorten_links($content); - if (mb_strlen($content_shortened) > 140) { $this->clientError(_('That\'s too long. '. 'Max notice size is 140 chars.')); @@ -158,17 +185,53 @@ class NewnoticeAction extends Action $replyto = 'false'; } -// $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, + if (isset($_FILES['attach']['error'])) { + switch ($_FILES['attach']['error']) { + case UPLOAD_ERR_NO_FILE: + // no file uploaded, nothing to do + break; + + case UPLOAD_ERR_OK: + $mimetype = $this->getUploadedFileType(); + if (!$this->isRespectsQuota($user)) { + die('clientError() should trigger an exception before reaching here.'); + } + break; + + case UPLOAD_ERR_INI_SIZE: + $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.')); + + case UPLOAD_ERR_FORM_SIZE: + $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.')); + + case UPLOAD_ERR_PARTIAL: + $this->clientError(_('The uploaded file was only partially uploaded.')); + + case UPLOAD_ERR_NO_TMP_DIR: + $this->clientError(_('Missing a temporary folder.')); + + case UPLOAD_ERR_CANT_WRITE: + $this->clientError(_('Failed to write file to disk.')); + + case UPLOAD_ERR_EXTENSION: + $this->clientError(_('File upload stopped by extension.')); + + default: + die('Should never reach here.'); + } + } + $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1, ($replyto == 'false') ? null : $replyto); if (is_string($notice)) { $this->clientError($notice); - return; } + if (isset($mimetype)) { + $this->storeFile($notice, $mimetype); + } $this->saveUrls($notice); - common_broadcast_notice($notice); if ($this->boolean('ajax')) { @@ -194,6 +257,33 @@ class NewnoticeAction extends Action } } + function storeFile($notice, $mimetype) { + $filename = basename($_FILES['attach']['name']); + $destination = "file/{$notice->id}-$filename"; + if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) { + $file = new File; + $file->url = common_local_url('file', array('notice' => $notice->id)); + $file->size = filesize(INSTALLDIR . "/$destination"); + $file->date = time(); + $file->mimetype = $mimetype; + if ($file_id = $file->insert()) { + $file_redir = new File_redirection; + $file_redir->url = common_path($destination); + $file_redir->file_id = $file_id; + $file_redir->insert(); + + $f2p = new File_to_post; + $f2p->file_id = $file_id; + $f2p->post_id = $notice->id; + $f2p->insert(); + } else { + $this->clientError(_('There was a database error while saving your file. Please try again.')); + } + } else { + $this->clientError(_('File could not be moved to destination directory.')); + } + } + /** save all urls in the notice to the db * * follow redirects and save all available file information @@ -203,7 +293,7 @@ class NewnoticeAction extends Action * * @return void */ - function saveUrls($notice) { + function saveUrls($notice, $uploaded = null) { common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id); } @@ -316,3 +406,4 @@ class NewnoticeAction extends Action $nli->show(); } } + diff --git a/actions/shownotice.php b/actions/shownotice.php index 2c469c9de..34c8a8e94 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -208,10 +208,10 @@ class ShownoticeAction extends Action function showContent() { - $this->elementStart('ul', array('class' => 'notices')); + $this->elementStart('ol', array('class' => 'notices xoxo')); $nli = new NoticeListItem($this->notice, $this); $nli->show(); - $this->elementEnd('ul'); + $this->elementEnd('ol'); } /** diff --git a/actions/tag.php b/actions/tag.php index 47420e4c3..e9351d241 100644 --- a/actions/tag.php +++ b/actions/tag.php @@ -49,8 +49,6 @@ class TagAction extends Action { $pop = new PopularNoticeSection($this); $pop->show(); - $freqatt = new FrequentAttachmentSection($this); - $freqatt->show(); } function title() diff --git a/classes/File.php b/classes/File.php index e5913115b..24ab11b8e 100644 --- a/classes/File.php +++ b/classes/File.php @@ -120,4 +120,30 @@ class File extends Memcached_DataObject File_to_post::processNew($file_id, $notice_id); return $x; } + + function isRespectsQuota($user) { + if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) { + return sprintf(_('No file may be larger than %d bytes ' . + 'and the file you sent was %d bytes. Try to upload a smaller version.'), + common_config('attachments', 'file_quota'), $_FILES['attach']['size']); + } + + $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'"; + $this->query($query); + $this->fetch(); + $total = $this->total + $_FILES['attach']['size']; + if ($total > common_config('attachments', 'user_quota')) { + return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota')); + } + + $query .= ' month(modified) = month(now()) and year(modified) = year(now())'; + $this->query($query); + $this->fetch(); + $total = $this->total + $_FILES['attach']['size']; + if ($total > common_config('attachments', 'monthly_quota')) { + return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota')); + } + return true; + } } + diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 0eae68178..212cc3615 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -133,7 +133,7 @@ class File_redirection extends Memcached_DataObject $file->limit(1); $file->orderBy('len'); $file->find(true); - if (!empty($file->id)) { + if (!empty($file->url) && (strlen($file->url) < strlen($long_url))) { return $file->url; } diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index 606560951..c0b356ece 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject public $__table = 'foreign_link'; // table name public $user_id; // int(4) primary_key not_null - public $foreign_id; // int(4) primary_key not_null + public $foreign_id; // bigint(8) primary_key not_null unsigned public $service; // int(4) primary_key not_null public $credentials; // varchar(255) public $noticesync; // tinyint(1) not_null default_1 diff --git a/classes/Group_inbox.php b/classes/Group_inbox.php index b80ba4272..b80ba4272 100755..100644 --- a/classes/Group_inbox.php +++ b/classes/Group_inbox.php diff --git a/classes/Group_member.php b/classes/Group_member.php index 3c23a991f..3c23a991f 100755..100644 --- a/classes/Group_member.php +++ b/classes/Group_member.php diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index 5f71f716b..52ad4100f 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -227,4 +227,22 @@ class Memcached_DataObject extends DB_DataObject $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry); return new ArrayWrapper($cached); } + + // We overload so that 'SET NAMES "utf8"' is called for + // each connection + + function _connect() + { + global $_DB_DATAOBJECT; + $exists = !empty($this->_database_dsn_md5) && + isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]); + $result = parent::_connect(); + if (!$exists) { + $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]; + if (common_config('db', 'utf8')) { + $DB->query('SET NAMES "utf8"'); + } + } + return $result; + } } diff --git a/classes/Notice.php b/classes/Notice.php index 1b5c0ab0a..0b1017e12 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -277,6 +277,18 @@ class Notice extends Memcached_DataObject return true; } + function getUploadedAttachment() { + $post = clone $this; + $query = 'select file.url as uploaded from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"'; + $post->query($query); + $post->fetch(); + $ret = $post->uploaded; +// var_dump($post); + $post->free(); +// die(); + return $ret; + } + function hasAttachments() { $post = clone $this; $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id); diff --git a/classes/Related_group.php b/classes/Related_group.php index c00ad9c44..c00ad9c44 100755..100644 --- a/classes/Related_group.php +++ b/classes/Related_group.php diff --git a/classes/Status_network.php b/classes/Status_network.php index f7747f71d..f7747f71d 100755..100644 --- a/classes/Status_network.php +++ b/classes/Status_network.php diff --git a/classes/User_group.php b/classes/User_group.php index 7cc31e702..7cc31e702 100755..100644 --- a/classes/User_group.php +++ b/classes/User_group.php diff --git a/classes/laconica.ini b/classes/laconica.ini index 316923af0..92bbb35d4 100644 --- a/classes/laconica.ini +++ b/classes/laconica.ini @@ -46,6 +46,64 @@ modified = 384 notice_id = K user_id = K +[file] +id = 129 +url = 2 +mimetype = 2 +size = 1 +title = 2 +date = 1 +protected = 1 + +[file__keys] +id = N + +[file_oembed] +id = 129 +file_id = 1 +version = 2 +type = 2 +provider = 2 +provider_url = 2 +width = 1 +height = 1 +html = 34 +title = 2 +author_name = 2 +author_url = 2 +url = 2 + +[file_oembed__keys] +id = N + +[file_redirection] +id = 129 +url = 2 +file_id = 1 +redirections = 1 +httpcode = 1 + +[file_redirection__keys] +id = N + +[file_thumbnail] +id = 129 +file_id = 1 +url = 2 +width = 1 +height = 1 + +[file_thumbnail__keys] +id = N + +[file_to_post] +id = 129 +file_id = 1 +post_id = 1 + +[file_to_post__keys] +id = N + [foreign_link] user_id = 129 foreign_id = 129 @@ -392,63 +450,3 @@ modified = 384 [user_openid__keys] canonical = K display = U - -[file] -id = 129 -url = 2 -mimetype = 2 -size = 1 -title = 2 -date = 1 -protected = 1 - -[file__keys] -id = N - -[file_oembed] -id = 129 -file_id = 129 -version = 2 -type = 2 -provider = 2 -provider_url = 2 -width = 1 -height = 1 -html = 34 -title = 2 -author_name = 2 -author_url = 2 -url = 2 - -[file_oembed__keys] -id = N - -[file_redirection] -id = 129 -url = 2 -file_id = 129 -redirections = 1 -httpcode = 1 - -[file_redirection__keys] -id = N - -[file_thumbnail] -id = 129 -file_id = 129 -url = 2 -width = 1 -height = 1 - -[file_thumbnail__keys] -id = N - -[file_to_post] -id = 129 -file_id = 129 -post_id = 129 - -[file_to_post__keys] -id = N - - diff --git a/config.php.sample b/config.php.sample index 826b086a3..636f4cf8e 100644 --- a/config.php.sample +++ b/config.php.sample @@ -3,219 +3,223 @@ if (!defined('LACONICA')) { exit(1); } -#If you have downloaded libraries in random little places, you -#can add the paths here +// If you have downloaded libraries in random little places, you +// can add the paths here -#$extra_path = array("/opt/php-openid-2.0.1", "/usr/local/share/php"); -#set_include_path(implode(PATH_SEPARATOR, $extra_path) . PATH_SEPARATOR . get_include_path()); +// $extra_path = array("/opt/php-openid-2.0.1", "/usr/local/share/php"); +// set_include_path(implode(PATH_SEPARATOR, $extra_path) . PATH_SEPARATOR . get_include_path()); -# We get called by common.php, $config is a tree with lots of config -# options -# These are for configuring your URLs +// We get called by common.php, $config is a tree with lots of config +// options +// These are for configuring your URLs $config['site']['name'] = 'Just another Laconica microblog'; $config['site']['server'] = 'localhost'; $config['site']['path'] = 'laconica'; -#$config['site']['fancy'] = false; -#$config['site']['theme'] = 'default'; -#To enable the built-in mobile style sheet, defaults to false. -#$config['site']['mobile'] = true; -#For contact email, defaults to $_SERVER["SERVER_ADMIN"] -#$config['site']['email'] = 'admin@example.net'; -#Brought by... -#$config['site']['broughtby'] = 'Individual or Company'; -#$config['site']['broughtbyurl'] = 'http://example.net/'; -#If you don't want to let users register (say, for a one-person install) -#Crude but effective -- register everybody, then lock down -#$config['site']['closed'] = true; -#Only allow registration for people invited by another user -#$config['site']['inviteonly'] = true; -#Make the site invisible to non-logged-in users -#$config['site']['private'] = true; - -# 'direct' for direct notice links in sections -# 'attachment' for notice attachment links in sections -# left undefined, no link is showed -#$config['site']['notice_link'] = 'attachment'; -#$config['site']['notice_link'] = 'direct'; - -# If you want logging sent to a file instead of syslog -#$config['site']['logfile'] = '/tmp/laconica.log'; - -# Enables extra log information, for example full details of PEAR DB errors -#$config['site']['logdebug'] = true; - -#To set your own logo, overriding the one in the theme -#$config['site']['logo'] = '/mylogo.png'; - -# This is a PEAR DB DSN, see http://pear.php.net/manual/en/package.database.db.intro-dsn.php -# Set it to match your actual database +// $config['site']['fancy'] = false; +// $config['site']['theme'] = 'default'; +// To enable the built-in mobile style sheet, defaults to false. +// $config['site']['mobile'] = true; +// For contact email, defaults to $_SERVER["SERVER_ADMIN"] +// $config['site']['email'] = 'admin@example.net'; +// Brought by... +// $config['site']['broughtby'] = 'Individual or Company'; +// $config['site']['broughtbyurl'] = 'http://example.net/'; +// If you don't want to let users register (say, for a one-person install) +// Crude but effective -- register everybody, then lock down +// $config['site']['closed'] = true; +// Only allow registration for people invited by another user +// $config['site']['inviteonly'] = true; +// Make the site invisible to non-logged-in users +// $config['site']['private'] = true; + +// If you want logging sent to a file instead of syslog +// $config['site']['logfile'] = '/tmp/laconica.log'; + +// Enables extra log information, for example full details of PEAR DB errors +// $config['site']['logdebug'] = true; + +// To set your own logo, overriding the one in the theme +// $config['site']['logo'] = '/mylogo.png'; + +// This is a PEAR DB DSN, see http://pear.php.net/manual/en/package.database.db.intro-dsn.php +// Set it to match your actual database $config['db']['database'] = 'mysql://laconica:microblog@localhost/laconica'; -#$config['db']['ini_your_db_name'] = $config['db']['schema_location'].'/laconica.ini'; -# *** WARNING *** WARNING *** WARNING *** WARNING *** -# Setting debug to a non-zero value will expose your DATABASE PASSWORD to Web users. -# !!!!!! DO NOT SET THIS ON PRODUCTION SERVERS !!!!!! DB_DataObject's bug, btw, not -# ours. -# *** WARNING *** WARNING *** WARNING *** WARNING *** -#$config['db']['debug'] = 0; -#$config['db']['db_driver'] = 'MDB2'; - -#Database type. For mysql, these defaults are fine. For postgresql, set -#'quote_identifiers' to true and 'type' to 'pgsql': -#$config['db']['quote_identifiers'] = false; -#$config['db']['type'] = 'mysql'; - -#session_set_cookie_params(0, '/'. $config['site']['path'] .'/'); - -#Standard fancy-url clashes prevented by not allowing nicknames on a blacklist -#Add your own here. Note: empty array by default -#$config['nickname']['blacklist'][] = 'scobleizer'; - -# sphinx search +// $config['db']['ini_your_db_name'] = $config['db']['schema_location'].'/laconica.ini'; +// *** WARNING *** WARNING *** WARNING *** WARNING *** +// Setting debug to a non-zero value will expose your DATABASE PASSWORD to Web users. +// !!!!!! DO NOT SET THIS ON PRODUCTION SERVERS !!!!!! DB_DataObject's bug, btw, not +// ours. +// *** WARNING *** WARNING *** WARNING *** WARNING *** +// $config['db']['debug'] = 0; +// $config['db']['db_driver'] = 'MDB2'; + +// Database type. For mysql, these defaults are fine. For postgresql, set +// 'quote_identifiers' to true and 'type' to 'pgsql': +// $config['db']['quote_identifiers'] = false; +// $config['db']['type'] = 'mysql'; + +// session_set_cookie_params(0, '/'. $config['site']['path'] .'/'); + +// Standard fancy-url clashes prevented by not allowing nicknames on a blacklist +// Add your own here. Note: empty array by default +// $config['nickname']['blacklist'][] = 'scobleizer'; + +// sphinx search $config['sphinx']['enabled'] = false; $config['sphinx']['server'] = 'localhost'; $config['sphinx']['port'] = 3312; -# Users to populate the 'Featured' tab -#$config['nickname']['featured'][] = 'scobleizer'; - -# xmpp -#$config['xmpp']['enabled'] = false; -#$config['xmpp']['server'] = 'server.example.net'; -#$config['xmpp']['host'] = NULL; # Only set if different from server -#$config['xmpp']['port'] = 5222; -#$config['xmpp']['user'] = 'update'; -#$config['xmpp']['encryption'] = false; -#$config['xmpp']['resource'] = 'uniquename'; -#$config['xmpp']['password'] = 'blahblahblah'; -#$config['xmpp']['public'][] = 'someindexer@example.net'; -#$config['xmpp']['debug'] = false; - -#Default locale info -#$config['site']['timezone'] = 'Pacific/Auckland'; -#$config['site']['language'] = 'en_NZ'; - -#Email info, used for all outbound email -#$config['mail']['notifyfrom'] = 'microblog@example.net'; -#$config['mail']['domain'] = 'microblog.example.net'; -# See http://pear.php.net/manual/en/package.mail.mail.factory.php for options -#$config['mail']['backend'] = 'smtp'; -#$config['mail']['params'] = array( -# 'host' => 'localhost', -# 'port' => 25, -# ); -#For incoming email, if enabled. Defaults to site server name. -#$config['mail']['domain'] = 'incoming.example.net'; - -#exponential decay factor for tags, default 10 days -#raise this if traffic is slow, lower it if it's fast -#$config['tag']['dropoff'] = 86400.0 * 10; - -#exponential decay factor for popular (most favorited notices) -#default 10 days -- similar to tag dropoff -#$config['popular']['dropoff'] = 86400.0 * 10; - -#optionally show non-local messages in public timeline -#$config['public']['localonly'] = false; - -#hide certain users from public pages, by ID -#$config['public']['blacklist'][] = 123; -#$config['public']['blacklist'][] = 2307; - -#Mark certain notice sources as automatic and thus not -#appropriate for public feed -#$config['public]['autosource'][] = 'twitterfeed'; -#$config['public]['autosource'][] = 'rssdent'; -#$config['public]['autosource'][] = 'Ping.Fm'; -#$config['public]['autosource'][] = 'HelloTxt'; -#$config['public]['autosource'][] = 'Updating.Me'; - -#Do notice broadcasts offline -#If you use this, you must run the six offline daemons in the -#background. See the README for details. -#$config['queue']['enabled'] = true; - -#Queue subsystem -#subsystems: internal (default) or stomp -#using stomp requires an external message queue server -#$config['queue']['subsystem'] = 'stomp'; -#$config['queue']['stomp_server'] = 'tcp://localhost:61613'; -#use different queue_basename for each laconica instance managed by the server -#$config['queue']['queue_basename'] = 'laconica'; - -#The following customise the behaviour of the various daemons: -#$config['daemon']['piddir'] = '/var/run'; -#$config['daemon']['user'] = false; -#$config['daemon']['group'] = false; - -#For installations with high traffic, laconica can use MemCached to cache -#frequently requested information. Only enable the following if you have -#MemCached up and running: -#$config['memcached']['enabled'] = false; -#$config['memcached']['server'] = 'localhost'; -#$config['memcached']['port'] = 11211; - -# Enable bidirectional Twitter bridge -#$config['twitterbridge']['enabled'] = true; - -#Twitter integration source attribute. Note: default is Laconica -#$config['integration']['source'] = 'Laconica'; - -# 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. - -#$config['throttle']['enabled'] = true; -#$config['throttle']['count'] = 100; -#$config['throttle']['timespan'] = 3600; - -# List of users banned from posting (nicknames and/or IDs) -#$config['profile']['banned'][] = 'hacker'; -#$config['profile']['banned'][] = 12345; - -# Config section for the built-in Facebook application -#$config['facebook']['apikey'] = 'APIKEY'; -#$config['facebook']['secret'] = 'SECRET'; - -# Facebook Connect plugin (Needs valid APIKEY above) -#require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php'); -#$fbc = new FBConnectPlugin(); - -# Add Google Analytics -# require_once('plugins/GoogleAnalyticsPlugin.php'); -# $ga = new GoogleAnalyticsPlugin('your secret code'); - -# Use Templating (template: /tpl/index.php) -# require_once('plugins/TemplatePlugin.php'); -# $tpl = new TemplatePlugin(); - -#Don't allow saying the same thing more than once per hour -#$config['site']['dupelimit'] = 3600; -#Don't enforce the dupe limit -#$config['site']['dupelimit'] = -1; - -#Base string for minting Tag URIs in Atom feeds. Defaults to -#"yourserver,2009". This needs to be configured properly for your Atom -#feeds to validate. See: http://www.faqs.org/rfcs/rfc4151.html and -#http://taguri.org/ Examples: -#$config['integration']['taguri'] = 'example.net,2008'; -#$config['integration']['taguri'] = 'admin@example.net,2009-03-09' - -#Don't use SSL -#$config['site']['ssl'] = 'never'; -#Use SSL only for sensitive pages (like login, password change) -#$config['site']['ssl'] = 'sometimes'; -#Use SSL for all pages -#$config['site']['ssl'] = 'always'; - -#Use a different hostname for SSL-encrypted pages -#$config['site']['sslserver'] = 'secure.example.org'; - -#If you have a lot of status networks on the same server, you can -#store the site data in a database and switch as follows -#Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet'); -#if (!Status_network::setupSite($_server, $_path)) { -# print "Error\n"; -# exit(1); -#} +// Users to populate the 'Featured' tab +// $config['nickname']['featured'][] = 'scobleizer'; + +// xmpp +// $config['xmpp']['enabled'] = false; +// $config['xmpp']['server'] = 'server.example.net'; +// $config['xmpp']['host'] = NULL; // Only set if different from server +// $config['xmpp']['port'] = 5222; +// $config['xmpp']['user'] = 'update'; +// $config['xmpp']['encryption'] = false; +// $config['xmpp']['resource'] = 'uniquename'; +// $config['xmpp']['password'] = 'blahblahblah'; +// $config['xmpp']['public'][] = 'someindexer@example.net'; +// $config['xmpp']['debug'] = false; + +// Default locale info +// $config['site']['timezone'] = 'Pacific/Auckland'; +// $config['site']['language'] = 'en_NZ'; + +// Email info, used for all outbound email +// $config['mail']['notifyfrom'] = 'microblog@example.net'; +// $config['mail']['domain'] = 'microblog.example.net'; +// See http://pear.php.net/manual/en/package.mail.mail.factory.php for options +// $config['mail']['backend'] = 'smtp'; +// $config['mail']['params'] = array( +// 'host' => 'localhost', +// 'port' => 25, +// ); +// For incoming email, if enabled. Defaults to site server name. +// $config['mail']['domain'] = 'incoming.example.net'; + +// exponential decay factor for tags, default 10 days +// raise this if traffic is slow, lower it if it's fast +// $config['tag']['dropoff'] = 86400.0 * 10; + +// exponential decay factor for popular (most favorited notices) +// default 10 days -- similar to tag dropoff +// $config['popular']['dropoff'] = 86400.0 * 10; + +// optionally show non-local messages in public timeline +// $config['public']['localonly'] = false; + +// hide certain users from public pages, by ID +// $config['public']['blacklist'][] = 123; +// $config['public']['blacklist'][] = 2307; + +// Mark certain notice sources as automatic and thus not +// appropriate for public feed +// $config['public]['autosource'][] = 'twitterfeed'; +// $config['public]['autosource'][] = 'rssdent'; +// $config['public]['autosource'][] = 'Ping.Fm'; +// $config['public]['autosource'][] = 'HelloTxt'; +// $config['public]['autosource'][] = 'Updating.Me'; + +// Do notice broadcasts offline +// If you use this, you must run the six offline daemons in the +// background. See the README for details. +// $config['queue']['enabled'] = true; + +// Queue subsystem +// subsystems: internal (default) or stomp +// using stomp requires an external message queue server +// $config['queue']['subsystem'] = 'stomp'; +// $config['queue']['stomp_server'] = 'tcp://localhost:61613'; +// use different queue_basename for each laconica instance managed by the server +// $config['queue']['queue_basename'] = 'laconica'; + +// The following customise the behaviour of the various daemons: +// $config['daemon']['piddir'] = '/var/run'; +// $config['daemon']['user'] = false; +// $config['daemon']['group'] = false; + +// For installations with high traffic, laconica can use MemCached to cache +// frequently requested information. Only enable the following if you have +// MemCached up and running: +// $config['memcached']['enabled'] = false; +// $config['memcached']['server'] = 'localhost'; +// $config['memcached']['port'] = 11211; + +// Twitter integration source attribute. Note: default is Laconica +// $config['integration']['source'] = 'Laconica'; + +// 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. + +// $config['throttle']['enabled'] = true; +// $config['throttle']['count'] = 100; +// $config['throttle']['timespan'] = 3600; + +// List of users banned from posting (nicknames and/or IDs) +// $config['profile']['banned'][] = 'hacker'; +// $config['profile']['banned'][] = 12345; + +// Config section for the built-in Facebook application +// $config['facebook']['apikey'] = 'APIKEY'; +// $config['facebook']['secret'] = 'SECRET'; + +// Add Google Analytics +// require_once('plugins/GoogleAnalyticsPlugin.php'); +// $ga = new GoogleAnalyticsPlugin('your secret code'); + +// Use Templating (template: /tpl/index.php) +// require_once('plugins/TemplatePlugin.php'); +// $tpl = new TemplatePlugin(); + +// Don't allow saying the same thing more than once per hour +// $config['site']['dupelimit'] = 3600; +// Don't enforce the dupe limit +// $config['site']['dupelimit'] = -1; + +// Base string for minting Tag URIs in Atom feeds. Defaults to +// "yourserver,2009". This needs to be configured properly for your Atom +// feeds to validate. See: http://www.faqs.org/rfcs/rfc4151.html and +// http://taguri.org/ Examples: +// $config['integration']['taguri'] = 'example.net,2008'; +// $config['integration']['taguri'] = 'admin@example.net,2009-03-09' + +// Don't use SSL +// $config['site']['ssl'] = 'never'; +// Use SSL only for sensitive pages (like login, password change) +// $config['site']['ssl'] = 'sometimes'; +// Use SSL for all pages +// $config['site']['ssl'] = 'always'; + +// Use a different hostname for SSL-encrypted pages +// $config['site']['sslserver'] = 'secure.example.org'; + +// If you have a lot of status networks on the same server, you can +// store the site data in a database and switch as follows +// Status_network::setupDB('localhost', 'statusnet', 'statuspass', 'statusnet'); +// if (!Status_network::setupSite($_server, $_path)) { +// print "Error\n"; +// exit(1); +// } + +// How often to send snapshots; in # of web hits. Ideally, +// try to do this once per month (that is, make this equal to number +// of hits per month) +// $config['snapshot']['frequency'] = 10000; +// If you don't want to report statistics to the central server, uncomment. +// $config['snapshot']['run'] = 'never'; +// If you want to report statistics in a cron job instead. +// $config['snapshot']['run'] = 'cron'; + +// Support for file uploads (attachments), +// select supported mimetypes and quotas (in bytes) +// $config['attachments']['supported'] = array('image/png', 'application/ogg'); +// $config['attachments']['file_quota'] = 5000000; +// $config['attachments']['user_quota'] = 50000000; +// $config['attachments']['monthly_quota'] = 15000000; + diff --git a/db/foreign_services.sql b/db/foreign_services.sql index 557ede024..79c04cee5 100644 --- a/db/foreign_services.sql +++ b/db/foreign_services.sql @@ -2,4 +2,5 @@ insert into foreign_service (id, name, description, created) values ('1','Twitter', 'Twitter Micro-blogging service', now()), - ('2','Facebook', 'Facebook', now()); + ('2','Facebook', 'Facebook', now()), + ('3','FacebookConnect', 'Facebook Connect', now()); diff --git a/db/laconica.sql b/db/laconica.sql index 344f0ff72..a11e31692 100644 --- a/db/laconica.sql +++ b/db/laconica.sql @@ -285,7 +285,7 @@ create table foreign_user ( create table foreign_link ( user_id int comment 'link to user on this system, if exists' references user (id), - foreign_id int comment 'link ' references foreign_user(id), + foreign_id bigint unsigned comment 'link to user on foreign service, if exists' references foreign_user(id), service int not null comment 'foreign key to service' references foreign_service(id), credentials varchar(255) comment 'authc credentials, typically a password', noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies', @@ -426,12 +426,12 @@ create table group_inbox ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table file ( - id integer primary key auto_increment, - url varchar(255), mimetype varchar(50), - size integer, - title varchar(255), - date integer(11), - protected integer(1), + id integer primary key auto_increment, + url varchar(255), mimetype varchar(50), + size integer, + title varchar(255), + date integer(11), + protected integer(1), unique(url) ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; @@ -447,40 +447,38 @@ create table file_oembed ( height integer, html text, title varchar(255), - author_name varchar(50), - author_url varchar(255), - url varchar(255), + author_name varchar(50), + author_url varchar(255), + url varchar(255), unique(file_id) ) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; create table file_redirection ( - id integer primary key auto_increment, - url varchar(255), - file_id integer, - redirections integer, - httpcode integer, + id integer primary key auto_increment, + url varchar(255), + file_id integer, + redirections integer, + httpcode integer, unique(url) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table file_thumbnail ( - id integer primary key auto_increment, - file_id integer, - url varchar(255), - width integer, - height integer, + id integer primary key auto_increment, + file_id integer, + url varchar(255), + width integer, + height integer, - unique(file_id), + unique(file_id), unique(url) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table file_to_post ( - id integer primary key auto_increment, - file_id integer, - post_id integer, + id integer primary key auto_increment, + file_id integer, + post_id integer, unique(file_id, post_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; - - diff --git a/db/notice_source.sql b/db/notice_source.sql index f026679d5..d5a280b82 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -2,13 +2,16 @@ INSERT INTO notice_source (code, name, url, created) VALUES ('adium', 'Adium', 'http://www.adiumx.com/', now()), + ('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()), ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()), ('bti','bti','http://gregkh.github.com/bti/', now()), ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()), ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), + ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()), ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()), + ('gravity', 'Gravity', 'http://mobileways.de/gravity', now()), ('Gwibber','Gwibber','http://launchpad.net/gwibber', now()), ('HelloTxt','HelloTxt','http://hellotxt.com/', now()), ('identicatools','Laconica Tools','http://bitbucketlabs.net/laconica-tools/', now()), @@ -27,6 +30,7 @@ VALUES ('pingvine','PingVine','http://pingvine.com/', now()), ('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()), ('posty','Posty','http://spreadingfunkyness.com/posty/', now()), + ('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()), ('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()), ('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()), ('rygh.no','rygh.no','http://rygh.no/', now()), diff --git a/extlib/MIME/Type.php b/extlib/MIME/Type.php new file mode 100644 index 000000000..c335f8d92 --- /dev/null +++ b/extlib/MIME/Type.php @@ -0,0 +1,523 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +// +----------------------------------------------------------------------+ +// | PHP version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2002, 2008 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/3_0.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: Ian Eure <ieure@php.net> | +// +----------------------------------------------------------------------+ +// +// $Id: Type.php,v 1.6 2009/01/16 11:49:45 cweiske Exp $ + +require_once 'PEAR.php'; + +$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd'); +$_fileCmd = 'file'; + +/** + * Class for working with MIME types + * + * @category MIME + * @package MIME_Type + * @license PHP License 3.0 + * @version 1.2.0 + * @link http://pear.php.net/package/MIME_Type + * @author Ian Eure <ieure@php.net> + */ +class MIME_Type +{ + /** + * The MIME media type + * + * @var string + */ + var $media = ''; + + /** + * The MIME media sub-type + * + * @var string + */ + var $subType = ''; + + /** + * Optional MIME parameters + * + * @var array + */ + var $parameters = array(); + + /** + * List of valid media types. + * A media type is the string in front of the slash. + * The media type of "text/xml" would be "text". + * + * @var array + */ + var $validMediaTypes = array( + 'text', + 'image', + 'audio', + 'video', + 'application', + 'multipart', + 'message' + ); + + + /** + * Constructor. + * + * If $type is set, if will be parsed and the appropriate class vars set. + * If not, you get an empty class. + * This is useful, but not quite as useful as parsing a type. + * + * @param string $type MIME type + * + * @return void + */ + function MIME_Type($type = false) + { + if ($type) { + $this->parse($type); + } + } + + + /** + * Parse a mime-type and set the class variables. + * + * @param string $type MIME type to parse + * + * @return void + */ + function parse($type) + { + $this->media = $this->getMedia($type); + $this->subType = $this->getSubType($type); + $this->parameters = array(); + if (MIME_Type::hasParameters($type)) { + require_once 'MIME/Type/Parameter.php'; + foreach (MIME_Type::getParameters($type) as $param) { + $param = new MIME_Type_Parameter($param); + $this->parameters[$param->name] = $param; + } + } + } + + + /** + * Does this type have any parameters? + * + * @param string $type MIME type to check + * + * @return boolean true if $type has parameters, false otherwise + * @static + */ + function hasParameters($type) + { + if (strstr($type, ';')) { + return true; + } + return false; + } + + + /** + * Get a MIME type's parameters + * + * @param string $type MIME type to get parameters of + * + * @return array $type's parameters + * @static + */ + function getParameters($type) + { + $params = array(); + $tmp = explode(';', $type); + for ($i = 1; $i < count($tmp); $i++) { + $params[] = trim($tmp[$i]); + } + return $params; + } + + + /** + * Strip parameters from a MIME type string. + * + * @param string $type MIME type string + * + * @return string MIME type with parameters removed + * @static + */ + function stripParameters($type) + { + if (strstr($type, ';')) { + return substr($type, 0, strpos($type, ';')); + } + return $type; + } + + + /** + * Removes comments from a media type, subtype or parameter. + * + * @param string $string String to strip comments from + * @param string &$comment Comment is stored in there. + * + * @return string String without comments + * @static + */ + function stripComments($string, &$comment) + { + if (strpos($string, '(') === false) { + return $string; + } + + $inquote = false; + $quoting = false; + $incomment = 0; + $newstring = ''; + + for ($n = 0; $n < strlen($string); $n++) { + if ($quoting) { + if ($incomment == 0) { + $newstring .= $string[$n]; + } else if ($comment !== null) { + $comment .= $string[$n]; + } + $quoting = false; + } else if ($string[$n] == '\\') { + $quoting = true; + } else if (!$inquote && $incomment > 0 && $string[$n] == ')') { + $incomment--; + if ($incomment == 0 && $comment !== null) { + $comment .= ' '; + } + } else if (!$inquote && $string[$n] == '(') { + $incomment++; + } else if ($string[$n] == '"') { + if ($inquote) { + $inquote = false; + } else { + $inquote = true; + } + } else if ($incomment == 0) { + $newstring .= $string[$n]; + } else if ($comment !== null) { + $comment .= $string[$n]; + } + } + + if ($comment !== null) { + $comment = trim($comment); + } + + return $newstring; + } + + + /** + * Get a MIME type's media + * + * @note 'media' refers to the portion before the first slash + * + * @param string $type MIME type to get media of + * + * @return string $type's media + * @static + */ + function getMedia($type) + { + $tmp = explode('/', $type); + return strtolower(trim(MIME_Type::stripComments($tmp[0], $null))); + } + + + /** + * Get a MIME type's subtype + * + * @param string $type MIME type to get subtype of + * + * @return string $type's subtype, null if invalid mime type + * @static + */ + function getSubType($type) + { + $tmp = explode('/', $type); + if (!isset($tmp[1])) { + return null; + } + $tmp = explode(';', $tmp[1]); + return strtolower(trim(MIME_Type::stripComments($tmp[0], $null))); + } + + + /** + * Create a textual MIME type from object values + * + * This function performs the opposite function of parse(). + * + * @return string MIME type string + */ + function get() + { + $type = strtolower($this->media . '/' . $this->subType); + if (count($this->parameters)) { + foreach ($this->parameters as $key => $null) { + $type .= '; ' . $this->parameters[$key]->get(); + } + } + return $type; + } + + + /** + * Is this type experimental? + * + * @note Experimental types are denoted by a leading 'x-' in the media or + * subtype, e.g. text/x-vcard or x-world/x-vrml. + * + * @param string $type MIME type to check + * + * @return boolean true if $type is experimental, false otherwise + * @static + */ + function isExperimental($type) + { + if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' || + substr(MIME_Type::getSubType($type), 0, 2) == 'x-') { + return true; + } + return false; + } + + + /** + * Is this a vendor MIME type? + * + * @note Vendor types are denoted with a leading 'vnd. in the subtype. + * + * @param string $type MIME type to check + * + * @return boolean true if $type is a vendor type, false otherwise + * @static + */ + function isVendor($type) + { + if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') { + return true; + } + return false; + } + + + /** + * Is this a wildcard type? + * + * @param string $type MIME type to check + * + * @return boolean true if $type is a wildcard, false otherwise + * @static + */ + function isWildcard($type) + { + if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') { + return true; + } + return false; + } + + + /** + * Perform a wildcard match on a MIME type + * + * Example: + * MIME_Type::wildcardMatch('image/*', 'image/png') + * + * @param string $card Wildcard to check against + * @param string $type MIME type to check + * + * @return boolean true if there was a match, false otherwise + * @static + */ + function wildcardMatch($card, $type) + { + if (!MIME_Type::isWildcard($card)) { + return false; + } + + if ($card == '*/*') { + return true; + } + + if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) { + return true; + } + + return false; + } + + + /** + * Add a parameter to this type + * + * @param string $name Attribute name + * @param string $value Attribute value + * @param string $comment Comment for this parameter + * + * @return void + */ + function addParameter($name, $value, $comment = false) + { + $tmp = new MIME_Type_Parameter(); + + $tmp->name = $name; + $tmp->value = $value; + $tmp->comment = $comment; + $this->parameters[$name] = $tmp; + } + + + /** + * Remove a parameter from this type + * + * @param string $name Parameter name + * + * @return void + */ + function removeParameter($name) + { + unset($this->parameters[$name]); + } + + + /** + * Autodetect a file's MIME-type + * + * This function may be called staticly. + * + * @internal Tries to use fileinfo extension at first. If that + * does not work, mime_magic is used. If this is also not available + * or does not succeed, "file" command is tried to be executed with + * System_Command. When that fails, too, then we use our in-built + * extension-to-mimetype-mapping list. + * + * @param string $file Path to the file to get the type of + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + * + * @since 1.0.0beta1 + * @static + */ + function autoDetect($file, $params = false) + { + // Sanity checks + if (!file_exists($file)) { + return PEAR::raiseError("File \"$file\" doesn't exist"); + } + + if (!is_readable($file)) { + return PEAR::raiseError("File \"$file\" is not readable"); + } + + if (function_exists('finfo_file')) { + $finfo = finfo_open(FILEINFO_MIME); + $type = finfo_file($finfo, $file); + finfo_close($finfo); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + + if (function_exists('mime_content_type')) { + $type = mime_content_type($file); + if ($type !== false && $type !== '') { + return MIME_Type::_handleDetection($type, $params); + } + } + + @include_once 'System/Command.php'; + if (class_exists('System_Command')) { + return MIME_Type::_handleDetection( + MIME_Type::_fileAutoDetect($file), + $params + ); + } + + require_once 'MIME/Type/Extension.php'; + $mte = new MIME_Type_Extension(); + return $mte->getMIMEType($file); + } + + + /** + * Handles a detected MIME type and modifies it if necessary. + * + * @param string $type MIME Type of a file + * @param bool $params Append MIME parameters if true + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + */ + function _handleDetection($type, $params) + { + // _fileAutoDetect() may have returned an error. + if (PEAR::isError($type)) { + return $type; + } + + // Don't return an empty string + if (!$type || !strlen($type)) { + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + // Strip parameters if present & requested + if (MIME_Type::hasParameters($type) && !$params) { + $type = MIME_Type::stripParameters($type); + } + + return $type; + } + + + /** + * Autodetect a file's MIME-type with 'file' and System_Command + * + * This function may be called staticly. + * + * @param string $file Path to the file to get the type of + * + * @return string $file's MIME-type + * + * @since 1.0.0beta1 + * @static + */ + function _fileAutoDetect($file) + { + $cmd = new System_Command(); + + // Make sure we have the 'file' command. + $fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd'); + if (!$cmd->which($fileCmd)) { + unset($cmd); + return PEAR::raiseError("Can't find file command \"{$fileCmd}\""); + } + + $cmd->pushCommand($fileCmd, "-bi " . escapeshellarg($file)); + $res = $cmd->execute(); + unset($cmd); + + return $res; + } +} + diff --git a/extlib/MIME/Type/Extension.php b/extlib/MIME/Type/Extension.php new file mode 100644 index 000000000..1987e2a10 --- /dev/null +++ b/extlib/MIME/Type/Extension.php @@ -0,0 +1,298 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +// +----------------------------------------------------------------------+ +// | PHP versions 4 and 5 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2009 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/3_0.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: Christian Schmidt <schmidt@php.net> | +// +----------------------------------------------------------------------+ +// +// $Id: Extension.php,v 1.1 2009/01/16 11:49:45 cweiske Exp $ + +require_once 'PEAR.php'; + +/** + * Class for mapping file extensions to MIME types. + * + * @category MIME + * @package MIME_Type + * @author Christian Schmidt <schmidt@php.net> + * @license PHP License 3.0 + * @version 1.2.0 + * @link http://pear.php.net/package/MIME_Type + */ +class MIME_Type_Extension +{ + /** + * Mapping between file extension and MIME type. + * + * @internal The array is sorted alphabetically by value and with primary + * extension first. Be careful about not adding duplicate keys - PHP + * silently ignores duplicates. The following command can be used for + * checking for duplicates: + * grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d + * application/octet-stream is generally used as fallback when no other + * MIME-type can be found, but the array does not contain a lot of such + * unknown extension. One entry exists, though, to allow detection of + * file extension for this MIME-type. + * + * @var array + */ + var $extensionToType = array ( + 'ez' => 'application/andrew-inset', + 'atom' => 'application/atom+xml', + 'jar' => 'application/java-archive', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'mathml' => 'application/mathml+xml', + 'doc' => 'application/msword', + 'dat' => 'application/octet-stream', + 'oda' => 'application/oda', + 'ogg' => 'application/ogg', + 'pdf' => 'application/pdf', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'rdf' => 'application/rdf+xml', + 'rss' => 'application/rss+xml', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'mif' => 'application/vnd.mif', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xls' => 'application/vnd.ms-excel', + 'xlb' => 'application/vnd.ms-excel', + 'xlt' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pps' => 'application/vnd.ms-powerpoint', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'vsd' => 'application/vnd.visio', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'vxml' => 'application/voicexml+xml', + 'bcpio' => 'application/x-bcpio', + 'vcd' => 'application/x-cdlink', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'application/x-csh', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'dxr' => 'application/x-director', + 'dvi' => 'application/x-dvi', + 'spl' => 'application/x-futuresplash', + 'tgz' => 'application/x-gtar', + 'gtar' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'js' => 'application/x-javascript', + 'skp' => 'application/x-koan', + 'skd' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'latex' => 'application/x-latex', + 'nc' => 'application/x-netcdf', + 'cdf' => 'application/x-netcdf', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'texinfo' => 'application/x-texinfo', + 'texi' => 'application/x-texinfo', + 't' => 'application/x-troff', + 'tr' => 'application/x-troff', + 'roff' => 'application/x-troff', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'ms' => 'application/x-troff-ms', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'xhtml' => 'application/xhtml+xml', + 'xht' => 'application/xhtml+xml', + 'xslt' => 'application/xslt+xml', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'dtd' => 'application/xml-dtd', + 'zip' => 'application/zip', + 'au' => 'audio/basic', + 'snd' => 'audio/basic', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'kar' => 'audio/midi', + 'mpga' => 'audio/mpeg', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'm3u' => 'audio/x-mpegurl', + 'wma' => 'audio/x-ms-wma', + 'wax' => 'audio/x-ms-wax', + 'ram' => 'audio/x-pn-realaudio', + 'ra' => 'audio/x-pn-realaudio', + 'rm' => 'application/vnd.rn-realmedia', + 'wav' => 'audio/x-wav', + 'pdb' => 'chemical/x-pdb', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/bmp', + 'cgm' => 'image/cgm', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'djvu' => 'image/vnd.djvu', + 'djv' => 'image/vnd.djvu', + 'wbmp' => 'image/vnd.wap.wbmp', + 'ras' => 'image/x-cmu-raster', + 'ico' => 'image/x-icon', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'psd' => 'image/x-photoshop', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'eml' => 'message/rfc822', + 'igs' => 'model/iges', + 'iges' => 'model/iges', + 'msh' => 'model/mesh', + 'mesh' => 'model/mesh', + 'silo' => 'model/mesh', + 'wrl' => 'model/vrml', + 'vrml' => 'model/vrml', + 'ics' => 'text/calendar', + 'ifb' => 'text/calendar', + 'css' => 'text/css', + 'csv' => 'text/csv', + 'html' => 'text/html', + 'htm' => 'text/html', + 'txt' => 'text/plain', + 'asc' => 'text/plain', + 'rtx' => 'text/richtext', + 'rtf' => 'text/rtf', + 'sgml' => 'text/sgml', + 'sgm' => 'text/sgml', + 'tsv' => 'text/tab-separated-values', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 'etx' => 'text/x-setext', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpe' => 'video/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'mxu' => 'video/vnd.mpegurl', + 'm4u' => 'video/vnd.mpegurl', + 'flv' => 'video/x-flv', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'wmv' => 'video/x-ms-wmv', + 'wm' => 'video/x-ms-wm', + 'wmx' => 'video/x-ms-wmx', + 'avi' => 'video/x-msvideo', + 'ogv' => 'video/ogg', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', + ); + + + + /** + * Autodetect a file's MIME-type. + * + * @param string $file Path to the file to get the type of + * + * @return string $file's MIME-type on success, PEAR_Error otherwise + */ + function getMIMEType($file) + { + $extension = substr(strrchr($file, '.'), 1); + if ($extension === false) { + return PEAR::raiseError("File has no extension."); + } + + if (!isset($this->extensionToType[$extension])) { + return PEAR::raiseError("Sorry, couldn't determine file type."); + } + + return $this->extensionToType[$extension]; + } + + + + /** + * Return default MIME-type for the specified extension. + * + * @param string $type MIME-type + * + * @return string A file extension without leading period. + */ + function getExtension($type) + { + require_once 'MIME/Type.php'; + // Strip parameters and comments. + $type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type); + + $extension = array_search($type, $this->extensionToType); + if ($extension === false) { + return PEAR::raiseError("Sorry, couldn't determine extension."); + } + return $extension; + } + +} + +?>
\ No newline at end of file diff --git a/extlib/MIME/Type/Parameter.php b/extlib/MIME/Type/Parameter.php new file mode 100644 index 000000000..399d3dd7a --- /dev/null +++ b/extlib/MIME/Type/Parameter.php @@ -0,0 +1,163 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4: */ +// +----------------------------------------------------------------------+ +// | PHP version 4 | +// +----------------------------------------------------------------------+ +// | Copyright (c) 1997-2002 The PHP Group | +// +----------------------------------------------------------------------+ +// | This source file is subject to version 3.0 of the PHP license, | +// | that is bundled with this package in the file LICENSE, and is | +// | available at through the world-wide-web at | +// | http://www.php.net/license/3_0.txt. | +// | If you did not receive a copy of the PHP license and are unable to | +// | obtain it through the world-wide-web, please send a note to | +// | license@php.net so we can mail you a copy immediately. | +// +----------------------------------------------------------------------+ +// | Authors: Ian Eure <ieure@php.net> | +// +----------------------------------------------------------------------+ +// +// $Id: Parameter.php,v 1.1 2007/03/25 10:10:21 cweiske Exp $ + +/** + * Class for working with MIME type parameters + * + * @version 1.2.0 + * @package MIME_Type + * @author Ian Eure <ieure@php.net> + */ +class MIME_Type_Parameter { + /** + * Parameter name + * + * @var string + */ + var $name; + + /** + * Parameter value + * + * @var string + */ + var $value; + + /** + * Parameter comment + * + * @var string + */ + var $comment; + + + /** + * Constructor. + * + * @param string $param MIME parameter to parse, if set. + * @return void + */ + function MIME_Type_Parameter($param = false) + { + if ($param) { + $this->parse($param); + } + } + + + /** + * Parse a MIME type parameter and set object fields + * + * @param string $param MIME type parameter to parse + * @return void + */ + function parse($param) + { + $comment = ''; + $param = MIME_Type::stripComments($param, $comment); + $this->name = $this->getAttribute($param); + $this->value = $this->getValue($param); + $this->comment = $comment; + } + + + /** + * Get a parameter attribute (e.g. name) + * + * @param string MIME type parameter + * @return string Attribute name + * @static + */ + function getAttribute($param) + { + $tmp = explode('=', $param); + return trim($tmp[0]); + } + + + /** + * Get a parameter value + * + * @param string $param MIME type parameter + * @return string Value + * @static + */ + function getValue($param) + { + $tmp = explode('=', $param, 2); + $value = $tmp[1]; + $value = trim($value); + if ($value[0] == '"' && $value[strlen($value)-1] == '"') { + $value = substr($value, 1, -1); + } + $value = str_replace('\\"', '"', $value); + return $value; + } + + + /** + * Get a parameter comment + * + * @param string $param MIME type parameter + * @return string Parameter comment + * @see getComment() + * @static + */ + function getComment($param) + { + $cs = strpos($param, '('); + $comment = substr($param, $cs); + return trim($comment, '() '); + } + + + /** + * Does this parameter have a comment? + * + * @param string $param MIME type parameter + * @return boolean true if $param has a comment, false otherwise + * @static + */ + function hasComment($param) + { + if (strstr($param, '(')) { + return true; + } + return false; + } + + + /** + * Get a string representation of this parameter + * + * This function performs the oppsite of parse() + * + * @return string String representation of parameter + */ + function get() + { + $val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"'; + if ($this->comment) { + $val .= ' (' . $this->comment . ')'; + } + return $val; + } +} +?>
\ No newline at end of file @@ -64,11 +64,13 @@ function handleError($error) function main() { // quick check for fancy URL auto-detection support in installer. - if (isset($_SERVER['REDIRECT_URL']) && ('/check-fancy' === $_SERVER['REDIRECT_URL'])) { + if (isset($_SERVER['REDIRECT_URL']) && ((dirname($_SERVER['REQUEST_URI']) . '/check-fancy') === $_SERVER['REDIRECT_URL'])) { die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs."); } global $user, $action, $config; + Snapshot::check(); + if (!_have_config()) { $msg = sprintf(_("No configuration file found. Try running ". "the installation program first.")); diff --git a/install.php b/install.php index 32915200b..133f2b30f 100644 --- a/install.php +++ b/install.php @@ -35,15 +35,17 @@ function main() function checkPrereqs() { + $pass = true; + if (file_exists(INSTALLDIR.'/config.php')) { ?><p class="error">Config file "config.php" already exists.</p> <?php - return false; + $pass = false; } if (version_compare(PHP_VERSION, '5.0.0', '<')) { ?><p class="error">Require PHP version 5 or greater.</p><?php - return false; + $pass = false; } $reqs = array('gd', 'mysql', 'curl', @@ -53,7 +55,7 @@ function checkPrereqs() foreach ($reqs as $req) { if (!checkExtension($req)) { ?><p class="error">Cannot load required extension: <code><?php echo $req; ?></code></p><?php - return false; + $pass = false; } } @@ -61,17 +63,17 @@ function checkPrereqs() ?><p class="error">Cannot write config file to: <code><?php echo INSTALLDIR; ?></code></p> <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?></code> <?php - return false; + $pass = false; } if (!is_writable(INSTALLDIR.'/avatar/')) { ?><p class="error">Cannot write avatar directory: <code><?php echo INSTALLDIR; ?>/avatar/</code></p> <p>On your server, try this command: <code>chmod a+w <?php echo INSTALLDIR; ?>/avatar/</code></p> <? - return false; + $pass = false; } - return true; + return $pass; } function checkExtension($name) @@ -115,16 +117,16 @@ function showForm() <p class="form_guide" id='fancy-form_guide'>Enable fancy (pretty) URLs. Auto-detection failed, it depends on Javascript.</p> </li> <li> - <label for="host">Hostname</label> - <input type="text" id="host" name="host" /> - <p class="form_guide">Database hostname</p> - </li> - <li> <label for="host">Site path</label> <input type="text" id="path" name="path" value="$config_path" /> <p class="form_guide">Site path, following the "/" after the domain name in the URL. Empty is fine. Field should be filled automatically.</p> </li> <li> + <label for="host">Hostname</label> + <input type="text" id="host" name="host" /> + <p class="form_guide">Database hostname</p> + </li> + <li> <label for="host">Database</label> <input type="text" id="database" name="database" /> <p class="form_guide">Database name</p> @@ -173,36 +175,38 @@ function handlePost() <dd> <ul> <?php + $fail = false; + if (empty($host)) { updateStatus("No hostname specified.", true); - showForm(); - return; + $fail = true; } if (empty($database)) { updateStatus("No database specified.", true); - showForm(); - return; + $fail = true; } if (empty($username)) { updateStatus("No username specified.", true); - showForm(); - return; + $fail = true; } if (empty($password)) { updateStatus("No password specified.", true); - showForm(); - return; + $fail = true; } if (empty($sitename)) { updateStatus("No sitename specified.", true); - showForm(); - return; + $fail = true; } + if($fail){ + showForm(); + return; + } + updateStatus("Starting installation..."); updateStatus("Checking database..."); $conn = mysql_connect($host, $username, $password); @@ -247,7 +251,7 @@ function handlePost() } updateStatus("Done!"); if ($path) $path .= '/'; - updateStatus("You can visit your <a href='/$path'>new Laconica site</a)."); + updateStatus("You can visit your <a href='/$path'>new Laconica site</a>."); ?> <?php @@ -257,16 +261,17 @@ function writeConf($sitename, $sqlUrl, $fancy, $path) { $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['site']['path'] = \"$path\";\n\n". - "\$config['db']['database'] = \"$sqlUrl\";\n\n"); + "\$config['db']['database'] = \"$sqlUrl\";\n\n". + "?>"); return $res; } function runDbScript($filename, $conn) { -return true; $sql = trim(file_get_contents($filename)); $stmts = explode(';', $sql); foreach ($stmts as $stmt) { @@ -290,13 +295,13 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en_US" lang="en_US"> <head> <title>Install Laconica</title> - <link rel="stylesheet" type="text/css" href="theme/base/css/display.css?version=0.8" media="screen, projection, tv"/> + <link rel="shortcut icon" href="favicon.ico"/> <link rel="stylesheet" type="text/css" href="theme/default/css/display.css?version=0.8" media="screen, projection, tv"/> <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/base/css/ie.css?version=0.8" /><![endif]--> <!--[if lte IE 6]><link rel="stylesheet" type="text/css" theme/base/css/ie6.css?version=0.8" /><![endif]--> - <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/earthy/css/ie.css?version=0.8" /><![endif]--> - <script src='js/jquery.min.js'></script> - <script src='js/install.js'></script> + <!--[if IE]><link rel="stylesheet" type="text/css" href="theme/default/css/ie.css?version=0.8" /><![endif]--> + <script src="js/jquery.min.js"></script> + <script src="js/install.js"></script> </head> <body id="install"> <div id="wrap"> diff --git a/js/farbtastic/farbtastic.go.js b/js/farbtastic/farbtastic.go.js index e298c1dab..0149eca7d 100644 --- a/js/farbtastic/farbtastic.go.js +++ b/js/farbtastic/farbtastic.go.js @@ -1,29 +1,30 @@ +/** Init for Farbtastic library and page setup + * + * @package Laconica + * @author Sarven Capadisli <csarven@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ $(document).ready(function() { - function UpdateColors(e) { - var S = f.linked; - var C = f.color; - - if (S && S.value && S.value != C) { - UpdateSwatch(S); - - switch (parseInt(f.linked.id.slice(-1))) { - case 0: default: - $('body').css({'background-color':C}); - break; - case 1: - $('#content').css({'background-color':C}); - break; - case 2: - $('#aside_primary').css({'background-color':C}); - break; - case 3: - $('body').css({'color':C}); - break; - case 4: - $('a').css({'color':C}); - break; - } - S.value = C; + function UpdateColors(S) { + C = $(S).val(); + switch (parseInt(S.id.slice(-1))) { + case 0: default: + $('body').css({'background-color':C}); + break; + case 1: + $('#content').css({'background-color':C}); + break; + case 2: + $('#aside_primary').css({'background-color':C}); + break; + case 3: + $('body').css({'color':C}); + break; + case 4: + $('a').css({'color':C}); + break; } } @@ -33,35 +34,52 @@ $(document).ready(function() { } function UpdateSwatch(e) { - $(e).css({ - "background-color": e.value, - "color": f.hsl[2] > 0.5 ? "#000": "#fff" - }); + $(e).css({"background-color": e.value, + "color": f.hsl[2] > 0.5 ? "#000": "#fff"}); } - $('#settings_design_color').append('<div id="color-picker"></div>'); - $('#color-picker').hide(); - - var f = $.farbtastic('#color-picker', UpdateColors); - var swatches = $('#settings_design_color .swatch'); - - swatches - .each(UpdateColors) + function SynchColors(e) { + var S = f.linked; + var C = f.color; - .blur(function() { - $(this).val($(this).val().toUpperCase()); - }) + if (S && S.value && S.value != C) { + S.value = C; + UpdateSwatch(S); + UpdateColors(S); + } + } - .focus(function() { - $('#color-picker').show(); - UpdateFarbtastic(this); - }) + function Init() { + $('#settings_design_color').append('<div id="color-picker"></div>'); + $('#color-picker').hide(); - .change(function() { - UpdateFarbtastic(this); - UpdateSwatch(this); - }).change() + f = $.farbtastic('#color-picker', SynchColors); + swatches = $('#settings_design_color .swatch'); - ; + swatches + .each(SynchColors) + .blur(function() { + $(this).val($(this).val().toUpperCase()); + }) + .focus(function() { + $('#color-picker').show(); + UpdateFarbtastic(this); + }) + .change(function() { + UpdateFarbtastic(this); + UpdateSwatch(this); + UpdateColors(this); + }).change(); + } + var f, swatches; + Init(); + $('#form_settings_design').bind('reset', function(){ + setTimeout(function(){ + swatches.each(function(){UpdateColors(this);}); + $('#color-picker').remove(); + swatches.unbind(); + Init(); + },10); + }); }); diff --git a/js/jcrop/jquery.Jcrop.go.js b/js/jcrop/jquery.Jcrop.go.js index a0399d540..4e1cbfd1e 100644 --- a/js/jcrop/jquery.Jcrop.go.js +++ b/js/jcrop/jquery.Jcrop.go.js @@ -1,39 +1,48 @@ - $(function(){ - var x = ($('#avatar_crop_x').val()) ? $('#avatar_crop_x').val() : 0; - var y = ($('#avatar_crop_y').val()) ? $('#avatar_crop_y').val() : 0; - var w = ($('#avatar_crop_w').val()) ? $('#avatar_crop_w').val() : $("#avatar_original img").attr("width"); - var h = ($('#avatar_crop_h').val()) ? $('#avatar_crop_h').val() : $("#avatar_original img").attr("height"); +/** Init for Jcrop library and page setup + * + * @package Laconica + * @author Sarven Capadisli <csarven@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ - jQuery("#avatar_original img").Jcrop({ - onChange: showPreview, - setSelect: [ x, y, w, h ], - onSelect: updateCoords, - aspectRatio: 1, - boxWidth: 480, - boxHeight: 480, - bgColor: '#000', - bgOpacity: .4 - }); - }); +$(function(){ + var x = ($('#avatar_crop_x').val()) ? $('#avatar_crop_x').val() : 0; + var y = ($('#avatar_crop_y').val()) ? $('#avatar_crop_y').val() : 0; + var w = ($('#avatar_crop_w').val()) ? $('#avatar_crop_w').val() : $("#avatar_original img").attr("width"); + var h = ($('#avatar_crop_h').val()) ? $('#avatar_crop_h').val() : $("#avatar_original img").attr("height"); - function showPreview(coords) { - var rx = 96 / coords.w; - var ry = 96 / coords.h; + jQuery("#avatar_original img").Jcrop({ + onChange: showPreview, + setSelect: [ x, y, w, h ], + onSelect: updateCoords, + aspectRatio: 1, + boxWidth: 480, + boxHeight: 480, + bgColor: '#000', + bgOpacity: .4 + }); +}); - var img_width = $("#avatar_original img").attr("width"); - var img_height = $("#avatar_original img").attr("height"); +function showPreview(coords) { + var rx = 96 / coords.w; + var ry = 96 / coords.h; - $('#avatar_preview img').css({ - width: Math.round(rx *img_width) + 'px', - height: Math.round(ry * img_height) + 'px', - marginLeft: '-' + Math.round(rx * coords.x) + 'px', - marginTop: '-' + Math.round(ry * coords.y) + 'px' - }); - }; + var img_width = $("#avatar_original img").attr("width"); + var img_height = $("#avatar_original img").attr("height"); - function updateCoords(c) { - $('#avatar_crop_x').val(c.x); - $('#avatar_crop_y').val(c.y); - $('#avatar_crop_w').val(c.w); - $('#avatar_crop_h').val(c.h); - }; + $('#avatar_preview img').css({ + width: Math.round(rx *img_width) + 'px', + height: Math.round(ry * img_height) + 'px', + marginLeft: '-' + Math.round(rx * coords.x) + 'px', + marginTop: '-' + Math.round(ry * coords.y) + 'px' + }); +}; + +function updateCoords(c) { + $('#avatar_crop_x').val(c.x); + $('#avatar_crop_y').val(c.y); + $('#avatar_crop_w').val(c.w); + $('#avatar_crop_h').val(c.h); +}; diff --git a/js/jquery.form.js b/js/jquery.form.js index cb8b5a660..936b847ab 100644 --- a/js/jquery.form.js +++ b/js/jquery.form.js @@ -157,7 +157,7 @@ $.fn.ajaxSubmit = function(options) { function fileUpload() {
var form = $form[0];
- if ($(':input[@name=submit]', form).length) {
+ if ($(':input[name=submit]', form).length) {
alert('Error: Form elements must not be named "submit".');
return;
}
@@ -570,7 +570,7 @@ $.fn.clearForm = function() { $.fn.clearFields = $.fn.clearInputs = function() {
return this.each(function() {
var t = this.type, tag = this.tagName.toLowerCase();
- if (t == 'text' || t == 'password' || tag == 'textarea')
+ if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea')
this.value = '';
else if (t == 'checkbox' || t == 'radio')
this.checked = false;
diff --git a/js/util.js b/js/util.js index 31d9eb4f5..b712ba8e2 100644 --- a/js/util.js +++ b/js/util.js @@ -17,10 +17,6 @@ */ $(document).ready(function(){ - $('.attachments').click(function() {$().jOverlay({zIndex:999, success:function(html) {$('.attachment').click(function() {$().jOverlay({url:$(this).attr('href') + '/ajax'}); return false; }); - }, url:$(this).attr('href') + '/ajax'}); return false; }); - $('.attachment').click(function() {$().jOverlay({url:$(this).attr('href') + '/ajax'}); return false; }); - // count character on keyup function counter(event){ var maxLength = 140; @@ -203,11 +199,11 @@ $(document).ready(function(){ $("#notices_primary .notices").prepend(document._importNode(li, true)); $("#notices_primary .notice:first").css({display:"none"}); $("#notices_primary .notice:first").fadeIn(2500); - NoticeHover(); NoticeReply(); } } $("#notice_data-text").val(""); + $("#notice_data-attach").val(""); counter(); } $("#form_notice").removeClass("processing"); @@ -219,26 +215,26 @@ $(document).ready(function(){ $("#form_notice").each(addAjaxHidden); NoticeHover(); NoticeReply(); + NoticeAttachments(); }); + function NoticeHover() { - $("#content .notice").hover( - function () { - $(this).addClass('hover'); - }, - function () { - $(this).removeClass('hover'); - } - ); + function mouseHandler(e) { + $(e.target).closest('li.hentry')[(e.type === 'mouseover') ? 'addClass' : 'removeClass']('hover'); + }; + $('#content .notices').mouseover(mouseHandler); + $('#content .notices').mouseout(mouseHandler); } + function NoticeReply() { if ($('#notice_data-text').length > 0) { $('#content .notice').each(function() { - var notice = $(this); - $('.notice_reply', $(this)).click(function() { - var nickname = ($('.author .nickname', notice).length > 0) ? $('.author .nickname', notice) : $('.author .nickname'); - NoticeReplySet(nickname.text(), $('.notice_id', notice).text()); + var notice = $(this)[0]; + $($('.notice_reply', notice)[0]).click(function() { + var nickname = ($('.author .nickname', notice).length > 0) ? $($('.author .nickname', notice)[0]) : $('.author .nickname'); + NoticeReplySet(nickname.text(), $($('.notice_id', notice)[0]).text()); return false; }); }); @@ -258,3 +254,53 @@ function NoticeReplySet(nick,id) { } return true; } + +function NoticeAttachments() { + $.fn.jOverlay.options = { + method : 'GET', + data : '', + url : '', + color : '#000', + opacity : '0.6', + zIndex : 99, + center : true, + imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif', + bgClickToClose : true, + success : function() { + $('#jOverlayContent').append('<button>×</button>'); + $('#jOverlayContent button').click($.closeOverlay); + }, + timeout : 0 + }; + + $('a.attachment').click(function() { + $().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); + return false; + }); + + var t; + $("body:not(#shownotice) a.thumbnail").hover( + function() { + var anchor = $(this); + $("a.thumbnail").children('img').hide(); + anchor.closest(".entry-title").addClass('ov'); + + if (anchor.children('img').length == 0) { + t = setTimeout(function() { + $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) { + anchor.append(data); + }); + }, 500); + } + else { + anchor.children('img').show(); + } + }, + function() { + clearTimeout(t); + $("a.thumbnail").children('img').hide(); + $(this).closest(".entry-title").removeClass('ov'); + } + ); +} + diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php index 9485fe3d6..45e4fa319 100644 --- a/lib/attachmentlist.php +++ b/lib/attachmentlist.php @@ -80,20 +80,22 @@ class AttachmentList extends Widget function show() { -// $this->out->elementStart('div', array('id' =>'attachments_primary')); - $this->out->elementStart('div', array('id' =>'content')); - $this->out->element('h2', null, _('Attachments')); - $this->out->elementStart('ul', array('class' => 'attachments')); - $atts = new File; $att = $atts->getAttachments($this->notice->id); + if (empty($att)) return 0; + $this->out->elementStart('dl', array('id' =>'attachments')); + $this->out->element('dt', null, _('Attachments')); + $this->out->elementStart('dd'); + $this->out->elementStart('ol', array('class' => 'attachments')); + foreach ($att as $n=>$attachment) { $item = $this->newListItem($attachment); $item->show(); } - $this->out->elementEnd('ul'); - $this->out->elementEnd('div'); + $this->out->elementEnd('dd'); + $this->out->elementEnd('ol'); + $this->out->elementEnd('dl'); return count($att); } @@ -171,7 +173,7 @@ class AttachmentListItem extends Widget } function linkTitle() { - return 'Our page for ' . $this->title(); + return $this->title(); } /** @@ -191,34 +193,25 @@ class AttachmentListItem extends Widget } function linkAttr() { - return array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $this->attachment->id))); + return array('class' => 'attachment', 'href' => $this->attachment->url, 'id' => 'attachment-' . $this->attachment->id); } function showLink() { - $attr = $this->linkAttr(); - $text = $this->linkTitle(); - $this->out->elementStart('h4'); - $this->out->element('a', $attr, $text); - - if ($this->attachment->url !== $this->title()) - $this->out->element('span', null, " ({$this->attachment->url})"); - - - $this->out->elementEnd('h4'); + $this->out->elementStart('a', $this->linkAttr()); + $this->out->element('span', null, $this->linkTitle()); + $this->showRepresentation(); + $this->out->elementEnd('a'); } function showNoticeAttachment() { $this->showLink(); - $this->showRepresentation(); } function showRepresentation() { $thumbnail = File_thumbnail::staticGet('file_id', $this->attachment->id); if (!empty($thumbnail)) { - $this->out->elementStart('a', $this->linkAttr()/*'href' => $this->linkTo()*/); $this->out->element('img', array('alt' => 'nothing to say', 'src' => $thumbnail->url, 'width' => $thumbnail->width, 'height' => $thumbnail->height)); - $this->out->elementEnd('a'); } } @@ -260,7 +253,7 @@ class Attachment extends AttachmentListItem } function linkTitle() { - return 'Direct link to ' . $this->title(); + return $this->attachment->url; } function showRepresentation() { @@ -275,6 +268,23 @@ class Attachment extends AttachmentListItem case 'image/jpeg': $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt')); break; + + case 'application/ogg': + case 'audio/x-speex': + case 'video/mpeg': + case 'audio/mpeg': + case 'video/mp4': + case 'video/quicktime': + $arr = array('type' => $this->attachment->mimetype, + 'data' => $this->attachment->url, + 'width' => 320, + 'height' => 240 + ); + $this->out->elementStart('object', $arr); + $this->out->element('param', array('name' => 'src', 'value' => $this->attachment->url)); + $this->out->element('param', array('name' => 'autoStart', 'value' => 1)); + $this->out->elementEnd('object'); + break; } } } else { diff --git a/lib/attachmentsection.php b/lib/attachmentsection.php deleted file mode 100644 index 20e620b9b..000000000 --- a/lib/attachmentsection.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php -/** - * Laconica, the distributed open-source microblogging tool - * - * Base class for sections showing lists of attachments - * - * 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 Widget - * @package Laconica - * @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); -} - -define('ATTACHMENTS_PER_SECTION', 6); - -/** - * Base class for sections showing lists of attachments - * - * These are the widgets that show interesting data about a person - * group, or site. - * - * @category Widget - * @package Laconica - * @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 AttachmentSection extends Section -{ - function showContent() - { - $attachments = $this->getAttachments(); - - $cnt = 0; - - $this->out->elementStart('ul', 'attachments'); - - while ($attachments->fetch() && ++$cnt <= ATTACHMENTS_PER_SECTION) { - $this->showAttachment($attachments); - } - - $this->out->elementEnd('ul'); - - return ($cnt > ATTACHMENTS_PER_SECTION); - } - - function getAttachments() - { - return null; - } - - function showAttachment($attachment) - { - $this->out->elementStart('li'); - $this->out->element('a', array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $attachment->file_id))), "Attachment tagged {$attachment->c} times"); - $this->out->elementEnd('li'); - } -} - diff --git a/lib/common.php b/lib/common.php index abdc22c0e..5aafdfe0e 100644 --- a/lib/common.php +++ b/lib/common.php @@ -19,7 +19,7 @@ if (!defined('LACONICA')) { exit(1); } -define('LACONICA_VERSION', '0.7.3'); +define('LACONICA_VERSION', '0.8.0dev'); define('AVATAR_PROFILE_SIZE', 96); define('AVATAR_STREAM_SIZE', 48); @@ -159,6 +159,46 @@ $config = 'newuser' => array('subscribe' => null, 'welcome' => null), + 'snapshot' => + array('run' => 'web', + 'frequency' => 10000, + 'reporturl' => 'http://laconi.ca/stats/report'), + 'attachments' => + array('supported' => array('image/png', + 'image/jpeg', + 'image/gif', + 'image/svg+xml', + 'audio/mpeg', + 'audio/x-speex', + 'application/ogg', + 'application/pdf', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.oasis.opendocument.chart', + 'application/vnd.oasis.opendocument.chart-template', + 'application/vnd.oasis.opendocument.image', + 'application/vnd.oasis.opendocument.image-template', + 'application/vnd.oasis.opendocument.formula', + 'application/vnd.oasis.opendocument.formula-template', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.oasis.opendocument.text-web', + 'application/x-zip', + 'application/zip', + 'text/plain', + 'video/mpeg', + 'video/mp4', + 'video/quicktime', + 'video/mpeg'), + 'file_quota' => 5000000, + 'user_quota' => 50000000, + 'monthly_quota' => 15000000, + ), ); $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options'); @@ -170,6 +210,7 @@ $config['db'] = 'require_prefix' => 'classes/', 'class_prefix' => '', 'mirror' => null, + 'utf8' => true, 'db_driver' => 'DB', # XXX: JanRain libs only work with DB 'quote_identifiers' => false, 'type' => 'mysql' ); @@ -219,19 +260,19 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db'] // XXX: how many of these could be auto-loaded on use? -require_once('Validate.php'); -require_once('markdown.php'); +require_once 'Validate.php'; +require_once 'markdown.php'; -require_once(INSTALLDIR.'/lib/util.php'); -require_once(INSTALLDIR.'/lib/action.php'); -require_once(INSTALLDIR.'/lib/theme.php'); -require_once(INSTALLDIR.'/lib/mail.php'); -require_once(INSTALLDIR.'/lib/subs.php'); -require_once(INSTALLDIR.'/lib/Shorturl_api.php'); -require_once(INSTALLDIR.'/lib/twitter.php'); +require_once INSTALLDIR.'/lib/util.php'; +require_once INSTALLDIR.'/lib/action.php'; +require_once INSTALLDIR.'/lib/theme.php'; +require_once INSTALLDIR.'/lib/mail.php'; +require_once INSTALLDIR.'/lib/subs.php'; +require_once INSTALLDIR.'/lib/Shorturl_api.php'; +require_once INSTALLDIR.'/lib/twitter.php'; -require_once(INSTALLDIR.'/lib/clientexception.php'); -require_once(INSTALLDIR.'/lib/serverexception.php'); +require_once INSTALLDIR.'/lib/clientexception.php'; +require_once INSTALLDIR.'/lib/serverexception.php'; // XXX: other formats here diff --git a/lib/facebookaction.php b/lib/facebookaction.php index 043a078cd..a445750f7 100644 --- a/lib/facebookaction.php +++ b/lib/facebookaction.php @@ -38,14 +38,14 @@ require_once INSTALLDIR.'/lib/noticeform.php'; class FacebookAction extends Action { - + var $facebook = null; var $fbuid = null; var $flink = null; var $action = null; var $app_uri = null; var $app_name = null; - + /** * Constructor * @@ -60,71 +60,71 @@ class FacebookAction extends Action function __construct($output='php://output', $indent=true, $facebook=null, $flink=null) { parent::__construct($output, $indent); - + $this->facebook = $facebook; $this->flink = $flink; - + if ($this->flink) { - $this->fbuid = $flink->foreign_id; + $this->fbuid = $flink->foreign_id; $this->user = $flink->getUser(); } - + $this->args = array(); } - + function prepare($argarray) - { + { parent::prepare($argarray); - + $this->facebook = getFacebook(); $this->fbuid = $this->facebook->require_login(); - + $this->action = $this->trimmed('action'); - + $app_props = $this->facebook->api_client->Admin_getAppProperties( array('canvas_name', 'application_name')); - + $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; $this->app_name = $app_props['application_name']; $this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); - + return true; - + } - + 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)); - + + $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)); } - + 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)); } - + /** * Start an Facebook ready HTML document * @@ -138,11 +138,11 @@ class FacebookAction extends Action * @return void */ - function startHTML($type=null) - { + function startHTML($type=null) + { $this->showStylesheets(); $this->showScripts(); - + $this->elementStart('div', array('class' => 'facebook-page')); } @@ -177,18 +177,18 @@ class FacebookAction extends Action $this->showFooter(); $this->elementEnd('div'); } - + function showAside() { } function showHead($error, $success) { - + if ($error) { $this->element("h1", null, $error); } - + if ($success) { $this->element("h1", null, $success); } @@ -198,10 +198,10 @@ class FacebookAction extends Action $this->element('fb:add-section-button', array('section' => 'profile')); $this->elementEnd('span'); $this->elementEnd('fb:if-section-not-added'); - + } - + // Make this into a widget later function showLocalNav() { @@ -229,8 +229,8 @@ class FacebookAction extends Action $this->elementEnd('li'); $this->elementEnd('ul'); - } - + } + /** * Show header of the page. * @@ -245,7 +245,7 @@ class FacebookAction extends Action $this->showNoticeForm(); $this->elementEnd('div'); } - + /** * Show page, a template method. * @@ -258,7 +258,7 @@ class FacebookAction extends Action $this->showBody(); $this->endHTML(); } - + function showInstructions() { @@ -278,7 +278,7 @@ class FacebookAction extends Action $this->element('a', array('href' => common_local_url('register')), _('Register')); $this->text($loginmsg_part2); - $this->elementEnd('p'); + $this->elementEnd('p'); $this->elementEnd('dd'); $this->elementEnd('dl'); @@ -317,7 +317,7 @@ class FacebookAction extends Action $this->elementEnd('ul'); $this->submit('submit', _('Login')); - $this->elementEnd('fieldset'); + $this->elementEnd('fieldset'); $this->elementEnd('form'); $this->elementStart('p'); @@ -329,73 +329,73 @@ class FacebookAction extends Action $this->elementEnd('div'); } - - + + function updateProfileBox($notice) { // Need to include inline CSS for styling the Profile box - $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url')); - $icon_url = $app_props['icon_url']; + $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url')); + $icon_url = $app_props['icon_url']; $style = '<style> - .entry-title *, - .entry-content * { - font-size:14px; - font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif; - } - .entry-title a, - .entry-content a { - color:#002E6E; - } + .entry-title *, + .entry-content * { + font-size:14px; + font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif; + } + .entry-title a, + .entry-content a { + color:#002E6E; + } .entry-title .vcard .photo { float:left; display:inline; - margin-right:11px; - margin-bottom:11px + margin-right:11px; + margin-bottom:11px } - .entry-title { - margin-bottom:11px; - } + .entry-title { + margin-bottom:11px; + } .entry-title p.entry-content { display:inline; - margin-left:5px; + margin-left:5px; } - div.entry-content { - clear:both; - } + div.entry-content { + clear:both; + } div.entry-content dl, div.entry-content dt, div.entry-content dd { display:inline; - text-transform:lowercase; + text-transform:lowercase; } div.entry-content dd, - div.entry-content .device dt { - margin-left:0; - margin-right:5px; + div.entry-content .device dt { + margin-left:0; + margin-right:5px; } div.entry-content dl.timestamp dt, - div.entry-content dl.response dt { + div.entry-content dl.response dt { display:none; } div.entry-content dd a { display:inline-block; } - #facebook_laconica_app { - text-indent:-9999px; - height:16px; - width:16px; - display:block; - background:url('.$icon_url.') no-repeat 0 0; - float:right; - } - </style>'; + #facebook_laconica_app { + text-indent:-9999px; + height:16px; + width:16px; + display:block; + background:url('.$icon_url.') no-repeat 0 0; + float:right; + } + </style>'; $this->xw->openMemory(); @@ -407,12 +407,12 @@ class FacebookAction extends Action $fbml_main = "<fb:narrow>$style " . $this->xw->outputMemory(false) . "</fb:narrow>"; - $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main); + $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main); $this->xw->openURI('php://output'); } - - + + /** * Generate pagination links * @@ -457,24 +457,24 @@ class FacebookAction extends Action $this->elementEnd('div'); } } - - function updateFacebookStatus($notice) + + function updateFacebookStatus($notice) { $prefix = $this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $this->fbuid); $content = "$prefix $notice->content"; - + if ($this->facebook->api_client->users_hasAppPermission('status_update', $this->fbuid)) { $this->facebook->api_client->users_setStatus($content, $this->fbuid, false, true); } } - + function saveNewNotice() { $user = $this->flink->getUser(); $content = $this->trimmed('status_textarea'); - + if (!$content) { $this->showPage(_('No notice content!')); return; @@ -492,9 +492,9 @@ class FacebookAction extends Action $cmd = $inter->handle_command($user, $content_shortened); if ($cmd) { - + // XXX fix this - + $cmd->execute(new WebChannel()); return; } @@ -510,20 +510,20 @@ class FacebookAction extends Action } common_broadcast_notice($notice); - + // Also update the user's Facebook status $this->updateFacebookStatus($notice); $this->updateProfileBox($notice); - + } } -class FacebookNoticeForm extends NoticeForm +class FacebookNoticeForm extends NoticeForm { - + var $post_action = null; - + /** * Constructor * @@ -532,13 +532,13 @@ class FacebookNoticeForm extends NoticeForm * @param string $content content to pre-fill */ - function __construct($out=null, $action=null, $content=null, + function __construct($out=null, $action=null, $content=null, $post_action=null, $user=null) { parent::__construct($out, $action, $content, $user); $this->post_action = $post_action; } - + /** * Action of the form * @@ -554,7 +554,7 @@ class FacebookNoticeForm extends NoticeForm class FacebookNoticeList extends NoticeList { - + /** * constructor * @@ -565,7 +565,7 @@ class FacebookNoticeList extends NoticeList { parent::__construct($notice, $out); } - + /** * show the list of notices * @@ -619,7 +619,7 @@ class FacebookNoticeList extends NoticeList } class FacebookNoticeListItem extends NoticeListItem -{ +{ /** * constructor @@ -646,51 +646,19 @@ class FacebookNoticeListItem extends NoticeListItem function show() { $this->showStart(); + $this->showNotice(); + $this->showNoticeInfo(); - $this->out->elementStart('div', 'entry-title'); - $this->showAuthor(); - $this->showContent(); - $this->out->elementEnd('div'); - - $this->out->elementStart('div', 'entry-content'); - $this->showNoticeLink(); - $this->showNoticeSource(); - $this->showReplyTo(); - $this->out->elementEnd('div'); + // XXX: Need to update to show attachements and controls $this->showEnd(); } - function showNoticeLink() - { - $noticeurl = common_local_url('shownotice', - array('notice' => $this->notice->id)); - // XXX: we need to figure this out better. Is this right? - if (strcmp($this->notice->uri, $noticeurl) != 0 && - preg_match('/^http/', $this->notice->uri)) { - $noticeurl = $this->notice->uri; - } - - $this->out->elementStart('dl', 'timestamp'); - $this->out->element('dt', null, _('Published')); - $this->out->elementStart('dd', null); - $this->out->elementStart('a', array('rel' => 'bookmark', - 'href' => $noticeurl)); - $dt = common_date_iso8601($this->notice->created); - $this->out->element('abbr', array('class' => 'published', - 'title' => $dt), - common_date_string($this->notice->created)); - $this->out->elementEnd('a'); - $this->out->elementEnd('dd'); - $this->out->elementEnd('dl'); - } - } - class FacebookProfileBoxNotice extends FacebookNoticeListItem -{ - +{ + /** * constructor * @@ -703,36 +671,24 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem { parent::__construct($notice, $out); } - + /** - * Recipe function for displaying a single notice in the - * Facebook App's Profile + * Recipe function for displaying a single notice in the + * Facebook App profile notice box * * @return void */ function show() { - - $this->out->elementStart('div', 'entry-title'); - $this->showAuthor(); - $this->showContent(); - $this->out->elementEnd('div'); - - $this->out->elementStart('div', 'entry-content'); - - $this->showNoticeLink(); - $this->showNoticeSource(); - $this->showReplyTo(); - $this->out->elementEnd('div'); - + $this->showNotice(); + $this->showNoticeInfo(); $this->showAppLink(); - } - function showAppLink() + function showAppLink() { - + $this->facebook = getFacebook(); $app_props = $this->facebook->api_client->Admin_getAppProperties( @@ -740,7 +696,7 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name']; $this->app_name = $app_props['application_name']; - + $this->out->elementStart('a', array('id' => 'facebook_laconica_app', 'href' => $this->app_uri)); $this->out->text($this->app_name); diff --git a/lib/form.php b/lib/form.php index 5317df471..f872aef0b 100644 --- a/lib/form.php +++ b/lib/form.php @@ -52,6 +52,8 @@ require_once INSTALLDIR.'/lib/widget.php'; class Form extends Widget { + var $enctype = null; + /** * Show the form * @@ -63,11 +65,15 @@ class Form extends Widget function show() { - $this->out->elementStart('form', - array('id' => $this->id(), - 'class' => $this->formClass(), - 'method' => 'post', - 'action' => $this->action())); + $attributes = array('id' => $this->id(), + 'class' => $this->formClass(), + 'method' => 'post', + 'action' => $this->action()); + + if (!empty($this->enctype)) { + $attributes['enctype'] = $this->enctype; + } + $this->out->elementStart('form', $attributes); $this->out->elementStart('fieldset'); $this->formLegend(); $this->sessionToken(); diff --git a/lib/frequentattachmentsection.php b/lib/frequentattachmentsection.php deleted file mode 100644 index 0ce0d1871..000000000 --- a/lib/frequentattachmentsection.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php -/** - * Laconica, the distributed open-source microblogging tool - * - * FIXME - * - * 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 Widget - * @package Laconica - * @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); -} - -/** - * FIXME - * - * These are the widgets that show interesting data about a person - * group, or site. - * - * @category Widget - * @package Laconica - * @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 FrequentAttachmentSection extends AttachmentSection -{ - function getAttachments() { - $notice_tag = new Notice_tag; - $query = 'select file_id, count(file_id) as c from notice_tag join file_to_post on post_id = notice_id where tag="' . $notice_tag->escape($this->out->tag) . '" group by file_id order by c desc'; - $notice_tag->query($query); - return $notice_tag; - } - - function title() - { - return sprintf(_('Attachments frequently tagged with %s'), $this->out->tag); - } - - function divId() - { - return 'frequent_attachments'; - } -} - diff --git a/lib/noticeform.php b/lib/noticeform.php index 606b5d028..5d7cf194e 100644 --- a/lib/noticeform.php +++ b/lib/noticeform.php @@ -89,7 +89,8 @@ class NoticeForm extends Form } else { $this->user = common_current_user(); } - + + $this->enctype = 'multipart/form-data'; } /** @@ -142,17 +143,21 @@ class NoticeForm extends Form 'rows' => 4, 'name' => 'status_textarea'), ($this->content) ? $this->content : ''); - $this->out->elementStart('dl', 'form_note'); $this->out->element('dt', null, _('Available characters')); $this->out->element('dd', array('id' => 'notice_text-count'), '140'); $this->out->elementEnd('dl'); - + $this->out->element('label', array('for' => 'notice_data-attach'), _('Attach')); + $this->out->element('input', array('id' => 'notice_data-attach', + 'type' => 'file', + 'name' => 'attach', + 'title' => _('Attach a file'))); if ($this->action) { $this->out->hidden('notice_return-to', $this->action, 'returnto'); } $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto'); + $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota')); } /** diff --git a/lib/noticelist.php b/lib/noticelist.php index 004905056..9ace341d8 100644 --- a/lib/noticelist.php +++ b/lib/noticelist.php @@ -34,6 +34,7 @@ if (!defined('LACONICA')) { require_once INSTALLDIR.'/lib/favorform.php'; require_once INSTALLDIR.'/lib/disfavorform.php'; +require_once INSTALLDIR.'/lib/attachmentlist.php'; /** * widget for displaying a list of notices @@ -85,7 +86,7 @@ class NoticeList extends Widget { $this->out->elementStart('div', array('id' =>'notices_primary')); $this->out->element('h2', null, _('Notices')); - $this->out->elementStart('ul', array('class' => 'notices')); + $this->out->elementStart('ol', array('class' => 'notices xoxo')); $cnt = 0; @@ -100,7 +101,7 @@ class NoticeList extends Widget $item->show(); } - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); $this->out->elementEnd('div'); return $cnt; @@ -180,86 +181,49 @@ class NoticeListItem extends Widget $this->showStart(); $this->showNotice(); $this->showNoticeAttachments(); - $this->showNoticeOptions(); $this->showNoticeInfo(); + $this->showNoticeOptions(); $this->showEnd(); } function showNotice() { -if(0) - $this->out->elementStart('entry-title'); -else - - if ('shownotice' === $this->out->args['action']) { - $width = '85%'; - } else { - $width = '90%'; - } - - - $this->out->elementStart('div', array('class' => 'entry-title', 'style' => "float: left; width: $width;")); + $this->out->elementStart('div', 'entry-title'); $this->showAuthor(); $this->showContent(); $this->out->elementEnd('div'); } - function showNoticeAttachments() - { - $f2p = new File_to_post; - $f2p->post_id = $this->notice->id; - $file = new File; - $file->joinAdd($f2p); - $file->selectAdd(); - $file->selectAdd('file.id as id'); - $count = $file->find(true); - if (!$count) return; - if (1 === $count) { - $href = common_local_url('attachment', array('attachment' => $file->id)); - $att_class = 'attachment'; - } else { - $href = common_local_url('attachments', array('notice' => $this->notice->id)); - $att_class = 'attachments'; + function showNoticeAttachments() { + if ($this->isUsedInList()) { + return; } + $al = new AttachmentList($this->notice, $this->out); + $al->show(); + } - $clip = theme_path('images/icons/clip', 'base'); - if ('shownotice' === $this->out->args['action']) { - $height = '96px'; - $width = '83%'; - $width_att = '15%'; - $clip .= '-big.png'; - $top = '70px'; - } else { - $height = '48px'; - $width = '90%'; - $width_att = '8%'; - $clip .= '.png'; - $top = '20px'; - } -if(0) - $this->out->elementStart('div', 'entry-attachments'); -else - $this->out->elementStart('p', array('class' => 'entry-attachments', 'style' => "float: right; width: $width_att; background: url($clip) no-repeat; text-align: right; height: $height;")); - $this->out->element('a', array('class' => $att_class, 'style' => "text-decoration: none; padding-top: $top; display: block; height: $height;", 'href' => $href, 'title' => "# of attachments: $count"), $count === 1 ? '' : $count); + function isUsedInList() { + return 'shownotice' !== $this->out->args['action']; + } +/* + function attachmentCount($discriminant = true) { + $file_oembed = new File_oembed; + $query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id; + $file_oembed->query($query); + $file_oembed->fetch(); + return intval($file_oembed->c); + } +*/ - $this->out->elementEnd('p'); + function showWithAttachment() { } function showNoticeInfo() { -if(0) $this->out->elementStart('div', 'entry-content'); -else - - if ('shownotice' === $this->out->args['action']) { - $width = '85%'; - } else { - $width = '90%'; - } - - $this->out->elementStart('div', array('class' => 'entry-content', 'style' => "float: left; width: $width;")); $this->showNoticeLink(); +// $this->showWithAttachment(); $this->showNoticeSource(); $this->showContext(); $this->out->elementEnd('div'); @@ -269,10 +233,7 @@ else { $user = common_current_user(); if ($user) { -if(0) $this->out->elementStart('div', 'notice-options'); -else - $this->out->elementStart('div', array('class' => 'notice-options', 'style' => 'float: right; width: 16%;')); $this->showFaveForm(); $this->showReplyLink(); $this->showDeleteLink(); @@ -403,6 +364,10 @@ else // versions (>> 0.4.x) $this->out->raw(common_render_content($this->notice->content, $this->notice)); } + $uploaded = $this->notice->getUploadedAttachment(); + if ($uploaded) { + $this->out->element('a', array('href' => $uploaded, 'class' => 'attachment'), $uploaded); + } $this->out->elementEnd('p'); } @@ -433,6 +398,7 @@ else $this->out->element('abbr', array('class' => 'published', 'title' => $dt), common_date_string($this->notice->created)); + $this->out->elementEnd('a'); $this->out->elementEnd('dd'); $this->out->elementEnd('dl'); diff --git a/lib/noticesection.php b/lib/noticesection.php index 37aafdaf6..ca1432686 100644 --- a/lib/noticesection.php +++ b/lib/noticesection.php @@ -52,12 +52,12 @@ class NoticeSection extends Section { $notices = $this->getNotices(); $cnt = 0; - $this->out->elementStart('ul', 'notices'); + $this->out->elementStart('ol', 'notices xoxo'); while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) { $this->showNotice($notices); } - $this->out->elementEnd('ul'); + $this->out->elementEnd('ol'); return ($cnt > NOTICES_PER_SECTION); } diff --git a/lib/router.php b/lib/router.php index 70ee0f3fb..456d1793e 100644 --- a/lib/router.php +++ b/lib/router.php @@ -153,24 +153,21 @@ class Router $m->connect('attachment/:attachment/ajax', array('action' => 'attachment_ajax'), - array('notice' => '[0-9]+')); - - $m->connect('attachment/:attachment', - array('action' => 'attachment'), - array('notice' => '[0-9]+')); + array('attachment' => '[0-9]+')); - // notice + $m->connect('attachment/:attachment/thumbnail', + array('action' => 'attachment_thumbnail'), + array('attachment' => '[0-9]+')); $m->connect('notice/new', array('action' => 'newnotice')); $m->connect('notice/new?replyto=:replyto', array('action' => 'newnotice'), array('replyto' => '[A-Za-z0-9_-]+')); - $m->connect('notice/:notice/attachments/ajax', - array('action' => 'attachments_ajax'), - array('notice' => '[0-9]+')); - $m->connect('notice/:notice/attachments', - array('action' => 'attachments'), - array('notice' => '[0-9]+')); + + $m->connect('notice/:notice/file', + array('action' => 'file'), + array('notice' => '[0-9]+')); + $m->connect('notice/:notice', array('action' => 'shownotice'), array('notice' => '[0-9]+')); diff --git a/lib/searchaction.php b/lib/searchaction.php index e7ad4affd..e74450e11 100644 --- a/lib/searchaction.php +++ b/lib/searchaction.php @@ -51,7 +51,7 @@ class SearchAction extends Action * * @return boolean true */ - function isReadOnly() + function isReadOnly($args) { return true; } diff --git a/lib/snapshot.php b/lib/snapshot.php new file mode 100644 index 000000000..4b05b502d --- /dev/null +++ b/lib/snapshot.php @@ -0,0 +1,227 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * A snapshot of site stats that can report itself to headquarters + * + * 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 Stats + * @package Laconica + * @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); +} + +/** + * A snapshot of site stats that can report itself to headquarters + * + * This class will collect statistics on the site and report them to + * a statistics server of the admin's choice. (Default is the big one + * at laconi.ca.) + * + * It can either be called from a cron job, or run occasionally by the + * Web site. + * + * @category Stats + * @package Laconica + * @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 Snapshot +{ + var $stats = null; + + /** + * Constructor for a snapshot + */ + + function __construct() + { + } + + /** + * Static function for reporting statistics + * + * This function checks whether it should report statistics, based on + * the current configuation settings. If it should, it creates a new + * Snapshot object, takes a snapshot, and reports it to headquarters. + * + * @return void + */ + + static function check() + { + switch (common_config('snapshot', 'run')) { + case 'web': + // skip if we're not running on the Web. + if (!isset($_SERVER) || !array_key_exists('REQUEST_METHOD', $_SERVER)) { + break; + } + // Run once every frequency hits + // XXX: do frequency by time (once a week, etc.) rather than + // hits + if (rand() % common_config('snapshot', 'frequency') == 0) { + $snapshot = new Snapshot(); + $snapshot->take(); + $snapshot->report(); + } + break; + case 'cron': + // skip if we're running on the Web + if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + break; + } + common_log(LOG_INFO, 'Running snapshot from cron job'); + // We're running from the command line; assume + + $snapshot = new Snapshot(); + $snapshot->take(); + common_log(LOG_INFO, count($snapshot->stats) . " statistics being uploaded."); + $snapshot->report(); + + break; + case 'never': + break; + default: + common_log(LOG_WARNING, "Unrecognized value for snapshot run config."); + } + } + + /** + * Take a snapshot of the server + * + * Builds an array of statistical and configuration data based + * on the local database and config files. We avoid grabbing any + * information that could be personal or private. + * + * @return void + */ + + function take() + { + $this->stats = array(); + + // Some basic identification stuff + + $this->stats['version'] = LACONICA_VERSION; + $this->stats['phpversion'] = phpversion(); + $this->stats['name'] = common_config('site', 'name'); + $this->stats['root'] = common_root_url(); + + // non-identifying stats on various tables. Primary + // interest is size and rate of activity of service. + + $tables = array('user', + 'notice', + 'subscription', + 'remote_profile', + 'user_group'); + + foreach ($tables as $table) { + $this->tableStats($table); + } + + // stats on some important config options + + $this->stats['theme'] = common_config('site', 'theme'); + $this->stats['dbtype'] = common_config('db', 'type'); + $this->stats['xmpp'] = common_config('xmpp', 'enabled'); + $this->stats['inboxes'] = common_config('inboxes', 'enabled'); + $this->stats['queue'] = common_config('queue', 'enabled'); + $this->stats['license'] = common_config('license', 'url'); + $this->stats['fancy'] = common_config('site', 'fancy'); + $this->stats['private'] = common_config('site', 'private'); + $this->stats['closed'] = common_config('site', 'closed'); + $this->stats['memcached'] = common_config('memcached', 'enabled'); + $this->stats['language'] = common_config('site', 'language'); + $this->stats['timezone'] = common_config('site', 'timezone'); + + } + + /** + * Reports statistics to headquarters + * + * Posts statistics to a reporting server. + * + * @return void + */ + + function report() + { + // XXX: Use OICU2 and OAuth to make authorized requests + + $postdata = http_build_query($this->stats); + + $opts = + array('http' => + array( + 'method' => 'POST', + 'header' => 'Content-type: '. + 'application/x-www-form-urlencoded', + 'content' => $postdata, + 'user_agent' => 'Laconica/'.LACONICA_VERSION + ) + ); + + $context = stream_context_create($opts); + + $reporturl = common_config('snapshot', 'reporturl'); + + $result = @file_get_contents($reporturl, false, $context); + + return $result; + } + + /** + * Updates statistics for a single table + * + * Determines the size of a table and its oldest and newest rows. + * Goal here is to see how active a site is. Note that it + * fills up the instance stats variable. + * + * @param string $table name of table to check + * + * @return void + */ + + function tableStats($table) + { + $inst = DB_DataObject::factory($table); + + $inst->selectAdd(); + $inst->selectAdd('count(*) as cnt, '. + 'min(created) as first, '. + 'max(created) as last'); + + if ($inst->find(true)) { + $this->stats[$table.'count'] = $inst->cnt; + $this->stats[$table.'first'] = $inst->first; + $this->stats[$table.'last'] = $inst->last; + } + + $inst->free(); + unset($inst); + } +} diff --git a/lib/util.php b/lib/util.php index fbef8764a..ab1272309 100644 --- a/lib/util.php +++ b/lib/util.php @@ -496,6 +496,32 @@ function common_linkify($url) { } $attrs = array('href' => $longurl, 'rel' => 'external'); + +// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID' +// where ID is the id of the attachment for the given URL. +// +// we need a better test telling what can be shown as an attachment +// we're currently picking up oembeds only. +// I think the best option is another file_view table in the db +// and associated dbobject. + $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'"; + $file = new File; + $file->query($query); + $file->fetch(); + + if (!empty($file->file_id)) { + $query = "select file_thumbnail.file_id as file_id from file join file_thumbnail on file.id = file_thumbnail.file_id where file.url='$longurl'"; + $file2 = new File; + $file2->query($query); + $file2->fetch(); + + if (empty($file2->file_id)) { + $attrs['class'] = 'attachment'; + } else { + $attrs['class'] = 'attachment thumbnail'; + } + $attrs['id'] = "attachment-{$file->file_id}"; + } return XMLStringer::estring('a', $attrs, $display); } diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php new file mode 100644 index 000000000..6eb09c3c0 --- /dev/null +++ b/plugins/FBConnect/FBCLoginGroupNav.php @@ -0,0 +1,110 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Menu for login group of actions + * + * 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 Menu + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Zach Copley <zach@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/widget.php'; + +/** + * Menu for login group of actions + * + * @category Output + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @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/ + * + * @see Widget + */ + +class FBCLoginGroupNav extends Widget +{ + var $action = null; + + /** + * Construction + * + * @param Action $action current action, used for output + */ + + function __construct($action=null) + { + parent::__construct($action); + $this->action = $action; + } + + /** + * Show the menu + * + * @return void + */ + + function show() + { + $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); + $this->action->element('dt', null, _('Local views')); + $this->action->elementStart('dd'); + + // 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')); + } + + $menu['openidlogin'] = array(_('OpenID'), + _('Login or register with OpenID')); + + $menu['FBConnectLogin'] = array(_('Facebook'), + _('Login or register using Facebook')); + + $action_name = $this->action->trimmed('action'); + $this->action->elementStart('ul', array('class' => 'nav')); + + foreach ($menu as $menuaction => $menudesc) { + $this->action->menuItem(common_local_url($menuaction), + $menudesc[0], + $menudesc[1], + $action_name === $menuaction); + } + + $this->action->elementEnd('ul'); + + $this->action->elementEnd('dd'); + $this->action->elementEnd('dl'); + } +} diff --git a/plugins/FBConnect/FBCSettingsNav.php b/plugins/FBConnect/FBCSettingsNav.php new file mode 100644 index 000000000..8b8411853 --- /dev/null +++ b/plugins/FBConnect/FBCSettingsNav.php @@ -0,0 +1,113 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Menu for login group of actions + * + * 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 Menu + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @author Zach Copley <zach@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/widget.php'; + +/** + * A widget for showing the connect group local nav menu + * + * @category Output + * @package Laconica + * @author Evan Prodromou <evan@controlyourself.ca> + * @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/ + * + * @see Widget + */ + +class FBCSettingsNav extends Widget +{ + var $action = null; + + /** + * Construction + * + * @param Action $action current action, used for output + */ + + function __construct($action=null) + { + parent::__construct($action); + $this->action = $action; + } + + /** + * Show the menu + * + * @return void + */ + + function show() + { + + $this->action->elementStart('dl', array('id' => 'site_nav_local_views')); + $this->action->element('dt', null, _('Local views')); + $this->action->elementStart('dd'); + + # 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')), + 'FBConnectSettings' => + array(_('Facebook'), + _('Facebook Connect settings'))); + + $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], + $action_name === $menuaction); + } + + $this->action->elementEnd('ul'); + + $this->action->elementEnd('dd'); + $this->action->elementEnd('dl'); + } +} diff --git a/plugins/FBConnect/FBC_XDReceiver.php b/plugins/FBConnect/FBC_XDReceiver.php new file mode 100644 index 000000000..57c98b4f1 --- /dev/null +++ b/plugins/FBConnect/FBC_XDReceiver.php @@ -0,0 +1,73 @@ +<?php + +if (!defined('LACONICA')) { + exit(1); +} + +/* + * Generates the cross domain communication channel file + * (xd_receiver.html). By generating it we can add some caching + * instructions. + * + * See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel + */ +class FBC_XDReceiverAction extends Action +{ + + /** + * Do we need to write to the database? + * + * @return boolean true + */ + + function isReadonly() + { + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + + function handle($args) + { + // Parent handling, including cache check + parent::handle($args); + $this->showPage(); + } + + function showPage() + { + // cache the xd_receiver + header('Cache-Control: max-age=225065900'); + header('Expires:'); + header('Pragma:'); + + $this->startXML('html', + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); + + $language = $this->getLanguage(); + + $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml', + 'xml:lang' => $language, + 'lang' => $language)); + $this->elementStart('head'); + $this->element('title', null, 'cross domain receiver page'); + $this->element('script', + array('src' => + 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js', + 'type' => 'text/javascript'), ''); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->elementEnd('body'); + + $this->elementEnd('html'); + } + +} + diff --git a/plugins/FBConnect/FBConnectAuth.php b/plugins/FBConnect/FBConnectAuth.php new file mode 100644 index 000000000..4699ce636 --- /dev/null +++ b/plugins/FBConnect/FBConnectAuth.php @@ -0,0 +1,454 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Plugin to enable Facebook Connect + * + * 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 Zach Copley <zach@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; + +class FBConnectauthAction extends Action +{ + + var $fbuid = null; + var $fb_fields = null; + + function prepare($args) { + parent::prepare($args); + + try { + + $this->fbuid = getFacebook()->get_loggedin_user(); + + if ($this->fbuid > 0) { + $this->fb_fields = $this->getFacebookFields($this->fbuid, + array('first_name', 'last_name', 'name')); + } else { + common_debug("No Facebook User found."); + } + + } catch (Exception $e) { + common_log(LOG_WARNING, 'Problem getting Facebook uid: ' . + $e->getMessage()); + } + + return true; + } + + function handle($args) + { + parent::handle($args); + + if (common_is_real_login()) { + + // User is already logged in. Does she already have a linked Facebook acct? + $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); + + if ($flink) { + + // User already has a linked Facebook account and shouldn't be here + common_debug('There is already a local user (' . $flink->user_id . + ') linked with this Facebook (' . $this->fbuid . ').'); + + // We don't want these cookies + getFacebook()->clear_cookie_state(); + + $this->clientError(_('There is already a local user linked with this Facebook.')); + + } else { + + // User came from the Facebook connect settings tab, and + // probably just wants to link/relink their Facebook account + $this->connectUser(); + } + + } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { + + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. Try again, please.')); + return; + } + if ($this->arg('create')) { + if (!$this->boolean('license')) { + $this->showForm(_('You can\'t register if you don\'t agree to the license.'), + $this->trimmed('newname')); + return; + } + $this->createNewUser(); + } else if ($this->arg('connect')) { + $this->connectNewUser(); + } else { + common_debug(print_r($this->args, true), __FILE__); + $this->showForm(_('Something weird happened.'), + $this->trimmed('newname')); + } + } else { + $this->tryLogin(); + } + } + + function showPageNotice() + { + if ($this->error) { + $this->element('div', array('class' => 'error'), $this->error); + } else { + $this->element('div', 'instructions', + sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name'))); + } + } + + function title() + { + return _('Facebook Account Setup'); + } + + function showForm($error=null, $username=null) + { + $this->error = $error; + $this->username = $username; + + $this->showPage(); + } + + function showPage() + { + parent::showPage(); + } + + function showContent() + { + if (!empty($this->message_text)) { + $this->element('p', null, $this->message); + return; + } + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_facebook_connect', + 'class' => 'form_settings', + 'action' => common_local_url('FBConnectAuth'))); + $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options')); + $this->element('legend', null, _('Connection options')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->element('input', array('type' => 'checkbox', + 'id' => 'license', + 'class' => 'checkbox', + 'name' => 'license', + 'value' => 'true')); + $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license')); + $this->text(_('My text and files are available under ')); + $this->element('a', array('href' => common_config('license', 'url')), + common_config('license', 'title')); + $this->text(_(' except this private data: password, email address, IM address, phone number.')); + $this->elementEnd('label'); + $this->elementEnd('li'); + $this->elementEnd('ul'); + + $this->elementStart('fieldset'); + $this->hidden('token', common_session_token()); + $this->element('legend', null, + _('Create new account')); + $this->element('p', null, + _('Create a new user with this nickname.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('newname', _('New nickname'), + ($this->username) ? $this->username : '', + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('create', _('Create')); + $this->elementEnd('fieldset'); + + $this->elementStart('fieldset'); + $this->element('legend', null, + _('Connect existing account')); + $this->element('p', null, + _('If you already have an account, login with your username and password to connect it to your Facebook.')); + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('nickname', _('Existing nickname')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->password('password', _('Password')); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('connect', _('Connect')); + $this->elementEnd('fieldset'); + + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + function message($msg) + { + $this->message_text = $msg; + $this->showPage(); + } + + function createNewUser() + { + + if (common_config('site', 'closed')) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = null; + + if (common_config('site', 'inviteonly')) { + $code = $_SESSION['invitecode']; + if (empty($code)) { + $this->clientError(_('Registration not allowed.')); + return; + } + + $invite = Invitation::staticGet($code); + + if (empty($invite)) { + $this->clientError(_('Not a valid invitation code.')); + return; + } + } + + $nickname = $this->trimmed('newname'); + + if (!Validate::string($nickname, array('min_length' => 1, + 'max_length' => 64, + 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { + $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.')); + return; + } + + if (!User::allowed_nickname($nickname)) { + $this->showForm(_('Nickname not allowed.')); + return; + } + + if (User::staticGet('nickname', $nickname)) { + $this->showForm(_('Nickname already in use. Try another one.')); + return; + } + + $fullname = trim($this->fb_fields['firstname'] . + ' ' . $this->fb_fields['lastname']); + + $args = array('nickname' => $nickname, 'fullname' => $fullname); + + if (!empty($invite)) { + $args['code'] = $invite->code; + } + + $user = User::register($args); + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_set_user($user); + common_real_login(true); + + common_debug("Registered new user $user->id from Facebook user $this->fbuid"); + + common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), + 303); + } + + function connectNewUser() + { + $nickname = $this->trimmed('nickname'); + $password = $this->trimmed('password'); + + if (!common_check_user($nickname, $password)) { + $this->showForm(_('Invalid username or password.')); + return; + } + + $user = User::staticGet('nickname', $nickname); + + if ($user) { + common_debug("Legit user to connect to Facebook: $nickname"); + } + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_debug("Connected Facebook user $this->fbuid to local user $user->id"); + + common_set_user($user); + common_real_login(true); + + $this->goHome($user->nickname); + } + + function connectUser() + { + $user = common_current_user(); + + $result = $this->flinkUser($user->id, $this->fbuid); + + if (!$result) { + $this->serverError(_('Error connecting user to Facebook.')); + return; + } + + common_debug("Connected Facebook user $this->fbuid to local user $user->id"); + + // Return to Facebook connection settings tab + common_redirect(common_local_url('FBConnectSettings'), 303); + } + + function tryLogin() + { + common_debug("Trying Facebook Login..."); + + $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE); + + if ($flink) { + $user = $flink->getUser(); + + if ($user) { + + common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)"); + + common_set_user($user); + common_real_login(true); + $this->goHome($user->nickname); + } + + } else { + + common_debug("No flink found for fbuid: $this->fbuid"); + + $this->showForm(null, $this->bestNewNickname()); + } + } + + function goHome($nickname) + { + $url = common_get_returnto(); + if ($url) { + // We don't have to return to it again + common_set_returnto(null); + } else { + $url = common_local_url('all', + array('nickname' => + $nickname)); + } + + common_redirect($url, 303); + } + + function flinkUser($user_id, $fbuid) + { + $flink = new Foreign_link(); + $flink->user_id = $user_id; + $flink->foreign_id = $fbuid; + $flink->service = FACEBOOK_CONNECT_SERVICE; + $flink->created = common_sql_now(); + + $flink_id = $flink->insert(); + + return $flink_id; + } + + function bestNewNickname() + { + if (!empty($this->fb_fields['name'])) { + $nickname = $this->nicknamize($this->fb_fields['name']); + if ($this->isNewNickname($nickname)) { + return $nickname; + } + } + + // Try the full name + + $fullname = trim($this->fb_fields['firstname'] . + ' ' . $this->fb_fields['lastname']); + + if (!empty($fullname)) { + $fullname = $this->nicknamize($fullname); + if ($this->isNewNickname($fullname)) { + return $fullname; + } + } + + return null; + } + + // Given a string, try to make it work as a nickname + + function nicknamize($str) + { + $str = preg_replace('/\W/', '', $str); + return strtolower($str); + } + + function isNewNickname($str) + { + if (!Validate::string($str, array('min_length' => 1, + 'max_length' => 64, + 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { + return false; + } + if (!User::allowed_nickname($str)) { + return false; + } + if (User::staticGet('nickname', $str)) { + return false; + } + return true; + } + + // XXX: Consider moving this to lib/facebookutil.php + function getFacebookFields($fb_uid, $fields) { + try { + + $facebook = getFacebook(); + + $infos = $facebook->api_client->users_getInfo($fb_uid, $fields); + + if (empty($infos)) { + return null; + } + return reset($infos); + + } catch (Exception $e) { + common_log(LOG_WARNING, "Facebook client failure when requesting " . + join(",", $fields) . " on uid " . $fb_uid . + " : ". $e->getMessage()); + return null; + } + } + +} diff --git a/plugins/FBConnect/FBConnectLogin.php b/plugins/FBConnect/FBConnectLogin.php index c2a288571..205086cd8 100644 --- a/plugins/FBConnect/FBConnectLogin.php +++ b/plugins/FBConnect/FBConnectLogin.php @@ -1,12 +1,9 @@ <?php -/** - * Laconica, the distributed open-source microblogging tool +/* + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2008, Controlez-Vous, Inc. * - * Plugin to enable Facebook Connect - * - * PHP version 5 - * - * LICENCE: This program is free software: you can redistribute it and/or modify + * 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. @@ -18,354 +15,53 @@ * * 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 Zach Copley <zach@controlyourself.ca> - * @copyright 2009 Control Yourself, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ */ -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; -require_once INSTALLDIR . '/lib/facebookutil.php'; - -class FBConnectloginAction extends Action -{ - - var $fbuid = null; - var $fb_fields = null; - - function prepare($args) { - parent::prepare($args); - - $this->fbuid = getFacebook()->get_loggedin_user(); - $this->fb_fields = $this->getFacebookFields($this->fbuid, - array('first_name', 'last_name', 'name')); +if (!defined('LACONICA')) { + exit(1); +} - return true; - } +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php'; +class FBConnectLoginAction extends Action +{ function handle($args) { parent::handle($args); if (common_is_real_login()) { $this->clientError(_('Already logged in.')); - } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { - $token = $this->trimmed('token'); - if (!$token || $token != common_session_token()) { - $this->showForm(_('There was a problem with your session token. Try again, please.')); - return; - } - if ($this->arg('create')) { - if (!$this->boolean('license')) { - $this->showForm(_('You can\'t register if you don\'t agree to the license.'), - $this->trimmed('newname')); - return; - } - $this->createNewUser(); - } else if ($this->arg('connect')) { - $this->connectUser(); - } else { - common_debug(print_r($this->args, true), __FILE__); - $this->showForm(_('Something weird happened.'), - $this->trimmed('newname')); - } - } else { - $this->tryLogin(); - } - } - - function showPageNotice() - { - if ($this->error) { - $this->element('div', array('class' => 'error'), $this->error); - } else { - $this->element('div', 'instructions', - sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name'))); - } - } - - function title() - { - return _('Facebook Account Setup'); - } - - function showForm($error=null, $username=null) - { - $this->error = $error; - $this->username = $username; - - $this->showPage(); - } - - function showPage() - { - parent::showPage(); - } - - function showContent() - { - if (!empty($this->message_text)) { - $this->element('p', null, $this->message); - return; } - $this->elementStart('form', array('method' => 'post', - 'id' => 'account_connect', - 'action' => common_local_url('fbconnectlogin'))); - $this->hidden('token', common_session_token()); - $this->element('h2', null, - _('Create new account')); - $this->element('p', null, - _('Create a new user with this nickname.')); - $this->input('newname', _('New nickname'), - ($this->username) ? $this->username : '', - _('1-64 lowercase letters or numbers, no punctuation or spaces')); - $this->elementStart('p'); - $this->element('input', array('type' => 'checkbox', - 'id' => 'license', - 'name' => 'license', - 'value' => 'true')); - $this->text(_('My text and files are available under ')); - $this->element('a', array('href' => common_config('license', 'url')), - common_config('license', 'title')); - $this->text(_(' except this private data: password, email address, IM address, phone number.')); - $this->elementEnd('p'); - $this->submit('create', _('Create')); - $this->element('h2', null, - _('Connect existing account')); - $this->element('p', null, - _('If you already have an account, login with your username and password to connect it to your Facebook.')); - $this->input('nickname', _('Existing nickname')); - $this->password('password', _('Password')); - $this->submit('connect', _('Connect')); - $this->elementEnd('form'); - } - - function message($msg) - { - $this->message_text = $msg; $this->showPage(); } - function createNewUser() - { - - if (common_config('site', 'closed')) { - $this->clientError(_('Registration not allowed.')); - return; - } - - $invite = null; - - if (common_config('site', 'inviteonly')) { - $code = $_SESSION['invitecode']; - if (empty($code)) { - $this->clientError(_('Registration not allowed.')); - return; - } - - $invite = Invitation::staticGet($code); - - if (empty($invite)) { - $this->clientError(_('Not a valid invitation code.')); - return; - } - } - - $nickname = $this->trimmed('newname'); - - if (!Validate::string($nickname, array('min_length' => 1, - 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { - $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.')); - return; - } - - if (!User::allowed_nickname($nickname)) { - $this->showForm(_('Nickname not allowed.')); - return; - } - - if (User::staticGet('nickname', $nickname)) { - $this->showForm(_('Nickname already in use. Try another one.')); - return; - } - - $fullname = trim($this->fb_fields['firstname'] . - ' ' . $this->fb_fields['lastname']); - - $args = array('nickname' => $nickname, 'fullname' => $fullname); - - if (!empty($invite)) { - $args['code'] = $invite->code; - } - - $user = User::register($args); - - $result = $this->flinkUser($user->id, $this->fbuid); - - if (!$result) { - $this->serverError(_('Error connecting user to Facebook.')); - return; - } - - common_set_user($user); - common_real_login(true); - - common_debug("Registered new user $user->id from Facebook user $this->fbuid"); - - common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)), - 303); - } - - function connectUser() - { - $nickname = $this->trimmed('nickname'); - $password = $this->trimmed('password'); - - if (!common_check_user($nickname, $password)) { - $this->showForm(_('Invalid username or password.')); - return; - } - - $user = User::staticGet('nickname', $nickname); - - if ($user) { - common_debug("Legit user to connect to Facebook: $nickname"); - } - - $result = $this->flinkUser($user->id, $this->fbuid); - - if (!$result) { - $this->serverError(_('Error connecting user to Facebook.')); - return; - } - - common_debug("Connected Facebook user $this->fbuid to local user $user->id"); - - common_set_user($user); - common_real_login(true); - - $this->goHome($user->nickname); - } - - function tryLogin() - { - common_debug("Trying Facebook Login..."); - - $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE); - - if ($flink) { - $user = $flink->getUser(); - - if ($user) { - - common_debug("Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)"); - - common_set_user($user); - common_real_login(true); - $this->goHome($user->nickname); - } - - } else { - $this->showForm(null, $this->bestNewNickname()); - } - } - - function goHome($nickname) + function getInstructions() { - $url = common_get_returnto(); - if ($url) { - // We don't have to return to it again - common_set_returnto(null); - } else { - $url = common_local_url('all', - array('nickname' => - $nickname)); - } - - common_redirect($url, 303); + return _('Login with your Facebook Account'); } - function flinkUser($user_id, $fbuid) + function showPageNotice() { - $flink = new Foreign_link(); - $flink->user_id = $user_id; - $flink->foreign_id = $fbuid; - $flink->service = FACEBOOK_SERVICE; - $flink->created = common_sql_now(); - - $flink_id = $flink->insert(); - - return $flink_id; + $instr = $this->getInstructions(); + $output = common_markup_to_html($instr); + $this->elementStart('div', 'instructions'); + $this->raw($output); + $this->elementEnd('div'); } - function bestNewNickname() - { - if (!empty($this->fb_fields['name'])) { - $nickname = $this->nicknamize($this->fb_fields['name']); - if ($this->isNewNickname($nickname)) { - return $nickname; - } - } - - // Try the full name - - $fullname = trim($this->fb_fields['firstname'] . - ' ' . $this->fb_fields['lastname']); - - if (!empty($fullname)) { - $fullname = $this->nicknamize($fullname); - if ($this->isNewNickname($fullname)) { - return $fullname; - } - } - - return null; - } - - // Given a string, try to make it work as a nickname - - function nicknamize($str) - { - $str = preg_replace('/\W/', '', $str); - return strtolower($str); - } - - function isNewNickname($str) + function title() { - if (!Validate::string($str, array('min_length' => 1, - 'max_length' => 64, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA_LOWER))) { - return false; - } - if (!User::allowed_nickname($str)) { - return false; - } - if (User::staticGet('nickname', $str)) { - return false; - } - return true; + return _('Facebook Login'); } - // XXX: Consider moving this to lib/facebookutil.php - function getFacebookFields($fb_uid, $fields) { - try { - $infos = getFacebook()->api_client->users_getInfo($fb_uid, $fields); + function showContent() { - if (empty($infos)) { - return null; - } - return reset($infos); + $this->elementStart('fieldset'); + $this->element('fb:login-button', array('onlogin' => 'goto_login()', + 'length' => 'long')); - } catch (Exception $e) { - error_log("Failure in the api when requesting " . join(",", $fields) - ." on uid " . $fb_uid . " : ". $e->getMessage()); - return null; - } + $this->elementEnd('fieldset'); } } diff --git a/plugins/FBConnect/FBConnectPlugin.css b/plugins/FBConnect/FBConnectPlugin.css new file mode 100644 index 000000000..564fdaee9 --- /dev/null +++ b/plugins/FBConnect/FBConnectPlugin.css @@ -0,0 +1,37 @@ +/** Styles for Facebook logo and Facebook user profile avatar. + * + * @package Laconica + * @author Sarven Capadisli <csarven@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +#site_nav_global_primary #nav_fb { +position:relative; +margin-left:18px; +margin-right:-7px; +} + +#nav_fb .fb_profile_pic_rendered img { +position:relative; +top:3px; +left:0; +display:inline; +border:1px solid #3B5998; +padding:1px; +} + +#nav_fb img { +position:absolute; +top:-13px; +left:-11px; +display:inline; +} + +#settings_facebook_connect_options legend { +display:none; +} +#form_settings_facebook_connect fieldset fieldset legend { +display:block; +} diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php index 342a62492..a366985be 100644 --- a/plugins/FBConnect/FBConnectPlugin.php +++ b/plugins/FBConnect/FBConnectPlugin.php @@ -31,8 +31,15 @@ if (!defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; +define("FACEBOOK_CONNECT_SERVICE", 3); + require_once INSTALLDIR . '/lib/facebookutil.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php'; +require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php'; /** * Plugin to enable Facebook Connect @@ -46,7 +53,6 @@ require_once INSTALLDIR . '/lib/facebookutil.php'; class FBConnectPlugin extends Plugin { - function __construct() { parent::__construct(); @@ -54,206 +60,240 @@ class FBConnectPlugin extends Plugin // Hook in new actions function onRouterInitialized(&$m) { - $m->connect('main/facebookconnect', array('action' => 'fbconnectlogin')); + $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth')); + $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin')); + $m->connect('settings/facebook', array('action' => 'FBConnectSettings')); + $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver')); } // Add in xmlns:fb function onStartShowHTML($action) { + // XXX: Horrible hack to make Safari, FF2, and Chrome work with + // Facebook Connect. These browser cannot use Facebook's + // DOM parsing routines unless the mime type of the page is + // text/html even though Facebook Connect uses XHTML. This is + // A bug in Facebook Connect, and this is a temporary solution + // until they fix their JavaScript libs. + header('Content-Type: text/html'); - // XXX: This is probably a bad place to do general processing - // so maybe I need to make some new events? Maybe in - // Action::prepare? - - $name = get_class($action); + $action->extraHeaders(); - // Avoid a redirect loop - if (!in_array($name, array('FBConnectloginAction', 'ClientErrorAction'))) { + $action->startXML('html', + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); - $this->checkFacebookUser($action); + $language = $action->getLanguage(); - } + $action->elementStart('html', + array('xmlns' => 'http://www.w3.org/1999/xhtml', + 'xmlns:fb' => 'http://www.facebook.com/2008/fbml', + 'xml:lang' => $language, + 'lang' => $language)); - $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ? - $_SERVER['HTTP_ACCEPT'] : null; + return false; + } - // XXX: allow content negotiation for RDF, RSS, or XRDS + // Note: this script needs to appear in the <body> - $cp = common_accept_to_prefs($httpaccept); - $sp = common_accept_to_prefs(PAGE_TYPE_PREFS); + function onStartShowHeader($action) + { + $apikey = common_config('facebook', 'apikey'); + $plugin_path = common_path('plugins/FBConnect'); - $type = common_negotiate_type($cp, $sp); + $login_url = common_local_url('FBConnectAuth'); + $logout_url = common_local_url('logout'); - if (!$type) { - throw new ClientException(_('This page is not available in a '. - 'media type you accept'), 406); - } + // XXX: Facebook says we don't need this FB_RequireFeatures(), + // but we actually do, for IE and Safari. Gar. + $html = sprintf('<script type="text/javascript"> + window.onload = function () { + FB_RequireFeatures( + ["XFBML"], + function() { + FB.Facebook.init("%s", "../xd_receiver.html"); + } + ); } - header('Content-Type: '.$type); + function goto_login() { + window.location = "%s"; + } - $action->extraHeaders(); + function goto_logout() { + window.location = "%s"; + } + </script>', $apikey, + $login_url, $logout_url); - $action->startXML('html', - '-//W3C//DTD XHTML 1.0 Strict//EN', - 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'); + $action->raw($html); + } - $language = $action->getLanguage(); + // Note: this script needs to appear as close as possible to </body> - $action->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml', - 'xmlns:fb' => 'http://www.facebook.com/2008/fbml', - 'xml:lang' => $language, - 'lang' => $language)); + function onEndShowFooter($action) + { - return false; + $action->element('script', + array('type' => 'text/javascript', + 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'), + ''); + } + function onEndShowLaconicaStyles($action) + { + $action->element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => common_path('plugins/FBConnect/FBConnectPlugin.css'))); } - function onEndShowLaconicaScripts($action) + function onStartPrimaryNav($action) { + $user = common_current_user(); - $action->element('script', - array('type' => 'text/javascript', - 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'), - ' '); + if ($user) { - $apikey = common_config('facebook', 'apikey'); - $plugin_path = common_path('plugins/FBConnect'); + $flink = Foreign_link::getByUserId($user->id, + FACEBOOK_CONNECT_SERVICE); + $fbuid = 0; - $url = common_get_returnto(); + if ($flink) { - if ($url) { - // We don't have to return to it again - common_set_returnto(null); - } else { - $url = common_local_url('public'); - } + try { - $html = sprintf('<script type="text/javascript">FB.init("%s", "%s/xd_receiver.htm"); + $facebook = getFacebook(); + $fbuid = getFacebook()->get_loggedin_user(); - function refresh_page() { - window.location = "%s"; - } + } catch (Exception $e) { + common_log(LOG_WARNING, + 'Problem getting Facebook client: ' . + $e->getMessage()); + } - </script>', $apikey, $plugin_path, $url); + // Display Facebook Logged in indicator w/Facebook favicon + if ($fbuid > 0) { - $action->raw($html); - } + $action->elementStart('li', array('id' => 'nav_fb')); + $action->elementStart('fb:profile-pic', array('uid' => $flink->foreign_id, + 'linked' => 'false', + 'width' => 16, + 'height' => 16)); + $action->elementEnd('fb:profile-pic'); - function onStartPrimaryNav($action) - { - $user = common_current_user(); + $iconurl = common_path('/plugins/FBConnect/fbfavicon.ico'); + $action->element('img', array('src' => $iconurl)); - if ($user) { - $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), - _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); - $action->menuItem(common_local_url('profilesettings'), - _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); - if (common_config('xmpp', 'enabled')) { - $action->menuItem(common_local_url('imsettings'), - _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect'); - } else { - $action->menuItem(common_local_url('smssettings'), - _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); - } - $action->menuItem(common_local_url('invite'), - _('Invite'), - sprintf(_('Invite friends and colleagues to join you on %s'), - common_config('site', 'name')), - false, 'nav_invitecontact'); + $action->elementEnd('li'); - // Need to override the Logout link to make it do FB stuff + } + } - $logout_url = common_local_url('logout'); - $title = _('Logout from the site'); - $text = _('Logout'); + $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)), + _('Home'), _('Personal profile and friends timeline'), false, 'nav_home'); + $action->menuItem(common_local_url('profilesettings'), + _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account'); + if (common_config('xmpp', 'enabled')) { + $action->menuItem(common_local_url('imsettings'), + _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect'); + } else { + $action->menuItem(common_local_url('smssettings'), + _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect'); + } + $action->menuItem(common_local_url('invite'), + _('Invite'), + sprintf(_('Invite friends and colleagues to join you on %s'), + common_config('site', 'name')), + false, 'nav_invitecontact'); + + // Need to override the Logout link to make it do FB stuff + if ($flink && $fbuid > 0) { - $html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' . - 'onclick="FB.Connect.logoutAndRedirect(\'%s\')">%s</a></li>', - $logout_url, $title, $logout_url, $text); + $logout_url = common_local_url('logout'); + $title = _('Logout from the site'); + $text = _('Logout'); - $action->raw($html); + $html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' . + 'onclick="FB.Connect.logout(function() { goto_logout() })">%s</a></li>', + $logout_url, $title, $text); + $action->raw($html); + + } else { + $action->menuItem(common_local_url('logout'), + _('Logout'), _('Logout from the site'), false, 'nav_logout'); + } } else { if (!common_config('site', 'closed')) { $action->menuItem(common_local_url('register'), - _('Register'), _('Create an account'), false, 'nav_register'); + _('Register'), _('Create an account'), false, 'nav_register'); } $action->menuItem(common_local_url('openidlogin'), - _('OpenID'), _('Login with OpenID'), false, 'nav_openid'); + _('OpenID'), _('Login with OpenID'), false, 'nav_openid'); $action->menuItem(common_local_url('login'), - _('Login'), _('Login to the site'), false, 'nav_login'); + _('Login'), _('Login to the site'), false, 'nav_login'); } $action->menuItem(common_local_url('doc', array('title' => 'help')), - _('Help'), _('Help me!'), false, 'nav_help'); + _('Help'), _('Help me!'), false, 'nav_help'); $action->menuItem(common_local_url('peoplesearch'), - _('Search'), _('Search for people or text'), false, 'nav_search'); - - // Tack on "Connect with Facebook" button - - // XXX: Maybe this looks bad and should not go here. Where should it go? - - if (!$user) { - $action->elementStart('li'); - $action->element('fb:login-button', array('onlogin' => 'refresh_page()', - 'length' => 'long')); - $action->elementEnd('li'); - } + _('Search'), _('Search for people or text'), false, 'nav_search'); return false; } - function checkFacebookUser() { + function onStartShowLocalNavBlock($action) + { + $action_name = get_class($action); - $user = common_current_user(); + $login_actions = array('LoginAction', 'RegisterAction', + 'OpenidloginAction', 'FBConnectLoginAction'); - if ($user) { - return; + if (in_array($action_name, $login_actions)) { + $nav = new FBCLoginGroupNav($action); + $nav->show(); + return false; } - try { - - $facebook = getFacebook(); - $fbuid = $facebook->get_loggedin_user(); + $connect_actions = array('SmssettingsAction', + 'TwittersettingsAction', 'FBConnectSettingsAction'); - // If you're a Facebook user and you're logged in do nothing + if (in_array($action_name, $connect_actions)) { + $nav = new FBCSettingsNav($action); + $nav->show(); + return false; + } - // If you're a Facebook user and you're not logged in - // redirect to Facebook connect login page because that means you have clicked - // the 'connect with Facebook' button and have cookies + return true; + } - if ($fbuid > 0) { + function onStartLogout($action) + { + $user = common_current_user(); - if ($facebook->api_client->users_isAppUser($fbuid) || - $facebook->api_client->added) { + $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE); - // user should be connected... + $action->logout(); - common_debug("Facebook user found: $fbuid"); + if ($flink) { - if ($user) { - common_debug("Facebook user is logged in."); - return; + $facebook = getFacebook(); - } else { - common_debug("Facebook user is NOT logged in."); - common_redirect(common_local_url('fbconnectlogin'), 303); - } + try { + $fbuid = $facebook->get_loggedin_user(); - } else { - common_debug("No Facebook connect user found."); + if ($fbuid > 0) { + $facebook->logout(common_local_url('public')); } - } - } catch (Exception $e) { - common_debug('Expired FB session.'); + } catch (Exception $e) { + common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' . + $e->getMessage()); + } } + return true; } } - - diff --git a/plugins/FBConnect/FBConnectSettings.php b/plugins/FBConnect/FBConnectSettings.php new file mode 100644 index 000000000..034ecebae --- /dev/null +++ b/plugins/FBConnect/FBConnectSettings.php @@ -0,0 +1,203 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Facebook Connect settings + * + * 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 Settings + * @package Laconica + * @author Zach Copley <zach@controlyourself.ca> + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/connectsettingsaction.php'; + +/** + * Facebook Connect settings action + * + * @category Settings + * @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 FBConnectSettingsAction extends ConnectSettingsAction +{ + /** + * Title of the page + * + * @return string Title of the page + */ + + function title() + { + return _('Facebook Connect Settings'); + } + + /** + * Instructions for use + * + * @return instructions for use + */ + + function getInstructions() + { + return _('Manage how your account connects to Facebook'); + } + + /** + * Content area of the page + * + * Shows a form for uploading an avatar. + * + * @return void + */ + + function showContent() + { + $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); + + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_facebook', + 'class' => 'form_settings', + 'action' => + common_local_url('FBConnectSettings'))); + + if (!$flink) { + + $this->element('p', 'instructions', + _('There is no Facebook user connected to this account.')); + + $this->element('fb:login-button', array('onlogin' => 'goto_login()', + 'length' => 'long')); + + } else { + + $this->element('p', 'form_note', + _('Connected Facebook user')); + + $this->elementStart('p', array('class' => 'facebook-user-display')); + $this->elementStart('fb:profile-pic', + array('uid' => $flink->foreign_id, + 'size' => 'small', + 'linked' => 'true', + 'facebook-logo' => 'true')); + $this->elementEnd('fb:profile-pic'); + + $this->elementStart('fb:name', array('uid' => $flink->foreign_id, + 'useyou' => 'false')); + $this->elementEnd('fb:name'); + $this->elementEnd('p'); + + $this->hidden('token', common_session_token()); + + $this->elementStart('fieldset'); + + $this->element('legend', null, _('Disconnect my account from Facebook')); + + if (!$user->password) { + + $this->elementStart('p', array('class' => 'form_guide')); + $this->text(_('Disconnecting your Faceboook ' . + 'would make it impossible to log in! Please ')); + $this->element('a', + array('href' => common_local_url('passwordsettings')), + _('set a password')); + + $this->text(_(' first.')); + $this->elementEnd('p'); + } else { + + $note = 'Keep your %s account but disconnect from Facebook. ' . + 'You\'ll use your %s password to log in.'; + + $site = common_config('site', 'name'); + + $this->element('p', 'instructions', + sprintf($note, $site, $site)); + + $this->submit('disconnect', _('Disconnect')); + } + + $this->elementEnd('fieldset'); + } + + $this->elementEnd('form'); + } + + /** + * Handle post + * + * Disconnects the current Facebook user from the current user's account + * + * @return void + */ + + function handlePost() + { + // CSRF protection + $token = $this->trimmed('token'); + if (!$token || $token != common_session_token()) { + $this->showForm(_('There was a problem with your session token. '. + 'Try again, please.')); + return; + } + + if ($this->arg('disconnect')) { + + $user = common_current_user(); + + $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE); + $result = $flink->delete(); + + if ($result === false) { + common_log_db_error($user, 'DELETE', __FILE__); + $this->serverError(_('Couldn\'t delete link to Facebook.')); + return; + } + + try { + + // Clear FB Connect cookies out + $facebook = getFacebook(); + $facebook->clear_cookie_state(); + + } catch (Exception $e) { + common_log(LOG_WARNING, + 'Couldn\'t clear Facebook cookies: ' . + $e->getMessage()); + } + + $this->showForm(_('You have disconnected from Facebook.'), true); + + } else { + $this->showForm(_('Not sure what you\'re trying to do.')); + return; + } + + } + +} diff --git a/plugins/FBConnect/fbfavicon.ico b/plugins/FBConnect/fbfavicon.ico Binary files differnew file mode 100644 index 000000000..c57c0342f --- /dev/null +++ b/plugins/FBConnect/fbfavicon.ico diff --git a/plugins/FBConnect/xd_receiver.htm b/plugins/FBConnect/xd_receiver.htm deleted file mode 100644 index 43fb2c4e4..000000000 --- a/plugins/FBConnect/xd_receiver.htm +++ /dev/null @@ -1,10 +0,0 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" > -<head> - <title>cross domain receiver page</title> -</head> -<body> - <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js" type="text/javascript"></script> -</body> -</html> diff --git a/plugins/WikiHashtagsPlugin.php b/plugins/WikiHashtagsPlugin.php new file mode 100644 index 000000000..6d186a5fe --- /dev/null +++ b/plugins/WikiHashtagsPlugin.php @@ -0,0 +1,109 @@ +<?php +/** + * Laconica, the distributed open-source microblogging tool + * + * Plugin to show WikiHashtags content in the sidebar + * + * 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 Evan Prodromou <evan@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); +} + +define('WIKIHASHTAGSPLUGIN_VERSION', '0.1'); + +/** + * Plugin to use WikiHashtags + * + * @category Plugin + * @package Laconica + * @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/ + * + * @see Event + */ + +class WikiHashtagsPlugin extends Plugin +{ + function __construct($code=null) + { + parent::__construct(); + } + + function onStartShowSections($action) + { + $name = $action->trimmed('action'); + + if ($name == 'tag') { + + $taginput = $action->trimmed('tag'); + $tag = common_canonical_tag($taginput); + + if (!empty($tag)) { + + $url = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=render', + urlencode($tag)); + $editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit', + urlencode($tag)); + + $context = stream_context_create(array('http' => array('method' => "GET", + 'header' => + "User-Agent: " . $this->userAgent()))); + $html = @file_get_contents($url, false, $context); + + $action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section')); + + if (!empty($html)) { + $action->element('style', null, + "span.editsection { display: none }\n". + "table.toc { display: none }"); + $action->raw($html); + $action->elementStart('p'); + $action->element('a', array('href' => $editurl, + 'title' => sprintf(_('Edit the article for #%s on WikiHashtags'), $tag)), + _('Edit')); + $action->element('a', array('href' => 'http://www.gnu.org/copyleft/fdl.html', + 'title' => _('Shared under the terms of the GNU Free Documentation License'), + 'rel' => 'license'), + 'GNU FDL'); + $action->elementEnd('p'); + } else { + $action->element('a', array('href' => $editurl), + sprintf(_('Start the article for #%s on WikiHashtags'), $tag)); + } + + $action->elementEnd('div'); + } + } + + return true; + } + + function userAgent() + { + return 'WikiHashtagsPlugin/'.WIKIHASHTAGSPLUGIN_VERSION . + ' Laconica/' . LACONICA_VERSION; + } +} diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php new file mode 100644 index 000000000..e5021ff34 --- /dev/null +++ b/scripts/fixup_utf8.php @@ -0,0 +1,141 @@ +#!/usr/bin/env php +<?php +/* + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2009, Control Yourself, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +# Abort if called from a web server +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(1); +} + +ini_set("max_execution_time", "0"); +ini_set("max_input_time", "0"); +set_time_limit(0); +mb_internal_encoding('UTF-8'); + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); +require_once('DB.php'); + +function fixup_utf8($id) { + + $dbl = doConnect('latin1'); + + if (empty($dbl)) { + return; + } + + $dbu = doConnect('utf8'); + + if (empty($dbu)) { + return; + } + + // Do a separate DB connection + + $sth = $dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?"); + + if (PEAR::isError($sth)) { + echo "ERROR: " . $sth->getMessage() . "\n"; + return; + } + + $sql = 'SELECT id, content, rendered FROM notice ' . + 'WHERE LENGTH(content) != CHAR_LENGTH(content)'; + + if (!empty($id)) { + $sql .= ' AND id < ' . $id; + } + + $sql .= ' ORDER BY id DESC'; + + $rn = $dbl->query($sql); + + if (PEAR::isError($rn)) { + echo "ERROR: " . $rn->getMessage() . "\n"; + return; + } + + echo "Number of rows: " . $rn->numRows() . "\n"; + + $notice = array(); + + while (DB_OK == $rn->fetchInto($notice)) { + + $id = ($notice[0])+0; + $content = bin2hex($notice[1]); + $rendered = bin2hex($notice[2]); + + echo "$id..."; + + $result =& $dbu->execute($sth, array($content, $rendered, $id)); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + continue; + } + + $cnt = $dbu->affectedRows(); + + if ($cnt != 1) { + echo "ERROR: 0 rows affected\n"; + continue; + } + + $notice = Notice::staticGet('id', $id); + $notice->decache(); + + echo "OK\n"; + } +} + +function doConnect($charset) +{ + $db = DB::connect(common_config('db', 'database'), + array('persistent' => false)); + + if (PEAR::isError($db)) { + echo "ERROR: " . $db->getMessage() . "\n"; + return NULL; + } + + $result = $db->query("SET NAMES $charset"); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + $db->disconnect(); + return NULL; + } + + $result = $db->autoCommit(true); + + if (PEAR::isError($result)) { + echo "ERROR: " . $result->getMessage() . "\n"; + $db->disconnect(); + return NULL; + } + + return $db; +} + +$id = ($argc > 1) ? $argv[1] : null; + +fixup_utf8($id); diff --git a/scripts/reportsnapshot.php b/scripts/reportsnapshot.php new file mode 100644 index 000000000..e332d856c --- /dev/null +++ b/scripts/reportsnapshot.php @@ -0,0 +1,37 @@ +#!/usr/bin/env php +<?php +/* + * Laconica - a distributed open-source microblogging tool + * Copyright (C) 2009, Control Yourself, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +# Abort if called from a web server +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(1); +} + +ini_set("max_execution_time", "0"); +ini_set("max_input_time", "0"); +set_time_limit(0); +mb_internal_encoding('UTF-8'); + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); + +Snapshot::check(); diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php index 9dfadc760..a61ce1b0d 100755 --- a/scripts/twitterstatusfetcher.php +++ b/scripts/twitterstatusfetcher.php @@ -214,7 +214,8 @@ class TwitterStatusFetcher extends Daemon return; } - foreach ($timeline as $status) { + // Reverse to preserve order + foreach (array_reverse($timeline) as $status) { // Hacktastic: filter out stuff coming from this Laconica $source = mb_strtolower(common_config('integration', 'source')); diff --git a/theme/base/css/display.css b/theme/base/css/display.css index 8bd0ae1c4..dc275e19f 100644 --- a/theme/base/css/display.css +++ b/theme/base/css/display.css @@ -198,9 +198,11 @@ padding:0 7px; } +.form_settings input.form_action-primary { +padding:0; +} .form_settings input.form_action-secondary { margin-left:29px; -padding:0; } #form_search .submit { @@ -450,6 +452,21 @@ float:left; font-size:1.3em; margin-bottom:7px; } +#form_notice label[for=notice_data-attach] { +text-indent:-9999px; +} +#form_notice label[for=notice_data-attach], +#form_notice #notice_data-attach { +position:absolute; +top:25px; +right:49px; +width:16px; +height:16px; +cursor:pointer; +} +#form_notice #notice_data-attach { +text-indent:-279px; +} #form_notice #notice_submit label { display:none; } @@ -740,12 +757,14 @@ border-top-style:dotted; .notices li { list-style-type:none; } -.notices li.hover { -border-radius:4px; --moz-border-radius:4px; --webkit-border-radius:4px; +.notices .notices { +margin-top:7px; +margin-left:5%; +width:95%; +float:left; } + /* NOTICES */ #notices_primary { float:left; @@ -794,6 +813,9 @@ float:left; width:100%; overflow:hidden; } +.notice .entry-title.ov { +overflow:visible; +} #shownotice .notice .entry-title { font-size:2.2em; } @@ -818,7 +840,7 @@ clear:left; float:left; font-size:0.95em; margin-left:59px; -width:65%; +width:60%; } #showstream .notice div.entry-content, #shownotice .notice div.entry-content { @@ -848,15 +870,12 @@ display:inline-block; text-transform:lowercase; } - .notice-options { -padding-left:2%; -float:left; -width:50%; position:relative; font-size:0.95em; -width:12.5%; +width:90px; float:right; +margin-right:11px; } .notice-options a { @@ -918,6 +937,75 @@ padding:0; } +.notice .attachment { +position:relative; +padding-left:16px; +} +#attachments .attachment { +padding-left:0; +} +.notice .attachment img { +position:absolute; +top:18px; +left:0; +z-index:99; +} +#shownotice .notice .attachment img { +position:static; +} + +#attachments { +clear:both; +float:left; +width:100%; +margin-top:18px; +} +#attachments dt { +font-weight:bold; +font-size:1.3em; +margin-bottom:4px; +} + +#attachments ol li { +margin-bottom:18px; +list-style-type:decimal; +float:left; +clear:both; +} + +#jOverlayContent, +#jOverlayContent #content, +#jOverlayContent #content_inner { +width: auto !important; +margin-bottom:0; +} +#jOverlayContent #content { +padding:11px; +min-height:auto; +} +#jOverlayContent .external span { +display:block; +margin-bottom:11px; +} +#jOverlayContent button { +position:absolute; +top:0; +right:0; +width:29px; +height:29px; +text-align:center; +font-weight:bold; +padding:0; +} +#jOverlayContent h1 { +max-width:475px; +} +#jOverlayContent #content { +border-radius:7px; +-moz-border-radius:7px; +-webkit-border-radius:7px; +} + #usergroups #new_group { float: left; margin-right: 2em; @@ -1040,8 +1128,6 @@ margin-left:18px; } - - /* TOP_POSTERS */ .section tbody td { padding-right:18px; @@ -1166,6 +1252,7 @@ width:33%; } #settings_design_color .form_data label { float:none; +display:block; } #settings_design_color .form_data .swatch { padding:11px; diff --git a/theme/base/css/facebookapp.css b/theme/base/css/facebookapp.css index 163b41fb4..e6b1c9ee5 100644 --- a/theme/base/css/facebookapp.css +++ b/theme/base/css/facebookapp.css @@ -1,6 +1,3 @@ -@import url("display.css"); -@import url("../../identica/css/display.css"); - * { font-size:14px; font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif; diff --git a/theme/base/css/ie.css b/theme/base/css/ie.css index 5d8bea8ae..8183fee67 100644 --- a/theme/base/css/ie.css +++ b/theme/base/css/ie.css @@ -30,3 +30,12 @@ margin-right:4px; .entity_profile { width:64%; } +.notice { +z-index:1; +} +.notice:hover { +z-index:9999; +} +.notice .thumbnail img { +z-index:9999; +}
\ No newline at end of file diff --git a/theme/base/images/icons/clip-inline.png b/theme/base/images/icons/clip-inline.png Binary files differnew file mode 100644 index 000000000..870f8b2e8 --- /dev/null +++ b/theme/base/images/icons/clip-inline.png diff --git a/theme/base/images/icons/twotone/green/clip-01.gif b/theme/base/images/icons/twotone/green/clip-01.gif Binary files differnew file mode 100644 index 000000000..f2dee7e5e --- /dev/null +++ b/theme/base/images/icons/twotone/green/clip-01.gif diff --git a/theme/base/images/icons/twotone/green/clip-02.gif b/theme/base/images/icons/twotone/green/clip-02.gif Binary files differnew file mode 100644 index 000000000..77a729799 --- /dev/null +++ b/theme/base/images/icons/twotone/green/clip-02.gif diff --git a/theme/biz/css/base.css b/theme/biz/css/base.css index 22bbced08..696fd0645 100644 --- a/theme/biz/css/base.css +++ b/theme/biz/css/base.css @@ -198,11 +198,14 @@ padding:0 7px; } +.form_settings input.form_action-primary { +padding:0; +} .form_settings input.form_action-secondary { margin-left:29px; -padding:0; } + #form_search .submit { margin-left:11px; } @@ -443,6 +446,27 @@ float:left; font-size:1.3em; margin-bottom:7px; } +#form_notice label { +display:block; +float:left; +font-size:1.3em; +margin-bottom:7px; +} +#form_notice label[for=notice_data-attach] { +text-indent:-9999px; +} +#form_notice label[for=notice_data-attach], +#form_notice #notice_data-attach { +position:absolute; +top:25px; +right:49px; +width:16px; +height:16px; +cursor:pointer; +} +#form_notice #notice_data-attach { +text-indent:-279px; +} #form_notice #notice_submit label { display:none; } diff --git a/theme/biz/css/display.css b/theme/biz/css/display.css index a7d360c53..3af4c06b9 100644 --- a/theme/biz/css/display.css +++ b/theme/biz/css/display.css @@ -36,7 +36,7 @@ border-color:#aaa; border-color:#ddd; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -65,7 +65,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#002E6E; } @@ -102,6 +102,13 @@ color:#333; #form_notice.warning #notice_text-count { color:#000; } +#form_notice label[for=notice_data-attach] { +background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +} +#form_notice #notice_data-attach { +opacity:0; +} + #form_notice.processing #notice_action-submit { background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css index e97889685..12f186a56 100644 --- a/theme/cloudy/css/display.css +++ b/theme/cloudy/css/display.css @@ -199,9 +199,11 @@ padding:0 7px; } +.form_settings input.form_action-primary { +padding:0; +} .form_settings input.form_action-secondary { margin-left:29px; -padding:0; } #form_search .submit { @@ -1267,7 +1269,7 @@ border-color:#aaa; border-color:#ddd; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -1296,7 +1298,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#0084B4; } diff --git a/theme/default/css/display.css b/theme/default/css/display.css index e4b57ef49..34f6b3b8a 100644 --- a/theme/default/css/display.css +++ b/theme/default/css/display.css @@ -33,7 +33,7 @@ border-color:#aaa; border-color:#C3D6DF; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -60,7 +60,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#002E6E; } @@ -82,6 +82,13 @@ color:#333; #form_notice.warning #notice_text-count { color:#000; } +#form_notice label[for=notice_data-attach] { +background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +} +#form_notice #notice_data-attach { +opacity:0; +} + #form_notice.processing #notice_action-submit { background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; @@ -175,10 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif); } /* NOTICES */ -.notices li.over { -background-color:#fcfcfc; +.notice .attachment { +background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%; +} +#attachments .attachment { +background:none; } - .notice-options .notice_reply a, .notice-options form input.submit { background-color:transparent; @@ -197,7 +206,9 @@ background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-r } .notices div.entry-content, -.notices div.notice-options { +.notices div.notice-options, +.notices li.hover .notices div.entry-content, +.notices li.hover .notices div.notice-options { opacity:0.4; } .notices li.hover div.entry-content, @@ -214,6 +225,19 @@ font-family:sans-serif; .notices li.hover { background-color:#fcfcfc; } + +.notices .notices { +background-color:rgba(200, 200, 200, 0.050); +} +.notices .notices .notices { +background-color:rgba(200, 200, 200, 0.100); +} +.notices .notices .notices .notices { +background-color:rgba(200, 200, 200, 0.150); +} +.notices .notices .notices .notices .notices { +background-color:rgba(200, 200, 200, 0.300); +} /*END: NOTICES */ #new_group a { diff --git a/theme/h4ck3r/css/base.css b/theme/h4ck3r/css/base.css index 5060bbb8b..41b3a77e6 100644 --- a/theme/h4ck3r/css/base.css +++ b/theme/h4ck3r/css/base.css @@ -189,9 +189,11 @@ padding:0 7px; } +.form_settings input.form_action-primary { +padding:0; +} .form_settings input.form_action-secondary { margin-left:29px; -padding:0; } #form_search .submit { diff --git a/theme/h4ck3r/css/display.css b/theme/h4ck3r/css/display.css index c7631a8eb..31d49a58e 100644 --- a/theme/h4ck3r/css/display.css +++ b/theme/h4ck3r/css/display.css @@ -38,7 +38,7 @@ color:#ccc; border-color:#ddd; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -65,7 +65,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#0f0; } diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css index 9d625848f..8a03a4d77 100644 --- a/theme/identica/css/display.css +++ b/theme/identica/css/display.css @@ -33,7 +33,7 @@ border-color:#aaa; border-color:#ddd; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -60,7 +60,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#002E6E; } @@ -82,6 +82,13 @@ color:#333; #form_notice.warning #notice_text-count { color:#000; } +#form_notice label[for=notice_data-attach] { +background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%; +} +#form_notice #notice_data-attach { +opacity:0; +} + #form_notice.processing #notice_action-submit { background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%; cursor:wait; @@ -175,10 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif); } /* NOTICES */ -.notices li.over { -background-color:#fcfcfc; +.notice .attachment { +background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%; +} +#attachments .attachment { +background:none; } - .notice-options .notice_reply a, .notice-options form input.submit { background-color:transparent; @@ -197,7 +206,9 @@ background:transparent url(../../base/images/icons/twotone/green/trash.gif) no-r } .notices div.entry-content, -.notices div.notice-options { +.notices div.notice-options, +.notices li.hover .notices div.entry-content, +.notices li.hover .notices div.notice-options { opacity:0.4; } .notices li.hover div.entry-content, @@ -214,6 +225,19 @@ font-family:sans-serif; .notices li.hover { background-color:#fcfcfc; } + +.notices .notices { +background-color:rgba(200, 200, 200, 0.050); +} +.notices .notices .notices { +background-color:rgba(200, 200, 200, 0.100); +} +.notices .notices .notices .notices { +background-color:rgba(200, 200, 200, 0.150); +} +.notices .notices .notices .notices .notices { +background-color:rgba(200, 200, 200, 0.300); +} /*END: NOTICES */ #new_group a { diff --git a/theme/otalk/css/base.css b/theme/otalk/css/base.css index 32e8891d2..b39992570 100644 --- a/theme/otalk/css/base.css +++ b/theme/otalk/css/base.css @@ -198,9 +198,11 @@ padding:0 7px; } +.form_settings input.form_action-primary { +padding:0; +} .form_settings input.form_action-secondary { margin-left:29px; -padding:0; } #form_search .submit { diff --git a/theme/otalk/css/display.css b/theme/otalk/css/display.css index 6c646791b..d2a4719a8 100644 --- a/theme/otalk/css/display.css +++ b/theme/otalk/css/display.css @@ -37,7 +37,7 @@ border-color:#aaa; border-color:#ddd; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -64,7 +64,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#8F0000; } diff --git a/theme/pigeonthoughts/css/base.css b/theme/pigeonthoughts/css/base.css index 179719820..08427d3c8 100644 --- a/theme/pigeonthoughts/css/base.css +++ b/theme/pigeonthoughts/css/base.css @@ -199,9 +199,11 @@ padding:0 7px; } +.form_settings input.form_action-primary { +padding:0; +} .form_settings input.form_action-secondary { margin-left:29px; -padding:0; } #form_search .submit { diff --git a/theme/pigeonthoughts/css/display.css b/theme/pigeonthoughts/css/display.css index 19341ef7f..af31cf78d 100644 --- a/theme/pigeonthoughts/css/display.css +++ b/theme/pigeonthoughts/css/display.css @@ -36,7 +36,7 @@ border-color:#aaa; border-color:#ddd; } -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { background:none; } @@ -63,7 +63,7 @@ div.notice-options input, .entity_send-a-message a, .form_user_nudge input.submit, .entity_nudge p, -.form_settings input.form_action-secondary { +.form_settings input.form_action-primary { color:#000; } |