summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--EVENTS.txt14
-rw-r--r--actions/attachment.php209
-rw-r--r--actions/attachment_ajax.php141
-rw-r--r--actions/attachments.php292
-rw-r--r--actions/attachments_ajax.php115
-rw-r--r--actions/designsettings.php95
-rw-r--r--actions/logout.php16
-rw-r--r--actions/newnotice.php23
-rw-r--r--actions/showstream.php16
-rw-r--r--actions/tag.php3
-rw-r--r--actions/userrss.php24
-rw-r--r--classes/File.php123
-rw-r--r--classes/File_oembed.php87
-rw-r--r--classes/File_redirection.php274
-rw-r--r--classes/File_thumbnail.php55
-rw-r--r--classes/File_to_post.php60
-rw-r--r--classes/Notice.php26
-rw-r--r--classes/Profile.php54
-rw-r--r--classes/User.php11
-rw-r--r--[-rwxr-xr-x]classes/laconica.ini61
-rw-r--r--classes/laconica.links.ini14
-rw-r--r--db/laconica.sql59
-rw-r--r--db/laconica_pg.sql58
-rw-r--r--extlib/facebook/facebook.php147
-rw-r--r--extlib/facebook/facebook_desktop.php2
-rwxr-xr-x[-rw-r--r--]extlib/facebook/facebookapi_php5_restlib.php1026
-rw-r--r--index.php6
-rw-r--r--install.php31
-rw-r--r--js/farbtastic/farbtastic.go.js69
-rw-r--r--js/install.js18
-rw-r--r--js/jquery.joverlay.min.js6
-rw-r--r--js/util.js4
-rw-r--r--lib/Shorturl_api.php3
-rw-r--r--lib/action.php29
-rw-r--r--lib/attachmentlist.php300
-rw-r--r--lib/attachmentnoticesection.php75
-rw-r--r--lib/attachmentsection.php80
-rw-r--r--lib/attachmenttagcloudsection.php83
-rw-r--r--lib/facebookutil.php15
-rw-r--r--lib/frequentattachmentsection.php66
-rw-r--r--lib/noticelist.php71
-rw-r--r--lib/noticesection.php35
-rw-r--r--lib/popularnoticesection.php2
-rw-r--r--lib/profileaction.php13
-rw-r--r--lib/router.php24
-rw-r--r--lib/rssaction.php6
-rw-r--r--lib/subgroupnav.php68
-rw-r--r--lib/tagcloudsection.php6
-rw-r--r--lib/util.php111
-rw-r--r--plugins/FBConnect/FBConnectLogin.php371
-rw-r--r--plugins/FBConnect/FBConnectPlugin.php259
-rw-r--r--plugins/FBConnect/xd_receiver.htm10
-rw-r--r--theme/base/css/display.css26
-rw-r--r--theme/base/images/icons/clip-big.pngbin0 -> 11245 bytes
-rw-r--r--theme/base/images/icons/clip.pngbin0 -> 2298 bytes
-rw-r--r--theme/biz/logo.pngbin4988 -> 2228 bytes
-rw-r--r--theme/cloudy/css/display.css4
-rw-r--r--theme/cloudy/default-avatar-mini.pngbin1006 -> 1349 bytes
-rw-r--r--theme/cloudy/default-avatar-profile.pngbin9026 -> 9256 bytes
-rw-r--r--theme/cloudy/default-avatar-stream.pngbin2963 -> 3829 bytes
-rw-r--r--theme/cloudy/logo.pngbin4988 -> 2228 bytes
-rw-r--r--theme/default/css/display.css7
-rw-r--r--theme/earthy/logo.pngbin4988 -> 0 bytes
-rw-r--r--theme/h4ck3r/css/base.css (renamed from theme/earthy/css/base.css)143
-rw-r--r--theme/h4ck3r/css/display.css (renamed from theme/earthy/css/display.css)73
-rw-r--r--theme/h4ck3r/css/ie.css (renamed from theme/earthy/css/ie.css)0
-rw-r--r--theme/h4ck3r/default-avatar-mini.png (renamed from theme/earthy/default-avatar-mini.png)bin646 -> 646 bytes
-rw-r--r--theme/h4ck3r/default-avatar-profile.png (renamed from theme/earthy/default-avatar-profile.png)bin2853 -> 2853 bytes
-rw-r--r--theme/h4ck3r/default-avatar-stream.png (renamed from theme/earthy/default-avatar-stream.png)bin1487 -> 1487 bytes
-rw-r--r--theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gifbin0 -> 432979 bytes
-rw-r--r--theme/h4ck3r/logo.pngbin0 -> 2228 bytes
-rw-r--r--theme/identica/css/display.css7
-rw-r--r--theme/otalk/css/base.css16
-rw-r--r--theme/otalk/css/display.css1
-rw-r--r--theme/otalk/logo.pngbin4988 -> 2228 bytes
-rw-r--r--theme/pigeonthoughts/logo.pngbin4988 -> 2228 bytes
-rw-r--r--theme/readme.txt10
77 files changed, 4509 insertions, 544 deletions
diff --git a/EVENTS.txt b/EVENTS.txt
index 5edf59245..8e917f11d 100644
--- a/EVENTS.txt
+++ b/EVENTS.txt
@@ -100,6 +100,20 @@ StartPublicGroupNav: Showing the public group nav menu
EndPublicGroupNav: At the end of the public group nav menu
- $action: the current action
+StartSubGroupNav: Showing the subscriptions group nav menu
+- $action: the current action
+
+EndSubGroupNav: At the end of the subscriptions group nav menu
+- $action: the current action
+
RouterInitialized: After the router instance has been initialized
- $m: the Net_URL_Mapper that has just been set up
+StartLogout: Before logging out
+- $action: the logout action
+
+EndLogout: After logging out
+- $action: the logout action
+
+ArgsInitialized: After the argument array has been initialized
+- $args: associative array of arguments, can be modified
diff --git a/actions/attachment.php b/actions/attachment.php
new file mode 100644
index 000000000..b9187ff08
--- /dev/null
+++ b/actions/attachment.php
@@ -0,0 +1,209 @@
+<?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 AttachmentAction extends Action
+{
+ /**
+ * Attachment object to show
+ */
+
+ var $attachment = null;
+
+ /**
+ * 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('attachment');
+
+ $this->attachment = File::staticGet($id);
+
+ if (!$this->attachment) {
+ $this->clientError(_('No such attachment.'), 404);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Is this action read-only?
+ *
+ * @return boolean true
+ */
+
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /**
+ * Title of the page
+ *
+ * @return string title of the page
+ */
+ function title()
+ {
+ $a = new Attachment($this->attachment);
+ return $a->title();
+ }
+
+ /**
+ * 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)) . '"';
+ }
+*/
+
+
+ /**
+ * Handle input
+ *
+ * Only handles get, so just show the page.
+ *
+ * @param array $args $_REQUEST data (unused)
+ *
+ * @return void
+ */
+
+ function handle($args)
+ {
+ parent::handle($args);
+ $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()
+ {
+ $this->elementStart('ul', array('class' => 'attachments'));
+ $ali = new Attachment($this->attachment, $this);
+ $cnt = $ali->show();
+ $this->elementEnd('ul');
+ }
+
+ /**
+ * Don't show page notice
+ *
+ * @return void
+ */
+
+ function showPageNoticeBlock()
+ {
+ }
+
+ /**
+ * Show aside: this attachments appears in what notices
+ *
+ * @return void
+ */
+ function showSections() {
+ $ns = new AttachmentNoticeSection($this);
+ $ns->show();
+ $atcs = new AttachmentTagCloudSection($this);
+ $atcs->show();
+ }
+}
+
diff --git a/actions/attachment_ajax.php b/actions/attachment_ajax.php
new file mode 100644
index 000000000..1620b27dd
--- /dev/null
+++ b/actions/attachment_ajax.php
@@ -0,0 +1,141 @@
+<?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.'/actions/attachment.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 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
+ */
+ function showPage()
+ {
+ if (Event::handle('StartShowBody', array($this))) {
+ $this->showCore();
+ Event::handle('EndShowBody', array($this));
+ }
+ }
+
+ /**
+ * Show core.
+ *
+ * Shows local navigation, content block and aside.
+ *
+ * @return nothing
+ */
+ function showCore()
+ {
+ $this->elementStart('div', array('id' => 'core'));
+ if (Event::handle('StartShowContentBlock', array($this))) {
+ $this->showContentBlock();
+ Event::handle('EndShowContentBlock', array($this));
+ }
+ $this->elementEnd('div');
+ }
+
+
+
+ /**
+ * 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
new file mode 100644
index 000000000..6b31c839d
--- /dev/null
+++ b/actions/attachments.php
@@ -0,0 +1,292 @@
+<?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/attachments_ajax.php b/actions/attachments_ajax.php
new file mode 100644
index 000000000..402d8b5e7
--- /dev/null
+++ b/actions/attachments_ajax.php
@@ -0,0 +1,115 @@
+<?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.'/actions/attachments.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 Attachments_ajaxAction extends AttachmentsAction
+{
+ 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.
+ *
+ * @return nothing
+ */
+ function showPage()
+ {
+ if (Event::handle('StartShowBody', array($this))) {
+ $this->showCore();
+ Event::handle('EndShowBody', array($this));
+ }
+ }
+
+ /**
+ * Show core.
+ *
+ * Shows local navigation, content block and aside.
+ *
+ * @return nothing
+ */
+ function showCore()
+ {
+ $this->elementStart('div', array('id' => 'core'));
+ if (Event::handle('StartShowContentBlock', array($this))) {
+ $this->showContentBlock();
+ Event::handle('EndShowContentBlock', array($this));
+ }
+ $this->elementEnd('div');
+ }
+
+
+
+
+}
+
diff --git a/actions/designsettings.php b/actions/designsettings.php
index cdd950e78..a85b36a25 100644
--- a/actions/designsettings.php
+++ b/actions/designsettings.php
@@ -76,14 +76,22 @@ class DesignsettingsAction extends AccountSettingsAction
'action' =>
common_local_url('designsettings')));
$this->elementStart('fieldset');
-// $this->element('legend', null, _('Design settings'));
$this->hidden('token', common_session_token());
$this->elementStart('fieldset', array('id' => 'settings_design_background-image'));
$this->element('legend', null, _('Change background image'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
- $this->element('p', null, _('Upload background image'));
+ $this->element('label', array('for' => 'design_ background-image_file'),
+ _('Upload file'));
+ $this->element('input', array('name' => 'design_background-image_file',
+ 'type' => 'file',
+ 'id' => 'design_background-image_file'));
+ $this->element('p', 'form_guide', _('You can upload your personal background image. The maximum file size is 2Mb.'));
+ $this->element('input', array('name' => 'MAX_FILE_SIZE',
+ 'type' => 'hidden',
+ 'id' => 'MAX_FILE_SIZE',
+ 'value' => ImageFile::maxFileSizeInt()));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementEnd('fieldset');
@@ -91,28 +99,57 @@ class DesignsettingsAction extends AccountSettingsAction
$this->elementStart('fieldset', array('id' => 'settings_design_color'));
$this->element('legend', null, _('Change colours'));
$this->elementStart('ul', 'form_data');
- $this->elementStart('li');
- $this->input('color-1', _('Background color'), '#F0F2F5', null);
- $this->elementEnd('li');
- $this->elementStart('li');
- $this->input('color-2', _('Content background color'), '#FFFFFF', null);
- $this->elementEnd('li');
- $this->elementStart('li');
- $this->input('color-3', _('Sidebar background color'), '#CEE1E9', null);
- $this->elementEnd('li');
- $this->elementStart('li');
- $this->input('color-4', _('Text color'), '#000000', null);
- $this->elementEnd('li');
- $this->elementStart('li');
- $this->input('color-5', _('Link color'), '#002E6E', null);
- $this->elementEnd('li');
+
+ //This is a JSON object in the DB field. Here for testing. Remove later.
+ $userSwatch = '{"body":{"background-color":"#F0F2F5"},
+ "#content":{"background-color":"#FFFFFF"},
+ "#aside_primary":{"background-color":"#CEE1E9"},
+ "html body":{"color":"#000000"},
+ "a":{"color":"#002E6E"}}';
+
+ //Default theme swatch -- Where should this be stored?
+ $defaultSwatch = array('body' => array('background-color' => '#F0F2F5'),
+ '#content' => array('background-color' => '#FFFFFF'),
+ '#aside_primary' => array('background-color' => '#CEE1E9'),
+ 'html body' => array('color' => '#000000'),
+ 'a' => array('color' => '#002E6E'));
+
+ $userSwatch = ($userSwatch) ? json_decode($userSwatch, true) : $defaultSwatch;
+
+ $s = 0;
+ $labelSwatch = array('Background',
+ 'Content',
+ 'Sidebar',
+ 'Text',
+ 'Links');
+ foreach($userSwatch as $propertyvalue => $value) {
+ $foo = array_values($value);
+ $this->elementStart('li');
+ $this->element('label', array('for' => 'swatch-'.$s), _($labelSwatch[$s]));
+ $this->element('input', array('name' => 'swatch-'.$s, //prefer swatch[$s] ?
+ 'type' => 'text',
+ 'id' => 'swatch-'.$s,
+ 'class' => 'swatch',
+ 'maxlength' => '7',
+ 'size' => '7',
+ 'value' => $foo[0]));
+ $this->elementEnd('li');
+ $s++;
+ }
+
$this->elementEnd('ul');
- $this->element('div', array('id' => 'color-picker'));
$this->elementEnd('fieldset');
-
$this->submit('save', _('Save'));
-
+ $this->element('input', array('type' => 'reset',
+ 'value' => 'Reset',
+ 'class' => 'form_action-secondary'));
+
+/*TODO: Check submitted form values:
+json_encode(form values)
+if submitted Swatch == DefaultSwatch, don't store in DB.
+else store in BD
+*/
$this->elementEnd('fieldset');
$this->elementEnd('form');
@@ -187,7 +224,7 @@ class DesignsettingsAction extends AccountSettingsAction
/**
- * Add the jCrop stylesheet
+ * Add the Farbtastic stylesheet
*
* @return void
*/
@@ -205,7 +242,7 @@ class DesignsettingsAction extends AccountSettingsAction
}
/**
- * Add the jCrop scripts
+ * Add the Farbtastic scripts
*
* @return void
*/
@@ -214,14 +251,12 @@ class DesignsettingsAction extends AccountSettingsAction
{
parent::showScripts();
-// if ($this->mode == 'crop') {
- $farbtasticPack = common_path('js/farbtastic/farbtastic.js');
- $farbtasticGo = common_path('js/farbtastic/farbtastic.go.js');
+ $farbtasticPack = common_path('js/farbtastic/farbtastic.js');
+ $farbtasticGo = common_path('js/farbtastic/farbtastic.go.js');
- $this->element('script', array('type' => 'text/javascript',
- 'src' => $farbtasticPack));
- $this->element('script', array('type' => 'text/javascript',
- 'src' => $farbtasticGo));
-// }
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => $farbtasticPack));
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => $farbtasticGo));
}
}
diff --git a/actions/logout.php b/actions/logout.php
index 9f3bfe247..c34b10987 100644
--- a/actions/logout.php
+++ b/actions/logout.php
@@ -70,10 +70,20 @@ class LogoutAction extends Action
if (!common_logged_in()) {
$this->clientError(_('Not logged in.'));
} else {
- common_set_user(null);
- common_real_login(false); // not logged in
- common_forgetme(); // don't log back in!
+ if (Event::handle('StartLogout', array($this))) {
+ $this->logout();
+ }
+ Event::handle('EndLogout', array($this));
+
common_redirect(common_local_url('public'), 303);
}
}
+
+ function logout()
+ {
+ common_set_user(null);
+ common_real_login(false); // not logged in
+ common_forgetme(); // don't log back in!
+ }
+
}
diff --git a/actions/newnotice.php b/actions/newnotice.php
index cbd04c58b..ae0ff9636 100644
--- a/actions/newnotice.php
+++ b/actions/newnotice.php
@@ -158,7 +158,8 @@ class NewnoticeAction extends Action
$replyto = 'false';
}
- $notice = Notice::saveNew($user->id, $content, 'web', 1,
+// $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
+ $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
($replyto == 'false') ? null : $replyto);
if (is_string($notice)) {
@@ -166,6 +167,8 @@ class NewnoticeAction extends Action
return;
}
+ $this->saveUrls($notice);
+
common_broadcast_notice($notice);
if ($this->boolean('ajax')) {
@@ -191,6 +194,24 @@ class NewnoticeAction extends Action
}
}
+ /** save all urls in the notice to the db
+ *
+ * follow redirects and save all available file information
+ * (mimetype, date, size, oembed, etc.)
+ *
+ * @param class $notice Notice to pull URLs from
+ *
+ * @return void
+ */
+ function saveUrls($notice) {
+ common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id);
+ }
+
+ function saveUrl($data) {
+ list($url, $notice_id) = $data;
+ $zzz = File::processNew($url, $notice_id);
+ }
+
/**
* Show an Ajax-y error message
*
diff --git a/actions/showstream.php b/actions/showstream.php
index 82665e5b8..678a3174c 100644
--- a/actions/showstream.php
+++ b/actions/showstream.php
@@ -68,6 +68,9 @@ class ShowstreamAction extends ProfileAction
} else {
$base = $this->user->nickname;
}
+ if (!empty($this->tag)) {
+ $base .= sprintf(_(' tagged %s'), $this->tag);
+ }
if ($this->page == 1) {
return $base;
@@ -110,6 +113,15 @@ class ShowstreamAction extends ProfileAction
function getFeeds()
{
+ if (!empty($this->tag)) {
+ return array(new Feed(Feed::RSS1,
+ common_local_url('userrss',
+ array('nickname' => $this->user->nickname,
+ 'tag' => $this->tag)),
+ sprintf(_('Notice feed for %s tagged %s (RSS 1.0)'),
+ $this->user->nickname, $this->tag)));
+ }
+
return array(new Feed(Feed::RSS1,
common_local_url('userrss',
array('nickname' => $this->user->nickname)),
@@ -363,7 +375,9 @@ class ShowstreamAction extends ProfileAction
function showNotices()
{
- $notice = $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+ $notice = empty($this->tag)
+ ? $this->user->getNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1)
+ : $this->user->getTaggedNotices(($this->page-1)*NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1, 0, 0, null, $this->tag);
$pnl = new ProfileNoticeList($notice, $this);
$cnt = $pnl->show();
diff --git a/actions/tag.php b/actions/tag.php
index 02f3e3522..47420e4c3 100644
--- a/actions/tag.php
+++ b/actions/tag.php
@@ -49,9 +49,10 @@ class TagAction extends Action
{
$pop = new PopularNoticeSection($this);
$pop->show();
+ $freqatt = new FrequentAttachmentSection($this);
+ $freqatt->show();
}
-
function title()
{
if ($this->page == 1) {
diff --git a/actions/userrss.php b/actions/userrss.php
index 5861d9ee3..2280509b2 100644
--- a/actions/userrss.php
+++ b/actions/userrss.php
@@ -25,14 +25,15 @@ require_once(INSTALLDIR.'/lib/rssaction.php');
class UserrssAction extends Rss10Action
{
-
var $user = null;
+ var $tag = null;
function prepare($args)
{
parent::prepare($args);
- $nickname = $this->trimmed('nickname');
+ $nickname = $this->trimmed('nickname');
$this->user = User::staticGet('nickname', $nickname);
+ $this->tag = $this->trimmed('tag');
if (!$this->user) {
$this->clientError(_('No such user.'));
@@ -42,6 +43,25 @@ class UserrssAction extends Rss10Action
}
}
+ function getTaggedNotices($tag = null, $limit=0)
+ {
+ $user = $this->user;
+
+ if (is_null($user)) {
+ return null;
+ }
+
+ $notice = $user->getTaggedNotices(0, ($limit == 0) ? NOTICES_PER_PAGE : $limit, 0, 0, null, $tag);
+
+ $notices = array();
+ while ($notice->fetch()) {
+ $notices[] = clone($notice);
+ }
+
+ return $notices;
+ }
+
+
function getNotices($limit=0)
{
diff --git a/classes/File.php b/classes/File.php
new file mode 100644
index 000000000..e5913115b
--- /dev/null
+++ b/classes/File.php
@@ -0,0 +1,123 @@
+<?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.'/classes/Memcached_DataObject.php';
+require_once INSTALLDIR.'/classes/File_redirection.php';
+require_once INSTALLDIR.'/classes/File_oembed.php';
+require_once INSTALLDIR.'/classes/File_thumbnail.php';
+require_once INSTALLDIR.'/classes/File_to_post.php';
+//require_once INSTALLDIR.'/classes/File_redirection.php';
+
+/**
+ * Table Definition for file
+ */
+
+class File extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'file'; // table name
+ public $id; // int(11) not_null primary_key group_by
+ public $url; // varchar(255) unique_key
+ public $mimetype; // varchar(50)
+ public $size; // int(11) group_by
+ public $title; // varchar(255)
+ public $date; // int(11) group_by
+ public $protected; // int(1) group_by
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function isProtected($url) {
+ return 'http://www.facebook.com/login.php' === $url;
+ }
+
+ function getAttachments($post_id) {
+ $query = "select file.* from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $this->escape($post_id);
+ $this->query($query);
+ $att = array();
+ while ($this->fetch()) {
+ $att[] = clone($this);
+ }
+ $this->free();
+ return $att;
+ }
+
+ function saveNew($redir_data, $given_url) {
+ $x = new File;
+ $x->url = $given_url;
+ if (!empty($redir_data['protected'])) $x->protected = $redir_data['protected'];
+ if (!empty($redir_data['title'])) $x->title = $redir_data['title'];
+ if (!empty($redir_data['type'])) $x->mimetype = $redir_data['type'];
+ if (!empty($redir_data['size'])) $x->size = intval($redir_data['size']);
+ if (isset($redir_data['time']) && $redir_data['time'] > 0) $x->date = intval($redir_data['time']);
+ $file_id = $x->insert();
+
+ if (isset($redir_data['type'])
+ && ('text/html' === substr($redir_data['type'], 0, 9))
+ && ($oembed_data = File_oembed::_getOembed($given_url))
+ && isset($oembed_data['json'])) {
+
+ File_oembed::saveNew($oembed_data['json'], $file_id);
+ }
+ return $x;
+ }
+
+ function processNew($given_url, $notice_id) {
+ if (empty($given_url)) return -1; // error, no url to process
+ $given_url = File_redirection::_canonUrl($given_url);
+ if (empty($given_url)) return -1; // error, no url to process
+ $file = File::staticGet('url', $given_url);
+ if (empty($file->id)) {
+ $file_redir = File_redirection::staticGet('url', $given_url);
+ if (empty($file_redir->id)) {
+ $redir_data = File_redirection::where($given_url);
+ $redir_url = $redir_data['url'];
+ if ($redir_url === $given_url) {
+ $x = File::saveNew($redir_data, $given_url);
+ $file_id = $x->id;
+
+ } else {
+ $x = File::processNew($redir_url, $notice_id);
+ $file_id = $x->id;
+ File_redirection::saveNew($redir_data, $file_id, $given_url);
+ }
+ } else {
+ $file_id = $file_redir->file_id;
+ }
+ } else {
+ $file_id = $file->id;
+ $x = $file;
+ }
+
+ if (empty($x)) {
+ $x = File::staticGet($file_id);
+ if (empty($x)) die('Impossible!');
+ }
+
+ File_to_post::processNew($file_id, $notice_id);
+ return $x;
+ }
+}
diff --git a/classes/File_oembed.php b/classes/File_oembed.php
new file mode 100644
index 000000000..f1b2cb13c
--- /dev/null
+++ b/classes/File_oembed.php
@@ -0,0 +1,87 @@
+<?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.'/classes/Memcached_DataObject.php';
+
+/**
+ * Table Definition for file_oembed
+ */
+
+class File_oembed extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'file_oembed'; // table name
+ public $id; // int(11) not_null primary_key group_by
+ public $file_id; // int(11) unique_key group_by
+ public $version; // varchar(20)
+ public $type; // varchar(20)
+ public $provider; // varchar(50)
+ public $provider_url; // varchar(255)
+ public $width; // int(11) group_by
+ public $height; // int(11) group_by
+ public $html; // blob(65535) blob
+ public $title; // varchar(255)
+ public $author_name; // varchar(50)
+ public $author_url; // varchar(255)
+ public $url; // varchar(255)
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_oembed',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+
+ function _getOembed($url, $maxwidth = 500, $maxheight = 400, $format = 'json') {
+ $cmd = 'http://oohembed.com/oohembed/?url=' . urlencode($url);
+ if (is_int($maxwidth)) $cmd .= "&maxwidth=$maxwidth";
+ if (is_int($maxheight)) $cmd .= "&maxheight=$maxheight";
+ if (is_string($format)) $cmd .= "&format=$format";
+ $oe = @file_get_contents($cmd);
+ if (false === $oe) return false;
+ return array($format => (('json' === $format) ? json_decode($oe, true) : $oe));
+ }
+
+ function saveNew($data, $file_id) {
+ $file_oembed = new File_oembed;
+ $file_oembed->file_id = $file_id;
+ $file_oembed->version = $data['version'];
+ $file_oembed->type = $data['type'];
+ if (!empty($data['provider_name'])) $file_oembed->provider = $data['provider_name'];
+ if (!isset($file_oembed->provider) && !empty($data['provide'])) $file_oembed->provider = $data['provider'];
+ if (!empty($data['provide_url'])) $file_oembed->provider_url = $data['provider_url'];
+ if (!empty($data['width'])) $file_oembed->width = intval($data['width']);
+ if (!empty($data['height'])) $file_oembed->height = intval($data['height']);
+ if (!empty($data['html'])) $file_oembed->html = $data['html'];
+ if (!empty($data['title'])) $file_oembed->title = $data['title'];
+ if (!empty($data['author_name'])) $file_oembed->author_name = $data['author_name'];
+ if (!empty($data['author_url'])) $file_oembed->author_url = $data['author_url'];
+ if (!empty($data['url'])) $file_oembed->url = $data['url'];
+ $file_oembed->insert();
+ if (!empty($data['thumbnail_url'])) {
+ File_thumbnail::saveNew($data, $file_id);
+ }
+ }
+}
+
+
diff --git a/classes/File_redirection.php b/classes/File_redirection.php
new file mode 100644
index 000000000..0eae68178
--- /dev/null
+++ b/classes/File_redirection.php
@@ -0,0 +1,274 @@
+<?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.'/classes/Memcached_DataObject.php';
+require_once INSTALLDIR.'/classes/File.php';
+require_once INSTALLDIR.'/classes/File_oembed.php';
+
+define('USER_AGENT', 'Laconica user agent / file probe');
+
+
+/**
+ * Table Definition for file_redirection
+ */
+
+class File_redirection extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'file_redirection'; // table name
+ public $id; // int(11) not_null primary_key group_by
+ public $url; // varchar(255) unique_key
+ public $file_id; // int(11) group_by
+ public $redirections; // int(11) group_by
+ public $httpcode; // int(11) group_by
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_redirection',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+
+
+ function _commonCurl($url, $redirs) {
+ $curlh = curl_init();
+ curl_setopt($curlh, CURLOPT_URL, $url);
+ curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects
+ curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait
+ curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow
+ curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT);
+ curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects
+ curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curlh, CURLOPT_FILETIME, true);
+ curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output
+ return $curlh;
+ }
+
+ function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) {
+ if ($redirs < 0) return false;
+
+ // let's see if we know this...
+ $a = File::staticGet('url', $short_url);
+ if (empty($a->id)) {
+ $b = File_redirection::staticGet('url', $short_url);
+ if (empty($b->id)) {
+ // we'll have to figure it out
+ } else {
+ // this is a redirect to $b->file_id
+ $a = File::staticGet($b->file_id);
+ $url = $a->url;
+ }
+ } else {
+ // this is a direct link to $a->url
+ $url = $a->url;
+ }
+ if (isset($url)) {
+ return $url;
+ }
+
+
+
+ $curlh = File_redirection::_commonCurl($short_url, $redirs);
+ // Don't include body in output
+ curl_setopt($curlh, CURLOPT_NOBODY, true);
+ curl_exec($curlh);
+ $info = curl_getinfo($curlh);
+ curl_close($curlh);
+
+ if (405 == $info['http_code']) {
+ $curlh = File_redirection::_commonCurl($short_url, $redirs);
+ curl_exec($curlh);
+ $info = curl_getinfo($curlh);
+ curl_close($curlh);
+ }
+
+ if (!empty($info['redirect_count']) && File::isProtected($info['url'])) {
+ return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true);
+ }
+
+ $ret = array('code' => $info['http_code']
+ , 'redirects' => $info['redirect_count']
+ , 'url' => $info['url']);
+
+ if (!empty($info['content_type'])) $ret['type'] = $info['content_type'];
+ if ($protected) $ret['protected'] = true;
+ if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length'];
+ if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime'];
+ return $ret;
+ }
+
+ function where($in_url) {
+ $ret = File_redirection::_redirectWhere_imp($in_url);
+ return $ret;
+ }
+
+ function makeShort($long_url) {
+ $long_url = File_redirection::_canonUrl($long_url);
+ // do we already know this long_url and have a short redirection for it?
+ $file = new File;
+ $file_redir = new File_redirection;
+ $file->url = $long_url;
+ $file->joinAdd($file_redir);
+ $file->selectAdd('length(file_redirection.url) as len');
+ $file->limit(1);
+ $file->orderBy('len');
+ $file->find(true);
+ if (!empty($file->id)) {
+ return $file->url;
+ }
+
+ // if yet unknown, we must find a short url according to user settings
+ $short_url = File_redirection::_userMakeShort($long_url, common_current_user());
+ return $short_url;
+ }
+
+ function _userMakeShort($long_url, $user) {
+ if (empty($user)) {
+ // common current user does not find a user when called from the XMPP daemon
+ // therefore we'll set one here fix, so that XMPP given URLs may be shortened
+ $user->urlshorteningservice = 'ur1.ca';
+ }
+ $curlh = curl_init();
+ curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
+ curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
+ curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
+
+ switch($user->urlshorteningservice) {
+ case 'ur1.ca':
+ require_once INSTALLDIR.'/lib/Shorturl_api.php';
+ $short_url_service = new LilUrl;
+ $short_url = $short_url_service->shorten($long_url);
+ break;
+
+ case '2tu.us':
+ $short_url_service = new TightUrl;
+ require_once INSTALLDIR.'/lib/Shorturl_api.php';
+ $short_url = $short_url_service->shorten($long_url);
+ break;
+
+ case 'ptiturl.com':
+ require_once INSTALLDIR.'/lib/Shorturl_api.php';
+ $short_url_service = new PtitUrl;
+ $short_url = $short_url_service->shorten($long_url);
+ break;
+
+ case 'bit.ly':
+ curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($long_url));
+ $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
+ break;
+
+ case 'is.gd':
+ curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($long_url));
+ $short_url = curl_exec($curlh);
+ break;
+ case 'snipr.com':
+ curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($long_url));
+ $short_url = curl_exec($curlh);
+ break;
+ case 'metamark.net':
+ curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($long_url));
+ $short_url = curl_exec($curlh);
+ break;
+ case 'tinyurl.com':
+ curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($long_url));
+ $short_url = curl_exec($curlh);
+ break;
+ default:
+ $short_url = false;
+ }
+
+ curl_close($curlh);
+
+ if ($short_url) {
+ $short_url = (string)$short_url;
+ // store it
+ $file = File::staticGet('url', $long_url);
+ if (empty($file)) {
+ $redir_data = File_redirection::where($long_url);
+ $file = File::saveNew($redir_data, $long_url);
+ $file_id = $file->id;
+ if (!empty($redir_data['oembed']['json'])) {
+ File_oembed::saveNew($redir_data['oembed']['json'], $file_id);
+ }
+ } else {
+ $file_id = $file->id;
+ }
+ $file_redir = File_redirection::staticGet('url', $short_url);
+ if (empty($file_redir)) {
+ $file_redir = new File_redirection;
+ $file_redir->url = $short_url;
+ $file_redir->file_id = $file_id;
+ $file_redir->insert();
+ }
+ return $short_url;
+ }
+ return $long_url;
+ }
+
+ function _canonUrl($in_url, $default_scheme = 'http://') {
+ if (empty($in_url)) return false;
+ $out_url = $in_url;
+ $p = parse_url($out_url);
+ if (empty($p['host']) || empty($p['scheme'])) {
+ list($scheme) = explode(':', $in_url, 2);
+ switch ($scheme) {
+ case 'fax':
+ case 'tel':
+ $out_url = str_replace('.-()', '', $out_url);
+ break;
+
+ case 'mailto':
+ case 'aim':
+ case 'jabber':
+ case 'xmpp':
+ // don't touch anything
+ break;
+
+ default:
+ $out_url = $default_scheme . ltrim($out_url, '/');
+ $p = parse_url($out_url);
+ if (empty($p['scheme'])) return false;
+ break;
+ }
+ }
+
+ if (('ftp' == $p['scheme']) || ('http' == $p['scheme']) || ('https' == $p['scheme'])) {
+ if (empty($p['host'])) return false;
+ if (empty($p['path'])) {
+ $out_url .= '/';
+ }
+ }
+
+ return $out_url;
+ }
+
+ function saveNew($data, $file_id, $url) {
+ $file_redir = new File_redirection;
+ $file_redir->url = $url;
+ $file_redir->file_id = $file_id;
+ $file_redir->redirections = intval($data['redirects']);
+ $file_redir->httpcode = intval($data['code']);
+ $file_redir->insert();
+ }
+}
+
diff --git a/classes/File_thumbnail.php b/classes/File_thumbnail.php
new file mode 100644
index 000000000..1a65b92c9
--- /dev/null
+++ b/classes/File_thumbnail.php
@@ -0,0 +1,55 @@
+<?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.'/classes/Memcached_DataObject.php';
+
+/**
+ * Table Definition for file_thumbnail
+ */
+
+class File_thumbnail extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'file_thumbnail'; // table name
+ public $id; // int(11) not_null primary_key group_by
+ public $file_id; // int(11) unique_key group_by
+ public $url; // varchar(255) unique_key
+ public $width; // int(11) group_by
+ public $height; // int(11) group_by
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_thumbnail',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function saveNew($data, $file_id) {
+ $tn = new File_thumbnail;
+ $tn->file_id = $file_id;
+ $tn->url = $data['thumbnail_url'];
+ $tn->width = intval($data['thumbnail_width']);
+ $tn->height = intval($data['thumbnail_height']);
+ $tn->insert();
+ }
+}
+
diff --git a/classes/File_to_post.php b/classes/File_to_post.php
new file mode 100644
index 000000000..00ddebe6b
--- /dev/null
+++ b/classes/File_to_post.php
@@ -0,0 +1,60 @@
+<?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.'/classes/Memcached_DataObject.php';
+
+/**
+ * Table Definition for file_to_post
+ */
+
+class File_to_post extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'file_to_post'; // table name
+ public $id; // int(11) not_null primary_key group_by
+ public $file_id; // int(11) multiple_key group_by
+ public $post_id; // int(11) group_by
+
+ /* Static get */
+ function staticGet($k,$v=NULL) { return DB_DataObject::staticGet('File_to_post',$k,$v); }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+
+ function processNew($file_id, $notice_id) {
+ static $seen = array();
+ if (empty($seen[$notice_id]) || !in_array($file_id, $seen[$notice_id])) {
+ $f2p = new File_to_post;
+ $f2p->file_id = $file_id;
+ $f2p->post_id = $notice_id;
+ $f2p->insert();
+ if (empty($seen[$notice_id])) {
+ $seen[$notice_id] = array($file_id);
+ } else {
+ $seen[$notice_id][] = $file_id;
+ }
+ }
+
+ }
+}
+
diff --git a/classes/Notice.php b/classes/Notice.php
index 382d160ab..1b5c0ab0a 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -124,8 +124,6 @@ class Notice extends Memcached_DataObject
$profile = Profile::staticGet($profile_id);
- $final = common_shorten_links($content);
-
if (!$profile) {
common_log(LOG_ERR, 'Problem saving notice. Unknown user.');
return _('Problem saving notice. Unknown user.');
@@ -136,7 +134,7 @@ class Notice extends Memcached_DataObject
return _('Too many notices too fast; take a breather and post again in a few minutes.');
}
- if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
+ if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $content)) {
common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
}
@@ -167,8 +165,8 @@ class Notice extends Memcached_DataObject
$notice->reply_to = $reply_to;
$notice->created = common_sql_now();
- $notice->content = $final;
- $notice->rendered = common_render_content($final, $notice);
+ $notice->content = $content;
+ $notice->rendered = common_render_content($content, $notice);
$notice->source = $source;
$notice->uri = $uri;
@@ -279,6 +277,16 @@ class Notice extends Memcached_DataObject
return true;
}
+ 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);
+ $post->query($query);
+ $post->fetch();
+ $n_attachments = intval($post->n_attachments);
+ $post->free();
+ return $n_attachments;
+ }
+
function blowCaches($blowLast=false)
{
$this->blowSubsCache($blowLast);
@@ -1016,7 +1024,7 @@ class Notice extends Memcached_DataObject
}
}
- function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
+ function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null, $tag=null)
{
$cache = common_memcache();
@@ -1024,7 +1032,7 @@ class Notice extends Memcached_DataObject
$since_id != 0 || $before_id != 0 || !is_null($since) ||
($offset + $limit) > NOTICE_CACHE_WINDOW) {
return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
- $before_id, $since)));
+ $before_id, $since, $tag)));
}
$idkey = common_cache_key($cachekey);
@@ -1044,7 +1052,7 @@ class Notice extends Memcached_DataObject
$window = explode(',', $laststr);
$last_id = $window[0];
$new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
- $last_id, 0, null)));
+ $last_id, 0, null, $tag)));
$new_window = array_merge($new_ids, $window);
@@ -1059,7 +1067,7 @@ class Notice extends Memcached_DataObject
}
$window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW,
- 0, 0, null)));
+ 0, 0, null, $tag)));
$windowstr = implode(',', $window);
diff --git a/classes/Profile.php b/classes/Profile.php
index ae5641d79..afc0ea4f7 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -153,18 +153,66 @@ class Profile extends Memcached_DataObject
return null;
}
- function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0)
+ function getTaggedNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null, $tag=null)
+ {
+ // XXX: I'm not sure this is going to be any faster. It probably isn't.
+ $ids = Notice::stream(array($this, '_streamTaggedDirect'),
+ array(),
+ 'profile:notice_ids:' . $this->id,
+ $offset, $limit, $since_id, $before_id, $since, $tag);
+ common_debug(print_r($ids, true));
+ return Notice::getStreamByIds($ids);
+ }
+
+ function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
// XXX: I'm not sure this is going to be any faster. It probably isn't.
$ids = Notice::stream(array($this, '_streamDirect'),
array(),
'profile:notice_ids:' . $this->id,
- $offset, $limit, $since_id, $before_id);
+ $offset, $limit, $since_id, $before_id, $since);
return Notice::getStreamByIds($ids);
}
- function _streamDirect($offset, $limit, $since_id, $before_id, $since)
+ function _streamTaggedDirect($offset, $limit, $since_id, $before_id, $since=null, $tag=null)
+ {
+ common_debug('_streamTaggedDirect()');
+ $notice = new Notice();
+ $notice->profile_id = $this->id;
+ $query = "select id from notice join notice_tag on id=notice_id where tag='" . $notice->escape($tag) . "' and profile_id=" . $notice->escape($notice->profile_id);
+ if ($since_id != 0) {
+ $query .= " and id > $since_id";
+ }
+
+ if ($before_id != 0) {
+ $query .= " and id < $before_id";
+ }
+
+ if (!is_null($since)) {
+ $query .= " and created > '" . date('Y-m-d H:i:s', $since) . "'";
+ }
+
+ $query .= ' order by id DESC';
+
+ if (!is_null($offset)) {
+ $query .= " limit $offset, $limit";
+ }
+ $notice->query($query);
+ $ids = array();
+
+ while ($notice->fetch()) {
+ common_debug(print_r($notice, true));
+ $ids[] = $notice->id;
+ }
+
+ return $ids;
+ }
+
+
+
+
+ function _streamDirect($offset, $limit, $since_id, $before_id, $since = null)
{
$notice = new Notice();
diff --git a/classes/User.php b/classes/User.php
index b5ac7b220..ea8ba4081 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -407,13 +407,22 @@ class User extends Memcached_DataObject
return Notice::getStreamByIds($ids);
}
+ function getTaggedNotices($tag, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null) {
+ $profile = $this->getProfile();
+ if (!$profile) {
+ return null;
+ } else {
+ return $profile->getTaggedNotices($tag, $offset, $limit, $since_id, $before_id, $since);
+ }
+ }
+
function getNotices($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
{
$profile = $this->getProfile();
if (!$profile) {
return null;
} else {
- return $profile->getNotices($offset, $limit, $since_id, $before_id);
+ return $profile->getNotices($offset, $limit, $since_id, $before_id, $since);
}
}
diff --git a/classes/laconica.ini b/classes/laconica.ini
index 5a905a4bb..316923af0 100755..100644
--- a/classes/laconica.ini
+++ b/classes/laconica.ini
@@ -1,4 +1,3 @@
-
[avatar]
profile_id = 129
original = 17
@@ -393,3 +392,63 @@ 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/classes/laconica.links.ini b/classes/laconica.links.ini
index 173b18726..95c63f3c0 100644
--- a/classes/laconica.links.ini
+++ b/classes/laconica.links.ini
@@ -41,3 +41,17 @@ subscribed = profile:id
[fave]
notice_id = notice:id
user_id = user:id
+
+[file_oembed]
+file_id = file:id
+
+[file_redirection]
+file_id = file:id
+
+[file_thumbnail]
+file_id = file:id
+
+[file_to_post]
+file_id = file:id
+post_id = notice:id
+
diff --git a/db/laconica.sql b/db/laconica.sql
index d9e21a7b5..344f0ff72 100644
--- a/db/laconica.sql
+++ b/db/laconica.sql
@@ -425,3 +425,62 @@ 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),
+
+ unique(url)
+) ENGINE=MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+create table file_oembed (
+ id integer primary key auto_increment,
+ file_id integer,
+ version varchar(20),
+ type varchar(20),
+ provider varchar(50),
+ provider_url varchar(255),
+ width integer,
+ height integer,
+ html text,
+ title 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,
+
+ 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,
+
+ 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,
+
+ unique(file_id, post_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+
diff --git a/db/laconica_pg.sql b/db/laconica_pg.sql
index a27a616f2..b213bbd50 100644
--- a/db/laconica_pg.sql
+++ b/db/laconica_pg.sql
@@ -427,6 +427,64 @@ create table group_inbox (
);
create index group_inbox_created_idx on group_inbox using btree(created);
+
+/*attachments and URLs stuff */
+create sequence file_seq;
+create table file (
+ id bigint default nextval('file_seq') primary key /* comment 'unique identifier' */,
+ url varchar(255) unique,
+ mimetype varchar(50),
+ size integer,
+ title varchar(255),
+ date integer(11),
+ protected integer(1)
+);
+
+create sequence file_oembed_seq;
+create table file_oembed (
+ id bigint default nextval('file_oembed_seq') primary key /* comment 'unique identifier' */,
+ file_id bigint unique,
+ version varchar(20),
+ type varchar(20),
+ provider varchar(50),
+ provider_url varchar(255),
+ width integer,
+ height integer,
+ html text,
+ title varchar(255),
+ author_name varchar(50),
+ author_url varchar(255),
+ url varchar(255),
+);
+
+create sequence file_redirection_seq;
+create table file_redirection (
+ id bigint default nextval('file_redirection_seq') primary key /* comment 'unique identifier' */,
+ url varchar(255) unique,
+ file_id bigint,
+ redirections integer,
+ httpcode integer
+);
+
+create sequence file_thumbnail_seq;
+create table file_thumbnail (
+ id bigint default nextval('file_thumbnail_seq') primary key /* comment 'unique identifier' */,
+ file_id bigint unique,
+ url varchar(255) unique,
+ width integer,
+ height integer
+);
+
+create sequence file_to_post_seq;
+create table file_to_post (
+ id bigint default nextval('file_to_post_seq') primary key /* comment 'unique identifier' */,
+ file_id bigint,
+ post_id bigint,
+
+ unique(file_id, post_id)
+);
+
+
/* Textsearch stuff */
create index textsearch_idx on profile using gist(textsearch);
diff --git a/extlib/facebook/facebook.php b/extlib/facebook/facebook.php
index 35de6be50..fee1dd086 100644
--- a/extlib/facebook/facebook.php
+++ b/extlib/facebook/facebook.php
@@ -1,5 +1,5 @@
<?php
-// Copyright 2004-2008 Facebook. All Rights Reserved.
+// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
@@ -30,13 +30,12 @@
// +---------------------------------------------------------------------------+
// | For help with this library, contact developers-help@facebook.com |
// +---------------------------------------------------------------------------+
-//
+
include_once 'facebookapi_php5_restlib.php';
define('FACEBOOK_API_VALIDATION_ERROR', 1);
class Facebook {
public $api_client;
-
public $api_key;
public $secret;
public $generate_session_secret;
@@ -213,28 +212,55 @@ class Facebook {
}
}
- // Invalidate the session currently being used, and clear any state associated with it
+ // Invalidate the session currently being used, and clear any state associated
+ // with it. Note that the user will still remain logged into Facebook.
public function expire_session() {
if ($this->api_client->auth_expireSession()) {
- if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
- $cookies = array('user', 'session_key', 'expires', 'ss');
- foreach ($cookies as $name) {
- setcookie($this->api_key . '_' . $name, false, time() - 3600);
- unset($_COOKIE[$this->api_key . '_' . $name]);
- }
- setcookie($this->api_key, false, time() - 3600);
- unset($_COOKIE[$this->api_key]);
- }
-
- // now, clear the rest of the stored state
- $this->user = 0;
- $this->api_client->session_key = 0;
+ $this->clear_cookie_state();
return true;
} else {
return false;
}
}
+ /** Logs the user out of all temporary application sessions as well as their
+ * Facebook session. Note this will only work if the user has a valid current
+ * session with the application.
+ *
+ * @param string $next URL to redirect to upon logging out
+ *
+ */
+ public function logout($next) {
+ $logout_url = $this->get_logout_url($next);
+
+ // Clear any stored state
+ $this->clear_cookie_state();
+
+ $this->redirect($logout_url);
+ }
+
+ /**
+ * Clears any persistent state stored about the user, including
+ * cookies and information related to the current session in the
+ * client.
+ *
+ */
+ public function clear_cookie_state() {
+ if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
+ $cookies = array('user', 'session_key', 'expires', 'ss');
+ foreach ($cookies as $name) {
+ setcookie($this->api_key . '_' . $name, false, time() - 3600);
+ unset($_COOKIE[$this->api_key . '_' . $name]);
+ }
+ setcookie($this->api_key, false, time() - 3600);
+ unset($_COOKIE[$this->api_key]);
+ }
+
+ // now, clear the rest of the stored state
+ $this->user = 0;
+ $this->api_client->session_key = 0;
+ }
+
public function redirect($url) {
if ($this->in_fb_canvas()) {
echo '<fb:redirect url="' . $url . '"/>';
@@ -249,7 +275,8 @@ class Facebook {
}
public function in_frame() {
- return isset($this->fb_params['in_canvas']) || isset($this->fb_params['in_iframe']);
+ return isset($this->fb_params['in_canvas'])
+ || isset($this->fb_params['in_iframe']);
}
public function in_fb_canvas() {
return isset($this->fb_params['in_canvas']);
@@ -296,14 +323,42 @@ class Facebook {
}
public function get_add_url($next=null) {
- return self::get_facebook_url().'/add.php?api_key='.$this->api_key .
- ($next ? '&next=' . urlencode($next) : '');
+ $page = self::get_facebook_url().'/add.php';
+ $params = array('api_key' => $this->api_key);
+
+ if ($next) {
+ $params['next'] = $next;
+ }
+
+ return $page . '?' . http_build_query($params);
}
public function get_login_url($next, $canvas) {
- return self::get_facebook_url().'/login.php?v=1.0&api_key=' . $this->api_key .
- ($next ? '&next=' . urlencode($next) : '') .
- ($canvas ? '&canvas' : '');
+ $page = self::get_facebook_url().'/login.php';
+ $params = array('api_key' => $this->api_key,
+ 'v' => '1.0');
+
+ if ($next) {
+ $params['next'] = $next;
+ }
+ if ($canvas) {
+ $params['canvas'] = '1';
+ }
+
+ return $page . '?' . http_build_query($params);
+ }
+
+ public function get_logout_url($next) {
+ $page = self::get_facebook_url().'/logout.php';
+ $params = array('app_key' => $this->api_key,
+ 'session_key' => $this->api_client->session_key);
+
+ if ($next) {
+ $params['connect_next'] = 1;
+ $params['next'] = $next;
+ }
+
+ return $page . '?' . http_build_query($params);
}
public function set_user($user, $session_key, $expires=null, $session_secret=null) {
@@ -410,7 +465,20 @@ class Facebook {
return $fb_params;
}
- /*
+ /**
+ * Validates the account that a user was trying to set up an
+ * independent account through Facebook Connect.
+ *
+ * @param user The user attempting to set up an independent account.
+ * @param hash The hash passed to the reclamation URL used.
+ * @return bool True if the user is the one that selected the
+ * reclamation link.
+ */
+ public function verify_account_reclamation($user, $hash) {
+ return $hash == md5($user . $this->secret);
+ }
+
+ /**
* Validates that a given set of parameters match their signature.
* Parameters all match a given input prefix, such as "fb_sig".
*
@@ -422,6 +490,37 @@ class Facebook {
return self::generate_sig($fb_params, $this->secret) == $expected_sig;
}
+ /**
+ * Validate the given signed public session data structure with
+ * public key of the app that
+ * the session proof belongs to.
+ *
+ * @param $signed_data the session info that is passed by another app
+ * @param string $public_key Optional public key of the app. If this
+ * is not passed, function will make an API call to get it.
+ * return true if the session proof passed verification.
+ */
+ public function verify_signed_public_session_data($signed_data,
+ $public_key = null) {
+
+ // If public key is not already provided, we need to get it through API
+ if (!$public_key) {
+ $public_key = $this->api_client->auth_getAppPublicKey(
+ $signed_data['api_key']);
+ }
+
+ // Create data to verify
+ $data_to_serialize = $signed_data;
+ unset($data_to_serialize['sig']);
+ $serialized_data = implode('_', $data_to_serialize);
+
+ // Decode signature
+ $signature = base64_decode($signed_data['sig']);
+ $result = openssl_verify($serialized_data, $signature, $public_key,
+ OPENSSL_ALGO_SHA1);
+ return $result == 1;
+ }
+
/*
* Generate a signature using the application secret key.
*
diff --git a/extlib/facebook/facebook_desktop.php b/extlib/facebook/facebook_desktop.php
index 90cdf66bd..e79a2ca34 100644
--- a/extlib/facebook/facebook_desktop.php
+++ b/extlib/facebook/facebook_desktop.php
@@ -1,5 +1,5 @@
<?php
-// Copyright 2004-2008 Facebook. All Rights Reserved.
+// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
diff --git a/extlib/facebook/facebookapi_php5_restlib.php b/extlib/facebook/facebookapi_php5_restlib.php
index 389f40a9d..3fec06e8a 100644..100755
--- a/extlib/facebook/facebookapi_php5_restlib.php
+++ b/extlib/facebook/facebookapi_php5_restlib.php
@@ -1,9 +1,10 @@
<?php
+// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007-2008 Facebook, Inc. |
+// | Copyright (c) 2007-2009 Facebook, Inc. |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
@@ -32,6 +33,7 @@
//
include_once 'jsonwrapper/jsonwrapper.php';
+
class FacebookRestClient {
public $secret;
public $session_key;
@@ -50,7 +52,9 @@ class FacebookRestClient {
public $canvas_user;
public $batch_mode;
private $batch_queue;
+ private $pending_batch;
private $call_as_apikey;
+ private $use_curl_if_available;
const BATCH_MODE_DEFAULT = 0;
const BATCH_MODE_SERVER_PARALLEL = 0;
@@ -70,7 +74,8 @@ class FacebookRestClient {
$this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT;
$this->last_call_id = 0;
$this->call_as_apikey = '';
- $this->server_addr = Facebook::get_facebook_url('api') . '/restserver.php';
+ $this->use_curl_if_available = true;
+ $this->server_addr = Facebook::get_facebook_url('api') . '/restserver.php';
if (!empty($GLOBALS['facebook_config']['debug'])) {
$this->cur_id = 0;
@@ -123,39 +128,61 @@ function toggleDisplay(id, type) {
}
/**
+ * Normally, if the cURL library/PHP extension is available, it is used for
+ * HTTP transactions. This allows that behavior to be overridden, falling
+ * back to a vanilla-PHP implementation even if cURL is installed.
+ *
+ * @param $use_curl_if_available bool whether or not to use cURL if available
+ */
+ public function set_use_curl_if_available($use_curl_if_available) {
+ $this->use_curl_if_available = $use_curl_if_available;
+ }
+
+ /**
* Start a batch operation.
*/
public function begin_batch() {
- if($this->batch_queue !== null) {
+ if ($this->pending_batch()) {
$code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
- throw new FacebookRestClientException($code,
- FacebookAPIErrorCodes::$api_error_descriptions[$code]);
+ $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+ throw new FacebookRestClientException($description, $code);
}
$this->batch_queue = array();
+ $this->pending_batch = true;
}
/*
* End current batch operation
*/
public function end_batch() {
- if($this->batch_queue === null) {
+ if (!$this->pending_batch()) {
$code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
- throw new FacebookRestClientException($code,
- FacebookAPIErrorCodes::$api_error_descriptions[$code]);
+ $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+ throw new FacebookRestClientException($description, $code);
}
- $this->execute_server_side_batch();
+ $this->pending_batch = false;
+ $this->execute_server_side_batch();
$this->batch_queue = null;
}
+ /**
+ * are we currently queueing up calls for a batch?
+ */
+ public function pending_batch() {
+ return $this->pending_batch;
+ }
+
private function execute_server_side_batch() {
$item_count = count($this->batch_queue);
$method_feed = array();
foreach($this->batch_queue as $batch_item) {
- $method_feed[] = $this->create_post_string($batch_item['m'],
- $batch_item['p']);
+ $method = $batch_item['m'];
+ $params = $batch_item['p'];
+ $this->finalize_params($method, $params);
+ $method_feed[] = $this->create_post_string($method, $params);
}
$method_feed_json = json_encode($method_feed);
@@ -202,6 +229,18 @@ function toggleDisplay(id, type) {
$this->call_as_apikey = '';
}
+
+ /*
+ * If a page is loaded via HTTPS, then all images and static
+ * resources need to be printed with HTTPS urls to avoid
+ * mixed content warnings. If your page loads with an HTTPS
+ * url, then call set_use_ssl_resources to retrieve the correct
+ * urls.
+ */
+ public function set_use_ssl_resources($is_ssl = true) {
+ $this->use_ssl_resources = $is_ssl;
+ }
+
/**
* Returns public information for an application (as shown in the application
* directory) by either application ID, API key, or canvas page name.
@@ -231,7 +270,7 @@ function toggleDisplay(id, type) {
* @return string An authentication token.
*/
public function auth_createToken() {
- return $this->call_method('facebook.auth.createToken', array());
+ return $this->call_method('facebook.auth.createToken');
}
/**
@@ -246,8 +285,7 @@ function toggleDisplay(id, type) {
* @return array An assoc array containing session_key, uid
*/
public function auth_getSession($auth_token, $generate_session_secret=false) {
- //Check if we are in batch mode
- if($this->batch_queue === null) {
+ if (!$this->pending_batch()) {
$result = $this->call_method('facebook.auth.getSession',
array('auth_token' => $auth_token,
'generate_session_secret' => $generate_session_secret));
@@ -271,7 +309,7 @@ function toggleDisplay(id, type) {
* API_EC_PARAM_UNKNOWN
*/
public function auth_promoteSession() {
- return $this->call_method('facebook.auth.promoteSession', array());
+ return $this->call_method('facebook.auth.promoteSession');
}
/**
@@ -282,7 +320,20 @@ function toggleDisplay(id, type) {
* @return bool true if session expiration was successful, false otherwise
*/
public function auth_expireSession() {
- return $this->call_method('facebook.auth.expireSession', array());
+ return $this->call_method('facebook.auth.expireSession');
+ }
+
+ /**
+ * Revokes the given extended permission that the user granted at some
+ * prior time (for instance, offline_access or email). If no user is
+ * provided, it will be revoked for the user of the current session.
+ *
+ * @param string $perm The permission to revoke
+ * @param int $uid The user for whom to revoke the permission.
+ */
+ public function auth_revokeExtendedPermission($perm, $uid=null) {
+ return $this->call_method('facebook.auth.revokeExtendedPermission',
+ array('perm' => $perm, 'uid' => $uid));
}
/**
@@ -303,6 +354,30 @@ function toggleDisplay(id, type) {
}
/**
+ * Get public key that is needed to verify digital signature
+ * an app may pass to other apps. The public key is only used by
+ * other apps for verification purposes.
+ * @param string API key of an app
+ * @return string The public key for the app.
+ */
+ public function auth_getAppPublicKey($target_app_key) {
+ return $this->call_method('facebook.auth.getAppPublicKey',
+ array('target_app_key' => $target_app_key));
+ }
+
+ /**
+ * Get a structure that can be passed to another app
+ * as proof of session. The other app can verify it using public
+ * key of this app.
+ *
+ * @return signed public session data structure.
+ */
+ public function auth_getSignedPublicSessionData() {
+ return $this->call_method('facebook.auth.getSignedPublicSessionData',
+ array());
+ }
+
+ /**
* Returns the number of unconnected friends that exist in this application.
* This number is determined based on the accounts registered through
* connect.registerUsers() (see below).
@@ -363,8 +438,9 @@ function toggleDisplay(id, type) {
*
* @param int $uid (Optional) User associated with events. A null
* parameter will default to the session user.
- * @param array $eids (Optional) Filter by these event ids. A null
- * parameter will get all events for the user.
+ * @param array/string $eids (Optional) Filter by these event
+ * ids. A null parameter will get all events for
+ * the user. (A csv list will work but is deprecated)
* @param int $start_time (Optional) Filter with this unix time as lower
* bound. A null or zero parameter indicates no
* lower bound.
@@ -718,12 +794,15 @@ function toggleDisplay(id, type) {
* @param string $body_general (Optional) Additional markup that extends
* the body of a short story.
* @param int $story_size (Optional) A story size (see above)
+ * @param string $user_message (Optional) A user message for a short
+ * story.
*
* @return bool true on success
*/
public function &feed_publishUserAction(
$template_bundle_id, $template_data, $target_ids='', $body_general='',
- $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE) {
+ $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE,
+ $user_message='') {
if (is_array($template_data)) {
$template_data = json_encode($template_data);
@@ -739,7 +818,107 @@ function toggleDisplay(id, type) {
'template_data' => $template_data,
'target_ids' => $target_ids,
'body_general' => $body_general,
- 'story_size' => $story_size));
+ 'story_size' => $story_size,
+ 'user_message' => $user_message));
+ }
+
+
+ /**
+ * Publish a post to the user's stream.
+ *
+ * @param $message the user's message
+ * @param $attachment the post's attachment (optional)
+ * @param $action links the post's action links (optional)
+ * @param $target_id the user on whose wall the post will be posted
+ * (optional)
+ * @param $uid the actor (defaults to session user)
+ * @return string the post id
+ */
+ public function stream_publish(
+ $message, $attachment = null, $action_links = null, $target_id = null,
+ $uid = null) {
+
+ return $this->call_method(
+ 'facebook.stream.publish',
+ array('message' => $message,
+ 'attachment' => $attachment,
+ 'action_links' => $action_links,
+ 'target_id' => $target_id,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Remove a post from the user's stream.
+ * Currently, you may only remove stories you application created.
+ *
+ * @param $post_id the post id
+ * @param $uid the actor (defaults to session user)
+ * @return bool
+ */
+ public function stream_remove($post_id, $uid = null) {
+ return $this->call_method(
+ 'facebook.stream.remove',
+ array('post_id' => $post_id,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Add a comment to a stream post
+ *
+ * @param $post_id the post id
+ * @param $comment the comment text
+ * @param $uid the actor (defaults to session user)
+ * @return string the id of the created comment
+ */
+ public function stream_addComment($post_id, $comment, $uid = null) {
+ return $this->call_method(
+ 'facebook.stream.addComment',
+ array('post_id' => $post_id,
+ 'comment' => $comment,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+
+ /**
+ * Remove a comment from a stream post
+ *
+ * @param $comment_id the comment id
+ * @param $uid the actor (defaults to session user)
+ * @return bool
+ */
+ public function stream_removeComment($comment_id, $uid = null) {
+ return $this->call_method(
+ 'facebook.stream.removeComment',
+ array('comment_id' => $comment_id,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Add a like to a stream post
+ *
+ * @param $post_id the post id
+ * @param $uid the actor (defaults to session user)
+ * @return bool
+ */
+ public function stream_addLike($post_id, $uid = null) {
+ return $this->call_method(
+ 'facebook.stream.addLike',
+ array('post_id' => $post_id,
+ 'uid' => $this->get_uid($uid)));
+ }
+
+ /**
+ * Remove a like from a stream post
+ *
+ * @param $post_id the post id
+ * @param $uid the actor (defaults to session user)
+ * @return bool
+ */
+ public function stream_removeLike($post_id, $uid = null) {
+ return $this->call_method(
+ 'facebook.stream.removeLike',
+ array('post_id' => $post_id,
+ 'uid' => $this->get_uid($uid)));
}
/**
@@ -750,7 +929,7 @@ function toggleDisplay(id, type) {
* @return array An array of feed story objects.
*/
public function &feed_getAppFriendStories() {
- return $this->call_method('facebook.feed.getAppFriendStories', array());
+ return $this->call_method('facebook.feed.getAppFriendStories');
}
/**
@@ -771,33 +950,42 @@ function toggleDisplay(id, type) {
* Returns whether or not pairs of users are friends.
* Note that the Facebook friend relationship is symmetric.
*
- * @param array $uids1 array of ids (id_1, id_2,...) of some length X
- * @param array $uids2 array of ids (id_A, id_B,...) of SAME length X
+ * @param array/string $uids1 list of ids (id_1, id_2,...)
+ * of some length X (csv is deprecated)
+ * @param array/string $uids2 list of ids (id_A, id_B,...)
+ * of SAME length X (csv is deprecated)
*
* @return array An array with uid1, uid2, and bool if friends, e.g.:
* array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1),
* 1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0)
* ...)
+ * @error
+ * API_EC_PARAM_USER_ID_LIST
*/
public function &friends_areFriends($uids1, $uids2) {
return $this->call_method('facebook.friends.areFriends',
- array('uids1' => $uids1, 'uids2' => $uids2));
+ array('uids1' => $uids1,
+ 'uids2' => $uids2));
}
/**
* Returns the friends of the current session user.
*
* @param int $flid (Optional) Only return friends on this friend list.
+ * @param int $uid (Optional) Return friends for this user.
*
* @return array An array of friends
*/
- public function &friends_get($flid=null) {
+ public function &friends_get($flid=null, $uid = null) {
if (isset($this->friends_list)) {
return $this->friends_list;
}
$params = array();
- if (isset($this->canvas_user)) {
- $params['uid'] = $this->canvas_user;
+ if (!$uid && isset($this->canvas_user)) {
+ $uid = $this->canvas_user;
+ }
+ if ($uid) {
+ $params['uid'] = $uid;
}
if ($flid) {
$params['flid'] = $flid;
@@ -812,7 +1000,7 @@ function toggleDisplay(id, type) {
* @return array An array of friend list objects
*/
public function &friends_getLists() {
- return $this->call_method('facebook.friends.getLists', array());
+ return $this->call_method('facebook.friends.getLists');
}
/**
@@ -822,7 +1010,7 @@ function toggleDisplay(id, type) {
* @return array An array of friends also using the app
*/
public function &friends_getAppUsers() {
- return $this->call_method('facebook.friends.getAppUsers', array());
+ return $this->call_method('facebook.friends.getAppUsers');
}
/**
@@ -830,8 +1018,9 @@ function toggleDisplay(id, type) {
*
* @param int $uid (Optional) User associated with groups. A null
* parameter will default to the session user.
- * @param array $gids (Optional) Group ids to query. A null parameter will
- * get all groups for the user.
+ * @param array/string $gids (Optional) Array of group ids to query. A null
+ * parameter will get all groups for the user.
+ * (csv is deprecated)
*
* @return array An array of group objects
*/
@@ -890,6 +1079,40 @@ function toggleDisplay(id, type) {
}
/**
+ * Retrieves links posted by the given user.
+ *
+ * @param int $uid The user whose links you wish to retrieve
+ * @param int $limit The maximimum number of links to retrieve
+ * @param array $link_ids (Optional) Array of specific link
+ * IDs to retrieve by this user
+ *
+ * @return array An array of links.
+ */
+ public function &links_get($uid, $limit, $link_ids = null) {
+ return $this->call_method('links.get',
+ array('uid' => $uid,
+ 'limit' => $limit,
+ 'link_ids' => $link_ids));
+ }
+
+ /**
+ * Posts a link on Facebook.
+ *
+ * @param string $url URL/link you wish to post
+ * @param string $comment (Optional) A comment about this link
+ * @param int $uid (Optional) User ID that is posting this link;
+ * defaults to current session user
+ *
+ * @return bool
+ */
+ public function &links_post($url, $comment='', $uid = null) {
+ return $this->call_method('links.post',
+ array('uid' => $uid,
+ 'url' => $url,
+ 'comment' => $comment));
+ }
+
+ /**
* Permissions API
*/
@@ -946,6 +1169,78 @@ function toggleDisplay(id, type) {
}
/**
+ * Creates a note with the specified title and content.
+ *
+ * @param string $title Title of the note.
+ * @param string $content Content of the note.
+ * @param int $uid (Optional) The user for whom you are creating a
+ * note; defaults to current session user
+ *
+ * @return int The ID of the note that was just created.
+ */
+ public function &notes_create($title, $content, $uid = null) {
+ return $this->call_method('notes.create',
+ array('uid' => $uid,
+ 'title' => $title,
+ 'content' => $content));
+ }
+
+ /**
+ * Deletes the specified note.
+ *
+ * @param int $note_id ID of the note you wish to delete
+ * @param int $uid (Optional) Owner of the note you wish to delete;
+ * defaults to current session user
+ *
+ * @return bool
+ */
+ public function &notes_delete($note_id, $uid = null) {
+ return $this->call_method('notes.delete',
+ array('uid' => $uid,
+ 'note_id' => $note_id));
+ }
+
+ /**
+ * Edits a note, replacing its title and contents with the title
+ * and contents specified.
+ *
+ * @param int $note_id ID of the note you wish to edit
+ * @param string $title Replacement title for the note
+ * @param string $content Replacement content for the note
+ * @param int $uid (Optional) Owner of the note you wish to edit;
+ * defaults to current session user
+ *
+ * @return bool
+ */
+ public function &notes_edit($note_id, $title, $content, $uid = null) {
+ return $this->call_method('notes.edit',
+ array('uid' => $uid,
+ 'note_id' => $note_id,
+ 'title' => $title,
+ 'content' => $content));
+ }
+
+ /**
+ * Retrieves all notes by a user. If note_ids are specified,
+ * retrieves only those specific notes by that user.
+ *
+ * @param int $uid User whose notes you wish to retrieve
+ * @param array $note_ids (Optional) List of specific note
+ * IDs by this user to retrieve
+ *
+ * @return array A list of all of the given user's notes, or an empty list
+ * if the viewer lacks permissions or if there are no visible
+ * notes.
+ */
+ public function &notes_get($uid, $note_ids = null) {
+
+ return $this->call_method('notes.get',
+ array('uid' => $uid,
+ 'note_ids' => $note_ids));
+ }
+
+
+ /**
* Returns the outstanding notifications for the session user.
*
* @return array An assoc array of notification count objects for
@@ -954,13 +1249,15 @@ function toggleDisplay(id, type) {
* and an eid list of 'event_invites'
*/
public function &notifications_get() {
- return $this->call_method('facebook.notifications.get', array());
+ return $this->call_method('facebook.notifications.get');
}
/**
* Sends a notification to the specified users.
*
* @return A comma separated list of successful recipients
+ * @error
+ * API_EC_PARAM_USER_ID_LIST
*/
public function &notifications_send($to_ids, $notification, $type) {
return $this->call_method('facebook.notifications.send',
@@ -972,12 +1269,14 @@ function toggleDisplay(id, type) {
/**
* Sends an email to the specified user of the application.
*
- * @param array $recipients id of the recipients
+ * @param array/string $recipients array of ids of the recipients (csv is deprecated)
* @param string $subject subject of the email
* @param string $text (plain text) body of the email
* @param string $fbml fbml markup for an html version of the email
*
* @return string A comma separated list of successful recipients
+ * @error
+ * API_EC_PARAM_USER_ID_LIST
*/
public function &notifications_sendEmail($recipients,
$subject,
@@ -993,9 +1292,9 @@ function toggleDisplay(id, type) {
/**
* Returns the requested info fields for the requested set of pages.
*
- * @param array $page_ids an array of page ids
- * @param array $fields an array of strings describing the info fields
- * desired
+ * @param array/string $page_ids an array of page ids (csv is deprecated)
+ * @param array/string $fields an array of strings describing the
+ * info fields desired (csv is deprecated)
* @param int $uid (Optional) limit results to pages of which this
* user is a fan.
* @param string type limits results to a particular type of page.
@@ -1090,7 +1389,7 @@ function toggleDisplay(id, type) {
'tag_text' => $tag_text,
'x' => $x,
'y' => $y,
- 'tags' => json_encode($tags),
+ 'tags' => (is_array($tags)) ? json_encode($tags) : null,
'owner_uid' => $this->get_uid($owner_uid)));
}
@@ -1128,7 +1427,8 @@ function toggleDisplay(id, type) {
* @param int $subj_id (Optional) Filter by uid of user tagged in the photos.
* @param int $aid (Optional) Filter by an album, as returned by
* photos_getAlbums.
- * @param array $pids (Optional) Restrict to a list of pids
+ * @param array/string $pids (Optional) Restrict to an array of pids
+ * (csv is deprecated)
*
* Note that at least one of these parameters needs to be specified, or an
* error is returned.
@@ -1143,9 +1443,10 @@ function toggleDisplay(id, type) {
/**
* Returns the albums created by the given user.
*
- * @param int $uid (Optional) The uid of the user whose albums you want.
- * A null will return the albums of the session user.
- * @param array $aids (Optional) A list of aids to restrict the query.
+ * @param int $uid (Optional) The uid of the user whose albums you want.
+ * A null will return the albums of the session user.
+ * @param string $aids (Optional) An array of aids to restrict
+ * the query. (csv is deprecated)
*
* Note that at least one of the (uid, aids) parameters must be specified.
*
@@ -1172,16 +1473,66 @@ function toggleDisplay(id, type) {
}
/**
+ * Uploads a photo.
+ *
+ * @param string $file The location of the photo on the local filesystem.
+ * @param int $aid (Optional) The album into which to upload the
+ * photo.
+ * @param string $caption (Optional) A caption for the photo.
+ * @param int uid (Optional) The user ID of the user whose photo you
+ * are uploading
+ *
+ * @return array An array of user objects
+ */
+ public function photos_upload($file, $aid=null, $caption=null, $uid=null) {
+ return $this->call_upload_method('facebook.photos.upload',
+ array('aid' => $aid,
+ 'caption' => $caption,
+ 'uid' => $uid),
+ $file);
+ }
+
+
+ /**
+ * Uploads a video.
+ *
+ * @param string $file The location of the video on the local filesystem.
+ * @param string $title (Optional) A title for the video. Titles over 65 characters in length will be truncated.
+ * @param string $description (Optional) A description for the video.
+ *
+ * @return array An array with the video's ID, title, description, and a link to view it on Facebook.
+ */
+ public function video_upload($file, $title=null, $description=null) {
+ return $this->call_upload_method('facebook.video.upload',
+ array('title' => $title,
+ 'description' => $description),
+ $file,
+ Facebook::get_facebook_url('api-video') . '/restserver.php');
+ }
+
+ /**
+ * Returns an array with the video limitations imposed on the current session's
+ * associated user. Maximum length is measured in seconds; maximum size is
+ * measured in bytes.
+ *
+ * @return array Array with "length" and "size" keys
+ */
+ public function &video_getUploadLimits() {
+ return $this->call_method('facebook.video.getUploadLimits');
+ }
+
+ /**
* Returns the requested info fields for the requested set of users.
*
- * @param array $uids An array of user ids
- * @param array $fields An array of info field names desired
+ * @param array/string $uids An array of user ids (csv is deprecated)
+ * @param array/string $fields An array of info field names desired (csv is deprecated)
*
* @return array An array of user objects
*/
public function &users_getInfo($uids, $fields) {
return $this->call_method('facebook.users.getInfo',
- array('uids' => $uids, 'fields' => $fields));
+ array('uids' => $uids,
+ 'fields' => $fields));
}
/**
@@ -1194,14 +1545,15 @@ function toggleDisplay(id, type) {
* users, use users.getInfo instead, so that proper privacy rules will be
* applied.
*
- * @param array $uids An array of user ids
- * @param array $fields An array of info field names desired
+ * @param array/string $uids An array of user ids (csv is deprecated)
+ * @param array/string $fields An array of info field names desired (csv is deprecated)
*
* @return array An array of user objects
*/
public function &users_getStandardInfo($uids, $fields) {
return $this->call_method('facebook.users.getStandardInfo',
- array('uids' => $uids, 'fields' => $fields));
+ array('uids' => $uids,
+ 'fields' => $fields));
}
/**
@@ -1210,7 +1562,7 @@ function toggleDisplay(id, type) {
* @return integer User id
*/
public function &users_getLoggedInUser() {
- return $this->call_method('facebook.users.getLoggedInUser', array());
+ return $this->call_method('facebook.users.getLoggedInUser');
}
/**
@@ -1239,6 +1591,17 @@ function toggleDisplay(id, type) {
}
/**
+ * Returns whether or not the user corresponding to the current
+ * session object is verified by Facebook. See the documentation
+ * for Users.isVerified for details.
+ *
+ * @return boolean true if the user is verified
+ */
+ public function &users_isVerified() {
+ return $this->call_method('facebook.users.isVerified');
+ }
+
+ /**
* Sets the users' current status message. Message does NOT contain the
* word "is" , so make sure to include a verb.
*
@@ -1269,6 +1632,69 @@ function toggleDisplay(id, type) {
}
/**
+ * Gets the stream on behalf of a user using a set of users. This
+ * call will return the latest $limit queries between $start_time
+ * and $end_time.
+ *
+ * @param int $viewer_id user making the call (def: session)
+ * @param array $source_ids users/pages to look at (def: all connections)
+ * @param int $start_time start time to look for stories (def: 1 day ago)
+ * @param int $end_time end time to look for stories (def: now)
+ * @param int $limit number of stories to attempt to fetch (def: 30)
+ * @param string $filter_key key returned by stream.getFilters to fetch
+ *
+ * @return array(
+ * 'posts' => array of posts,
+ * 'profiles' => array of profile metadata of users/pages in posts
+ * 'albums' => array of album metadata in posts
+ * )
+ */
+ public function &stream_get($viewer_id = null,
+ $source_ids = null,
+ $start_time = 0,
+ $end_time = 0,
+ $limit = 30,
+ $filter_key = '') {
+ $args = array(
+ 'viewer_id' => $viewer_id,
+ 'source_ids' => $source_ids,
+ 'start_time' => $start_time,
+ 'end_time' => $end_time,
+ 'limit' => $limit,
+ 'filter_key' => $filter_key);
+ return $this->call_method('facebook.stream.get', $args);
+ }
+
+ /**
+ * Gets the filters (with relevant filter keys for stream.get) for a
+ * particular user. These filters are typical things like news feed,
+ * friend lists, networks. They can be used to filter the stream
+ * without complex queries to determine which ids belong in which groups.
+ *
+ * @param int $uid user to get filters for
+ *
+ * @return array of stream filter objects
+ */
+ public function &stream_getFilters($uid = null) {
+ $args = array('uid' => $uid);
+ return $this->call_method('facebook.stream.getFilters', $args);
+ }
+
+ /**
+ * Gets the full comments given a post_id from stream.get or the
+ * stream FQL table. Initially, only a set of preview comments are
+ * returned because some posts can have many comments.
+ *
+ * @param string $post_id id of the post to get comments for
+ *
+ * @return array of comment objects
+ */
+ public function &stream_getComments($post_id) {
+ $args = array('post_id' => $post_id);
+ return $this->call_method('facebook.stream.getComments', $args);
+ }
+
+ /**
* Sets the FBML for the profile of the user attached to this session.
*
* @param string $markup The FBML that describes the profile
@@ -1690,7 +2116,7 @@ function toggleDisplay(id, type) {
* API_EC_DATA_UNKNOWN_ERROR
*/
public function &data_getObjectTypes() {
- return $this->call_method('facebook.data.getObjectTypes', array());
+ return $this->call_method('facebook.data.getObjectTypes');
}
/**
@@ -2315,12 +2741,14 @@ function toggleDisplay(id, type) {
*
* @param string $integration_point_name Name of an integration point
* (see developer wiki for list).
+ * @param int $uid Specific user to check the limit.
*
* @return int Integration point allocation value
*/
- public function &admin_getAllocation($integration_point_name) {
+ public function &admin_getAllocation($integration_point_name, $uid=null) {
return $this->call_method('facebook.admin.getAllocation',
- array('integration_point_name' => $integration_point_name));
+ array('integration_point_name' => $integration_point_name,
+ 'uid' => $uid));
}
/**
@@ -2376,28 +2804,75 @@ function toggleDisplay(id, type) {
*/
public function admin_getRestrictionInfo() {
return json_decode(
- $this->call_method('admin.getRestrictionInfo', array()),
+ $this->call_method('admin.getRestrictionInfo'),
true);
}
+
+ /**
+ * Bans a list of users from the app. Banned users can't
+ * access the app's canvas page and forums.
+ *
+ * @param array $uids an array of user ids
+ * @return bool true on success
+ */
+ public function admin_banUsers($uids) {
+ return $this->call_method(
+ 'admin.banUsers', array('uids' => json_encode($uids)));
+ }
+
+ /**
+ * Unban users that have been previously banned with
+ * admin_banUsers().
+ *
+ * @param array $uids an array of user ids
+ * @return bool true on success
+ */
+ public function admin_unbanUsers($uids) {
+ return $this->call_method(
+ 'admin.unbanUsers', array('uids' => json_encode($uids)));
+ }
+
+ /**
+ * Gets the list of users that have been banned from the application.
+ * $uids is an optional parameter that filters the result with the list
+ * of provided user ids. If $uids is provided,
+ * only banned user ids that are contained in $uids are returned.
+ *
+ * @param array $uids an array of user ids to filter by
+ * @return bool true on success
+ */
+
+ public function admin_getBannedUsers($uids = null) {
+ return $this->call_method(
+ 'admin.getBannedUsers',
+ array('uids' => $uids ? json_encode($uids) : null));
+ }
+
/* UTILITY FUNCTIONS */
/**
- * Calls the specified method with the specified parameters.
+ * Calls the specified normal POST method with the specified parameters.
*
* @param string $method Name of the Facebook method to invoke
* @param array $params A map of param names => param values
*
- * @return mixed Result of method call
+ * @return mixed Result of method call; this returns a reference to support
+ * 'delayed returns' when in a batch context.
+ * See: http://wiki.developers.facebook.com/index.php/Using_batching_API
*/
- public function & call_method($method, $params) {
- //Check if we are in batch mode
- if($this->batch_queue === null) {
+ public function &call_method($method, $params = array()) {
+ if (!$this->pending_batch()) {
if ($this->call_as_apikey) {
$params['call_as_apikey'] = $this->call_as_apikey;
}
- $xml = $this->post_request($method, $params);
- $result = $this->convert_xml_to_result($xml, $method, $params);
+ $data = $this->post_request($method, $params);
+ if (empty($params['format']) || strtolower($params['format']) != 'json') {
+ $result = $this->convert_xml_to_result($data, $method, $params);
+ }
+ else {
+ $result = json_decode($data, true);
+ }
if (is_array($result) && isset($result['error_code'])) {
throw new FacebookRestClientException($result['error_msg'],
@@ -2413,11 +2888,46 @@ function toggleDisplay(id, type) {
return $result;
}
- private function convert_xml_to_result($xml, $method, $params) {
+ /**
+ * Calls the specified file-upload POST method with the specified parameters
+ *
+ * @param string $method Name of the Facebook method to invoke
+ * @param array $params A map of param names => param values
+ * @param string $file A path to the file to upload (required)
+ *
+ * @return array A dictionary representing the response.
+ */
+ public function call_upload_method($method, $params, $file, $server_addr = null) {
+ if (!$this->pending_batch()) {
+ if (!file_exists($file)) {
+ $code =
+ FacebookAPIErrorCodes::API_EC_PARAM;
+ $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+ throw new FacebookRestClientException($description, $code);
+ }
+
+ $xml = $this->post_upload_request($method, $params, $file, $server_addr);
+ $result = $this->convert_xml_to_result($xml, $method, $params);
+
+ if (is_array($result) && isset($result['error_code'])) {
+ throw new FacebookRestClientException($result['error_msg'],
+ $result['error_code']);
+ }
+ }
+ else {
+ $code =
+ FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
+ $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+ throw new FacebookRestClientException($description, $code);
+ }
+
+ return $result;
+ }
+
+ protected function convert_xml_to_result($xml, $method, $params) {
$sxml = simplexml_load_string($xml);
$result = self::convert_simplexml_to_array($sxml);
-
if (!empty($GLOBALS['facebook_config']['debug'])) {
// output the raw xml and its corresponding php object, for debugging:
print '<div style="margin: 10px 30px; padding: 5px; border: 2px solid black; background: gray; color: white; font-size: 12px; font-weight: bold;">';
@@ -2436,7 +2946,25 @@ function toggleDisplay(id, type) {
return $result;
}
- private function create_post_string($method, $params) {
+ private function finalize_params($method, &$params) {
+ $this->add_standard_params($method, $params);
+ // we need to do this before signing the params
+ $this->convert_array_values_to_json($params);
+ $params['sig'] = Facebook::generate_sig($params, $this->secret);
+ }
+
+ private function convert_array_values_to_json(&$params) {
+ foreach ($params as $key => &$val) {
+ if (is_array($val)) {
+ $val = json_encode($val);
+ }
+ }
+ }
+
+ private function add_standard_params($method, &$params) {
+ if ($this->call_as_apikey) {
+ $params['call_as_apikey'] = $this->call_as_apikey;
+ }
$params['method'] = $method;
$params['session_key'] = $this->session_key;
$params['api_key'] = $this->api_key;
@@ -2448,50 +2976,118 @@ function toggleDisplay(id, type) {
if (!isset($params['v'])) {
$params['v'] = '1.0';
}
+ if (isset($this->use_ssl_resources) &&
+ $this->use_ssl_resources) {
+ $params['return_ssl_resources'] = true;
+ }
+ }
+
+ private function create_post_string($method, $params) {
$post_params = array();
foreach ($params as $key => &$val) {
- if (is_array($val)) $val = implode(',', $val);
$post_params[] = $key.'='.urlencode($val);
}
- $secret = $this->secret;
- $post_params[] = 'sig='.Facebook::generate_sig($params, $secret);
return implode('&', $post_params);
}
- public function post_request($method, $params) {
+ private function run_multipart_http_transaction($method, $params, $file, $server_addr) {
- $post_string = $this->create_post_string($method, $params);
+ // the format of this message is specified in RFC1867/RFC1341.
+ // we add twenty pseudo-random digits to the end of the boundary string.
+ $boundary = '--------------------------FbMuLtIpArT' .
+ sprintf("%010d", mt_rand()) .
+ sprintf("%010d", mt_rand());
+ $content_type = 'multipart/form-data; boundary=' . $boundary;
+ // within the message, we prepend two extra hyphens.
+ $delimiter = '--' . $boundary;
+ $close_delimiter = $delimiter . '--';
+ $content_lines = array();
+ foreach ($params as $key => &$val) {
+ $content_lines[] = $delimiter;
+ $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"';
+ $content_lines[] = '';
+ $content_lines[] = $val;
+ }
+ // now add the file data
+ $content_lines[] = $delimiter;
+ $content_lines[] =
+ 'Content-Disposition: form-data; filename="' . $file . '"';
+ $content_lines[] = 'Content-Type: application/octet-stream';
+ $content_lines[] = '';
+ $content_lines[] = file_get_contents($file);
+ $content_lines[] = $close_delimiter;
+ $content_lines[] = '';
+ $content = implode("\r\n", $content_lines);
+ return $this->run_http_post_transaction($content_type, $content, $server_addr);
+ }
- if (function_exists('curl_init')) {
- // Use CURL if installed...
+ public function post_request($method, $params) {
+ $this->finalize_params($method, $params);
+ $post_string = $this->create_post_string($method, $params);
+ if ($this->use_curl_if_available && function_exists('curl_init')) {
$useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->server_addr);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
+ curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$result = curl_exec($ch);
curl_close($ch);
} else {
- // Non-CURL based version...
$content_type = 'application/x-www-form-urlencoded';
- $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) '.phpversion();
- $context =
- array('http' =>
+ $content = $post_string;
+ $result = $this->run_http_post_transaction($content_type,
+ $content,
+ $this->server_addr);
+ }
+ return $result;
+ }
+
+ private function post_upload_request($method, $params, $file, $server_addr = null) {
+ $server_addr = $server_addr ? $server_addr : $this->server_addr;
+ $this->finalize_params($method, $params);
+ if ($this->use_curl_if_available && function_exists('curl_init')) {
+ // prepending '@' causes cURL to upload the file; the key is ignored.
+ $params['_file'] = '@' . $file;
+ $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
+ $ch = curl_init();
+ curl_setopt($ch, CURLOPT_URL, $server_addr);
+ // this has to come before the POSTFIELDS set!
+ curl_setopt($ch, CURLOPT_POST, 1 );
+ // passing an array gets curl to use the multipart/form-data content type
+ curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
+ $result = curl_exec($ch);
+ curl_close($ch);
+ } else {
+ $result = $this->run_multipart_http_transaction($method, $params, $file, $server_addr);
+ }
+ return $result;
+ }
+
+ private function run_http_post_transaction($content_type, $content, $server_addr) {
+
+ $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion();
+ $content_length = strlen($content);
+ $context =
+ array('http' =>
array('method' => 'POST',
- 'header' => 'Content-type: '.$content_type."\r\n".
- 'User-Agent: '.$user_agent."\r\n".
- 'Content-length: ' . strlen($post_string),
- 'content' => $post_string));
- $contextid=stream_context_create($context);
- $sock=fopen($this->server_addr, 'r', false, $contextid);
- if ($sock) {
- $result='';
- while (!feof($sock))
- $result.=fgets($sock, 4096);
-
- fclose($sock);
+ 'user_agent' => $user_agent,
+ 'header' => 'Content-Type: ' . $content_type . "\r\n" .
+ 'Content-Length: ' . $content_length,
+ 'content' => $content));
+ $context_id = stream_context_create($context);
+ $sock = fopen($server_addr, 'r', false, $context_id);
+
+ $result = '';
+ if ($sock) {
+ while (!feof($sock)) {
+ $result .= fgets($sock, 4096);
}
+ fclose($sock);
}
return $result;
}
@@ -2541,6 +3137,14 @@ class FacebookAPIErrorCodes {
const API_EC_METHOD = 3;
const API_EC_TOO_MANY_CALLS = 4;
const API_EC_BAD_IP = 5;
+ const API_EC_HOST_API = 6;
+ const API_EC_HOST_UP = 7;
+ const API_EC_SECURE = 8;
+ const API_EC_RATE = 9;
+ const API_EC_PERMISSION_DENIED = 10;
+ const API_EC_DEPRECATED = 11;
+ const API_EC_VERSION = 12;
+ const API_EC_INTERNAL_FQL_ERROR = 13;
/*
* PARAMETER ERRORS
@@ -2550,27 +3154,121 @@ class FacebookAPIErrorCodes {
const API_EC_PARAM_SESSION_KEY = 102;
const API_EC_PARAM_CALL_ID = 103;
const API_EC_PARAM_SIGNATURE = 104;
+ const API_EC_PARAM_TOO_MANY = 105;
const API_EC_PARAM_USER_ID = 110;
const API_EC_PARAM_USER_FIELD = 111;
const API_EC_PARAM_SOCIAL_FIELD = 112;
+ const API_EC_PARAM_EMAIL = 113;
+ const API_EC_PARAM_USER_ID_LIST = 114;
+ const API_EC_PARAM_FIELD_LIST = 115;
const API_EC_PARAM_ALBUM_ID = 120;
+ const API_EC_PARAM_PHOTO_ID = 121;
+ const API_EC_PARAM_FEED_PRIORITY = 130;
+ const API_EC_PARAM_CATEGORY = 140;
+ const API_EC_PARAM_SUBCATEGORY = 141;
+ const API_EC_PARAM_TITLE = 142;
+ const API_EC_PARAM_DESCRIPTION = 143;
+ const API_EC_PARAM_BAD_JSON = 144;
const API_EC_PARAM_BAD_EID = 150;
const API_EC_PARAM_UNKNOWN_CITY = 151;
+ const API_EC_PARAM_BAD_PAGE_TYPE = 152;
/*
* USER PERMISSIONS ERRORS
*/
const API_EC_PERMISSION = 200;
const API_EC_PERMISSION_USER = 210;
+ const API_EC_PERMISSION_NO_DEVELOPERS = 211;
const API_EC_PERMISSION_ALBUM = 220;
const API_EC_PERMISSION_PHOTO = 221;
+ const API_EC_PERMISSION_MESSAGE = 230;
+ const API_EC_PERMISSION_OTHER_USER = 240;
+ const API_EC_PERMISSION_STATUS_UPDATE = 250;
+ const API_EC_PERMISSION_PHOTO_UPLOAD = 260;
+ const API_EC_PERMISSION_VIDEO_UPLOAD = 261;
+ const API_EC_PERMISSION_SMS = 270;
+ const API_EC_PERMISSION_CREATE_LISTING = 280;
+ const API_EC_PERMISSION_CREATE_NOTE = 281;
+ const API_EC_PERMISSION_SHARE_ITEM = 282;
const API_EC_PERMISSION_EVENT = 290;
+ const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291;
+ const API_EC_PERMISSION_LIVEMESSAGE = 292;
const API_EC_PERMISSION_RSVP_EVENT = 299;
- const FQL_EC_PARSER = 601;
+ /*
+ * DATA EDIT ERRORS
+ */
+ const API_EC_EDIT = 300;
+ const API_EC_EDIT_USER_DATA = 310;
+ const API_EC_EDIT_PHOTO = 320;
+ const API_EC_EDIT_ALBUM_SIZE = 321;
+ const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322;
+ const API_EC_EDIT_PHOTO_TAG_PHOTO = 323;
+ const API_EC_EDIT_PHOTO_FILE = 324;
+ const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325;
+ const API_EC_EDIT_PHOTO_TAG_LIMIT = 326;
+ const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327;
+ const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328;
+
+ const API_EC_MALFORMED_MARKUP = 329;
+ const API_EC_EDIT_MARKUP = 330;
+
+ const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340;
+ const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341;
+ const API_EC_EDIT_FEED_TITLE_LINK = 342;
+ const API_EC_EDIT_FEED_TITLE_LENGTH = 343;
+ const API_EC_EDIT_FEED_TITLE_NAME = 344;
+ const API_EC_EDIT_FEED_TITLE_BLANK = 345;
+ const API_EC_EDIT_FEED_BODY_LENGTH = 346;
+ const API_EC_EDIT_FEED_PHOTO_SRC = 347;
+ const API_EC_EDIT_FEED_PHOTO_LINK = 348;
+
+ const API_EC_EDIT_VIDEO_SIZE = 350;
+ const API_EC_EDIT_VIDEO_INVALID_FILE = 351;
+ const API_EC_EDIT_VIDEO_INVALID_TYPE = 352;
+ const API_EC_EDIT_VIDEO_FILE = 353;
+
+ const API_EC_EDIT_FEED_TITLE_ARRAY = 360;
+ const API_EC_EDIT_FEED_TITLE_PARAMS = 361;
+ const API_EC_EDIT_FEED_BODY_ARRAY = 362;
+ const API_EC_EDIT_FEED_BODY_PARAMS = 363;
+ const API_EC_EDIT_FEED_PHOTO = 364;
+ const API_EC_EDIT_FEED_TEMPLATE = 365;
+ const API_EC_EDIT_FEED_TARGET = 366;
+ const API_EC_EDIT_FEED_MARKUP = 367;
+
+ /**
+ * SESSION ERRORS
+ */
+ const API_EC_SESSION_TIMED_OUT = 450;
+ const API_EC_SESSION_METHOD = 451;
+ const API_EC_SESSION_INVALID = 452;
+ const API_EC_SESSION_REQUIRED = 453;
+ const API_EC_SESSION_REQUIRED_FOR_SECRET = 454;
+ const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455;
+
+
+ /**
+ * FQL ERRORS
+ */
+ const FQL_EC_UNKNOWN_ERROR = 600;
+ const FQL_EC_PARSER = 601; // backwards compatibility
+ const FQL_EC_PARSER_ERROR = 601;
const FQL_EC_UNKNOWN_FIELD = 602;
const FQL_EC_UNKNOWN_TABLE = 603;
- const FQL_EC_NOT_INDEXABLE = 604;
+ const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility
+ const FQL_EC_NO_INDEX = 604;
+ const FQL_EC_UNKNOWN_FUNCTION = 605;
+ const FQL_EC_INVALID_PARAM = 606;
+ const FQL_EC_INVALID_FIELD = 607;
+ const FQL_EC_INVALID_SESSION = 608;
+ const FQL_EC_UNSUPPORTED_APP_TYPE = 609;
+ const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610;
+ const FQL_EC_DEPRECATED_TABLE = 611;
+ const FQL_EC_EXTENDED_PERMISSION = 612;
+ const FQL_EC_RATE_LIMIT_EXCEEDED = 613;
+
+ const API_EC_REF_SET_FAILED = 700;
/**
* DATA STORE API ERRORS
@@ -2581,52 +3279,122 @@ class FacebookAPIErrorCodes {
const API_EC_DATA_OBJECT_NOT_FOUND = 803;
const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804;
const API_EC_DATA_DATABASE_ERROR = 805;
+ const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806;
+ const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807;
+ const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808;
+ const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809;
+ const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810;
+ const API_EC_DATA_MALFORMED_ACTION_LINK = 811;
+ const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812;
/*
- * Batch ERROR
+ * APPLICATION INFO ERRORS
*/
- const API_EC_BATCH_ALREADY_STARTED = 900;
- const API_EC_BATCH_NOT_STARTED = 901;
- const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 902;
+ const API_EC_NO_SUCH_APP = 900;
+ /*
+ * BATCH ERRORS
+ */
+ const API_EC_BATCH_TOO_MANY_ITEMS = 950;
+ const API_EC_BATCH_ALREADY_STARTED = 951;
+ const API_EC_BATCH_NOT_STARTED = 952;
+ const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953;
+
+ /*
+ * EVENT API ERRORS
+ */
+ const API_EC_EVENT_INVALID_TIME = 1000;
+
+ /*
+ * INFO BOX ERRORS
+ */
+ const API_EC_INFO_NO_INFORMATION = 1050;
+ const API_EC_INFO_SET_FAILED = 1051;
+
+ /*
+ * LIVEMESSAGE API ERRORS
+ */
+ const API_EC_LIVEMESSAGE_SEND_FAILED = 1100;
+ const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101;
+ const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102;
+
+ /*
+ * CONNECT SESSION ERRORS
+ */
+ const API_EC_CONNECT_FEED_DISABLED = 1300;
+
+ /*
+ * Platform tag bundles errors
+ */
+ const API_EC_TAG_BUNDLE_QUOTA = 1400;
+
+ /*
+ * SHARE
+ */
+ const API_EC_SHARE_BAD_URL = 1500;
+
+ /*
+ * NOTES
+ */
+ const API_EC_NOTE_CANNOT_MODIFY = 1600;
+
+ /*
+ * COMMENTS
+ */
+ const API_EC_COMMENTS_UNKNOWN = 1700;
+ const API_EC_COMMENTS_POST_TOO_LONG = 1701;
+ const API_EC_COMMENTS_DB_DOWN = 1702;
+ const API_EC_COMMENTS_INVALID_XID = 1703;
+ const API_EC_COMMENTS_INVALID_UID = 1704;
+ const API_EC_COMMENTS_INVALID_POST = 1705;
+
+ /**
+ * This array is no longer maintained; to view the description of an error
+ * code, please look at the message element of the API response or visit
+ * the developer wiki at http://wiki.developers.facebook.com/.
+ */
public static $api_error_descriptions = array(
- API_EC_SUCCESS => 'Success',
- API_EC_UNKNOWN => 'An unknown error occurred',
- API_EC_SERVICE => 'Service temporarily unavailable',
- API_EC_METHOD => 'Unknown method',
- API_EC_TOO_MANY_CALLS => 'Application request limit reached',
- API_EC_BAD_IP => 'Unauthorized source IP address',
- API_EC_PARAM => 'Invalid parameter',
- API_EC_PARAM_API_KEY => 'Invalid API key',
- API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
- API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous',
- API_EC_PARAM_SIGNATURE => 'Incorrect signature',
- API_EC_PARAM_USER_ID => 'Invalid user id',
- API_EC_PARAM_USER_FIELD => 'Invalid user info field',
- API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
- API_EC_PARAM_ALBUM_ID => 'Invalid album id',
- API_EC_PARAM_BAD_EID => 'Invalid eid',
- API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
- API_EC_PERMISSION => 'Permissions error',
- API_EC_PERMISSION_USER => 'User not visible',
- API_EC_PERMISSION_ALBUM => 'Album not visible',
- API_EC_PERMISSION_PHOTO => 'Photo not visible',
- API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event',
- API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
- FQL_EC_PARSER => 'FQL: Parser Error',
- FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field',
- FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table',
- FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable',
- FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function',
- FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in',
- API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
- API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
- API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
- API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
- API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
- API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
- API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
- API_EC_BATCH_NOT_STARTED => 'end_batch called before start_batch',
- API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode',
+ self::API_EC_SUCCESS => 'Success',
+ self::API_EC_UNKNOWN => 'An unknown error occurred',
+ self::API_EC_SERVICE => 'Service temporarily unavailable',
+ self::API_EC_METHOD => 'Unknown method',
+ self::API_EC_TOO_MANY_CALLS => 'Application request limit reached',
+ self::API_EC_BAD_IP => 'Unauthorized source IP address',
+ self::API_EC_PARAM => 'Invalid parameter',
+ self::API_EC_PARAM_API_KEY => 'Invalid API key',
+ self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
+ self::API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous',
+ self::API_EC_PARAM_SIGNATURE => 'Incorrect signature',
+ self::API_EC_PARAM_USER_ID => 'Invalid user id',
+ self::API_EC_PARAM_USER_FIELD => 'Invalid user info field',
+ self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
+ self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list',
+ self::API_EC_PARAM_FIELD_LIST => 'Invalid field list',
+ self::API_EC_PARAM_ALBUM_ID => 'Invalid album id',
+ self::API_EC_PARAM_BAD_EID => 'Invalid eid',
+ self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
+ self::API_EC_PERMISSION => 'Permissions error',
+ self::API_EC_PERMISSION_USER => 'User not visible',
+ self::API_EC_PERMISSION_NO_DEVELOPERS => 'Application has no developers',
+ self::API_EC_PERMISSION_ALBUM => 'Album not visible',
+ self::API_EC_PERMISSION_PHOTO => 'Photo not visible',
+ self::API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event',
+ self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
+ self::API_EC_EDIT_ALBUM_SIZE => 'Album is full',
+ self::FQL_EC_PARSER => 'FQL: Parser Error',
+ self::FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field',
+ self::FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table',
+ self::FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable',
+ self::FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function',
+ self::FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in',
+ self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
+ self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
+ self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
+ self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
+ self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
+ self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
+ self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
+ self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch',
+ self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode'
);
}
diff --git a/index.php b/index.php
index e24bde917..9ff1c2c56 100644
--- a/index.php
+++ b/index.php
@@ -63,6 +63,10 @@ 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'])) {
+ die("Fancy URL support detection succeeded. We suggest you enable this to get fancy (pretty) URLs.");
+ }
global $user, $action, $config;
if (!_have_config()) {
@@ -101,6 +105,8 @@ function main()
$args = array_merge($args, $_REQUEST);
+ Event::handle('ArgsInitialize', array(&$args));
+
$action = $args['action'];
if (!$action || !preg_match('/^[a-zA-Z0-9_-]*$/', $action)) {
diff --git a/install.php b/install.php
index 66e8e8712..32915200b 100644
--- a/install.php
+++ b/install.php
@@ -86,7 +86,8 @@ function checkExtension($name)
function showForm()
{
-?>
+ $config_path = htmlentities(trim(dirname($_SERVER['REQUEST_URI']), '/'));
+ echo<<<E_O_T
</ul>
</dd>
</dl>
@@ -108,12 +109,22 @@ function showForm()
<p class="form_guide">The name of your site</p>
</li>
<li>
+ <label for="fancy-enable">Fancy URLs</label>
+ <input type="radio" name="fancy" id="fancy-enable" value="enable" checked='checked' /> enable<br />
+ <input type="radio" name="fancy" id="fancy-disable" value="" /> disable<br />
+ <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">Database</label>
<input type="text" id="database" name="database" />
<p class="form_guide">Database name</p>
@@ -132,7 +143,8 @@ function showForm()
<input type="submit" name="submit" class="submit" value="Submit" />
</fieldset>
</form>
-<?php
+
+E_O_T;
}
function updateStatus($status, $error=false)
@@ -148,11 +160,13 @@ function handlePost()
?>
<?php
- $host = $_POST['host'];
+ $host = $_POST['host'];
$database = $_POST['database'];
$username = $_POST['username'];
$password = $_POST['password'];
$sitename = $_POST['sitename'];
+ $path = $_POST['path'];
+ $fancy = !empty($_POST['fancy']);
?>
<dl class="system_notice">
<dt>Page notice</dt>
@@ -225,29 +239,34 @@ function handlePost()
}
updateStatus("Writing config file...");
$sqlUrl = "mysqli://$username:$password@$host/$database";
- $res = writeConf($sitename, $sqlUrl);
+ $res = writeConf($sitename, $sqlUrl, $fancy, $path);
if (!$res) {
updateStatus("Can't write config file.", true);
showForm();
return;
}
updateStatus("Done!");
+ if ($path) $path .= '/';
+ updateStatus("You can visit your <a href='/$path'>new Laconica site</a).");
?>
<?php
}
-function writeConf($sitename, $sqlUrl)
+function writeConf($sitename, $sqlUrl, $fancy, $path)
{
$res = file_put_contents(INSTALLDIR.'/config.php',
"<?php\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");
return $res;
}
function runDbScript($filename, $conn)
{
+return true;
$sql = trim(file_get_contents($filename));
$stmts = explode(';', $sql);
foreach ($stmts as $stmt) {
@@ -276,6 +295,8 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
<!--[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>
</head>
<body id="install">
<div id="wrap">
diff --git a/js/farbtastic/farbtastic.go.js b/js/farbtastic/farbtastic.go.js
index 21a1530bc..e298c1dab 100644
--- a/js/farbtastic/farbtastic.go.js
+++ b/js/farbtastic/farbtastic.go.js
@@ -1,10 +1,67 @@
$(document).ready(function() {
- var f = $.farbtastic('#color-picker');
- var colors = $('#settings_design_color input');
+ function UpdateColors(e) {
+ var S = f.linked;
+ var C = f.color;
- colors
- .each(function () { f.linkTo(this); })
- .focus(function() {
- f.linkTo(this);
+ 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 UpdateFarbtastic(e) {
+ f.linked = e;
+ f.setColor(e.value);
+ }
+
+ function UpdateSwatch(e) {
+ $(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)
+
+ .blur(function() {
+ $(this).val($(this).val().toUpperCase());
+ })
+
+ .focus(function() {
+ $('#color-picker').show();
+ UpdateFarbtastic(this);
+ })
+
+ .change(function() {
+ UpdateFarbtastic(this);
+ UpdateSwatch(this);
+ }).change()
+
+ ;
+
});
diff --git a/js/install.js b/js/install.js
new file mode 100644
index 000000000..32a54111e
--- /dev/null
+++ b/js/install.js
@@ -0,0 +1,18 @@
+$(document).ready(function(){
+ $.ajax({url:'check-fancy',
+ type:'GET',
+ success:function(data, textStatus) {
+ $('#fancy-enable').attr('checked', true);
+ $('#fancy-disable').attr('checked', false);
+ $('#fancy-form_guide').text(data);
+ },
+ error:function(XMLHttpRequest, textStatus, errorThrown) {
+ $('#fancy-enable').attr('checked', false);
+ $('#fancy-disable').attr('checked', true);
+ $('#fancy-enable').attr('disabled', true);
+ $('#fancy-disable').attr('disabled', true);
+ $('#fancy-form_guide').text("Fancy URL support detection failed, disabling this option. Make sure you renamed htaccess.sample to .htaccess.");
+ }
+ });
+});
+
diff --git a/js/jquery.joverlay.min.js b/js/jquery.joverlay.min.js
new file mode 100644
index 000000000..c9168506a
--- /dev/null
+++ b/js/jquery.joverlay.min.js
@@ -0,0 +1,6 @@
+/* Copyright (c) 2009 Alvaro A. Lima Jr http://alvarojunior.com/jquery/joverlay.html
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
+ * Version: 0.6 (Abr 23, 2009)
+ * Requires: jQuery 1.3+
+ */
+(function($){var f=$.browser.msie&&$.browser.version==6.0;var g=null;$.fn.jOverlay=function(b){var b=$.extend({},$.fn.jOverlay.options,b);if(g!=null){clearTimeout(g)}var c=this.is('*')?this:'#jOverlayContent';var d=f?'absolute':'fixed';var e=b.imgLoading?"<img id='jOverlayLoading' src='"+b.imgLoading+"' style='position:"+d+"; z-index:"+(b.zIndex+9)+";'/>":'';$('body').prepend(e+"<div id='jOverlay' />"+"<div id='jOverlayContent' style='position:"+d+"; z-index:"+(b.zIndex+5)+"; display:none;'/>");$('#jOverlayLoading').load(function(){if(b.center){$.center(this)}});if(f){$("select").hide();$("#jOverlayContent select").show()}$('#jOverlay').css({backgroundColor:b.color,position:d,top:'0px',left:'0px',filter:'alpha(opacity='+(b.opacity*100)+')',opacity:b.opacity,zIndex:b.zIndex,width:!f?'100%':$(window).width()+'px',height:!f?'100%':$(document).height()+'px'}).show();if(this.is('*')){$('#jOverlayContent').html(this.addClass('jOverlayChildren').show()).show();if(b.center){$.center('#jOverlayContent')}if(!b.url&&$.isFunction(b.success)){b.success(this.html())}}if(b.url){$.ajax({type:b.method,data:b.data,url:b.url,success:function(a){$('#jOverlayLoading').fadeOut(600);$(c).html(a).show();if(b.center){$.center('#jOverlayContent')}if($.isFunction(b.success)){b.success(a)}}})}if(f){$(window).scroll(function(){if(b.center){$.center('#jOverlayContent')}});$(window).resize(function(){$('#jOverlay').css({width:$(window).width()+'px',height:$(document).height()+'px'});if(b.center){$.center('#jOverlayContent')}})}$(document).keydown(function(a){if(a.keyCode==27){$.closeOverlay()}});if(b.bgClickToClose){$('#jOverlay').click($.closeOverlay)}if(Number(b.timeout)>0){g=setTimeout($.closeOverlay,Number(b.timeout))}};$.center=function(a){var a=$(a);var b=a.height();var c=a.width();a.css({width:c+'px',marginLeft:'-'+(c/2)+'px',marginTop:'-'+b/2+'px',height:'auto',top:!f?'50%':$(window).scrollTop()+($(window).height()/2)+"px",left:'50%'})};$.fn.jOverlay.options={method:'GET',data:'',url:'',color:'#000',opacity:'0.6',zIndex:9999,center:true,imgLoading:'',bgClickToClose:true,success:null,timeout:0};$.closeOverlay=function(){if(f){$("select").show()}$('#jOverlayContent .jOverlayChildren').hide().prependTo($('body'));$('#jOverlayLoading, #jOverlayContent, #jOverlay').remove()}})(jQuery); \ No newline at end of file
diff --git a/js/util.js b/js/util.js
index 3f14bc61c..31d9eb4f5 100644
--- a/js/util.js
+++ b/js/util.js
@@ -17,6 +17,10 @@
*/
$(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;
diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php
index fe106cb83..924aa93a8 100644
--- a/lib/Shorturl_api.php
+++ b/lib/Shorturl_api.php
@@ -22,6 +22,7 @@ if (!defined('LACONICA')) { exit(1); }
class ShortUrlApi
{
protected $service_url;
+ protected $long_limit = 27;
function __construct($service_url)
{
@@ -39,7 +40,7 @@ class ShortUrlApi
}
private function is_long($url) {
- return strlen($url) >= 30;
+ return strlen($url) >= $this->long_limit;
}
protected function http_post($data) {
diff --git a/lib/action.php b/lib/action.php
index 3e43ffe3e..6a69d2651 100644
--- a/lib/action.php
+++ b/lib/action.php
@@ -98,15 +98,15 @@ class Action extends HTMLOutputter // lawsuit
Event::handle('EndShowHTML', array($this));
}
if (Event::handle('StartShowHead', array($this))) {
- $this->showHead();
+ $this->showHead();
Event::handle('EndShowHead', array($this));
}
if (Event::handle('StartShowBody', array($this))) {
- $this->showBody();
+ $this->showBody();
Event::handle('EndShowBody', array($this));
}
if (Event::handle('StartEndHTML', array($this))) {
- $this->endHTML();
+ $this->endHTML();
Event::handle('EndEndHTML', array($this));
}
}
@@ -243,6 +243,12 @@ class Action extends HTMLOutputter // lawsuit
$this->element('script', array('type' => 'text/javascript',
'src' => common_path('js/jquery.form.js')),
' ');
+
+ $this->element('script', array('type' => 'text/javascript',
+ 'src' => common_path('js/jquery.joverlay.min.js')),
+ ' ');
+
+
Event::handle('EndShowJQueryScripts', array($this));
}
if (Event::handle('StartShowLaconicaScripts', array($this))) {
@@ -347,7 +353,7 @@ class Action extends HTMLOutputter // lawsuit
{
$this->elementStart('body', (common_current_user()) ? array('id' => $this->trimmed('action'),
'class' => 'user_in')
- : array('id' => $this->trimmed('action')));
+ : array('id' => $this->trimmed('action')));
$this->elementStart('div', array('id' => 'wrap'));
if (Event::handle('StartShowHeader', array($this))) {
$this->showHeader();
@@ -431,10 +437,10 @@ class Action extends HTMLOutputter // lawsuit
_('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
}
$this->menuItem(common_local_url('invite'),
- _('Invite'),
- sprintf(_('Invite friends and colleagues to join you on %s'),
- common_config('site', 'name')),
- false, 'nav_invitecontact');
+ _('Invite'),
+ sprintf(_('Invite friends and colleagues to join you on %s'),
+ common_config('site', 'name')),
+ false, 'nav_invitecontact');
$this->menuItem(common_local_url('logout'),
_('Logout'), _('Logout from the site'), false, 'nav_logout');
}
@@ -591,7 +597,10 @@ class Action extends HTMLOutputter // lawsuit
'class' => 'system_notice'));
$this->element('dt', null, _('Page notice'));
$this->elementStart('dd');
- $this->showPageNotice();
+ if (Event::handle('StartShowPageNotice', array($this))) {
+ $this->showPageNotice();
+ Event::handle('EndShowPageNotice', array($this));
+ }
$this->elementEnd('dd');
$this->elementEnd('dl');
}
@@ -629,7 +638,7 @@ class Action extends HTMLOutputter // lawsuit
$this->elementStart('div', array('id' => 'aside_primary',
'class' => 'aside'));
if (Event::handle('StartShowExportData', array($this))) {
- $this->showExportData();
+ $this->showExportData();
Event::handle('EndShowExportData', array($this));
}
if (Event::handle('StartShowSections', array($this))) {
diff --git a/lib/attachmentlist.php b/lib/attachmentlist.php
new file mode 100644
index 000000000..9485fe3d6
--- /dev/null
+++ b/lib/attachmentlist.php
@@ -0,0 +1,300 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * widget for displaying a list of 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 UI
+ * @package Laconica
+ * @author Evan Prodromou <evan@controlyourself.ca>
+ * @author Sarven Capadisli <csarven@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);
+}
+
+/**
+ * widget for displaying a list of notice attachments
+ *
+ * There are a number of actions that display a list of notices, in
+ * reverse chronological order. This widget abstracts out most of the
+ * code for UI for notice lists. It's overridden to hide some
+ * data for e.g. the profile page.
+ *
+ * @category UI
+ * @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 Notice
+ * @see StreamAction
+ * @see NoticeListItem
+ * @see ProfileNoticeList
+ */
+
+class AttachmentList extends Widget
+{
+ /** the current stream of notices being displayed. */
+
+ var $notice = null;
+
+ /**
+ * constructor
+ *
+ * @param Notice $notice stream of notices from DB_DataObject
+ */
+
+ function __construct($notice, $out=null)
+ {
+ parent::__construct($out);
+ $this->notice = $notice;
+ }
+
+ /**
+ * show the list of notices
+ *
+ * "Uses up" the stream by looping through it. So, probably can't
+ * be called twice on the same list.
+ *
+ * @return int count of notices listed.
+ */
+
+ 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);
+ foreach ($att as $n=>$attachment) {
+ $item = $this->newListItem($attachment);
+ $item->show();
+ }
+
+ $this->out->elementEnd('ul');
+ $this->out->elementEnd('div');
+
+ return count($att);
+ }
+
+ /**
+ * returns a new list item for the current notice
+ *
+ * Recipe (factory?) method; overridden by sub-classes to give
+ * a different list item class.
+ *
+ * @param Notice $notice the current notice
+ *
+ * @return NoticeListItem a list item for displaying the notice
+ */
+
+ function newListItem($attachment)
+ {
+ return new AttachmentListItem($attachment, $this->out);
+ }
+}
+
+/**
+ * widget for displaying a single notice
+ *
+ * This widget has the core smarts for showing a single notice: what to display,
+ * where, and under which circumstances. Its key method is show(); this is a recipe
+ * that calls all the other show*() methods to build up a single notice. The
+ * ProfileNoticeListItem subclass, for example, overrides showAuthor() to skip
+ * author info (since that's implicit by the data in the page).
+ *
+ * @category UI
+ * @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 NoticeList
+ * @see ProfileNoticeListItem
+ */
+
+class AttachmentListItem extends Widget
+{
+ /** The attachment this item will show. */
+
+ var $attachment = null;
+
+ var $oembed = null;
+
+ /**
+ * constructor
+ *
+ * Also initializes the profile attribute.
+ *
+ * @param Notice $notice The notice we'll display
+ */
+
+ function __construct($attachment, $out=null)
+ {
+ parent::__construct($out);
+ $this->attachment = $attachment;
+ $this->oembed = File_oembed::staticGet('file_id', $this->attachment->id);
+ }
+
+ function title() {
+ if (empty($this->attachment->title)) {
+ if (empty($this->oembed->title)) {
+ $title = $this->attachment->url;
+ } else {
+ $title = $this->oembed->title;
+ }
+ } else {
+ $title = $this->attachment->title;
+ }
+
+ return $title;
+ }
+
+ function linkTitle() {
+ return 'Our page for ' . $this->title();
+ }
+
+ /**
+ * recipe function for displaying a single notice.
+ *
+ * This uses all the other methods to correctly display a notice. Override
+ * it or one of the others to fine-tune the output.
+ *
+ * @return void
+ */
+
+ function show()
+ {
+ $this->showStart();
+ $this->showNoticeAttachment();
+ $this->showEnd();
+ }
+
+ function linkAttr() {
+ return array('class' => 'attachment', 'href' => common_local_url('attachment', array('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');
+ }
+
+ 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');
+ }
+ }
+
+ /**
+ * start a single notice.
+ *
+ * @return void
+ */
+
+ function showStart()
+ {
+ // XXX: RDFa
+ // TODO: add notice_type class e.g., notice_video, notice_image
+ $this->out->elementStart('li');
+ }
+
+ /**
+ * finish the notice
+ *
+ * Close the last elements in the notice list item
+ *
+ * @return void
+ */
+
+ function showEnd()
+ {
+ $this->out->elementEnd('li');
+ }
+}
+
+class Attachment extends AttachmentListItem
+{
+ function show() {
+ $this->showNoticeAttachment();
+ }
+
+ function linkAttr() {
+ return array('class' => 'external', 'href' => $this->attachment->url);
+ }
+
+ function linkTitle() {
+ return 'Direct link to ' . $this->title();
+ }
+
+ function showRepresentation() {
+ if (empty($this->oembed->type)) {
+ if (empty($this->attachment->mimetype)) {
+ $this->out->element('pre', null, 'oh well... not sure how to handle the following: ' . print_r($this->attachment, true));
+ } else {
+ switch ($this->attachment->mimetype) {
+ case 'image/gif':
+ case 'image/png':
+ case 'image/jpg':
+ case 'image/jpeg':
+ $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
+ break;
+ }
+ }
+ } else {
+ switch ($this->oembed->type) {
+ case 'rich':
+ case 'video':
+ case 'link':
+ if (!empty($this->oembed->html)) {
+ $this->out->raw($this->oembed->html);
+ }
+ break;
+
+ case 'photo':
+ $this->out->element('img', array('src' => $this->oembed->url, 'width' => $this->oembed->width, 'height' => $this->oembed->height, 'alt' => 'alt'));
+ break;
+
+ default:
+ $this->out->element('pre', null, 'oh well... not sure how to handle the following oembed: ' . print_r($this->oembed, true));
+ }
+ }
+ }
+}
+
diff --git a/lib/attachmentnoticesection.php b/lib/attachmentnoticesection.php
new file mode 100644
index 000000000..eb3176376
--- /dev/null
+++ b/lib/attachmentnoticesection.php
@@ -0,0 +1,75 @@
+<?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 AttachmentNoticeSection extends NoticeSection
+{
+ function showContent() {
+ parent::showContent();
+ return false;
+ }
+
+ function getNotices()
+ {
+ $notice = new Notice;
+ $f2p = new File_to_post;
+ $f2p->file_id = $this->out->attachment->id;
+ $notice->joinAdd($f2p);
+ $notice->orderBy('created desc');
+ $notice->selectAdd('post_id as id');
+ $notice->find();
+ return $notice;
+ }
+
+ function title()
+ {
+ return _('Notices where this attachment appears');
+ }
+
+ function divId()
+ {
+ return 'popular_notices';
+ }
+}
+
diff --git a/lib/attachmentsection.php b/lib/attachmentsection.php
new file mode 100644
index 000000000..20e620b9b
--- /dev/null
+++ b/lib/attachmentsection.php
@@ -0,0 +1,80 @@
+<?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/attachmenttagcloudsection.php b/lib/attachmenttagcloudsection.php
new file mode 100644
index 000000000..50bfceccb
--- /dev/null
+++ b/lib/attachmenttagcloudsection.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Laconica, the distributed open-source microblogging tool
+ *
+ * Attachment tag cloud section
+ *
+ * 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);
+}
+
+/**
+ * Attachment tag cloud section
+ *
+ * @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 AttachmentTagCloudSection extends TagCloudSection
+{
+ function title()
+ {
+ return _('Tags for this attachment');
+ }
+
+ function showTag($tag, $weight, $relative)
+ {
+ if ($relative > 0.5) {
+ $rel = 'tag-cloud-7';
+ } else if ($relative > 0.4) {
+ $rel = 'tag-cloud-6';
+ } else if ($relative > 0.3) {
+ $rel = 'tag-cloud-5';
+ } else if ($relative > 0.2) {
+ $rel = 'tag-cloud-4';
+ } else if ($relative > 0.1) {
+ $rel = 'tag-cloud-3';
+ } else if ($relative > 0.05) {
+ $rel = 'tag-cloud-2';
+ } else {
+ $rel = 'tag-cloud-1';
+ }
+
+ $this->out->elementStart('li', $rel);
+ $this->out->element('a', array('href' => $this->tagUrl($tag)),
+ $tag);
+ $this->out->elementEnd('li');
+ }
+
+ function getTags()
+ {
+ $notice_tag = new Notice_tag;
+ $query = 'select tag,count(tag) as weight from notice_tag join file_to_post on (notice_tag.notice_id=post_id) join notice on notice_id = notice.id where file_id=' . $notice_tag->escape($this->out->attachment->id) . ' group by tag order by weight desc';
+ $notice_tag->query($query);
+ return $notice_tag;
+ }
+}
+
diff --git a/lib/facebookutil.php b/lib/facebookutil.php
index ec3987273..242d2e06f 100644
--- a/lib/facebookutil.php
+++ b/lib/facebookutil.php
@@ -27,9 +27,21 @@ define("FACEBOOK_PROMPTED_UPDATE_PREF", 2);
function getFacebook()
{
+ static $facebook = null;
+
$apikey = common_config('facebook', 'apikey');
$secret = common_config('facebook', 'secret');
- return new Facebook($apikey, $secret);
+
+ if ($facebook === null) {
+ $facebook = new Facebook($apikey, $secret);
+ }
+
+ if (!$facebook) {
+ common_log(LOG_ERR, 'Could not make new Facebook client obj!',
+ __FILE__);
+ }
+
+ return $facebook;
}
function updateProfileBox($facebook, $flink, $notice) {
@@ -92,7 +104,6 @@ function isFacebookBound($notice, $flink) {
}
-
function facebookBroadcastNotice($notice)
{
$facebook = getFacebook();
diff --git a/lib/frequentattachmentsection.php b/lib/frequentattachmentsection.php
new file mode 100644
index 000000000..0ce0d1871
--- /dev/null
+++ b/lib/frequentattachmentsection.php
@@ -0,0 +1,66 @@
+<?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/noticelist.php b/lib/noticelist.php
index 8fccba73e..004905056 100644
--- a/lib/noticelist.php
+++ b/lib/noticelist.php
@@ -179,22 +179,86 @@ class NoticeListItem extends Widget
{
$this->showStart();
$this->showNotice();
- $this->showNoticeInfo();
+ $this->showNoticeAttachments();
$this->showNoticeOptions();
+ $this->showNoticeInfo();
$this->showEnd();
}
function showNotice()
{
- $this->out->elementStart('div', 'entry-title');
+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->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';
+ }
+
+ $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);
+
+
+ $this->out->elementEnd('p');
+ }
+
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->showNoticeSource();
$this->showContext();
@@ -205,7 +269,10 @@ class NoticeListItem extends Widget
{
$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();
diff --git a/lib/noticesection.php b/lib/noticesection.php
index 94c2738ef..37aafdaf6 100644
--- a/lib/noticesection.php
+++ b/lib/noticesection.php
@@ -51,17 +51,13 @@ class NoticeSection extends Section
function showContent()
{
$notices = $this->getNotices();
-
$cnt = 0;
-
$this->out->elementStart('ul', 'notices');
-
while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
$this->showNotice($notices);
}
$this->out->elementEnd('ul');
-
return ($cnt > NOTICES_PER_SECTION);
}
@@ -100,6 +96,37 @@ class NoticeSection extends Section
$this->out->elementStart('p', 'entry-content');
$this->out->raw($notice->rendered);
+
+ $notice_link_cfg = common_config('site', 'notice_link');
+ if ('direct' === $notice_link_cfg) {
+ $this->out->text(' (');
+ $this->out->element('a', array('href' => $notice->uri), 'see');
+ $this->out->text(')');
+ } elseif ('attachment' === $notice_link_cfg) {
+ if ($count = $notice->hasAttachments()) {
+ // link to attachment(s) pages
+ if (1 === $count) {
+ $f2p = File_to_post::staticGet('post_id', $notice->id);
+ $href = common_local_url('attachment', array('attachment' => $f2p->file_id));
+ $att_class = 'attachment';
+ } else {
+ $href = common_local_url('attachments', array('notice' => $notice->id));
+ $att_class = 'attachments';
+ }
+
+ $clip = theme_path('images/icons/clip.png', 'base');
+ $this->out->elementStart('a', array('class' => $att_class, 'style' => "font-style: italic;", 'href' => $href, 'title' => "# of attachments: $count"));
+ $this->out->raw(" ($count&nbsp");
+ $this->out->element('img', array('style' => 'display: inline', 'align' => 'top', 'width' => 20, 'height' => 20, 'src' => $clip, 'alt' => 'alt'));
+ $this->out->text(')');
+ $this->out->elementEnd('a');
+ } else {
+ $this->out->text(' (');
+ $this->out->element('a', array('href' => $notice->uri), 'see');
+ $this->out->text(')');
+ }
+ }
+
$this->out->elementEnd('p');
if (!empty($notice->value)) {
$this->out->elementStart('p');
diff --git a/lib/popularnoticesection.php b/lib/popularnoticesection.php
index a8d47ef54..375d5538b 100644
--- a/lib/popularnoticesection.php
+++ b/lib/popularnoticesection.php
@@ -51,7 +51,7 @@ class PopularNoticeSection extends NoticeSection
if (common_config('db', 'type') == 'pgsql') {
$weightexpr='sum(exp(-extract(epoch from (now() - fave.modified)) / %s))';
if (!empty($this->out->tag)) {
- $tag = pg_escape_string($this->tag);
+ $tag = pg_escape_string($this->out->tag);
}
} else {
$weightexpr='sum(exp(-(now() - fave.modified) / %s))';
diff --git a/lib/profileaction.php b/lib/profileaction.php
index 1f2e30994..a3437ff4d 100644
--- a/lib/profileaction.php
+++ b/lib/profileaction.php
@@ -49,16 +49,17 @@ require_once INSTALLDIR.'/lib/groupminilist.php';
class ProfileAction extends Action
{
- var $user = null;
- var $page = null;
+ var $user = null;
+ var $page = null;
var $profile = null;
+ var $tag = null;
function prepare($args)
{
parent::prepare($args);
$nickname_arg = $this->arg('nickname');
- $nickname = common_canonical_nickname($nickname_arg);
+ $nickname = common_canonical_nickname($nickname_arg);
// Permanent redirect on non-canonical nickname
@@ -85,10 +86,9 @@ class ProfileAction extends Action
return false;
}
+ $this->tag = $this->trimmed('tag');
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
-
common_set_returnto($this->selfUrl());
-
return true;
}
@@ -244,4 +244,5 @@ class ProfileAction extends Action
$this->elementEnd('div');
}
-} \ No newline at end of file
+}
+
diff --git a/lib/router.php b/lib/router.php
index 9308c818a..70ee0f3fb 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -151,12 +151,26 @@ class Router
$m->connect('search/notice/rss?q=:q', array('action' => 'noticesearchrss'),
array('q' => '.+'));
+ $m->connect('attachment/:attachment/ajax',
+ array('action' => 'attachment_ajax'),
+ array('notice' => '[0-9]+'));
+
+ $m->connect('attachment/:attachment',
+ array('action' => 'attachment'),
+ array('notice' => '[0-9]+'));
+
// notice
$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',
array('action' => 'shownotice'),
array('notice' => '[0-9]+'));
@@ -412,6 +426,16 @@ class Router
array('size' => '(original|96|48|24)',
'nickname' => '[a-zA-Z0-9]{1,64}'));
+ $m->connect(':nickname/tag/:tag/rss',
+ array('action' => 'userrss'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'),
+ array('tag' => '[a-zA-Z0-9]+'));
+
+ $m->connect(':nickname/tag/:tag',
+ array('action' => 'showstream'),
+ array('nickname' => '[a-zA-Z0-9]{1,64}'),
+ array('tag' => '[a-zA-Z0-9]+'));
+
$m->connect(':nickname',
array('action' => 'showstream'),
array('nickname' => '[a-zA-Z0-9]{1,64}'));
diff --git a/lib/rssaction.php b/lib/rssaction.php
index ddba862dc..2f25ed7e4 100644
--- a/lib/rssaction.php
+++ b/lib/rssaction.php
@@ -97,7 +97,11 @@ class Rss10Action extends Action
// Parent handling, including cache check
parent::handle($args);
// Get the list of notices
- $this->notices = $this->getNotices($this->limit);
+ if (empty($this->tag)) {
+ $this->notices = $this->getNotices($this->limit);
+ } else {
+ $this->notices = $this->getTaggedNotices($this->tag, $this->limit);
+ }
$this->showRss();
}
diff --git a/lib/subgroupnav.php b/lib/subgroupnav.php
index 31c3ea0b5..4a9b36ae8 100644
--- a/lib/subgroupnav.php
+++ b/lib/subgroupnav.php
@@ -74,38 +74,44 @@ class SubGroupNav extends Widget
$this->out->elementStart('ul', array('class' => 'nav'));
- $this->out->menuItem(common_local_url('subscriptions',
- array('nickname' =>
- $this->user->nickname)),
- _('Subscriptions'),
- sprintf(_('People %s subscribes to'),
- $this->user->nickname),
- $action == 'subscriptions',
- 'nav_subscriptions');
- $this->out->menuItem(common_local_url('subscribers',
- array('nickname' =>
- $this->user->nickname)),
- _('Subscribers'),
- sprintf(_('People subscribed to %s'),
- $this->user->nickname),
- $action == 'subscribers',
- 'nav_subscribers');
- $this->out->menuItem(common_local_url('usergroups',
- array('nickname' =>
- $this->user->nickname)),
- _('Groups'),
- sprintf(_('Groups %s is a member of'),
- $this->user->nickname),
- $action == 'usergroups',
- 'nav_usergroups');
- if (!is_null($cur) && $this->user->id === $cur->id) {
- $this->out->menuItem(common_local_url('invite'),
- _('Invite'),
- sprintf(_('Invite friends and colleagues to join you on %s'),
- common_config('site', 'name')),
- $action == 'invite',
- 'nav_invite');
+ if (Event::handle('StartSubGroupNav', array($this))) {
+
+ $this->out->menuItem(common_local_url('subscriptions',
+ array('nickname' =>
+ $this->user->nickname)),
+ _('Subscriptions'),
+ sprintf(_('People %s subscribes to'),
+ $this->user->nickname),
+ $action == 'subscriptions',
+ 'nav_subscriptions');
+ $this->out->menuItem(common_local_url('subscribers',
+ array('nickname' =>
+ $this->user->nickname)),
+ _('Subscribers'),
+ sprintf(_('People subscribed to %s'),
+ $this->user->nickname),
+ $action == 'subscribers',
+ 'nav_subscribers');
+ $this->out->menuItem(common_local_url('usergroups',
+ array('nickname' =>
+ $this->user->nickname)),
+ _('Groups'),
+ sprintf(_('Groups %s is a member of'),
+ $this->user->nickname),
+ $action == 'usergroups',
+ 'nav_usergroups');
+ if (!is_null($cur) && $this->user->id === $cur->id) {
+ $this->out->menuItem(common_local_url('invite'),
+ _('Invite'),
+ sprintf(_('Invite friends and colleagues to join you on %s'),
+ common_config('site', 'name')),
+ $action == 'invite',
+ 'nav_invite');
+ }
+
+ Event::handle('EndSubGroupNav', array($this));
}
+
$this->out->elementEnd('ul');
}
}
diff --git a/lib/tagcloudsection.php b/lib/tagcloudsection.php
index ff2aca6d6..62f7d8961 100644
--- a/lib/tagcloudsection.php
+++ b/lib/tagcloudsection.php
@@ -114,7 +114,11 @@ class TagCloudSection extends Section
function tagUrl($tag)
{
- return common_local_url('tag', array('tag' => $tag));
+ if ('showstream' === $this->out->trimmed('action')) {
+ return common_local_url('showstream', array('nickname' => $this->out->profile->nickname, 'tag' => $tag));
+ } else {
+ return common_local_url('tag', array('tag' => $tag));
+ }
}
function divId()
diff --git a/lib/util.php b/lib/util.php
index 198185338..fbef8764a 100644
--- a/lib/util.php
+++ b/lib/util.php
@@ -395,7 +395,7 @@ function common_render_text($text)
return $r;
}
-function common_replace_urls_callback($text, $callback) {
+function common_replace_urls_callback($text, $callback, $notice_id = null) {
// Start off with a regex
$regex = '#'.
'(?:'.
@@ -466,7 +466,11 @@ function common_replace_urls_callback($text, $callback) {
$url = (mb_strpos($orig_url, htmlspecialchars($url)) === FALSE) ? $url:htmlspecialchars($url);
// Call user specified func
- $modified_url = call_user_func($callback, $url);
+ if (empty($notice_id)) {
+ $modified_url = call_user_func($callback, $url);
+ } else {
+ $modified_url = call_user_func($callback, array($url, $notice_id));
+ }
// Replace it!
$start = mb_strpos($text, $url, $offset);
@@ -481,107 +485,24 @@ function common_linkify($url) {
// It comes in special'd, so we unspecial it before passing to the stringifying
// functions
$url = htmlspecialchars_decode($url);
- $display = $url;
- $url = (!preg_match('#^([a-z]+://|(mailto|aim|tel):)#i', $url)) ? 'http://'.$url : $url;
-
- $attrs = array('href' => $url, 'rel' => 'external');
-
- if ($longurl = common_longurl($url)) {
- $attrs['title'] = $longurl;
+ $display = File_redirection::_canonUrl($url);
+ $longurl_data = File_redirection::where($url);
+ if (is_array($longurl_data)) {
+ $longurl = $longurl_data['url'];
+ } elseif (is_string($longurl_data)) {
+ $longurl = $longurl_data;
+ } else {
+ die('impossible to linkify');
}
+ $attrs = array('href' => $longurl, 'rel' => 'external');
return XMLStringer::estring('a', $attrs, $display);
}
-function common_longurl($short_url)
-{
- $long_url = common_shorten_link($short_url, true);
- if ($long_url === $short_url) return false;
- return $long_url;
-}
-
-function common_longurl2($uri)
-{
- $uri_e = urlencode($uri);
- $longurl = unserialize(file_get_contents("http://api.longurl.org/v1/expand?format=php&url=$uri_e"));
- if (empty($longurl['long_url']) || $uri === $longurl['long_url']) return false;
- return stripslashes($longurl['long_url']);
-}
-
function common_shorten_links($text)
{
if (mb_strlen($text) <= 140) return $text;
- static $cache = array();
- if (isset($cache[$text])) return $cache[$text];
- // \s = not a horizontal whitespace character (since PHP 5.2.4)
- return $cache[$text] = common_replace_urls_callback($text, 'common_shorten_link');;
-}
-
-function common_shorten_link($url, $reverse = false)
-{
-
- static $url_cache = array();
- if ($reverse) return isset($url_cache[$url]) ? $url_cache[$url] : $url;
-
- $user = common_current_user();
- if (!isset($user)) {
- // common current user does not find a user when called from the XMPP daemon
- // therefore we'll set one here fix, so that XMPP given URLs may be shortened
- $user->urlshorteningservice = 'ur1.ca';
- }
- $curlh = curl_init();
- curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait
- curl_setopt($curlh, CURLOPT_USERAGENT, 'Laconica');
- curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true);
-
- switch($user->urlshorteningservice) {
- case 'ur1.ca':
- $short_url_service = new LilUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case '2tu.us':
- $short_url_service = new TightUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case 'ptiturl.com':
- $short_url_service = new PtitUrl;
- $short_url = $short_url_service->shorten($url);
- break;
-
- case 'bit.ly':
- curl_setopt($curlh, CURLOPT_URL, 'http://bit.ly/api?method=shorten&long_url='.urlencode($url));
- $short_url = current(json_decode(curl_exec($curlh))->results)->hashUrl;
- break;
-
- case 'is.gd':
- curl_setopt($curlh, CURLOPT_URL, 'http://is.gd/api.php?longurl='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'snipr.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://snipr.com/site/snip?r=simple&link='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'metamark.net':
- curl_setopt($curlh, CURLOPT_URL, 'http://metamark.net/api/rest/simple?long_url='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- case 'tinyurl.com':
- curl_setopt($curlh, CURLOPT_URL, 'http://tinyurl.com/api-create.php?url='.urlencode($url));
- $short_url = curl_exec($curlh);
- break;
- default:
- $short_url = false;
- }
-
- curl_close($curlh);
-
- if ($short_url) {
- $url_cache[(string)$short_url] = $url;
- return (string)$short_url;
- }
- return $url;
+ return common_replace_urls_callback($text, array('File_redirection', 'makeShort'));
}
function common_xml_safe_str($str)
diff --git a/plugins/FBConnect/FBConnectLogin.php b/plugins/FBConnect/FBConnectLogin.php
new file mode 100644
index 000000000..c2a288571
--- /dev/null
+++ b/plugins/FBConnect/FBConnectLogin.php
@@ -0,0 +1,371 @@
+<?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/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'));
+
+ return true;
+ }
+
+ 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)
+ {
+ $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_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 {
+ $infos = getFacebook()->api_client->users_getInfo($fb_uid, $fields);
+
+ if (empty($infos)) {
+ return null;
+ }
+ return reset($infos);
+
+ } catch (Exception $e) {
+ error_log("Failure in the api when requesting " . join(",", $fields)
+ ." on uid " . $fb_uid . " : ". $e->getMessage());
+ return null;
+ }
+ }
+
+}
diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php
new file mode 100644
index 000000000..342a62492
--- /dev/null
+++ b/plugins/FBConnect/FBConnectPlugin.php
@@ -0,0 +1,259 @@
+<?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/
+ */
+
+if (!defined('LACONICA')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
+require_once INSTALLDIR . '/lib/facebookutil.php';
+
+/**
+ * Plugin to enable Facebook Connect
+ *
+ * @category Plugin
+ * @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 FBConnectPlugin extends Plugin
+{
+
+ function __construct()
+ {
+ parent::__construct();
+ }
+
+ // Hook in new actions
+ function onRouterInitialized(&$m) {
+ $m->connect('main/facebookconnect', array('action' => 'fbconnectlogin'));
+ }
+
+ // Add in xmlns:fb
+ function onStartShowHTML($action)
+ {
+
+ // 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);
+
+ // Avoid a redirect loop
+ if (!in_array($name, array('FBConnectloginAction', 'ClientErrorAction'))) {
+
+ $this->checkFacebookUser($action);
+
+ }
+
+ $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
+ $_SERVER['HTTP_ACCEPT'] : null;
+
+ // XXX: allow content negotiation for RDF, RSS, or XRDS
+
+ $cp = common_accept_to_prefs($httpaccept);
+ $sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
+
+ $type = common_negotiate_type($cp, $sp);
+
+ if (!$type) {
+ throw new ClientException(_('This page is not available in a '.
+ 'media type you accept'), 406);
+ }
+
+
+ header('Content-Type: '.$type);
+
+ $action->extraHeaders();
+
+ $action->startXML('html',
+ '-//W3C//DTD XHTML 1.0 Strict//EN',
+ 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+
+ $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));
+
+ return false;
+
+ }
+
+ function onEndShowLaconicaScripts($action)
+ {
+
+ $action->element('script',
+ array('type' => 'text/javascript',
+ 'src' => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
+ ' ');
+
+ $apikey = common_config('facebook', 'apikey');
+ $plugin_path = common_path('plugins/FBConnect');
+
+ $url = common_get_returnto();
+
+ if ($url) {
+ // We don't have to return to it again
+ common_set_returnto(null);
+ } else {
+ $url = common_local_url('public');
+ }
+
+ $html = sprintf('<script type="text/javascript">FB.init("%s", "%s/xd_receiver.htm");
+
+ function refresh_page() {
+ window.location = "%s";
+ }
+
+ </script>', $apikey, $plugin_path, $url);
+
+
+ $action->raw($html);
+ }
+
+ function onStartPrimaryNav($action)
+ {
+ $user = common_current_user();
+
+ 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');
+
+ // 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');
+
+ $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);
+
+ $action->raw($html);
+
+ }
+ else {
+ if (!common_config('site', 'closed')) {
+ $action->menuItem(common_local_url('register'),
+ _('Register'), _('Create an account'), false, 'nav_register');
+ }
+ $action->menuItem(common_local_url('openidlogin'),
+ _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
+ $action->menuItem(common_local_url('login'),
+ _('Login'), _('Login to the site'), false, 'nav_login');
+ }
+
+ $action->menuItem(common_local_url('doc', array('title' => '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');
+ }
+
+ return false;
+ }
+
+ function checkFacebookUser() {
+
+ $user = common_current_user();
+
+ if ($user) {
+ return;
+ }
+
+ try {
+
+ $facebook = getFacebook();
+ $fbuid = $facebook->get_loggedin_user();
+
+ // If you're a Facebook user and you're logged in do nothing
+
+ // 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
+
+ if ($fbuid > 0) {
+
+ if ($facebook->api_client->users_isAppUser($fbuid) ||
+ $facebook->api_client->added) {
+
+ // user should be connected...
+
+ common_debug("Facebook user found: $fbuid");
+
+ if ($user) {
+ common_debug("Facebook user is logged in.");
+ return;
+
+ } else {
+ common_debug("Facebook user is NOT logged in.");
+ common_redirect(common_local_url('fbconnectlogin'), 303);
+ }
+
+ } else {
+ common_debug("No Facebook connect user found.");
+ }
+ }
+
+ } catch (Exception $e) {
+ common_debug('Expired FB session.');
+ }
+
+ }
+
+}
+
+
diff --git a/plugins/FBConnect/xd_receiver.htm b/plugins/FBConnect/xd_receiver.htm
new file mode 100644
index 000000000..43fb2c4e4
--- /dev/null
+++ b/plugins/FBConnect/xd_receiver.htm
@@ -0,0 +1,10 @@
+<!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/theme/base/css/display.css b/theme/base/css/display.css
index 10fc63638..8bd0ae1c4 100644
--- a/theme/base/css/display.css
+++ b/theme/base/css/display.css
@@ -495,7 +495,7 @@ line-height:1.618;
/* entity_profile */
.entity_profile {
position:relative;
-width:67.702%;
+width:74.702%;
min-height:123px;
float:left;
margin-bottom:18px;
@@ -531,12 +531,15 @@ margin-bottom:4px;
.entity_profile .entity_nickname {
margin-left:11px;
display:inline;
-font-weight:bold;
}
.entity_profile .entity_nickname {
margin-left:0;
}
-
+.entity_profile .fn,
+.entity_profile .nickname {
+font-size:1.1em;
+font-weight:bold;
+}
.entity_profile .entity_fn dd:before {
content: "(";
font-weight:normal;
@@ -558,7 +561,7 @@ display:none;
/*entity_actions*/
.entity_actions {
float:right;
-margin-left:4.35%;
+margin-left:2.35%;
max-width:25%;
}
.entity_actions h2 {
@@ -636,6 +639,7 @@ margin-bottom:29px;
clear:both;
float:left;
width:100%;
+list-style-position:inside;
}
.aside .section h2 {
text-transform:uppercase;
@@ -659,6 +663,7 @@ list-style-type:none;
float:left;
margin-right:7px;
margin-bottom:7px;
+display:inline;
}
.section .entities li .photo {
margin-right:0;
@@ -1039,7 +1044,7 @@ margin-left:18px;
/* TOP_POSTERS */
.section tbody td {
-padding-right:11px;
+padding-right:18px;
padding-bottom:11px;
}
.section .vcard .photo {
@@ -1156,6 +1161,17 @@ width:400px;
margin-right:28px;
}
+#settings_design_color .form_data li {
+width:33%;
+}
+#settings_design_color .form_data label {
+float:none;
+}
+#settings_design_color .form_data .swatch {
+padding:11px;
+margin-left:0;
+}
+
.instructions ul {
list-style-position:inside;
}
diff --git a/theme/base/images/icons/clip-big.png b/theme/base/images/icons/clip-big.png
new file mode 100644
index 000000000..3945f56cc
--- /dev/null
+++ b/theme/base/images/icons/clip-big.png
Binary files differ
diff --git a/theme/base/images/icons/clip.png b/theme/base/images/icons/clip.png
new file mode 100644
index 000000000..3c5a17d18
--- /dev/null
+++ b/theme/base/images/icons/clip.png
Binary files differ
diff --git a/theme/biz/logo.png b/theme/biz/logo.png
index 7c68b34f6..fdead6c4a 100644
--- a/theme/biz/logo.png
+++ b/theme/biz/logo.png
Binary files differ
diff --git a/theme/cloudy/css/display.css b/theme/cloudy/css/display.css
index b87722eec..e97889685 100644
--- a/theme/cloudy/css/display.css
+++ b/theme/cloudy/css/display.css
@@ -12,7 +12,7 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
-html { font-size: 100%; background-color:#fff; height:100%; }
+html { font-size: 100%; background-color:#fff; }
body {
background-color:#fff;
color:#000;
@@ -126,7 +126,7 @@ margin-left:0;
.form_settings label {
margin-top:2px;
-width:145px;
+width:143px;
}
.form_actions label {
diff --git a/theme/cloudy/default-avatar-mini.png b/theme/cloudy/default-avatar-mini.png
index c0f1d411f..4fd8bd9e1 100644
--- a/theme/cloudy/default-avatar-mini.png
+++ b/theme/cloudy/default-avatar-mini.png
Binary files differ
diff --git a/theme/cloudy/default-avatar-profile.png b/theme/cloudy/default-avatar-profile.png
index 9f281f94f..eb08571d9 100644
--- a/theme/cloudy/default-avatar-profile.png
+++ b/theme/cloudy/default-avatar-profile.png
Binary files differ
diff --git a/theme/cloudy/default-avatar-stream.png b/theme/cloudy/default-avatar-stream.png
index 8d505871c..926b8a9ca 100644
--- a/theme/cloudy/default-avatar-stream.png
+++ b/theme/cloudy/default-avatar-stream.png
Binary files differ
diff --git a/theme/cloudy/logo.png b/theme/cloudy/logo.png
index 7c68b34f6..fdead6c4a 100644
--- a/theme/cloudy/logo.png
+++ b/theme/cloudy/logo.png
Binary files differ
diff --git a/theme/default/css/display.css b/theme/default/css/display.css
index 1fc99eff7..e4b57ef49 100644
--- a/theme/default/css/display.css
+++ b/theme/default/css/display.css
@@ -72,13 +72,6 @@ border-top-color:#D1D9E4;
border-top-color:#C3D6DF;
}
-#content .notice p.entry-content a:visited {
-background-color:#fcfcfc;
-}
-#content .notice p.entry-content .vcard a {
-background-color:#fcfffc;
-}
-
#aside_primary {
background-color:#CEE1E9;
}
diff --git a/theme/earthy/logo.png b/theme/earthy/logo.png
deleted file mode 100644
index 7c68b34f6..000000000
--- a/theme/earthy/logo.png
+++ /dev/null
Binary files differ
diff --git a/theme/earthy/css/base.css b/theme/h4ck3r/css/base.css
index 6f46eef97..5060bbb8b 100644
--- a/theme/earthy/css/base.css
+++ b/theme/h4ck3r/css/base.css
@@ -1,4 +1,4 @@
-/** theme: earthy base
+/** theme: h4ck3r base
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
@@ -12,7 +12,7 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
-html { background-color:#fff; height:100%; }
+html { font-size: 100%; background-color:#fff; height:100%; }
body {
background-color:#fff;
color:#000;
@@ -28,7 +28,6 @@ overflow:hidden;
h1 {
font-size:1.4em;
margin-bottom:18px;
-text-align:right;
}
#showstream h1 { display:none; }
h2 { font-size:1.3em; }
@@ -52,9 +51,6 @@ font-size:1em;
input, textarea, select {
border-width:2px;
border-style: solid;
-border-radius:4px;
--moz-border-radius:4px;
--webkit-border-radius:4px;
}
input.submit {
@@ -87,10 +83,7 @@ border:0;
.error,
.success {
-padding:4px 7px;
-border-radius:4px;
--moz-border-radius:4px;
--webkit-border-radius:4px;
+padding:4px 1.55%;
margin-bottom:18px;
}
form label.submit {
@@ -192,9 +185,6 @@ margin-left:0;
}
.form_settings .form_note {
-border-radius:4px;
--moz-border-radius:4px;
--webkit-border-radius:4px;
padding:0 7px;
}
@@ -249,11 +239,11 @@ display:none;
}
#site_notice {
-position:absolute;
-top:65px;
-right:18px;
-width:250px;
-width:24%;
+float:left;
+clear:right;
+margin-top:7px;
+margin-right:18px;
+width:31%;
}
#page_notice {
clear:both;
@@ -262,17 +252,17 @@ margin-bottom:18px;
#anon_notice {
-float:left;
-width:43.2%;
+float:right;
+clear:right;
+width:41.2%;
padding:1.1%;
-border-radius:7px;
--moz-border-radius:7px;
--webkit-border-radius:7px;
border-width:2px;
-border-style:solid;
+border-style:dashed;
line-height:1.5;
font-size:1.1em;
font-weight:bold;
+-moz-transform:skewX(-30deg) scale(0.85);
+-webkit-transform:skewX(-30deg) scale(0.85);
}
@@ -283,6 +273,7 @@ padding:18px;
}
#site_nav_local_views {
+width:100%;
float:right;
}
#site_nav_local_views dt {
@@ -297,12 +288,8 @@ list-style-type:none;
float:left;
text-decoration:none;
padding:4px 11px;
--moz-border-radius-topleft:4px;
--moz-border-radius-topright:4px;
--webkit-border-top-left-radius:4px;
--webkit-border-top-right-radius:4px;
border-width:1px;
-border-style:solid;
+border-style:dashed;
border-bottom:0;
text-shadow: 2px 2px 2px #ddd;
font-weight:bold;
@@ -310,8 +297,6 @@ font-weight:bold;
#site_nav_local_views .nav {
float:left;
width:100%;
-border-bottom-width:1px;
-border-bottom-style:solid;
}
#site_nav_global_primary dt,
@@ -387,15 +372,15 @@ margin-bottom:1em;
}
#content {
-width:63.009%;
+width:60.009%;
min-height:259px;
-padding-top:1.795%;
-padding-bottom:1.795%;
+padding:1.795%;
float:right;
-clear:both;
-border-radius:7px;
-border-style:solid;
-border-width:0;
+border-style:dashed;
+border-width:1px;
+}
+#shownotice #content {
+min-height:0;
}
#content_inner {
@@ -409,33 +394,27 @@ width:27.917%;
min-height:259px;
float:right;
margin-right:4.385%;
-margin-top:73px;
padding:1.795%;
-border-radius:7px;
--moz-border-radius:7px;
--webkit-border-radius:7px;
border-width:1px;
-border-style:solid;
+border-style:dashed;
}
#form_notice {
-width:45.664%;
-float:left;
+width:43.664%;
+float:right;
position:relative;
line-height:1;
}
#form_notice fieldset {
border:0;
padding:0;
+position:relative;
}
#form_notice legend {
display:none;
}
#form_notice textarea {
float:left;
-border-radius:7px;
--moz-border-radius:7px;
--webkit-border-radius:7px;
width:80.789%;
height:67px;
line-height:1.5;
@@ -481,7 +460,13 @@ margin-bottom:7px;
margin-left:18px;
float:left;
}
-
+#form_notice .error {
+float:left;
+clear:both;
+width:96.9%;
+margin-bottom:0;
+line-height:1.618;
+}
/* entity_profile */
.entity_profile {
@@ -715,32 +700,18 @@ margin-right:11px;
.notice,
.profile {
position:relative;
+padding-top:11px;
+padding-bottom:11px;
clear:both;
float:left;
width:100%;
-border-width:1px;
-border-style:solid;
-border-radius:7px;
--moz-border-radius:7px;
--webkit-border-radius:7px;
-}
-#content .notice,
-#content .profile {
-padding:1.795%;
-margin-bottom:44px;
+border-top-width:1px;
+border-top-style:dashed;
}
-#content .notice {
-width:96.25%;
-}
-
.notices li {
list-style-type:none;
}
-.notices li.hover {
-border-radius:4px;
--moz-border-radius:4px;
--webkit-border-radius:4px;
-}
+
/* NOTICES */
#notices_primary {
@@ -770,16 +741,14 @@ overflow:hidden;
font-weight:bold;
}
-.notice .author .photo {
-margin-bottom:0;
-}
-
.vcard .photo {
display:inline;
margin-right:11px;
-margin-bottom:11px;
float:left;
}
+#shownotice .vcard .photo {
+margin-bottom:4px;
+}
.vcard .url {
text-decoration:none;
}
@@ -788,7 +757,7 @@ text-decoration:underline;
}
.notice .entry-title {
-float:left;
+display:inline;
width:100%;
overflow:hidden;
}
@@ -812,14 +781,9 @@ border-radius:4px;
}
.notice div.entry-content {
-clear:left;
float:left;
font-size:0.95em;
-margin-left:59px;
-width:70%;
-}
-#showstream .notice div.entry-content {
-margin-left:0;
+width:65%;
}
.notice .notice-options a,
@@ -846,23 +810,6 @@ text-transform:lowercase;
}
-
-.notice-data {
-position:absolute;
-top:18px;
-right:0;
-min-height:50px;
-margin-bottom:4px;
-}
-.notice .entry-content .notice-data dt {
-display:none;
-}
-
-.notice-data a {
-display:block;
-outline:none;
-}
-
.notice-options {
padding-left:2%;
float:left;
@@ -1040,6 +987,8 @@ padding-right:30px;
.hentry .entry-content p {
margin-bottom:18px;
}
+.system_notice ul,
+.instructions ul,
.hentry entry-content ol,
.hentry .entry-content ul {
list-style-position:inside;
diff --git a/theme/earthy/css/display.css b/theme/h4ck3r/css/display.css
index b67700f2d..c7631a8eb 100644
--- a/theme/earthy/css/display.css
+++ b/theme/h4ck3r/css/display.css
@@ -1,4 +1,4 @@
-/** theme: earthy
+/** theme: h4ck3r
*
* @package Laconica
* @author Sarven Capadisli <csarven@controlyourself.ca>
@@ -12,26 +12,27 @@
html,
body,
a:active {
-background-color:#665500;
+background-color:#000;
}
+
body {
-font-family: Verdana, sans-serif;
+background-image:url(../images/illustrations/illu_h4x0r1ng.gif);
+font-family: monospace;
font-size:1em;
+color:#647819;
}
address {
margin-right:7.18%;
}
-h1 {
-color:#fff;
-}
-
input, textarea, select, option {
-font-family: Verdana, sans-serif;
+font-family: monospace;
}
input, textarea, select,
.entity_remote_subscribe {
border-color:#aaa;
+background-color:#000;
+color:#ccc;
}
#filter_tags ul li {
border-color:#ddd;
@@ -45,7 +46,7 @@ input.submit,
#form_notice.warning #notice_text-count,
.form_settings .form_note,
.entity_remote_subscribe {
-background-color:#9BB43E;
+background-color:rgba(0, 255, 0, 0.5);
}
input:focus, textarea:focus, select:focus,
@@ -54,7 +55,7 @@ border-color:#9BB43E;
}
input.submit,
.entity_remote_subscribe {
-color:#dddd33;
+color:#fff;
}
a,
@@ -65,57 +66,45 @@ div.notice-options input,
.form_user_nudge input.submit,
.entity_nudge p,
.form_settings input.form_action-secondary {
-color:#ee4400;
+color:#0f0;
}
.notice,
.profile {
-border-color:#DDAA00;
+border-top-color:#333;
}
.section .profile {
-border-top-color:#aaaa66;
-}
-
-#content .notice p.entry-content a:visited {
-background-color:#fcfcfc;
-}
-#content .notice p.entry-content .vcard a {
-background-color:#fcfffc;
+border-top-color:#87B4C8;
}
#aside_primary {
-background-color:#DDAA00;
+background-color:rgba(0,128,0,0.3);
}
#notice_text-count {
-color:#333;
+color:#0f0;
}
#form_notice.warning #notice_text-count {
color:#000;
}
#form_notice.processing #notice_action-submit {
-background:#dddd33 url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
+background:#ccc url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
cursor:wait;
text-indent:-9999px;
}
#content,
-#site_nav_local_views .nav,
#site_nav_local_views a,
#aside_primary {
-border-color:#dddd33;
-}
-#content .notice,
-#content .profile,
-#site_nav_local_views .current a {
-background-color:#dddd33;
+border-color:#50964D;
}
+#content,
#site_nav_local_views .current a {
-color:#EE4400;
+background-color:rgba(0, 0, 0, 0.698);
}
+
#site_nav_local_views a {
-background-color:rgba(255, 255, 255, 0.2);
-color:#fff;
+background-color:rgba(0, 200, 0, 0.3);
}
#site_nav_local_views a:hover {
background-color:rgba(255, 255, 255, 0.4);
@@ -129,13 +118,11 @@ background-color:#EFF3DC;
}
#anon_notice {
-background-color:#aaaa66;
-color:#dddd33;
-border-color:#dddd33;
+color:#ccc;
+border-color:#50964D;
}
#showstream #anon_notice {
-background-color:#9BB43E;
}
#export_data li a {
@@ -167,12 +154,12 @@ background-color:transparent;
.form_user_subscribe input.submit,
.form_user_unsubscribe input.submit {
background-color:#9BB43E;
-color:#dddd33;
+color:#ccc;
}
.form_user_unsubscribe input.submit,
.form_group_leave input.submit,
.form_user_authorization input.reject {
-background-color:#aaaa66;
+background-color:#87B4C8;
}
.entity_edit a {
@@ -221,15 +208,13 @@ opacity:0.4;
opacity:1;
}
div.entry-content {
-color:#333;
+color:#ccc;
}
div.notice-options a,
div.notice-options input {
font-family:sans-serif;
}
-.notices li.hover {
-/*background-color:#fcfcfc;*/
-}
+
/*END: NOTICES */
#new_group a {
@@ -239,7 +224,7 @@ background:transparent url(../../base/images/icons/twotone/green/news.gif) no-re
.pagination .nav_prev a,
.pagination .nav_next a {
background-repeat:no-repeat;
-border-color:#DDAA00;
+border-color:#000;
}
.pagination .nav_prev a {
background-image:url(../../base/images/icons/twotone/green/arrow-left.gif);
diff --git a/theme/earthy/css/ie.css b/theme/h4ck3r/css/ie.css
index 2f463bb44..2f463bb44 100644
--- a/theme/earthy/css/ie.css
+++ b/theme/h4ck3r/css/ie.css
diff --git a/theme/earthy/default-avatar-mini.png b/theme/h4ck3r/default-avatar-mini.png
index 38b8692b4..38b8692b4 100644
--- a/theme/earthy/default-avatar-mini.png
+++ b/theme/h4ck3r/default-avatar-mini.png
Binary files differ
diff --git a/theme/earthy/default-avatar-profile.png b/theme/h4ck3r/default-avatar-profile.png
index f8357d4fc..f8357d4fc 100644
--- a/theme/earthy/default-avatar-profile.png
+++ b/theme/h4ck3r/default-avatar-profile.png
Binary files differ
diff --git a/theme/earthy/default-avatar-stream.png b/theme/h4ck3r/default-avatar-stream.png
index 6b63baa70..6b63baa70 100644
--- a/theme/earthy/default-avatar-stream.png
+++ b/theme/h4ck3r/default-avatar-stream.png
Binary files differ
diff --git a/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif b/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif
new file mode 100644
index 000000000..c233af391
--- /dev/null
+++ b/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif
Binary files differ
diff --git a/theme/h4ck3r/logo.png b/theme/h4ck3r/logo.png
new file mode 100644
index 000000000..fdead6c4a
--- /dev/null
+++ b/theme/h4ck3r/logo.png
Binary files differ
diff --git a/theme/identica/css/display.css b/theme/identica/css/display.css
index cc19da0f7..9d625848f 100644
--- a/theme/identica/css/display.css
+++ b/theme/identica/css/display.css
@@ -72,13 +72,6 @@ border-top-color:#CEE1E9;
border-top-color:#87B4C8;
}
-#content .notice p.entry-content a:visited {
-background-color:#fcfcfc;
-}
-#content .notice p.entry-content .vcard a {
-background-color:#fcfffc;
-}
-
#aside_primary {
background-color:#CEE1E9;
}
diff --git a/theme/otalk/css/base.css b/theme/otalk/css/base.css
index 379590d30..32e8891d2 100644
--- a/theme/otalk/css/base.css
+++ b/theme/otalk/css/base.css
@@ -12,7 +12,7 @@ img { display:block; border:0; }
a abbr { cursor: pointer; border-bottom:0; }
table { border-collapse:collapse; }
ol { list-style-position:inside; }
-html { font-size: 87.5%; background-color:#fff; height:100%; }
+html { font-size: 87.5%; background-color:#fff; }
body {
background-color:#fff;
color:#000;
@@ -386,12 +386,12 @@ margin-bottom:1em;
}
#content {
-width:100%;
+width:67.9%;
min-height:259px;
padding-top:1.795%;
padding-bottom:1.795%;
-
float:left;
+clear:left;
border-radius:7px;
-moz-border-radius:7px;
-moz-border-radius-topleft:0;
@@ -409,11 +409,11 @@ float:left;
}
#aside_primary {
-width:96.3%;
+width:27.917%;
min-height:259px;
float:left;
-clear:both;
padding:1.795%;
+margin-left:0.385%;
border-radius:7px;
-moz-border-radius:7px;
-webkit-border-radius:7px;
@@ -730,7 +730,7 @@ list-style-type:none;
}
#content .notice {
-width:25%;
+width:37%;
margin-left:17px;
margin-bottom:47px;
clear:none;
@@ -743,6 +743,10 @@ min-height:235px;
margin-bottom:18px;
}
+#shownotice #content .notice {
+width:96%;
+}
+
/* NOTICES */
#notices_primary {
diff --git a/theme/otalk/css/display.css b/theme/otalk/css/display.css
index 22e0530ec..6c646791b 100644
--- a/theme/otalk/css/display.css
+++ b/theme/otalk/css/display.css
@@ -15,7 +15,6 @@ html {
html,
body,
a:active {
-/*background-color:#F0F2F5;*/
}
body {
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
diff --git a/theme/otalk/logo.png b/theme/otalk/logo.png
index 7c68b34f6..fdead6c4a 100644
--- a/theme/otalk/logo.png
+++ b/theme/otalk/logo.png
Binary files differ
diff --git a/theme/pigeonthoughts/logo.png b/theme/pigeonthoughts/logo.png
index 7c68b34f6..fdead6c4a 100644
--- a/theme/pigeonthoughts/logo.png
+++ b/theme/pigeonthoughts/logo.png
Binary files differ
diff --git a/theme/readme.txt b/theme/readme.txt
index 4998b3c98..83b5a61d0 100644
--- a/theme/readme.txt
+++ b/theme/readme.txt
@@ -23,14 +23,16 @@ Only alter this file if you want to change the layout of the site. Please note t
./default/css/display.css contains only the background images and colour rules:
This file is a good basis for creating your own theme.
+Let's create a theme:
-1. Copy over the default theme to start off (replace 'mytheme'):
-cp -r ./default ./mytheme
+1. To start off, copy over the default theme:
+cp -r default mytheme
2. Edit your mytheme stylesheet:
-nano ./mytheme/css/display.css
+nano mytheme/css/display.css
-3. Search and replace a colour or a path to the background image of your choice.
+a) Search and replace your colours and background images, or
+b) Create your own layout either importing a separate stylesheet (e.g., change to @import url(base.css);) or simply place it before the rest of the rules.
4. Set /config.php to load 'mytheme':
$config['site']['theme'] = 'mytheme';