]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.8.x' of git://gitorious.org/laconica/dev into links
authorRobin Millette <millette@controlyourself.ca>
Fri, 15 May 2009 19:08:07 +0000 (15:08 -0400)
committerRobin Millette <millette@controlyourself.ca>
Fri, 15 May 2009 19:08:07 +0000 (15:08 -0400)
24 files changed:
actions/attachment.php [new file with mode: 0644]
actions/attachment_ajax.php [new file with mode: 0644]
actions/attachments.php [new file with mode: 0644]
actions/attachments_ajax.php [new file with mode: 0644]
actions/newnotice.php
actions/tag.php
classes/File.php [new file with mode: 0644]
classes/File_oembed.php [new file with mode: 0644]
classes/File_redirection.php [new file with mode: 0644]
classes/File_thumbnail.php [new file with mode: 0644]
classes/File_to_post.php [new file with mode: 0644]
classes/Notice.php
classes/laconica.ini [changed mode: 0755->0644]
classes/laconica.links.ini
js/jquery.joverlay.min.js [new file with mode: 0644]
js/util.js
lib/Shorturl_api.php
lib/action.php
lib/attachmentlist.php [new file with mode: 0644]
lib/noticelist.php
lib/router.php
lib/util.php
theme/base/images/icons/clip-big.png [new file with mode: 0644]
theme/base/images/icons/clip.png [new file with mode: 0644]

diff --git a/actions/attachment.php b/actions/attachment.php
new file mode 100644 (file)
index 0000000..981882a
--- /dev/null
@@ -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 (file)
index 0000000..1620b27
--- /dev/null
@@ -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 (file)
index 0000000..6b31c83
--- /dev/null
@@ -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 (file)
index 0000000..402d8b5
--- /dev/null
@@ -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');
+    }
+
+
+
+
+}
+
index cbd04c58b226c0a609c44996f7048bcf28f7abcd..ae0ff9636335809f2e9aaca4697d56b567e85332 100644 (file)
@@ -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
      *
index 02f3e35225cca6976fc7efe5a8b0930b346664eb..2202f9bb07041e713ced894331f3232795f84b23 100644 (file)
@@ -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 (file)
index 0000000..e591311
--- /dev/null
@@ -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 (file)
index 0000000..f1b2cb1
--- /dev/null
@@ -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 (file)
index 0000000..a71d1c0
--- /dev/null
@@ -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 (file)
index 0000000..1a65b92
--- /dev/null
@@ -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 (file)
index 0000000..00ddebe
--- /dev/null
@@ -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;
+            }
+        }
+
+    }
+}
+
index 382d160ab3fbe5c122f0e1cffa7b4bcc0ffc517a..30508070e53320b7783c898747c45aa2bc8e6e16 100644 (file)
@@ -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);
old mode 100755 (executable)
new mode 100644 (file)
index 5a905a4..316923a
@@ -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
+
+
index 173b187267d433c832eb542f931564bdf95276e5..95c63f3c09cc1f1f3b565dc47f730ea9427d1ecf 100644 (file)
@@ -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/js/jquery.joverlay.min.js b/js/jquery.joverlay.min.js
new file mode 100644 (file)
index 0000000..c916850
--- /dev/null
@@ -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
index 3f14bc61c6d831d35a822c704a637f702b4beda9..31d9eb4f54706a09a384ca2cf0604ae7904373ef 100644 (file)
  */
 
 $(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;
index fe106cb8376a06b69ac5ba142329917b808a4427..924aa93a8953ab7b0239660989cc1db849d43e97 100644 (file)
@@ -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) {
index 3e43ffe3e3ee1427d53c571ee4fb52b639b31aa6..fc123a6332933976e2b064a608467847645b35a3 100644 (file)
@@ -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 (file)
index 0000000..9485fe3
--- /dev/null
@@ -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));
+            }
+        }
+    }
+}
+
index 8fccba73e1c93788926f671b9c2193d0d7a5dcf0..55dd902b4a3def0933b3c7a2fed3d431dd4d661c 100644 (file)
@@ -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();
index 9308c818a4bf2e0f65824dcdd5203ad1d4ede2af..635e1d77e09dddde427d2e9d1c8a971bc0683250 100644 (file)
@@ -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]+'));
index 198185338547e930cbae0269389ed3992d5ccb51..25c0fb0a15915467e2a6a64f3a316122c7472cee 100644 (file)
@@ -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
new file mode 100644 (file)
index 0000000..3945f56
Binary files /dev/null and b/theme/base/images/icons/clip-big.png differ
diff --git a/theme/base/images/icons/clip.png b/theme/base/images/icons/clip.png
new file mode 100644 (file)
index 0000000..3c5a17d
Binary files /dev/null and b/theme/base/images/icons/clip.png differ