diff --git a/.gitignore b/.gitignore
index da6947bfdc..3418d8ee54 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
diff --git a/README b/README
index 4f93829601..9cbe84f0e5 100644
--- a/README
+++ b/README
@@ -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 8762b4bcd3..b25ba99f30 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 b9187ff081..16ee723d96 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');
+ if ($id = $this->trimmed('attachment')) {
+ $this->attachment = File::staticGet($id);
+ }
- $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 1620b27dda..3d83393c51 100644
--- a/actions/attachment_ajax.php
+++ b/actions/attachment_ajax.php
@@ -45,26 +45,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.
*
@@ -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
similarity index 56%
rename from actions/attachments_ajax.php
rename to actions/attachment_thumbnail.php
index 402d8b5e79..b4070e747b 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
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 6b31c839da..0000000000
--- a/actions/attachments.php
+++ /dev/null
@@ -1,292 +0,0 @@
-.
- *
- * @category Personal
- * @package Laconica
- * @author Evan Prodromou
- * @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
- * @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 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 05cfb76e3c..0d7cb9a874 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
+ * @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
+ * @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
; we skip, since the ConversationTree
+ * takes care of that.
+ *
+ * @return void
+ */
+
+ function showStart()
+ {
+ return;
+ }
+
+ /**
+ * finish the notice
+ *
+ * The default closes the
; 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 6c350b33ab..e733f9650a 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 a85b36a254..5774b85378 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 5946e6c984..00b35ef686 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 1302064ad7..2207580f77 100644
--- a/actions/facebookinvite.php
+++ b/actions/facebookinvite.php
@@ -17,7 +17,9 @@
* along with this program. If not, see .
*/
-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('');
@@ -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 236460c1c9..227e123160 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 0000000000..1179dbe9ac
--- /dev/null
+++ b/actions/file.php
@@ -0,0 +1,40 @@
+.
+ */
+
+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 ae0ff96363..02976a2ae2 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 2c469c9dee..34c8a8e946 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 47420e4c33..e9351d2419 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 e5913115b7..24ab11b8eb 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 0eae681783..212cc36158 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 6065609512..c0b356eced 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
old mode 100755
new mode 100644
diff --git a/classes/Group_member.php b/classes/Group_member.php
old mode 100755
new mode 100644
diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php
index 5f71f716b3..52ad4100fc 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 1b5c0ab0a5..0b1017e126 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
old mode 100755
new mode 100644
diff --git a/classes/Status_network.php b/classes/Status_network.php
old mode 100755
new mode 100644
diff --git a/classes/User_group.php b/classes/User_group.php
old mode 100755
new mode 100644
diff --git a/classes/laconica.ini b/classes/laconica.ini
index 316923af02..92bbb35d4c 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 826b086a3b..636f4cf8e2 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;
+// $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';
-# 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;
-# 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';
-#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
+// 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';
+// $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';
+// 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'] .'/');
+// 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';
+// 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
+// sphinx search
$config['sphinx']['enabled'] = false;
$config['sphinx']['server'] = 'localhost';
$config['sphinx']['port'] = 3312;
-# Users to populate the 'Featured' tab
-#$config['nickname']['featured'][] = 'scobleizer';
+// 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;
+// 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';
+// 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';
+// 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 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;
+// 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;
+// 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;
+// 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';
+// 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;
+// 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';
+// 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;
+// 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;
+// 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';
-#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.
-# 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;
-#$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;
-# 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';
-# 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');
-# Facebook Connect plugin (Needs valid APIKEY above)
-#require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php');
-#$fbc = new FBConnectPlugin();
+// Use Templating (template: /tpl/index.php)
+// require_once('plugins/TemplatePlugin.php');
+// $tpl = new TemplatePlugin();
-# Add Google Analytics
-# require_once('plugins/GoogleAnalyticsPlugin.php');
-# $ga = new GoogleAnalyticsPlugin('your secret code');
+// 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;
-# Use Templating (template: /tpl/index.php)
-# require_once('plugins/TemplatePlugin.php');
-# $tpl = new TemplatePlugin();
+// 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 allow saying the same thing more than once per hour
-#$config['site']['dupelimit'] = 3600;
-#Don't enforce the dupe limit
-#$config['site']['dupelimit'] = -1;
+// 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';
-#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'
+// Use a different hostname for SSL-encrypted pages
+// $config['site']['sslserver'] = 'secure.example.org';
-#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';
+// 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);
+// }
-#Use a different hostname for SSL-encrypted pages
-#$config['site']['sslserver'] = 'secure.example.org';
+// 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;
-#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);
-#}
diff --git a/db/foreign_services.sql b/db/foreign_services.sql
index 557ede0246..79c04cee56 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 344f0ff723..a11e316925 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 f026679d50..d5a280b82c 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 0000000000..c335f8d92d
--- /dev/null
+++ b/extlib/MIME/Type.php
@@ -0,0 +1,523 @@
+ |
+// +----------------------------------------------------------------------+
+//
+// $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
+ */
+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 0000000000..1987e2a10e
--- /dev/null
+++ b/extlib/MIME/Type/Extension.php
@@ -0,0 +1,298 @@
+ |
+// +----------------------------------------------------------------------+
+//
+// $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
+ * @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 0000000000..399d3dd7ad
--- /dev/null
+++ b/extlib/MIME/Type/Parameter.php
@@ -0,0 +1,163 @@
+ |
+// +----------------------------------------------------------------------+
+//
+// $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
+ */
+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
diff --git a/index.php b/index.php
index 9ff1c2c56a..4eff99dff5 100644
--- a/index.php
+++ b/index.php
@@ -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 32915200b2..133f2b30f6 100644
--- a/install.php
+++ b/install.php
@@ -35,15 +35,17 @@ function main()
function checkPrereqs()
{
+ $pass = true;
+
if (file_exists(INSTALLDIR.'/config.php')) {
?>
Config file "config.php" already exists.
Require PHP version 5 or greater.
Cannot load required extension:
Cannot write config file to:
On your server, try this command: chmod a+w
Cannot write avatar directory: /avatar/
On your server, try this command: chmod a+w /avatar/