diff options
-rw-r--r-- | actions/attachment.php | 250 | ||||
-rw-r--r-- | actions/attachment_ajax.php | 141 | ||||
-rw-r--r-- | actions/attachments.php | 292 | ||||
-rw-r--r-- | actions/attachments_ajax.php | 115 | ||||
-rw-r--r-- | actions/newnotice.php | 23 | ||||
-rw-r--r-- | actions/tag.php | 12 | ||||
-rw-r--r-- | classes/File.php | 123 | ||||
-rw-r--r-- | classes/File_oembed.php | 87 | ||||
-rw-r--r-- | classes/File_redirection.php | 276 | ||||
-rw-r--r-- | classes/File_thumbnail.php | 55 | ||||
-rw-r--r-- | classes/File_to_post.php | 60 | ||||
-rw-r--r-- | classes/Notice.php | 16 | ||||
-rw-r--r--[-rwxr-xr-x] | classes/laconica.ini | 61 | ||||
-rw-r--r-- | classes/laconica.links.ini | 14 | ||||
-rw-r--r-- | db/laconica.sql | 59 | ||||
-rw-r--r-- | db/laconica_pg.sql | 58 | ||||
-rw-r--r-- | js/jquery.joverlay.min.js | 6 | ||||
-rw-r--r-- | js/util.js | 4 | ||||
-rw-r--r-- | lib/Shorturl_api.php | 3 | ||||
-rw-r--r-- | lib/action.php | 12 | ||||
-rw-r--r-- | lib/attachmentlist.php | 300 | ||||
-rw-r--r-- | lib/noticelist.php | 71 | ||||
-rw-r--r-- | lib/router.php | 14 | ||||
-rw-r--r-- | lib/util.php | 114 | ||||
-rw-r--r-- | theme/base/images/icons/clip-big.png | bin | 0 -> 11245 bytes | |||
-rw-r--r-- | theme/base/images/icons/clip.png | bin | 0 -> 2298 bytes | |||
-rw-r--r-- | theme/biz/logo.png | bin | 4988 -> 2228 bytes | |||
-rw-r--r-- | theme/cloudy/css/display.css | 4 | ||||
-rw-r--r-- | theme/cloudy/default-avatar-mini.png | bin | 1006 -> 1349 bytes | |||
-rw-r--r-- | theme/cloudy/default-avatar-profile.png | bin | 9026 -> 9256 bytes | |||
-rw-r--r-- | theme/cloudy/default-avatar-stream.png | bin | 2963 -> 3829 bytes | |||
-rw-r--r-- | theme/cloudy/logo.png | bin | 4988 -> 2228 bytes | |||
-rw-r--r-- | theme/earthy/logo.png | bin | 4988 -> 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) | bin | 646 -> 646 bytes | |||
-rw-r--r-- | theme/h4ck3r/default-avatar-profile.png (renamed from theme/earthy/default-avatar-profile.png) | bin | 2853 -> 2853 bytes | |||
-rw-r--r-- | theme/h4ck3r/default-avatar-stream.png (renamed from theme/earthy/default-avatar-stream.png) | bin | 1487 -> 1487 bytes | |||
-rw-r--r-- | theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif | bin | 0 -> 432979 bytes | |||
-rw-r--r-- | theme/h4ck3r/logo.png | bin | 0 -> 2228 bytes | |||
-rw-r--r-- | theme/otalk/css/base.css | 16 | ||||
-rw-r--r-- | theme/otalk/css/display.css | 1 | ||||
-rw-r--r-- | theme/otalk/logo.png | bin | 4988 -> 2228 bytes | |||
-rw-r--r-- | theme/pigeonthoughts/logo.png | bin | 4988 -> 2228 bytes | |||
-rw-r--r-- | theme/readme.txt | 10 |
46 files changed, 2152 insertions, 261 deletions
diff --git a/actions/attachment.php b/actions/attachment.php new file mode 100644 index 000000000..981882a5b --- /dev/null +++ b/actions/attachment.php @@ -0,0 +1,250 @@ +<?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; + + /** + * Profile of the notice object + */ + +// var $profile = null; + + /** + * Avatar of the profile of the notice object + */ + +// var $avatar = 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 showAside() { + $notice = new Notice; + $f2p = new File_to_post; + $f2p->file_id = $this->attachment->id; + $notice->joinAdd($f2p); + $notice->orderBy('created desc'); + $x = $notice->find(); + $this->elementStart('ol'); + while($notice->fetch()) { + $this->elementStart('li'); + $profile = $notice->getProfile(); + $this->element('a', array('href' => $notice->uri), $profile->nickname . ' on ' . $notice->created); + $this->elementEnd('li'); + } + $this->elementEnd('ol'); + $notice->free(); + $f2p->free(); + + $notice_tag = new Notice_tag; + $attachment = new File; + + $query = 'select tag,count(tag) as c 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->attachment->id) . ' group by tag order by c desc'; + + $notice_tag->query($query); + $this->elementStart('ol'); + while($notice_tag->fetch()) { + $this->elementStart('li'); + $href = common_local_url('tag', array('tag' => $notice_tag->tag)); + $this->element('a', array('href' => $href), $notice_tag->tag . ' (' . $notice_tag->c . ')'); + $this->elementEnd('li'); + } + $this->elementEnd('ol'); + } +} 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/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/tag.php b/actions/tag.php index 02f3e3522..2202f9bb0 100644 --- a/actions/tag.php +++ b/actions/tag.php @@ -49,8 +49,18 @@ class TagAction extends Action { $pop = new PopularNoticeSection($this); $pop->show(); - } + $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->tag) . '" group by file_id order by c desc'; + $notice_tag->query($query); + $this->elementStart('ol'); + while ($notice_tag->fetch()) { + $this->elementStart('li'); + $this->element('a', array('class' => 'attachment', 'href' => common_local_url('attachment', array('attachment' => $notice_tag->file_id))), "Attachment tagged {$notice_tag->c} times"); + $this->elementEnd('li'); + } + $this->elementEnd('ol'); + } function title() { 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..a71d1c083 --- /dev/null +++ b/classes/File_redirection.php @@ -0,0 +1,276 @@ +<?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; +if(1) { + // 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..30508070e 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.'); @@ -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($this->id); + $post->query($query); + $post->fetch(); + $n_attachments = intval($post->n_attachments); + $post->free(); + return $n_attachments; + } + function blowCaches($blowLast=false) { $this->blowSubsCache($blowLast); 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/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..fc123a633 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))) { 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/noticelist.php b/lib/noticelist.php index 8fccba73e..55dd902b4 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/router.php b/lib/router.php index 9308c818a..635e1d77e 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]+')); diff --git a/lib/util.php b/lib/util.php index 198185338..25c0fb0a1 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,29 @@ 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'); + $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'); + } - if ($longurl = common_longurl($url)) { + $attrs = array('href' => $longurl, 'rel' => 'external'); +if(0){ + if ($longurl !== $url) { $attrs['title'] = $longurl; } - - 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']); + return XMLStringer::estring('a', $attrs, $display); } 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/theme/base/images/icons/clip-big.png b/theme/base/images/icons/clip-big.png Binary files differnew file mode 100644 index 000000000..3945f56cc --- /dev/null +++ b/theme/base/images/icons/clip-big.png diff --git a/theme/base/images/icons/clip.png b/theme/base/images/icons/clip.png Binary files differnew file mode 100644 index 000000000..3c5a17d18 --- /dev/null +++ b/theme/base/images/icons/clip.png diff --git a/theme/biz/logo.png b/theme/biz/logo.png Binary files differindex 7c68b34f6..fdead6c4a 100644 --- a/theme/biz/logo.png +++ b/theme/biz/logo.png 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 Binary files differindex c0f1d411f..4fd8bd9e1 100644 --- a/theme/cloudy/default-avatar-mini.png +++ b/theme/cloudy/default-avatar-mini.png diff --git a/theme/cloudy/default-avatar-profile.png b/theme/cloudy/default-avatar-profile.png Binary files differindex 9f281f94f..eb08571d9 100644 --- a/theme/cloudy/default-avatar-profile.png +++ b/theme/cloudy/default-avatar-profile.png diff --git a/theme/cloudy/default-avatar-stream.png b/theme/cloudy/default-avatar-stream.png Binary files differindex 8d505871c..926b8a9ca 100644 --- a/theme/cloudy/default-avatar-stream.png +++ b/theme/cloudy/default-avatar-stream.png diff --git a/theme/cloudy/logo.png b/theme/cloudy/logo.png Binary files differindex 7c68b34f6..fdead6c4a 100644 --- a/theme/cloudy/logo.png +++ b/theme/cloudy/logo.png diff --git a/theme/earthy/logo.png b/theme/earthy/logo.png Binary files differdeleted file mode 100644 index 7c68b34f6..000000000 --- a/theme/earthy/logo.png +++ /dev/null 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 Binary files differindex 38b8692b4..38b8692b4 100644 --- a/theme/earthy/default-avatar-mini.png +++ b/theme/h4ck3r/default-avatar-mini.png diff --git a/theme/earthy/default-avatar-profile.png b/theme/h4ck3r/default-avatar-profile.png Binary files differindex f8357d4fc..f8357d4fc 100644 --- a/theme/earthy/default-avatar-profile.png +++ b/theme/h4ck3r/default-avatar-profile.png diff --git a/theme/earthy/default-avatar-stream.png b/theme/h4ck3r/default-avatar-stream.png Binary files differindex 6b63baa70..6b63baa70 100644 --- a/theme/earthy/default-avatar-stream.png +++ b/theme/h4ck3r/default-avatar-stream.png diff --git a/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif b/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif Binary files differnew file mode 100644 index 000000000..c233af391 --- /dev/null +++ b/theme/h4ck3r/images/illustrations/illu_h4x0r1ng.gif diff --git a/theme/h4ck3r/logo.png b/theme/h4ck3r/logo.png Binary files differnew file mode 100644 index 000000000..fdead6c4a --- /dev/null +++ b/theme/h4ck3r/logo.png 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 Binary files differindex 7c68b34f6..fdead6c4a 100644 --- a/theme/otalk/logo.png +++ b/theme/otalk/logo.png diff --git a/theme/pigeonthoughts/logo.png b/theme/pigeonthoughts/logo.png Binary files differindex 7c68b34f6..fdead6c4a 100644 --- a/theme/pigeonthoughts/logo.png +++ b/theme/pigeonthoughts/logo.png 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'; |