]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.8.x' into userdesign
authorEvan Prodromou <evan@controlyourself.ca>
Wed, 10 Jun 2009 04:51:24 +0000 (21:51 -0700)
committerEvan Prodromou <evan@controlyourself.ca>
Wed, 10 Jun 2009 04:51:24 +0000 (21:51 -0700)
Conflicts:
actions/designsettings.php

66 files changed:
.gitignore
README
actions/api.php
actions/conversation.php
actions/designsettings.php
actions/facebookhome.php
actions/facebookinvite.php
actions/facebooksettings.php
actions/file.php [new file with mode: 0644]
actions/newnotice.php
actions/shownotice.php
actions/twitapiaccount.php
actions/twitapidirect_messages.php
actions/twitapistatuses.php
actions/twitapiusers.php
classes/Fave.php
classes/File.php
classes/File_redirection.php
classes/Foreign_link.php
classes/Memcached_DataObject.php
classes/Notice.php
classes/Notice_inbox.php
classes/Notice_tag.php
classes/Profile.php
classes/Reply.php
classes/User.php
classes/User_group.php
config.php.sample
db/laconica.sql
db/notice_source.sql
extlib/MIME/Type.php [new file with mode: 0644]
extlib/MIME/Type/Extension.php [new file with mode: 0644]
extlib/MIME/Type/Parameter.php [new file with mode: 0644]
js/jquery.form.js
js/util.js
lib/attachmentlist.php
lib/common.php
lib/facebookaction.php
lib/form.php
lib/mail.php
lib/noticeform.php
lib/noticelist.php
lib/noticesection.php
lib/omb.php
lib/router.php
lib/twitterapi.php
lib/util.php
plugins/FBConnect/FBC_XDReceiver.php [new file with mode: 0644]
plugins/FBConnect/FBConnectAuth.php
plugins/FBConnect/FBConnectLogin.php
plugins/FBConnect/FBConnectPlugin.php
plugins/FBConnect/FBConnectSettings.php
plugins/FBConnect/xd_receiver.htm [deleted file]
scripts/fixup_utf8.php [new file with mode: 0644]
scripts/getvaliddaemons.php
scripts/inboxqueuehandler.php [deleted file]
scripts/memcachedqueuehandler.php [deleted file]
scripts/stopdaemons.sh
theme/base/css/display.css
theme/base/css/ie.css
theme/base/images/icons/twotone/green/clip-01.gif [new file with mode: 0644]
theme/base/images/icons/twotone/green/clip-02.gif [new file with mode: 0644]
theme/biz/css/base.css
theme/biz/css/display.css
theme/default/css/display.css
theme/identica/css/display.css

index da6947bfdc3f27978ab534a07be6a96a4797a6ef..3418d8ee54d1178448469dd417721c9831c1ea2d 100644 (file)
@@ -1,5 +1,6 @@
 avatar/*
 files/*
+file/*
 _darcs/*
 logs/*
 config.php
@@ -16,3 +17,8 @@ dataobject.ini
 .buildpath
 .project
 .settings
+TODO.rym
+config-*.php
+good-config.php
+lac08.log
+php.log
diff --git a/README b/README
index 9207f3e900ddfa8251d530ac44e8c7e782fc6b7c..2099f94d62f46f953bd13716ef9b3abc0d98cd00 100644 (file)
--- a/README
+++ b/README
@@ -2,8 +2,8 @@
 README
 ------
 
-Laconica 0.7.3 ("You Are The Everything")
-7 April 2009
+Laconica 0.7.4 ("Can't Get There From Here")
+29 May 2009
 
 This is the README file for Laconica, the Open Source microblogging
 platform. It includes installation instructions, descriptions of
@@ -71,29 +71,29 @@ for additional terms.
 New this version
 ================
 
-This is a minor bug-fix and feature release since version 0.7.2.1,
-released Mar 11 2009. Notable changes this version:
-
-- A plugin to allow a templating language for customization
-- A plugin for Piwik Analytics engine
-- A bookmarklet for posting a notice about a Web page you're reading
-- A welcome notice ('welcomebot') and default subscription for new users
-- Support for SSL for some or all pages on the site
-- Better handling of empty notice lists on many pages
-- Major improvements to the Twitter friend-sync offline processing
-- subscribers, subscriptions, groups are listed on the Personal page.
-- "Invite" link restored to main menu
-- Better memory handling in FOAF output
-- Fix for SUP support (FriendFeed)
-- Correct and intelligent redirect HTTP status codes
-- Fix DB collations for search and sort
-- Better H1s and Titles using user full names
-- Fixes to make the linkback plugin operational
-- Better indication that a notice is being published by Ajax (spinner)
-- Better and unified Atom output
-- Hiding "register" and "join now" messages when site is closed
-- ping, twitter and facebook queuehandlers working better
-- Updated RPM spec
+This is a minor bug-fix and feature release since version 0.7.3,
+released Apr 4 2009. Notable changes this version:
+
+- Improved handling of UTF-8 characters. The new code is *not* backwards
+  compatible by default; see "Upgrading" below for instructions on
+  converting existing databases to the correct character set.
+- Unroll joins for large queries. This greatly enhanced database
+  performance -- up to 50x for some queries -- at the expense of making
+  an extra DB hit for some queries.
+- Added an optional plugin to use WikiHashtags
+  (http://hashtags.wikia.com/) for the sidebar on hashtag pages.
+- Optimized Twitter friend synchronization.
+- Better error handling for Ajax posting of notices, including
+  HTTP errors and timeouts.
+- Experimental Comet plugin -- supports the cometd and the Bayeux
+  protocol. Using this plugin, you can show "real time" updates on the
+  public and tag pages. However, server configuration is complex.
+- If queues are enabled, update inboxes and memcached off-line. Speeds
+  up posting considerably.
+- Correctly shorten links posted through XMPP.
+- <link> elements for pagination, supported by some browsers like Opera.
+- Corrected date format in search API.
+- Made the public XRDS file work correctly.
 
 Prerequisites
 =============
@@ -197,9 +197,9 @@ especially if you've previously installed PHP/MySQL packages.
 1. Unpack the tarball you downloaded on your Web server. Usually a
    command like this will work:
 
-          tar zxf laconica-0.7.3.tar.gz
+          tar zxf laconica-0.7.4.tar.gz
 
-   ...which will make a laconica-0.7.3 subdirectory in your current
+   ...which will make a laconica-0.7.4 subdirectory in your current
    directory. (If you don't have shell access on your Web server, you
    may have to unpack the tarball on your local computer and FTP the
    files to the server.)
@@ -207,7 +207,7 @@ especially if you've previously installed PHP/MySQL packages.
 2. Move the tarball to a directory of your choosing in your Web root
    directory. Usually something like this will work:
 
-          mv laconica-0.7.3 /var/www/mublog
+          mv laconica-0.7.4 /var/www/mublog
 
    This will make your Laconica instance available in the mublog path of
    your server, like "http://example.net/mublog". "microblog" or
@@ -694,10 +694,17 @@ to users on a remote site. (Or not... it's not well tested.) The
 Upgrading
 =========
 
+IMPORTANT NOTE: Laconica 0.7.4 introduced a fix for some
+incorrectly-stored international characters ("UTF-8"). For new
+installations, it will now store non-ASCII characters correctly.
+However, older installations will have the incorrect storage, and will
+consequently show up "wrong" in browsers. See below for how to deal
+with this situation.
+
 If you've been using Laconica 0.6, 0.5 or lower, or if you've been
 tracking the "git" version of the software, you will probably want
 to upgrade and keep your existing data. There is no automated upgrade
-procedure in Laconica 0.7.3. Try these step-by-step instructions; read
+procedure in Laconica 0.7.4. Try these step-by-step instructions; read
 to the end first before trying them.
 
 0. Download Laconica and set up all the prerequisites as if you were
@@ -783,6 +790,29 @@ problem.
 3. When fixup_inboxes is finished, you can set the enabled flag to
    'true'.
 
+UTF-8 Database
+--------------
+
+Laconica 0.7.4 introduced a fix for some incorrectly-stored
+international characters ("UTF-8"). This fix is not
+backwards-compatible; installations from before 0.7.4 will show
+non-ASCII characters of old notices incorrectly. This section explains
+what to do.
+
+0. You can disable the new behaviour by setting the 'db''utf8' config
+   option to "false". You should only do this until you're ready to
+   convert your DB to the new format.
+1. When you're ready to convert, you can run the fixup_utf8.php script
+   in the scripts/ subdirectory. If you've had the "new behaviour"
+   enabled (probably a good idea), you can give the ID of the first
+   "new" notice as a parameter, and only notices before that one will
+   be converted. Notices are converted in reverse chronological order,
+   so the most recent (and visible) ones will be converted first. The
+   script should work whether or not you have the 'db''utf8' config
+   option enabled.
+2. When you're ready, set $config['db']['utf8'] to true, so that
+   new notices will be stored correctly.
+
 Configuration options
 =====================
 
@@ -910,6 +940,10 @@ mirror: you can set this to an array of DSNs, like the above
        and adding the slaves to this array. Note that if you want some
        requests to go to the 'database' (master) server, you'll need
        to include it in this array, too.
+utf8: whether to talk to the database in UTF-8 mode. This is the default
+      with new installations, but older sites may want to turn it off
+      until they get their databases fixed up. See "UTF-8 database"
+      above for details.
 
 syslog
 ------
@@ -1162,6 +1196,37 @@ reporturl: URL to post statistics to. Defaults to Laconica developers'
            set 'run' to 'never' than to set this value to something
            nonsensical.
 
+
+attachments
+-----------
+
+The software lets users upload files with their notices. You can configure
+the types of accepted files by mime types and a trio of quota options:
+per file, per user (total), per user per month.
+
+We suggest the use of the pecl file_info extension to handle mime type
+detection.
+
+supported: an array of mime types you accept to store and distribute,
+           like 'image/gif', 'video/mpeg', 'audio/mpeg', etc. Make sure you
+           setup your server to properly reckognize the types you want to
+           support.
+
+For quotas, be sure you've set the upload_max_filesize and post_max_size
+in php.ini to be large enough to handle your upload. In httpd.conf
+(if you're using apache), check that the LimitRequestBody directive isn't
+set too low (it's optional, so it may not be there at all).
+
+file_quota: maximum size for a single file upload in bytes. A user can send
+            any amount of notices with attachments as long as each attachment
+            is smaller than file_quota.
+user_quota: total size in bytes a user can store on this server. Each user
+            can store any number of files as long as their total size does
+            not exceed the user_quota.
+monthly_quota: total size permitted in the current month. This is the total
+            size in bytes that a user can upload each month.
+
+
 Troubleshooting
 ===============
 
@@ -1174,7 +1239,7 @@ repository (see below), and you get a compilation error ("unexpected
 T_STRING") in the browser, check to see that you don't have any
 conflicts in your code.
 
-If you upgraded to Laconica 0.7.3 without reading the "Notice inboxes"
+If you upgraded to Laconica 0.7.4 without reading the "Notice inboxes"
 section above, and all your users' 'Personal' tabs are empty, read the
 "Notice inboxes" section above.
 
@@ -1265,6 +1330,8 @@ if anyone's been overlooked in error.
 * Ken Sedgwick
 * Brian Hendrickson
 * Tobias Diekershoff
+* Dan Moore
+* Fil
 
 Thanks also to the developers of our upstream library code and to the
 thousands of people who have tried out Identi.ca, installed Laconi.ca,
index 8762b4bcd3efc974831252987f72b8bb33bd4041..b8da852b536d469682f6fcee277894247696695e 100644 (file)
@@ -67,6 +67,7 @@ class ApiAction extends Action
                     $this->process_command();
                 } else {
                     # basic authentication failed
+                    common_log(LOG_WARNING, "Failed API auth attempt, nickname: $nickname.");
                     $this->show_basic_auth_error();
                 }
             }
@@ -143,8 +144,8 @@ class ApiAction extends Action
         }
 
         if (in_array($fullname, $bareauth)) {
-            # bareauth: only needs auth if without an argument
-            if ($this->api_arg) {
+            # bareauth: only needs auth if without an argument or query param specifying user
+            if ($this->api_arg || $this->arg('id') || is_numeric($this->arg('user_id')) || $this->arg('screen_name')) {
                 return false;
             } else {
                 return true;
index ef189016aa358bfca4be1c0934902893ab948466..0d7cb9a8744189935e76c7ff5242698f2f5f4d91 100644 (file)
@@ -179,14 +179,14 @@ class ConversationTree extends NoticeList
 
         $this->out->elementStart('div', array('id' =>'notices_primary'));
         $this->out->element('h2', null, _('Notices'));
-        $this->out->elementStart('ul', array('class' => 'notices'));
+        $this->out->elementStart('ol', array('class' => 'notices xoxo'));
 
         if (array_key_exists('root', $this->tree)) {
             $rootid = $this->tree['root'][0];
             $this->showNoticePlus($rootid);
         }
 
-        $this->out->elementEnd('ul');
+        $this->out->elementEnd('ol');
         $this->out->elementEnd('div');
 
         return $cnt;
@@ -215,13 +215,13 @@ class ConversationTree extends NoticeList
         if (array_key_exists($id, $this->tree)) {
             $children = $this->tree[$id];
 
-            $this->out->elementStart('ul', array('class' => 'notices'));
+            $this->out->elementStart('ol', array('class' => 'notices'));
 
             foreach ($children as $child) {
                 $this->showNoticePlus($child);
             }
 
-            $this->out->elementEnd('ul');
+            $this->out->elementEnd('ol');
         }
 
         $this->out->elementEnd('li');
@@ -295,4 +295,4 @@ class ConversationTreeItem extends NoticeListItem
     {
         return;
     }
-}
\ No newline at end of file
+}
index da88940427f8a4e698bbd1160bfb375bc48548bb..66476e6777620db4ba679da33cf4e44f319b706e 100644 (file)
@@ -68,7 +68,7 @@ class DesignsettingsAction extends AccountSettingsAction
     function showContent()
     {
         $user = common_current_user();
-        $this->elementStart('form', array('method' => 'POST',
+        $this->elementStart('form', array('method' => 'post',
                                           'id' => 'form_settings_design',
                                           'class' => 'form_settings',
                                           'action' =>
@@ -80,8 +80,8 @@ class DesignsettingsAction extends AccountSettingsAction
         $this->element('legend', null, _('Change background image'));
         $this->elementStart('ul', 'form_data');
         $this->elementStart('li');
-        $this->element('label', array('for' => 'design_ background-image_file'),
-                       _('Upload file'));
+        $this->element('label', array('for' => 'design_background-image_file'), 
+                                _('Upload file'));
         $this->element('input', array('name' => 'design_background-image_file',
                                       'type' => 'file',
                                       'id' => 'design_background-image_file'));
index 5946e6c984b36190428c31c28c095f43b12180e8..00b35ef6868aa238c50a8e1b8a9d3a1b13b64cd5 100644 (file)
@@ -115,7 +115,7 @@ class FacebookhomeAction extends FacebookAction
                 $flink->foreign_id = $this->fbuid;
                 $flink->service = FACEBOOK_SERVICE;
                 $flink->created = common_sql_now();
-                $flink->set_flags(true, false, false);
+                $flink->set_flags(true, false, false, false);
 
                 $flink_id = $flink->insert();
 
@@ -138,9 +138,6 @@ class FacebookhomeAction extends FacebookAction
 
     function setDefaults()
     {
-        // A default prefix string for notices
-        $this->facebook->api_client->data_setUserPreference(
-            FACEBOOK_NOTICE_PREFIX, 'dented: ');
         $this->facebook->api_client->data_setUserPreference(
             FACEBOOK_PROMPTED_UPDATE_PREF, 'false');
     }
index 1302064ad7fc6c8b33f540c3c8eb4df06beaccc3..2207580f778781e07de381b697fd08473c7a22a3 100644 (file)
@@ -17,7 +17,9 @@
  * along with this program.     If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('LACONICA')) { exit(1); }
+if (!defined('LACONICA')) {
+    exit(1);
+}
 
 require_once(INSTALLDIR.'/lib/facebookaction.php');
 
@@ -67,7 +69,7 @@ class FacebookinviteAction extends FacebookAction
     function showSuccessContent()
     {
 
-        $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'), 
+        $this->element('h2', null, sprintf(_('Thanks for inviting your friends to use %s'),
             common_config('site', 'name')));
         $this->element('p', null, _('Invitations have been sent to the following users:'));
 
@@ -89,16 +91,6 @@ class FacebookinviteAction extends FacebookAction
 
     function showFormContent()
     {
-
-        // Get a list of users who are already using the app for exclusion
-        $exclude_ids = $this->facebook->api_client->friends_getAppUsers();
-        $exclude_ids_csv = null;
-        
-        // fbml needs these as a csv string, not an array
-        if ($exclude_ids) {
-            $exclude_ids_csv = implode(',', $exclude_ids);
-        }
-
         $content = sprintf(_('You have been invited to %s'), common_config('site', 'name')) .
             htmlentities('<fb:req-choice url="' . $this->app_uri . '" label="Add"/>');
 
@@ -109,36 +101,43 @@ class FacebookinviteAction extends FacebookAction
                                                       'content' => $content));
         $this->hidden('invite', 'true');
         $actiontext = sprintf(_('Invite your friends to use %s'), common_config('site', 'name'));
-        
-        $multi_params = array('showborder' => 'false');    
+
+        $multi_params = array('showborder' => 'false');
         $multi_params['actiontext'] = $actiontext;
-        
-        if ($exclude_ids_csv) {
+        $multi_params['bypass'] = 'cancel';
+
+        // Get a list of users who are already using the app for exclusion
+        $exclude_ids = $this->facebook->api_client->friends_getAppUsers();
+        $exclude_ids_csv = null;
+
+        // fbml needs these as a csv string, not an array
+        if ($exclude_ids) {
+            $exclude_ids_csv = implode(',', $exclude_ids);
             $multi_params['exclude_ids'] = $exclude_ids_csv;
         }
 
-        $multi_params['bypass'] = 'cancel';
-                
         $this->element('fb:multi-friend-selector', $multi_params);
-
         $this->elementEnd('fb:request-form');
 
-        $this->element('h2', null, sprintf(_('Friends already using %s:'), 
-            common_config('site', 'name')));
-        $this->elementStart('ul', array('id' => 'facebook-friends'));
-        
-        foreach ($exclude_ids as $friend) {
-            $this->elementStart('li');
-            $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
-            $this->element('fb:name', array('uid' => $friend,
-                                            'capitalize' => 'true'));
-            $this->elementEnd('li');
-        }
+        if ($exclude_ids) {
 
-        $this->elementEnd("ul");
+            $this->element('h2', null, sprintf(_('Friends already using %s:'),
+                common_config('site', 'name')));
+            $this->elementStart('ul', array('id' => 'facebook-friends'));
+
+            foreach ($exclude_ids as $friend) {
+                $this->elementStart('li');
+                $this->element('fb:profile-pic', array('uid' => $friend, 'size' => 'square'));
+                $this->element('fb:name', array('uid' => $friend,
+                                                'capitalize' => 'true'));
+                $this->elementEnd('li');
+            }
+
+            $this->elementEnd("ul");
+        }
     }
-    
-    function title() 
+
+    function title()
     {
         return sprintf(_('Send invitations'));
     }
index 236460c1c9a42f1f15463aa69959fddb802fbf97..227e123160900d22a0b8d4d7563f229de4984e55 100644 (file)
@@ -55,7 +55,7 @@ class FacebooksettingsAction extends FacebookAction
         $prefix = $this->trimmed('prefix');
 
         $original = clone($this->flink);
-        $this->flink->set_flags($noticesync, $replysync, false);
+        $this->flink->set_flags($noticesync, $replysync, false, false);
         $result = $this->flink->update($original);
 
         $this->facebook->api_client->data_setUserPreference(FACEBOOK_NOTICE_PREFIX,
diff --git a/actions/file.php b/actions/file.php
new file mode 100644 (file)
index 0000000..1179dbe
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('LACONICA')) { exit(1); }
+
+require_once(INSTALLDIR.'/actions/shownotice.php');
+
+class FileAction extends ShowNoticeAction
+{
+    function showPage() {
+        $source_url = common_local_url('file', array('notice' => $this->notice->id));
+        $query = "select file_redirection.url as url from file join file_redirection on file.id = file_redirection.file_id where file.url = '$source_url'";
+        $file = new File_redirection;
+        $file->query($query);
+        $file->fetch();
+        if (empty($file->url)) {
+            die('nothing attached here');
+        } else {
+            header("Location: {$file->url}");
+            die();
+        }
+    }
+}
+
index ae0ff9636335809f2e9aaca4697d56b567e85332..02976a2ae2c20f84d44a3db0660d4bcf5226ffbb 100644 (file)
@@ -84,20 +84,24 @@ class NewnoticeAction extends Action
 
     function handle($args)
     {
-        parent::handle($args);
-
         if (!common_logged_in()) {
             $this->clientError(_('Not logged in.'));
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+            // check for this before token since all POST and FILES data
+            // is losts when size is exceeded
+            if (empty($_POST) && $_SERVER['CONTENT_LENGTH']) {
+                $this->clientError(sprintf(_('The server was unable to handle ' .
+                    'that much POST data (%s bytes) due to its current configuration.'),
+                    $_SERVER['CONTENT_LENGTH']));
+            }
+            parent::handle($args);
 
             // CSRF protection
             $token = $this->trimmed('token');
             if (!$token || $token != common_session_token()) {
                 $this->clientError(_('There was a problem with your session token. '.
                                      'Try again, please.'));
-                return;
             }
-
             try {
                 $this->saveNewNotice();
             } catch (Exception $e) {
@@ -109,6 +113,30 @@ class NewnoticeAction extends Action
         }
     }
 
+    function getUploadedFileType() {
+        require_once 'MIME/Type.php';
+
+        $filetype = MIME_Type::autoDetect($_FILES['attach']['tmp_name']);
+        if (in_array($filetype, common_config('attachments', 'supported'))) {
+            return $filetype;
+        }
+        $media = MIME_Type::getMedia($filetype);
+        if ('application' !== $media) {
+            $hint = sprintf(_(' Try using another %s format.'), $media);
+        } else {
+            $hint = '';
+        }
+        $this->clientError(sprintf(
+            _('%s is not a supported filetype on this server.'), $filetype) . $hint);
+    }
+
+    function isRespectsQuota($user) {
+        $file = new File;
+        $ret = $file->isRespectsQuota($user);
+        if (true === $ret) return true;
+        $this->clientError($ret);
+    }
+
     /**
      * Save a new notice, based on arguments
      *
@@ -131,7 +159,6 @@ class NewnoticeAction extends Action
             $this->clientError(_('No content!'));
         } else {
             $content_shortened = common_shorten_links($content);
-
             if (mb_strlen($content_shortened) > 140) {
                 $this->clientError(_('That\'s too long. '.
                                      'Max notice size is 140 chars.'));
@@ -158,17 +185,53 @@ class NewnoticeAction extends Action
             $replyto = 'false';
         }
 
-//        $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
+        if (isset($_FILES['attach']['error'])) {
+            switch ($_FILES['attach']['error']) {
+                case UPLOAD_ERR_NO_FILE:
+                    // no file uploaded, nothing to do
+                    break;
+
+                case UPLOAD_ERR_OK:
+                    $mimetype = $this->getUploadedFileType();
+                    if (!$this->isRespectsQuota($user)) {
+                        die('clientError() should trigger an exception before reaching here.');
+                    }
+                    break;
+
+                case UPLOAD_ERR_INI_SIZE:
+                    $this->clientError(_('The uploaded file exceeds the upload_max_filesize directive in php.ini.'));
+
+                case UPLOAD_ERR_FORM_SIZE:
+                    $this->clientError(_('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'));
+
+                case UPLOAD_ERR_PARTIAL:
+                    $this->clientError(_('The uploaded file was only partially uploaded.'));
+
+                case  UPLOAD_ERR_NO_TMP_DIR:
+                    $this->clientError(_('Missing a temporary folder.'));
+
+                case UPLOAD_ERR_CANT_WRITE:
+                    $this->clientError(_('Failed to write file to disk.'));
+
+                case UPLOAD_ERR_EXTENSION:
+                    $this->clientError(_('File upload stopped by extension.'));
+
+                default:
+                    die('Should never reach here.');
+            }
+        }
+
         $notice = Notice::saveNew($user->id, $content_shortened, 'web', 1,
                                   ($replyto == 'false') ? null : $replyto);
 
         if (is_string($notice)) {
             $this->clientError($notice);
-            return;
         }
 
+        if (isset($mimetype)) {
+            $this->storeFile($notice, $mimetype);
+        }
         $this->saveUrls($notice);
-
         common_broadcast_notice($notice);
 
         if ($this->boolean('ajax')) {
@@ -194,6 +257,33 @@ class NewnoticeAction extends Action
         }
     }
 
+    function storeFile($notice, $mimetype) {
+        $filename = basename($_FILES['attach']['name']);
+        $destination = "file/{$notice->id}-$filename";
+        if (move_uploaded_file($_FILES['attach']['tmp_name'], INSTALLDIR . "/$destination")) {
+            $file = new File;
+            $file->url = common_local_url('file', array('notice' => $notice->id));
+            $file->size = filesize(INSTALLDIR . "/$destination");
+            $file->date = time();
+            $file->mimetype = $mimetype;
+            if ($file_id = $file->insert()) {
+                $file_redir = new File_redirection;
+                $file_redir->url = common_path($destination);
+                $file_redir->file_id = $file_id;
+                $file_redir->insert();
+
+                $f2p = new File_to_post;
+                $f2p->file_id = $file_id; 
+                $f2p->post_id = $notice->id; 
+                $f2p->insert();
+            } else {
+                $this->clientError(_('There was a database error while saving your file. Please try again.'));
+            }
+        } else {
+            $this->clientError(_('File could not be moved to destination directory.'));
+        }
+    }
+
     /** save all urls in the notice to the db
      *
      * follow redirects and save all available file information
@@ -203,7 +293,7 @@ class NewnoticeAction extends Action
      *
      * @return void
      */
-    function saveUrls($notice) {
+    function saveUrls($notice, $uploaded = null) {
         common_replace_urls_callback($notice->content, array($this, 'saveUrl'), $notice->id);
     }
 
@@ -316,3 +406,4 @@ class NewnoticeAction extends Action
         $nli->show();
     }
 }
+
index 2c469c9deec69c88a546de8b239007614d6a142c..b0d973a991cad8e61cfa2d8ce632013e89f3201b 100644 (file)
@@ -122,7 +122,7 @@ class ShownoticeAction extends Action
 
     function lastModified()
     {
-        return max(strtotime($this->notice->created),
+        return max(strtotime($this->notice->modified),
                    strtotime($this->profile->modified),
                    ($this->avatar) ? strtotime($this->avatar->modified) : 0);
     }
@@ -208,10 +208,10 @@ class ShownoticeAction extends Action
 
     function showContent()
     {
-        $this->elementStart('ul', array('class' => 'notices'));
+        $this->elementStart('ol', array('class' => 'notices xoxo'));
         $nli = new NoticeListItem($this->notice, $this);
         $nli->show();
-        $this->elementEnd('ul');
+        $this->elementEnd('ol');
     }
 
     /**
index 68a18cb57b43c87d7a9e4e6090f5b5b1df019a9a..8b956f897a397606434d2a2d1040a71c31b937c8 100644 (file)
@@ -98,9 +98,31 @@ class TwitapiaccountAction extends TwitterapiAction
         $this->serverError(_('API method under construction.'), $code=501);
     }
 
+    // We don't have a rate limit, but some clients check this method.
+    // It always returns the same thing: 100 hit left.
     function rate_limit_status($args, $apidata)
     {
         parent::handle($args);
-        $this->serverError(_('API method under construction.'), $code=501);
+
+        $type = $apidata['content-type'];
+        $this->init_document($type);
+
+        if ($apidata['content-type'] == 'xml') {
+            $this->elementStart('hash');
+            $this->element('remaining-hits', array('type' => 'integer'), 100);
+            $this->element('hourly-limit', array('type' => 'integer'), 100);
+            $this->element('reset-time', array('type' => 'datetime'), null);
+            $this->element('reset_time_in_seconds', array('type' => 'integer'), 0);
+            $this->elementEnd('hash');
+        } elseif ($apidata['content-type'] == 'json') {
+
+            $out = array('reset_time_in_seconds' => 0,
+                         'remaining_hits' => 100,
+                         'hourly_limit' => 100,
+                         'reset_time' => '');
+            print json_encode($out);
+        }
+
+        $this->end_document($type);
     }
 }
index 7101db8df585354462150a1eae3e98364ca2b96a..d2dbdb619b180044a0388cd592382944eec4018a 100644 (file)
@@ -43,7 +43,7 @@ class Twitapidirect_messagesAction extends TwitterapiAction
         $count = $this->arg('count');
         $since = $this->arg('since');
         $since_id = $this->arg('since_id');
-        $before_id = $this->arg('before_id');
+        $max_id = $this->arg('max_id');
 
         $page = $this->arg('page');
 
@@ -74,8 +74,8 @@ class Twitapidirect_messagesAction extends TwitterapiAction
             $link = $server . $user->nickname . '/outbox';
         }
 
-        if ($before_id) {
-            $message->whereAdd("id < $before_id");
+        if ($max_id) {
+            $message->whereAdd("id <= $max_id");
         }
 
         if ($since_id) {
index 3abeba36727d2f8ae4ee6708f8d3aab95d00a64d..1fbde6639f3bf32b4be7131e8a28554e1acdcb5a 100644 (file)
@@ -45,22 +45,21 @@ class TwitapistatusesAction extends TwitterapiAction
 
         $page = $this->arg('page');
         $since_id = $this->arg('since_id');
-        $before_id = $this->arg('before_id');
+        $max_id = $this->arg('max_id');
 
-        // NOTE: page, since_id, and before_id are extensions to Twitter API -- TB
         if (!$page) {
             $page = 1;
         }
         if (!$since_id) {
             $since_id = 0;
         }
-        if (!$before_id) {
-            $before_id = 0;
+        if (!$max_id) {
+            $max_id = 0;
         }
 
         $since = strtotime($this->arg('since'));
 
-        $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $before_id, $since);
+        $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $max_id, $since);
 
         if ($notice) {
 
@@ -97,7 +96,7 @@ class TwitapistatusesAction extends TwitterapiAction
         $since_id = $this->arg('since_id');
         $count = $this->arg('count');
         $page = $this->arg('page');
-        $before_id = $this->arg('before_id');
+        $max_id = $this->arg('max_id');
 
         if (!$page) {
             $page = 1;
@@ -111,9 +110,8 @@ class TwitapistatusesAction extends TwitterapiAction
             $since_id = 0;
         }
 
-        // NOTE: before_id is an extension to Twitter API -- TB
-        if (!$before_id) {
-            $before_id = 0;
+        if (!$max_id) {
+            $max_id = 0;
         }
 
         $since = strtotime($this->arg('since'));
@@ -133,7 +131,7 @@ class TwitapistatusesAction extends TwitterapiAction
         $link = common_local_url('all', array('nickname' => $user->nickname));
         $subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), $user->nickname, $sitename);
 
-        $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $before_id, $since);
+        $notice = $user->noticesWithFriends(($page-1)*20, $count, $since_id, $max_id, $since);
 
         switch($apidata['content-type']) {
          case 'xml':
@@ -184,7 +182,7 @@ class TwitapistatusesAction extends TwitterapiAction
         $since = $this->arg('since');
         $since_id = $this->arg('since_id');
         $page = $this->arg('page');
-        $before_id = $this->arg('before_id');
+        $max_id = $this->arg('max_id');
 
         if (!$page) {
             $page = 1;
@@ -198,9 +196,8 @@ class TwitapistatusesAction extends TwitterapiAction
             $since_id = 0;
         }
 
-        // NOTE: before_id is an extensions to Twitter API -- TB
-        if (!$before_id) {
-            $before_id = 0;
+        if (!$max_id) {
+            $max_id = 0;
         }
 
         $since = strtotime($this->arg('since'));
@@ -220,7 +217,7 @@ class TwitapistatusesAction extends TwitterapiAction
 
         # XXX: since
 
-        $notice = $user->getNotices((($page-1)*20), $count, $since_id, $before_id, $since);
+        $notice = $user->getNotices((($page-1)*20), $count, $since_id, $max_id, $since);
 
         switch($apidata['content-type']) {
          case 'xml':
@@ -353,7 +350,7 @@ class TwitapistatusesAction extends TwitterapiAction
         $count = $this->arg('count');
         $page = $this->arg('page');
         $since_id = $this->arg('since_id');
-        $before_id = $this->arg('before_id');
+        $max_id = $this->arg('max_id');
 
         $user = $this->get_user($apidata['api_arg'], $apidata);
         $this->auth_user = $apidata['user'];
@@ -380,15 +377,14 @@ class TwitapistatusesAction extends TwitterapiAction
             $since_id = 0;
         }
 
-        // NOTE: before_id is an extension to Twitter API -- TB
-        if (!$before_id) {
-            $before_id = 0;
+        if (!$max_id) {
+            $max_id = 0;
         }
 
         $since = strtotime($this->arg('since'));
 
         $notice = $user->getReplies((($page-1)*20),
-            $count, $since_id, $before_id, $since);
+            $count, $since_id, $max_id, $since);
         $notices = array();
 
         while ($notice->fetch()) {
index 1542cfb33e3d064130093272e014ac4fa6262d81..b90bbfa985c9a6a381a5397647b54518225e6768 100644 (file)
@@ -25,110 +25,61 @@ class TwitapiusersAction extends TwitterapiAction
 {
 
     function show($args, $apidata)
-    {        
+    {
         parent::handle($args);
 
-        if (!in_array($apidata['content-type'], array('xml', 'json'))) {            
+        if (!in_array($apidata['content-type'], array('xml', 'json'))) {
             $this->clientError(_('API method not found!'), $code = 404);
             return;
         }
-                
-               $user = null;
-               $email = $this->arg('email');
-               $user_id = $this->arg('user_id');
-
-               if ($email) {
-                       $user = User::staticGet('email', $email);
-               } elseif ($user_id) {
-                       $user = $this->get_user($user_id);  
-               } elseif (isset($apidata['api_arg'])) {
-                       $user = $this->get_user($apidata['api_arg']);
-           } elseif (isset($apidata['user'])) {
-               $user = $apidata['user'];
-           }
-       
-               if (!$user) {               
-                       // XXX: Twitter returns a random(?) user instead of throwing and err! -- Zach
-                       $this->client_error(_('Not found.'), 404, $apidata['content-type']);
-                       return;
-               }
-
-               $profile = $user->getProfile();
-
-               if (!$profile) {
-                       common_server_error(_('User has no profile.'));
-                       return;
-               }
-
-               $twitter_user = $this->twitter_user_array($profile, true);
-
-               // Add in extended user fields offered up by this method
-               $twitter_user['created_at'] = $this->date_twitter($profile->created);
-
-               $subbed = DB_DataObject::factory('subscription');
-               $subbed->subscriber = $profile->id;
-               $subbed_count = (int) $subbed->count() - 1;
-
-               $notices = DB_DataObject::factory('notice');
-               $notices->profile_id = $profile->id;
-               $notice_count = (int) $notices->count();
-
-               $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
-               $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
-
-               // Other fields Twitter sends...
-               $twitter_user['profile_background_color'] = '';
-               $twitter_user['profile_background_image_url'] = '';
-               $twitter_user['profile_text_color'] = '';
-               $twitter_user['profile_link_color'] = '';
-               $twitter_user['profile_sidebar_fill_color'] = '';
-               $twitter_user['profile_sidebar_border_color'] = '';
-               $twitter_user['profile_background_tile'] = false;
-
-               $faves = DB_DataObject::factory('fave');
-               $faves->user_id = $user->id;
-               $faves_count = (int) $faves->count();
-               $twitter_user['favourites_count'] = $faves_count;
-
-               $timezone = 'UTC';
-
-               if ($user->timezone) {
-                       $timezone = $user->timezone;
-               }
-
-               $t = new DateTime;
-               $t->setTimezone(new DateTimeZone($timezone));
-               $twitter_user['utc_offset'] = $t->format('Z');
-               $twitter_user['time_zone'] = $timezone;
-
-               if (isset($apidata['user'])) {
-
-                       $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
-            
-                       // Notifications on?
-                       $sub = Subscription::pkeyGet(array('subscriber' =>
-                               $apidata['user']->id, 'subscribed' => $profile->id));
-            
-                       if ($sub) {
-                               $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
-                       }
-               }
-        
-               if ($apidata['content-type'] == 'xml') {
-                       $this->init_document('xml');
-                       $this->show_twitter_xml_user($twitter_user);
-                       $this->end_document('xml');
-               } elseif ($apidata['content-type'] == 'json') {
-                       $this->init_document('json');
-                       $this->show_json_objects($twitter_user);
-                       $this->end_document('json');
-               } else {
-                   
-                   // This is in case 'show' was called via /account/verify_credentials
-                   // without a format (xml or json).
+
+        $user = null;
+        $email = $this->arg('email');
+        $user_id = $this->arg('user_id');
+
+        // XXX: email field deprecated in Twitter's API
+
+        // XXX: Also: need to add screen_name param
+
+        if ($email) {
+            $user = User::staticGet('email', $email);
+        } elseif ($user_id) {
+            $user = $this->get_user($user_id);
+        } elseif (isset($apidata['api_arg'])) {
+            $user = $this->get_user($apidata['api_arg']);
+        } elseif (isset($apidata['user'])) {
+            $user = $apidata['user'];
+        }
+
+        if (!$user) {
+            $this->client_error(_('Not found.'), 404, $apidata['content-type']);
+            return;
+        }
+
+        $profile = $user->getProfile();
+
+        if (!$profile) {
+            common_server_error(_('User has no profile.'));
+            return;
+        }
+
+        $twitter_user = $this->twitter_user_array($profile, true);
+
+        if ($apidata['content-type'] == 'xml') {
+            $this->init_document('xml');
+            $this->show_twitter_xml_user($twitter_user);
+            $this->end_document('xml');
+        } elseif ($apidata['content-type'] == 'json') {
+            $this->init_document('json');
+            $this->show_json_objects($twitter_user);
+            $this->end_document('json');
+        } else {
+
+            // This is in case 'show' was called via /account/verify_credentials
+            // without a format (xml or json).
             header('Content-Type: text/html; charset=utf-8');
             print 'Authorized';
         }
 
-       }
+    }
 }
index 915b4572ffed5dd11e533a664c55e4d687d725b3..572334ce4f8cf17e8811cf0eab51cd77ed922549 100644 (file)
@@ -46,7 +46,7 @@ class Fave extends Memcached_DataObject
         return $ids;
     }
 
-    function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since)
+    function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
     {
         $fav = new Fave();
 
@@ -59,8 +59,8 @@ class Fave extends Memcached_DataObject
             $fav->whereAdd('notice_id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $fav->whereAdd('notice_id < ' . $before_id);
+        if ($max_id != 0) {
+            $fav->whereAdd('notice_id <= ' . $max_id);
         }
 
         if (!is_null($since)) {
index e5913115b7a3ed5a47bf473a30464b66dcfa3d69..24ab11b8eb12d595469c08aa2092dca65d013a6b 100644 (file)
@@ -120,4 +120,30 @@ class File extends Memcached_DataObject
         File_to_post::processNew($file_id, $notice_id);
         return $x;
     }
+
+    function isRespectsQuota($user) {
+        if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) {
+            return sprintf(_('No file may be larger than %d bytes ' .
+                'and the file you sent was %d bytes. Try to upload a smaller version.'),
+                common_config('attachments', 'file_quota'), $_FILES['attach']['size']);
+        }
+
+        $query = "select sum(size) as total from file join file_to_post on file_to_post.file_id = file.id join notice on file_to_post.post_id = notice.id where profile_id = {$user->id} and file.url like '%/notice/%/file'";
+        $this->query($query);
+        $this->fetch();
+        $total = $this->total + $_FILES['attach']['size'];
+        if ($total > common_config('attachments', 'user_quota')) {
+            return sprintf(_('A file this large would exceed your user quota of %d bytes.'), common_config('attachments', 'user_quota'));
+        }
+
+        $query .= ' month(modified) = month(now()) and year(modified) = year(now())';
+        $this->query($query);
+        $this->fetch();
+        $total = $this->total + $_FILES['attach']['size'];
+        if ($total > common_config('attachments', 'monthly_quota')) {
+            return sprintf(_('A file this large would exceed your monthly quota of %d bytes.'), common_config('attachments', 'monthly_quota'));
+        }
+        return true;
+    }
 }
+
index 0eae68178373757bf3bf5f042850610d4a341d9a..212cc36158ab3783fd6b72f7fbe9acf13eb28a7f 100644 (file)
@@ -133,7 +133,7 @@ class File_redirection extends Memcached_DataObject
         $file->limit(1);
         $file->orderBy('len');
         $file->find(true);
-        if (!empty($file->id)) {
+        if (!empty($file->url) && (strlen($file->url) < strlen($long_url))) {
             return $file->url;
         }
 
index 6065609512ce76e8461a3952b910e18cb94c50e1..c0b356ecedd10473e13447d7a14157b8388dc63f 100644 (file)
@@ -11,7 +11,7 @@ class Foreign_link extends Memcached_DataObject
 
     public $__table = 'foreign_link';                    // table name
     public $user_id;                         // int(4)  primary_key not_null
-    public $foreign_id;                      // int(4)  primary_key not_null
+    public $foreign_id;                      // bigint(8)  primary_key not_null unsigned
     public $service;                         // int(4)  primary_key not_null
     public $credentials;                     // varchar(255)
     public $noticesync;                      // tinyint(1)   not_null default_1
index 5f71f716b3dd569f84ef557420b03517ecc7c2f9..33ac70dd045643a5a57ff87e58d383a0bc52bd2b 100644 (file)
@@ -227,4 +227,28 @@ class Memcached_DataObject extends DB_DataObject
         $c->set($ckey, $cached, MEMCACHE_COMPRESSED, $expiry);
         return new ArrayWrapper($cached);
     }
+
+    // We overload so that 'SET NAMES "utf8"' is called for
+    // each connection
+
+    function _connect()
+    {
+        global $_DB_DATAOBJECT;
+        $exists = !empty($this->_database_dsn_md5) &&
+          isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]);
+        $result = parent::_connect();
+        if (!$exists) {
+            $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
+            if (common_config('db', 'type') == 'mysql' &&
+                common_config('db', 'utf8')) {
+                $conn = $DB->connection;
+                if ($DB instanceof DB_mysqli) {
+                    mysqli_set_charset($conn, 'utf8');
+                } else if ($DB instanceof DB_mysql) {
+                    mysql_set_charset('utf8', $conn);
+                }
+            }
+        }
+        return $result;
+    }
 }
index 1b5c0ab0a55afdc53592b860ec71ca414fa615a5..895b5d2c71c8a3720587ec57a16abb62f0adf7cc 100644 (file)
@@ -124,6 +124,8 @@ 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.');
@@ -134,7 +136,7 @@ class Notice extends Memcached_DataObject
             return _('Too many notices too fast; take a breather and post again in a few minutes.');
         }
 
-        if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $content)) {
+        if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) {
             common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.');
                        return _('Too many duplicate messages too quickly; take a breather and post again in a few minutes.');
         }
@@ -165,8 +167,8 @@ class Notice extends Memcached_DataObject
 
                $notice->reply_to = $reply_to;
                $notice->created = common_sql_now();
-               $notice->content = $content;
-               $notice->rendered = common_render_content($content, $notice);
+               $notice->content = $final;
+               $notice->rendered = common_render_content($final, $notice);
                $notice->source = $source;
                $notice->uri = $uri;
 
@@ -202,13 +204,9 @@ class Notice extends Memcached_DataObject
 
             $notice->saveReplies();
             $notice->saveTags();
-            $notice->saveGroups();
 
-            if (common_config('queue', 'enabled')) {
-                $notice->addToAuthorInbox();
-            } else {
-                $notice->addToInboxes();
-            }
+            $notice->addToInboxes();
+            $notice->saveGroups();
 
             $notice->query('COMMIT');
 
@@ -218,13 +216,7 @@ class Notice extends Memcached_DataObject
         # Clear the cache for subscribed users, so they'll update at next request
         # XXX: someone clever could prepend instead of clearing the cache
 
-        if (common_config('memcached', 'enabled')) {
-            if (common_config('queue', 'enabled')) {
-                $notice->blowAuthorCaches();
-            } else {
-                $notice->blowCaches();
-            }
-        }
+        $notice->blowCaches();
 
         return $notice;
     }
@@ -277,6 +269,18 @@ class Notice extends Memcached_DataObject
         return true;
     }
 
+    function getUploadedAttachment() {
+        $post = clone $this;
+        $query = 'select file.url as uploaded from file join file_to_post on file.id = file_id where post_id=' . $post->escape($post->id) . ' and url like "%/notice/%/file"';
+        $post->query($query);
+        $post->fetch();
+        $ret = $post->uploaded;
+//        var_dump($post);
+        $post->free();
+//        die();
+        return $ret;
+    }
+
     function hasAttachments() {
         $post = clone $this;
         $query = "select count(file_id) as n_attachments from file join file_to_post on (file_id = file.id) join notice on (post_id = notice.id) where post_id = " . $post->escape($post->id);
@@ -297,17 +301,6 @@ class Notice extends Memcached_DataObject
         $this->blowGroupCache($blowLast);
     }
 
-    function blowAuthorCaches($blowLast=false)
-    {
-        // Clear the user's cache
-        $cache = common_memcache();
-        if (!empty($cache)) {
-            $cache->delete(common_cache_key('notice_inbox:by_user:'.$this->profile_id));
-        }
-        $this->blowNoticeCache($blowLast);
-        $this->blowPublicCache($blowLast);
-    }
-
     function blowGroupCache($blowLast=false)
     {
         $cache = common_memcache();
@@ -443,22 +436,22 @@ class Notice extends Memcached_DataObject
     # XXX: too many args; we need to move to named params or even a separate
     # class for notice streams
 
-    static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $order=null, $since=null) {
+    static function getStream($qry, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $order=null, $since=null) {
 
         if (common_config('memcached', 'enabled')) {
 
-            # Skip the cache if this is a since, since_id or before_id qry
-            if ($since_id > 0 || $before_id > 0 || $since) {
-                return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+            # Skip the cache if this is a since, since_id or max_id qry
+            if ($since_id > 0 || $max_id > 0 || $since) {
+                return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
             } else {
                 return Notice::getCachedStream($qry, $cachekey, $offset, $limit, $order);
             }
         }
 
-        return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since);
+        return Notice::getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since);
     }
 
-    static function getStreamDirect($qry, $offset, $limit, $since_id, $before_id, $order, $since) {
+    static function getStreamDirect($qry, $offset, $limit, $since_id, $max_id, $order, $since) {
 
         $needAnd = false;
         $needWhere = true;
@@ -480,7 +473,7 @@ class Notice extends Memcached_DataObject
             $qry .= ' notice.id > ' . $since_id;
         }
 
-        if ($before_id > 0) {
+        if ($max_id > 0) {
 
             if ($needWhere) {
                 $qry .= ' WHERE ';
@@ -489,7 +482,7 @@ class Notice extends Memcached_DataObject
                 $qry .= ' AND ';
             }
 
-            $qry .= ' notice.id < ' . $before_id;
+            $qry .= ' notice.id <= ' . $max_id;
         }
 
         if ($since) {
@@ -647,17 +640,17 @@ class Notice extends Memcached_DataObject
         }
     }
 
-    function publicStream($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
+    function publicStream($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
     {
         $ids = Notice::stream(array('Notice', '_publicStreamDirect'),
                               array(),
                               'public',
-                              $offset, $limit, $since_id, $before_id, $since);
+                              $offset, $limit, $since_id, $max_id, $since);
 
         return Notice::getStreamByIds($ids);
     }
 
-    function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $before_id=0, $since=null)
+    function _publicStreamDirect($offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
     {
         $notice = new Notice();
 
@@ -681,8 +674,8 @@ class Notice extends Memcached_DataObject
             $notice->whereAdd('id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $notice->whereAdd('id < ' . $before_id);
+        if ($max_id != 0) {
+            $notice->whereAdd('id <= ' . $max_id);
         }
 
         if (!is_null($since)) {
@@ -726,33 +719,6 @@ class Notice extends Memcached_DataObject
         return;
     }
 
-    function addToAuthorInbox()
-    {
-        $enabled = common_config('inboxes', 'enabled');
-
-        if ($enabled === true || $enabled === 'transitional') {
-            $user = User::staticGet('id', $this->profile_id);
-            if (empty($user)) {
-                return;
-            }
-            $inbox = new Notice_inbox();
-            $UT = common_config('db','type')=='pgsql'?'"user"':'user';
-            $qry = 'INSERT INTO notice_inbox (user_id, notice_id, created) ' .
-              "SELECT $UT.id, " . $this->id . ", '" . $this->created . "' " .
-              "FROM $UT " .
-              "WHERE $UT.id = " . $this->profile_id . ' ' .
-              'AND NOT EXISTS (SELECT user_id, notice_id ' .
-              'FROM notice_inbox ' .
-              "WHERE user_id = " . $this->profile_id . ' '.
-              'AND notice_id = ' . $this->id . ' )';
-            if ($enabled === 'transitional') {
-                $qry .= " AND $UT.inboxed = 1";
-            }
-            $inbox->query($qry);
-        }
-        return;
-    }
-
     function saveGroups()
     {
         $enabled = common_config('inboxes', 'enabled');
@@ -1024,15 +990,15 @@ class Notice extends Memcached_DataObject
         }
     }
 
-    function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $before_id=0, $since=null, $tag=null)
+    function stream($fn, $args, $cachekey, $offset=0, $limit=20, $since_id=0, $max_id=0, $since=null)
     {
         $cache = common_memcache();
 
         if (empty($cache) ||
-            $since_id != 0 || $before_id != 0 || !is_null($since) ||
+            $since_id != 0 || $max_id != 0 || (!is_null($since) && $since > 0) ||
             ($offset + $limit) > NOTICE_CACHE_WINDOW) {
             return call_user_func_array($fn, array_merge($args, array($offset, $limit, $since_id,
-                                                                      $before_id, $since, $tag)));
+                                                                      $max_id, $since)));
         }
 
         $idkey = common_cache_key($cachekey);
index dec14b0d18897cf0fcb40ddf8e44305929ca5103..8a27e174785101b9b52b6e3bf9211b3867e00ab2 100644 (file)
@@ -43,15 +43,15 @@ class Notice_inbox extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    function stream($user_id, $offset, $limit, $since_id, $before_id, $since)
+    function stream($user_id, $offset, $limit, $since_id, $max_id, $since)
     {
         return Notice::stream(array('Notice_inbox', '_streamDirect'),
                               array($user_id),
                               'notice_inbox:by_user:'.$user_id,
-                              $offset, $limit, $since_id, $before_id, $since);
+                              $offset, $limit, $since_id, $max_id, $since);
     }
 
-    function _streamDirect($user_id, $offset, $limit, $since_id, $before_id, $since)
+    function _streamDirect($user_id, $offset, $limit, $since_id, $max_id, $since)
     {
         $inbox = new Notice_inbox();
 
@@ -61,8 +61,8 @@ class Notice_inbox extends Memcached_DataObject
             $inbox->whereAdd('notice_id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $inbox->whereAdd('notice_id < ' . $before_id);
+        if ($max_id != 0) {
+            $inbox->whereAdd('notice_id <= ' . $max_id);
         }
 
         if (!is_null($since)) {
index e5b7722430b99be54a6fe2927b13275f86f44df0..758a6659473ec491bd625758ac697090e74aa9b8 100644 (file)
@@ -46,7 +46,7 @@ class Notice_tag extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
-    function _streamDirect($tag, $offset, $limit, $since_id, $before_id, $since)
+    function _streamDirect($tag, $offset, $limit, $since_id, $max_id, $since)
     {
         $nt = new Notice_tag();
 
@@ -59,8 +59,8 @@ class Notice_tag extends Memcached_DataObject
             $nt->whereAdd('notice_id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $nt->whereAdd('notice_id < ' . $before_id);
+        if ($max_id != 0) {
+            $nt->whereAdd('notice_id < ' . $max_id);
         }
 
         if (!is_null($since)) {
index afc0ea4f74557b29824ef266a54be6fd59d81202..4a459b9740e4055b5a62c9349ba689b9dc9598fd 100644 (file)
@@ -170,7 +170,7 @@ class Profile extends Memcached_DataObject
         $ids = Notice::stream(array($this, '_streamDirect'),
                               array(),
                               'profile:notice_ids:' . $this->id,
-                              $offset, $limit, $since_id, $before_id, $since);
+                              $offset, $limit, $since_id, $max_id, $since);
 
         return Notice::getStreamByIds($ids);
     }
@@ -225,8 +225,8 @@ class Profile extends Memcached_DataObject
             $notice->whereAdd('id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $notice->whereAdd('id < ' . $before_id);
+        if ($max_id != 0) {
+            $notice->whereAdd('id <= ' . $max_id);
         }
 
         if (!is_null($since)) {
index 4439053b444934f05fb69fa9b4d5b396700f532e..49b1e05e517e1bd34bb7645cead3148990f27c0b 100644 (file)
@@ -22,16 +22,16 @@ class Reply extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function stream($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
     {
         $ids = Notice::stream(array('Reply', '_streamDirect'),
                               array($user_id),
                               'reply:stream:' . $user_id,
-                              $offset, $limit, $since_id, $before_id, $since);
+                              $offset, $limit, $since_id, $max_id, $since);
         return $ids;
     }
 
-    function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
+    function _streamDirect($user_id, $offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $max_id=0, $since=null)
     {
         $reply = new Reply();
         $reply->profile_id = $user_id;
@@ -40,8 +40,8 @@ class Reply extends Memcached_DataObject
             $reply->whereAdd('notice_id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $reply->whereAdd('notice_id < ' . $before_id);
+        if ($max_id != 0) {
+            $reply->whereAdd('notice_id < ' . $max_id);
         }
 
         if (!is_null($since)) {
index 8cc8285f1eb0222fb0365844248be12224193e0b..c7eede94e19e81c4f6231744f23c085e104f37e7 100644 (file)
@@ -402,7 +402,6 @@ class User extends Memcached_DataObject
     function getReplies($offset=0, $limit=NOTICES_PER_PAGE, $since_id=0, $before_id=0, $since=null)
     {
         $ids = Reply::stream($this->id, $offset, $limit, $since_id, $before_id, $since);
-        common_debug("Ids = " . implode(',', $ids));
         return Notice::getStreamByIds($ids);
     }
 
index 7cc31e7026d0aeb693d481c1a7c820b3807f17dc..a135015baca3a1f2d67601ebb9ae6f03d4c5dd0f 100644 (file)
@@ -58,7 +58,7 @@ class User_group extends Memcached_DataObject
         return Notice::getStreamByIds($ids);
     }
 
-    function _streamDirect($offset, $limit, $since_id, $before_id, $since)
+    function _streamDirect($offset, $limit, $since_id, $max_id, $since)
     {
         $inbox = new Group_inbox();
 
@@ -71,8 +71,8 @@ class User_group extends Memcached_DataObject
             $inbox->whereAdd('notice_id > ' . $since_id);
         }
 
-        if ($before_id != 0) {
-            $inbox->whereAdd('notice_id < ' . $before_id);
+        if ($max_id != 0) {
+            $inbox->whereAdd('notice_id <= ' . $max_id);
         }
 
         if (!is_null($since)) {
index 282826a7fb075957c32098c8753e0c277d88c911..636f4cf8e22255322c0452e154727f1ff3cb0f41 100644 (file)
@@ -215,3 +215,11 @@ $config['sphinx']['port'] = 3312;
 // $config['snapshot']['run'] = 'never';
 // If you want to report statistics in a cron job instead.
 // $config['snapshot']['run'] = 'cron';
+
+// Support for file uploads (attachments),
+// select supported mimetypes and quotas (in bytes)
+// $config['attachments']['supported'] = array('image/png', 'application/ogg');
+// $config['attachments']['file_quota'] = 5000000;
+// $config['attachments']['user_quota'] = 50000000;
+// $config['attachments']['monthly_quota'] = 15000000;
+
index 2ad482abacbc80d22ea1d327a5932233ae644dc9..2ca084e48df19b368c23596cb82fd1602732226e 100644 (file)
@@ -289,7 +289,7 @@ create table foreign_user (
 
 create table foreign_link (
      user_id int comment 'link to user on this system, if exists' references user (id),
-     foreign_id int comment 'link ' references foreign_user(id),
+     foreign_id bigint unsigned comment 'link to user on foreign service, if exists' references foreign_user(id),
      service int not null comment 'foreign key to service' references foreign_service(id),
      credentials varchar(255) comment 'authc credentials, typically a password',
      noticesync tinyint not null default 1 comment 'notice synchronization, bit 1 = sync outgoing, bit 2 = sync incoming, bit 3 = filter local replies',
index f351bb0668e2f80ead363eb4aeb8363f655a2148..983ea915023ded6aeaae99a9049cfde4ec29b05e 100644 (file)
@@ -2,13 +2,18 @@ INSERT INTO notice_source
     (code, name, url, created)
 VALUES
     ('adium', 'Adium', 'http://www.adiumx.com/', now()),
+    ('Afficheur', 'Afficheur', 'http://afficheur.sourceforge.jp/', now()),
+    ('AgentSolo.com','AgentSolo.com','http://www.agentsolo.com/', now()),
+    ('anyio', 'Any.IO', 'http://any.io/', now()),
     ('betwittered','BeTwittered','http://www.32hours.com/betwitteredinfo/', now()),
     ('bti','bti','http://gregkh.github.com/bti/', now()),
     ('cliqset', 'Cliqset', 'http://www.cliqset.com/', now()),
     ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()),
     ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()),
+    ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()),
     ('Facebook','Facebook','http://apps.facebook.com/identica/', now()),
     ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()),
+    ('get2gnow', 'get2gnow', 'http://uberchicgeekchick.com/?projects=get2gnow', now()),
     ('gravity', 'Gravity', 'http://mobileways.de/gravity', now()),
     ('Gwibber','Gwibber','http://launchpad.net/gwibber', now()),
     ('HelloTxt','HelloTxt','http://hellotxt.com/', now()),
@@ -28,6 +33,7 @@ VALUES
     ('pingvine','PingVine','http://pingvine.com/', now()),
     ('pocketwit','PockeTwit','http://code.google.com/p/pocketwit/', now()),
     ('posty','Posty','http://spreadingfunkyness.com/posty/', now()),
+    ('qtwitter','qTwitter','http://qtwitter.ayoy.net/', now()),
     ('royalewithcheese','Royale With Cheese','http://p.hellyeah.org/', now()),
     ('rssdent','rssdent','http://github.com/zcopley/rssdent/tree/master', now()),
     ('rygh.no','rygh.no','http://rygh.no/', now()),
diff --git a/extlib/MIME/Type.php b/extlib/MIME/Type.php
new file mode 100644 (file)
index 0000000..c335f8d
--- /dev/null
@@ -0,0 +1,523 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002, 2008 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/3_0.txt.                                  |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Ian Eure <ieure@php.net>                                    |
+// +----------------------------------------------------------------------+
+//
+// $Id: Type.php,v 1.6 2009/01/16 11:49:45 cweiske Exp $
+
+require_once 'PEAR.php';
+
+$_fileCmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+$_fileCmd = 'file';
+
+/**
+ * Class for working with MIME types
+ *
+ * @category MIME
+ * @package  MIME_Type
+ * @license  PHP License 3.0
+ * @version  1.2.0
+ * @link     http://pear.php.net/package/MIME_Type
+ * @author   Ian Eure <ieure@php.net>
+ */
+class MIME_Type
+{
+    /**
+     * The MIME media type
+     *
+     * @var string
+     */
+    var $media = '';
+
+    /**
+     * The MIME media sub-type
+     *
+     * @var string
+     */
+    var $subType = '';
+
+    /**
+     * Optional MIME parameters
+     *
+     * @var array
+     */
+    var $parameters = array();
+
+    /**
+     * List of valid media types.
+     * A media type is the string in front of the slash.
+     * The media type of "text/xml" would be "text".
+     *
+     * @var array
+     */
+    var $validMediaTypes = array(
+        'text',
+        'image',
+        'audio',
+        'video',
+        'application',
+        'multipart',
+        'message'
+    );
+
+
+    /**
+     * Constructor.
+     *
+     * If $type is set, if will be parsed and the appropriate class vars set.
+     * If not, you get an empty class.
+     * This is useful, but not quite as useful as parsing a type.
+     *
+     * @param string $type MIME type
+     *
+     * @return void
+     */
+    function MIME_Type($type = false)
+    {
+        if ($type) {
+            $this->parse($type);
+        }
+    }
+
+
+    /**
+     * Parse a mime-type and set the class variables.
+     *
+     * @param string $type MIME type to parse
+     *
+     * @return void
+     */
+    function parse($type)
+    {
+        $this->media      = $this->getMedia($type);
+        $this->subType    = $this->getSubType($type);
+        $this->parameters = array();
+        if (MIME_Type::hasParameters($type)) {
+            require_once 'MIME/Type/Parameter.php';
+            foreach (MIME_Type::getParameters($type) as $param) {
+                $param = new MIME_Type_Parameter($param);
+                $this->parameters[$param->name] = $param;
+            }
+        }
+    }
+
+
+    /**
+     * Does this type have any parameters?
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type has parameters, false otherwise
+     * @static
+     */
+    function hasParameters($type)
+    {
+        if (strstr($type, ';')) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Get a MIME type's parameters
+     *
+     * @param string $type MIME type to get parameters of
+     *
+     * @return array $type's parameters
+     * @static
+     */
+    function getParameters($type)
+    {
+        $params = array();
+        $tmp    = explode(';', $type);
+        for ($i = 1; $i < count($tmp); $i++) {
+            $params[] = trim($tmp[$i]);
+        }
+        return $params;
+    }
+
+
+    /**
+     * Strip parameters from a MIME type string.
+     *
+     * @param string $type MIME type string
+     *
+     * @return string MIME type with parameters removed
+     * @static
+     */
+    function stripParameters($type)
+    {
+        if (strstr($type, ';')) {
+            return substr($type, 0, strpos($type, ';'));
+        }
+        return $type;
+    }
+
+
+    /**
+     * Removes comments from a media type, subtype or parameter.
+     *
+     * @param string $string   String to strip comments from
+     * @param string &$comment Comment is stored in there.
+     *
+     * @return string   String without comments
+     * @static
+     */
+    function stripComments($string, &$comment)
+    {
+        if (strpos($string, '(') === false) {
+            return $string;
+        }
+
+        $inquote   = false;
+        $quoting   = false;
+        $incomment = 0;
+        $newstring = '';
+
+        for ($n = 0; $n < strlen($string); $n++) {
+            if ($quoting) {
+                if ($incomment == 0) {
+                    $newstring .= $string[$n];
+                } else if ($comment !== null) {
+                    $comment .= $string[$n];
+                }
+                $quoting = false;
+            } else if ($string[$n] == '\\') {
+                $quoting = true;
+            } else if (!$inquote && $incomment > 0 && $string[$n] == ')') {
+                $incomment--;
+                if ($incomment == 0 && $comment !== null) {
+                    $comment .= ' ';
+                }
+            } else if (!$inquote && $string[$n] == '(') {
+                $incomment++;
+            } else if ($string[$n] == '"') {
+                if ($inquote) {
+                    $inquote = false;
+                } else {
+                    $inquote = true;
+                }
+            } else if ($incomment == 0) {
+                $newstring .= $string[$n];
+            } else if ($comment !== null) {
+                $comment .= $string[$n];
+            }
+        }
+
+        if ($comment !== null) {
+            $comment = trim($comment);
+        }
+
+        return $newstring;
+    }
+
+
+    /**
+     * Get a MIME type's media
+     *
+     * @note 'media' refers to the portion before the first slash
+     *
+     * @param string $type MIME type to get media of
+     *
+     * @return string $type's media
+     * @static
+     */
+    function getMedia($type)
+    {
+        $tmp = explode('/', $type);
+        return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
+    }
+
+
+    /**
+     * Get a MIME type's subtype
+     *
+     * @param string $type MIME type to get subtype of
+     *
+     * @return string $type's subtype, null if invalid mime type
+     * @static
+     */
+    function getSubType($type)
+    {
+        $tmp = explode('/', $type);
+        if (!isset($tmp[1])) {
+            return null;
+        }
+        $tmp = explode(';', $tmp[1]);
+        return strtolower(trim(MIME_Type::stripComments($tmp[0], $null)));
+    }
+
+
+    /**
+     * Create a textual MIME type from object values
+     *
+     * This function performs the opposite function of parse().
+     *
+     * @return string MIME type string
+     */
+    function get()
+    {
+        $type = strtolower($this->media . '/' . $this->subType);
+        if (count($this->parameters)) {
+            foreach ($this->parameters as $key => $null) {
+                $type .= '; ' . $this->parameters[$key]->get();
+            }
+        }
+        return $type;
+    }
+
+
+    /**
+     * Is this type experimental?
+     *
+     * @note Experimental types are denoted by a leading 'x-' in the media or
+     *       subtype, e.g. text/x-vcard or x-world/x-vrml.
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type is experimental, false otherwise
+     * @static
+     */
+    function isExperimental($type)
+    {
+        if (substr(MIME_Type::getMedia($type), 0, 2) == 'x-' ||
+            substr(MIME_Type::getSubType($type), 0, 2) == 'x-') {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Is this a vendor MIME type?
+     *
+     * @note Vendor types are denoted with a leading 'vnd. in the subtype.
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type is a vendor type, false otherwise
+     * @static
+     */
+    function isVendor($type)
+    {
+        if (substr(MIME_Type::getSubType($type), 0, 4) == 'vnd.') {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Is this a wildcard type?
+     *
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if $type is a wildcard, false otherwise
+     * @static
+     */
+    function isWildcard($type)
+    {
+        if ($type == '*/*' || MIME_Type::getSubtype($type) == '*') {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Perform a wildcard match on a MIME type
+     *
+     * Example:
+     * MIME_Type::wildcardMatch('image/*', 'image/png')
+     *
+     * @param string $card Wildcard to check against
+     * @param string $type MIME type to check
+     *
+     * @return boolean true if there was a match, false otherwise
+     * @static
+     */
+    function wildcardMatch($card, $type)
+    {
+        if (!MIME_Type::isWildcard($card)) {
+            return false;
+        }
+
+        if ($card == '*/*') {
+            return true;
+        }
+
+        if (MIME_Type::getMedia($card) == MIME_Type::getMedia($type)) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Add a parameter to this type
+     *
+     * @param string $name    Attribute name
+     * @param string $value   Attribute value
+     * @param string $comment Comment for this parameter
+     *
+     * @return void
+     */
+    function addParameter($name, $value, $comment = false)
+    {
+        $tmp = new MIME_Type_Parameter();
+
+        $tmp->name               = $name;
+        $tmp->value              = $value;
+        $tmp->comment            = $comment;
+        $this->parameters[$name] = $tmp;
+    }
+
+
+    /**
+     * Remove a parameter from this type
+     *
+     * @param string $name Parameter name
+     *
+     * @return void
+     */
+    function removeParameter($name)
+    {
+        unset($this->parameters[$name]);
+    }
+
+
+    /**
+     * Autodetect a file's MIME-type
+     *
+     * This function may be called staticly.
+     *
+     * @internal Tries to use fileinfo extension at first. If that
+     *  does not work, mime_magic is used. If this is also not available
+     *  or does not succeed, "file" command is tried to be executed with
+     *  System_Command. When that fails, too, then we use our in-built
+     *  extension-to-mimetype-mapping list.
+     *
+     * @param string $file   Path to the file to get the type of
+     * @param bool   $params Append MIME parameters if true
+     *
+     * @return string $file's MIME-type on success, PEAR_Error otherwise
+     *
+     * @since 1.0.0beta1
+     * @static
+     */
+    function autoDetect($file, $params = false)
+    {
+        // Sanity checks
+        if (!file_exists($file)) {
+            return PEAR::raiseError("File \"$file\" doesn't exist");
+        }
+
+        if (!is_readable($file)) {
+            return PEAR::raiseError("File \"$file\" is not readable");
+        }
+
+        if (function_exists('finfo_file')) {
+            $finfo = finfo_open(FILEINFO_MIME);
+            $type  = finfo_file($finfo, $file);
+            finfo_close($finfo);
+            if ($type !== false && $type !== '') {
+                return MIME_Type::_handleDetection($type, $params);
+            }
+        }
+
+        if (function_exists('mime_content_type')) {
+            $type = mime_content_type($file);
+            if ($type !== false && $type !== '') {
+                return MIME_Type::_handleDetection($type, $params);
+            }
+        }
+
+        @include_once 'System/Command.php';
+        if (class_exists('System_Command')) {
+            return MIME_Type::_handleDetection(
+                MIME_Type::_fileAutoDetect($file),
+                $params
+            );
+        }
+
+        require_once 'MIME/Type/Extension.php';
+        $mte = new MIME_Type_Extension();
+        return $mte->getMIMEType($file);
+    }
+
+
+    /**
+     * Handles a detected MIME type and modifies it if necessary.
+     *
+     * @param string $type   MIME Type of a file
+     * @param bool   $params Append MIME parameters if true
+     *
+     * @return string $file's MIME-type on success, PEAR_Error otherwise
+     */
+    function _handleDetection($type, $params)
+    {
+        // _fileAutoDetect() may have returned an error.
+        if (PEAR::isError($type)) {
+            return $type;
+        }
+
+        // Don't return an empty string
+        if (!$type || !strlen($type)) {
+            return PEAR::raiseError("Sorry, couldn't determine file type.");
+        }
+
+        // Strip parameters if present & requested
+        if (MIME_Type::hasParameters($type) && !$params) {
+            $type = MIME_Type::stripParameters($type);
+        }
+
+        return $type;
+    }
+
+
+    /**
+     * Autodetect a file's MIME-type with 'file' and System_Command
+     *
+     * This function may be called staticly.
+     *
+     * @param string $file Path to the file to get the type of
+     *
+     * @return string $file's MIME-type
+     *
+     * @since 1.0.0beta1
+     * @static
+     */
+    function _fileAutoDetect($file)
+    {
+        $cmd = new System_Command();
+
+        // Make sure we have the 'file' command.
+        $fileCmd = PEAR::getStaticProperty('MIME_Type', 'fileCmd');
+        if (!$cmd->which($fileCmd)) {
+            unset($cmd);
+            return PEAR::raiseError("Can't find file command \"{$fileCmd}\"");
+        }
+
+        $cmd->pushCommand($fileCmd, "-bi " . escapeshellarg($file));
+        $res = $cmd->execute();
+        unset($cmd);
+
+        return $res;
+    }
+}
+
diff --git a/extlib/MIME/Type/Extension.php b/extlib/MIME/Type/Extension.php
new file mode 100644 (file)
index 0000000..1987e2a
--- /dev/null
@@ -0,0 +1,298 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP versions 4 and 5                                                 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2009 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/3_0.txt.                                  |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Christian Schmidt <schmidt@php.net>                         |
+// +----------------------------------------------------------------------+
+//
+// $Id: Extension.php,v 1.1 2009/01/16 11:49:45 cweiske Exp $
+
+require_once 'PEAR.php';
+
+/**
+ * Class for mapping file extensions to MIME types.
+ *
+ * @category MIME
+ * @package  MIME_Type
+ * @author   Christian Schmidt <schmidt@php.net>
+ * @license  PHP License 3.0
+ * @version  1.2.0
+ * @link     http://pear.php.net/package/MIME_Type
+ */
+class MIME_Type_Extension
+{
+    /**
+     * Mapping between file extension and MIME type.
+     *
+     * @internal The array is sorted alphabetically by value and with primary
+     *  extension first. Be careful about not adding duplicate keys - PHP
+     *  silently ignores duplicates. The following command can be used for
+     *  checking for duplicates:
+     *    grep "=> '" Extension.php | cut -d\' -f2 | sort | uniq -d
+     *  application/octet-stream is generally used as fallback when no other
+     *  MIME-type can be found, but the array does not contain a lot of such
+     *  unknown extension. One entry exists, though, to allow detection of
+     *  file extension for this MIME-type.
+     *
+     * @var array
+     */
+    var $extensionToType = array (
+        'ez'        => 'application/andrew-inset',
+        'atom'      => 'application/atom+xml',
+        'jar'       => 'application/java-archive',
+        'hqx'       => 'application/mac-binhex40',
+        'cpt'       => 'application/mac-compactpro',
+        'mathml'    => 'application/mathml+xml',
+        'doc'       => 'application/msword',
+        'dat'       => 'application/octet-stream',
+        'oda'       => 'application/oda',
+        'ogg'       => 'application/ogg',
+        'pdf'       => 'application/pdf',
+        'ai'        => 'application/postscript',
+        'eps'       => 'application/postscript',
+        'ps'        => 'application/postscript',
+        'rdf'       => 'application/rdf+xml',
+        'rss'       => 'application/rss+xml',
+        'smi'       => 'application/smil',
+        'smil'      => 'application/smil',
+        'gram'      => 'application/srgs',
+        'grxml'     => 'application/srgs+xml',
+        'kml'       => 'application/vnd.google-earth.kml+xml',
+        'kmz'       => 'application/vnd.google-earth.kmz',
+        'mif'       => 'application/vnd.mif',
+        'xul'       => 'application/vnd.mozilla.xul+xml',
+        'xls'       => 'application/vnd.ms-excel',
+        'xlb'       => 'application/vnd.ms-excel',
+        'xlt'       => 'application/vnd.ms-excel',
+        'xlam'      => 'application/vnd.ms-excel.addin.macroEnabled.12',
+        'xlsb'      => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+        'xlsm'      => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+        'xltm'      => 'application/vnd.ms-excel.template.macroEnabled.12',
+        'docm'      => 'application/vnd.ms-word.document.macroEnabled.12',
+        'dotm'      => 'application/vnd.ms-word.template.macroEnabled.12',
+        'ppam'      => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+        'pptm'      => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+        'ppsm'      => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+        'potm'      => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+        'ppt'       => 'application/vnd.ms-powerpoint',
+        'pps'       => 'application/vnd.ms-powerpoint',
+        'odc'       => 'application/vnd.oasis.opendocument.chart',
+        'odb'       => 'application/vnd.oasis.opendocument.database',
+        'odf'       => 'application/vnd.oasis.opendocument.formula',
+        'odg'       => 'application/vnd.oasis.opendocument.graphics',
+        'otg'       => 'application/vnd.oasis.opendocument.graphics-template',
+        'odi'       => 'application/vnd.oasis.opendocument.image',
+        'odp'       => 'application/vnd.oasis.opendocument.presentation',
+        'otp'       => 'application/vnd.oasis.opendocument.presentation-template',
+        'ods'       => 'application/vnd.oasis.opendocument.spreadsheet',
+        'ots'       => 'application/vnd.oasis.opendocument.spreadsheet-template',
+        'odt'       => 'application/vnd.oasis.opendocument.text',
+        'odm'       => 'application/vnd.oasis.opendocument.text-master',
+        'ott'       => 'application/vnd.oasis.opendocument.text-template',
+        'oth'       => 'application/vnd.oasis.opendocument.text-web',
+        'potx'      => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+        'ppsx'      => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+        'pptx'      => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+        'xlsx'      => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+        'xltx'      => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+        'docx'      => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+        'dotx'      => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+        'vsd'       => 'application/vnd.visio',
+        'wbxml'     => 'application/vnd.wap.wbxml',
+        'wmlc'      => 'application/vnd.wap.wmlc',
+        'wmlsc'     => 'application/vnd.wap.wmlscriptc',
+        'vxml'      => 'application/voicexml+xml',
+        'bcpio'     => 'application/x-bcpio',
+        'vcd'       => 'application/x-cdlink',
+        'pgn'       => 'application/x-chess-pgn',
+        'cpio'      => 'application/x-cpio',
+        'csh'       => 'application/x-csh',
+        'dcr'       => 'application/x-director',
+        'dir'       => 'application/x-director',
+        'dxr'       => 'application/x-director',
+        'dvi'       => 'application/x-dvi',
+        'spl'       => 'application/x-futuresplash',
+        'tgz'       => 'application/x-gtar',
+        'gtar'      => 'application/x-gtar',
+        'hdf'       => 'application/x-hdf',
+        'js'        => 'application/x-javascript',
+        'skp'       => 'application/x-koan',
+        'skd'       => 'application/x-koan',
+        'skt'       => 'application/x-koan',
+        'skm'       => 'application/x-koan',
+        'latex'     => 'application/x-latex',
+        'nc'        => 'application/x-netcdf',
+        'cdf'       => 'application/x-netcdf',
+        'sh'        => 'application/x-sh',
+        'shar'      => 'application/x-shar',
+        'swf'       => 'application/x-shockwave-flash',
+        'sit'       => 'application/x-stuffit',
+        'sv4cpio'   => 'application/x-sv4cpio',
+        'sv4crc'    => 'application/x-sv4crc',
+        'tar'       => 'application/x-tar',
+        'tcl'       => 'application/x-tcl',
+        'tex'       => 'application/x-tex',
+        'texinfo'   => 'application/x-texinfo',
+        'texi'      => 'application/x-texinfo',
+        't'         => 'application/x-troff',
+        'tr'        => 'application/x-troff',
+        'roff'      => 'application/x-troff',
+        'man'       => 'application/x-troff-man',
+        'me'        => 'application/x-troff-me',
+        'ms'        => 'application/x-troff-ms',
+        'ustar'     => 'application/x-ustar',
+        'src'       => 'application/x-wais-source',
+        'xhtml'     => 'application/xhtml+xml',
+        'xht'       => 'application/xhtml+xml',
+        'xslt'      => 'application/xslt+xml',
+        'xml'       => 'application/xml',
+        'xsl'       => 'application/xml',
+        'dtd'       => 'application/xml-dtd',
+        'zip'       => 'application/zip',
+        'au'        => 'audio/basic',
+        'snd'       => 'audio/basic',
+        'mid'       => 'audio/midi',
+        'midi'      => 'audio/midi',
+        'kar'       => 'audio/midi',
+        'mpga'      => 'audio/mpeg',
+        'mp2'       => 'audio/mpeg',
+        'mp3'       => 'audio/mpeg',
+        'aif'       => 'audio/x-aiff',
+        'aiff'      => 'audio/x-aiff',
+        'aifc'      => 'audio/x-aiff',
+        'm3u'       => 'audio/x-mpegurl',
+        'wma'       => 'audio/x-ms-wma',
+        'wax'       => 'audio/x-ms-wax',
+        'ram'       => 'audio/x-pn-realaudio',
+        'ra'        => 'audio/x-pn-realaudio',
+        'rm'        => 'application/vnd.rn-realmedia',
+        'wav'       => 'audio/x-wav',
+        'pdb'       => 'chemical/x-pdb',
+        'xyz'       => 'chemical/x-xyz',
+        'bmp'       => 'image/bmp',
+        'cgm'       => 'image/cgm',
+        'gif'       => 'image/gif',
+        'ief'       => 'image/ief',
+        'jpeg'      => 'image/jpeg',
+        'jpg'       => 'image/jpeg',
+        'jpe'       => 'image/jpeg',
+        'png'       => 'image/png',
+        'svg'       => 'image/svg+xml',
+        'tiff'      => 'image/tiff',
+        'tif'       => 'image/tiff',
+        'djvu'      => 'image/vnd.djvu',
+        'djv'       => 'image/vnd.djvu',
+        'wbmp'      => 'image/vnd.wap.wbmp',
+        'ras'       => 'image/x-cmu-raster',
+        'ico'       => 'image/x-icon',
+        'pnm'       => 'image/x-portable-anymap',
+        'pbm'       => 'image/x-portable-bitmap',
+        'pgm'       => 'image/x-portable-graymap',
+        'ppm'       => 'image/x-portable-pixmap',
+        'rgb'       => 'image/x-rgb',
+        'xbm'       => 'image/x-xbitmap',
+        'psd'       => 'image/x-photoshop',
+        'xpm'       => 'image/x-xpixmap',
+        'xwd'       => 'image/x-xwindowdump',
+        'eml'       => 'message/rfc822',
+        'igs'       => 'model/iges',
+        'iges'      => 'model/iges',
+        'msh'       => 'model/mesh',
+        'mesh'      => 'model/mesh',
+        'silo'      => 'model/mesh',
+        'wrl'       => 'model/vrml',
+        'vrml'      => 'model/vrml',
+        'ics'       => 'text/calendar',
+        'ifb'       => 'text/calendar',
+        'css'       => 'text/css',
+        'csv'       => 'text/csv',
+        'html'      => 'text/html',
+        'htm'       => 'text/html',
+        'txt'       => 'text/plain',
+        'asc'       => 'text/plain',
+        'rtx'       => 'text/richtext',
+        'rtf'       => 'text/rtf',
+        'sgml'      => 'text/sgml',
+        'sgm'       => 'text/sgml',
+        'tsv'       => 'text/tab-separated-values',
+        'wml'       => 'text/vnd.wap.wml',
+        'wmls'      => 'text/vnd.wap.wmlscript',
+        'etx'       => 'text/x-setext',
+        'mpeg'      => 'video/mpeg',
+        'mpg'       => 'video/mpeg',
+        'mpe'       => 'video/mpeg',
+        'qt'        => 'video/quicktime',
+        'mov'       => 'video/quicktime',
+        'mxu'       => 'video/vnd.mpegurl',
+        'm4u'       => 'video/vnd.mpegurl',
+        'flv'       => 'video/x-flv',
+        'asf'       => 'video/x-ms-asf',
+        'asx'       => 'video/x-ms-asf',
+        'wmv'       => 'video/x-ms-wmv',
+        'wm'        => 'video/x-ms-wm',
+        'wmx'       => 'video/x-ms-wmx',
+        'avi'       => 'video/x-msvideo',
+        'ogv'       => 'video/ogg',
+        'movie'     => 'video/x-sgi-movie',
+        'ice'       => 'x-conference/x-cooltalk',
+    );
+
+
+
+    /**
+     * Autodetect a file's MIME-type.
+     *
+     * @param string $file Path to the file to get the type of
+     *
+     * @return string $file's MIME-type on success, PEAR_Error otherwise
+     */
+    function getMIMEType($file)
+    {
+        $extension = substr(strrchr($file, '.'), 1);
+        if ($extension === false) {
+            return PEAR::raiseError("File has no extension.");
+        }
+
+        if (!isset($this->extensionToType[$extension])) {
+            return PEAR::raiseError("Sorry, couldn't determine file type.");
+        }
+
+        return $this->extensionToType[$extension];
+    }
+
+
+
+    /**
+     * Return default MIME-type for the specified extension.
+     *
+     * @param string $type MIME-type
+     *
+     * @return string A file extension without leading period.
+     */
+    function getExtension($type)
+    {
+        require_once 'MIME/Type.php';
+        // Strip parameters and comments.
+        $type = MIME_Type::getMedia($type) . '/' . MIME_Type::getSubType($type);
+
+        $extension = array_search($type, $this->extensionToType);
+        if ($extension === false) {
+            return PEAR::raiseError("Sorry, couldn't determine extension.");
+        }
+        return $extension;
+    }
+
+}
+
+?>
\ No newline at end of file
diff --git a/extlib/MIME/Type/Parameter.php b/extlib/MIME/Type/Parameter.php
new file mode 100644 (file)
index 0000000..399d3dd
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP version 4                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2002 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available at through the world-wide-web at                           |
+// | http://www.php.net/license/3_0.txt.                                  |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Authors: Ian Eure <ieure@php.net>                                    |
+// +----------------------------------------------------------------------+
+//
+// $Id: Parameter.php,v 1.1 2007/03/25 10:10:21 cweiske Exp $
+
+/**
+ * Class for working with MIME type parameters
+ *
+ * @version 1.2.0
+ * @package MIME_Type
+ * @author Ian Eure <ieure@php.net>
+ */
+class MIME_Type_Parameter {
+    /**
+     * Parameter name
+     *
+     * @var string
+     */
+    var $name;
+
+    /**
+     * Parameter value
+     *
+     * @var string
+     */
+    var $value;
+
+    /**
+     * Parameter comment
+     *
+     * @var string
+     */
+    var $comment;
+
+
+    /**
+     * Constructor.
+     *
+     * @param  string $param MIME parameter to parse, if set.
+     * @return void
+     */
+    function MIME_Type_Parameter($param = false)
+    {
+        if ($param) {
+            $this->parse($param);
+        }
+    }
+
+
+    /**
+     * Parse a MIME type parameter and set object fields
+     *
+     * @param  string $param MIME type parameter to parse
+     * @return void
+     */
+    function parse($param)
+    {
+        $comment = '';
+        $param   = MIME_Type::stripComments($param, $comment);
+        $this->name    = $this->getAttribute($param);
+        $this->value   = $this->getValue($param);
+        $this->comment = $comment;
+    }
+
+
+    /**
+     * Get a parameter attribute (e.g. name)
+     *
+     * @param  string MIME type parameter
+     * @return string Attribute name
+     * @static
+     */
+    function getAttribute($param)
+    {
+        $tmp = explode('=', $param);
+        return trim($tmp[0]);
+    }
+
+
+    /**
+     * Get a parameter value
+     *
+     * @param  string $param MIME type parameter
+     * @return string Value
+     * @static
+     */
+    function getValue($param)
+    {
+        $tmp = explode('=', $param, 2);
+        $value = $tmp[1];
+        $value = trim($value);
+        if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
+            $value = substr($value, 1, -1);
+        }
+        $value = str_replace('\\"', '"', $value);
+        return $value;
+    }
+
+
+    /**
+     * Get a parameter comment
+     *
+     * @param  string $param MIME type parameter
+     * @return string Parameter comment
+     * @see getComment()
+     * @static
+     */
+    function getComment($param)
+    {
+        $cs = strpos($param, '(');
+        $comment = substr($param, $cs);
+        return trim($comment, '() ');
+    }
+
+
+    /**
+     * Does this parameter have a comment?
+     *
+     * @param  string  $param MIME type parameter
+     * @return boolean true if $param has a comment, false otherwise
+     * @static
+     */
+    function hasComment($param)
+    {
+        if (strstr($param, '(')) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Get a string representation of this parameter
+     *
+     * This function performs the oppsite of parse()
+     *
+     * @return string String representation of parameter
+     */
+    function get()
+    {
+        $val = $this->name . '="' . str_replace('"', '\\"', $this->value) . '"';
+        if ($this->comment) {
+            $val .= ' (' . $this->comment . ')';
+        }
+        return $val;
+    }
+}
+?>
\ No newline at end of file
index cb8b5a6609a9c43b3804e4e90d44d5bddafbb249..936b847abe7bedc9ae44cf68cc75a48b1b94bf5c 100644 (file)
@@ -157,7 +157,7 @@ $.fn.ajaxSubmit = function(options) {
     function fileUpload() {\r
         var form = $form[0];\r
         \r
-        if ($(':input[@name=submit]', form).length) {\r
+        if ($(':input[name=submit]', form).length) {\r
             alert('Error: Form elements must not be named "submit".');\r
             return;\r
         }\r
@@ -570,7 +570,7 @@ $.fn.clearForm = function() {
 $.fn.clearFields = $.fn.clearInputs = function() {\r
     return this.each(function() {\r
         var t = this.type, tag = this.tagName.toLowerCase();\r
-        if (t == 'text' || t == 'password' || tag == 'textarea')\r
+        if (t == 'file' || t == 'text' || t == 'password' || tag == 'textarea')\r
             this.value = '';\r
         else if (t == 'checkbox' || t == 'radio')\r
             this.checked = false;\r
index b1b6ec82bd93e21d268aa3129f33d5f617dc2780..b712ba8e2be9bd24a8962d8b2dce2f933d52d5ea 100644 (file)
  */
 
 $(document).ready(function(){
-    $('a.attachment').click(function() {$().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'}); return false; });
-    $("a.thumbnail").hover(
-        function() {
-            var anchor = $(this);
-            $("a.thumbnail").children('img').remove();
-
-            setTimeout(function() {
-                anchor.closest(".entry-title").addClass('ov');
-                $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
-                    anchor.append(data);
-                });
-            }, 250);
-
-            setTimeout(function() {
-                anchor.children('img').remove();
-                anchor.closest(".entry-title").removeClass('ov');
-            }, 3000);
-        },
-        function() {
-            $(this).children('img').remove();
-            $(this).closest(".entry-title").removeClass('ov');
-        }
-    );
-
        // count character on keyup
        function counter(event){
                var maxLength = 140;
@@ -227,6 +203,7 @@ $(document).ready(function(){
                                                          }
                                                                                                        }
                                                                                                        $("#notice_data-text").val("");
+                                                                                               $("#notice_data-attach").val("");
                                                     counter();
                                                                                                }
                                                                                                $("#form_notice").removeClass("processing");
@@ -238,6 +215,7 @@ $(document).ready(function(){
        $("#form_notice").each(addAjaxHidden);
     NoticeHover();
     NoticeReply();
+    NoticeAttachments();
 });
 
 
@@ -276,3 +254,53 @@ function NoticeReplySet(nick,id) {
        }
        return true;
 }
+
+function NoticeAttachments() {
+    $.fn.jOverlay.options = {
+        method : 'GET',
+        data : '',
+        url : '',
+        color : '#000',
+        opacity : '0.6',
+        zIndex : 99,
+        center : true,
+        imgLoading : $('address .url')[0].href+'theme/base/images/illustrations/illu_progress_loading-01.gif',
+        bgClickToClose : true,
+        success : function() {
+            $('#jOverlayContent').append('<button>&#215;</button>');
+            $('#jOverlayContent button').click($.closeOverlay);
+        },
+        timeout : 0
+    };
+
+    $('a.attachment').click(function() {
+        $().jOverlay({url: $('address .url')[0].href+'/attachment/' + ($(this).attr('id').substring('attachment'.length + 1)) + '/ajax'});
+        return false;
+    });
+    
+    var t;
+    $("body:not(#shownotice) a.thumbnail").hover(
+        function() {
+            var anchor = $(this);
+            $("a.thumbnail").children('img').hide();
+            anchor.closest(".entry-title").addClass('ov');
+
+            if (anchor.children('img').length == 0) {
+                t = setTimeout(function() {
+                    $.get($('address .url')[0].href+'/attachment/' + (anchor.attr('id').substring('attachment'.length + 1)) + '/thumbnail', null, function(data) {
+                        anchor.append(data);
+                    });
+                }, 500);
+            }
+            else {
+                anchor.children('img').show();
+            }
+        },
+        function() {
+            clearTimeout(t);
+            $("a.thumbnail").children('img').hide();
+            $(this).closest(".entry-title").removeClass('ov');
+        }
+    );
+}
+
index dcf59b1bc15e5468866c8634540405c7f7185767..e1726df2876e8f24fd19f855e85a39ea0a7836f4 100644 (file)
@@ -79,20 +79,21 @@ class AttachmentList extends Widget
 
     function show()
     {
-        $this->out->elementStart('dl', array('id' =>'attachment'));
+        $atts = new File;
+        $att = $atts->getAttachments($this->notice->id);
+        if (empty($att)) return 0;
+        $this->out->elementStart('dl', array('id' =>'attachments'));
         $this->out->element('dt', null, _('Attachments'));
         $this->out->elementStart('dd');
-        $this->out->elementStart('ul', array('class' => 'attachments'));
+        $this->out->elementStart('ol', 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('dd');
-        $this->out->elementEnd('ul');
+        $this->out->elementEnd('ol');
         $this->out->elementEnd('dl');
 
         return count($att);
@@ -266,6 +267,23 @@ class Attachment extends AttachmentListItem
                 case 'image/jpeg':
                     $this->out->element('img', array('src' => $this->attachment->url, 'alt' => 'alt'));
                     break;
+
+                case 'application/ogg':
+                case 'audio/x-speex':
+                case 'video/mpeg':
+                case 'audio/mpeg':
+                case 'video/mp4':
+                case 'video/quicktime':
+                    $arr  = array('type' => $this->attachment->mimetype,
+                        'data' => $this->attachment->url,
+                        'width' => 320,
+                        'height' => 240
+                    );
+                    $this->out->elementStart('object', $arr);
+                    $this->out->element('param', array('name' => 'src', 'value' => $this->attachment->url));
+                    $this->out->element('param', array('name' => 'autoStart', 'value' => 1));
+                    $this->out->elementEnd('object');
+                    break;
                 }
             }
         } else {
index 0ce46442deb99e3d928aed6bfa16c96b1a488401..5aafdfe0ee9568e6936833ab7d979f1037f0eaf5 100644 (file)
@@ -163,6 +163,42 @@ $config =
         array('run' => 'web',
               'frequency' => 10000,
               'reporturl' => 'http://laconi.ca/stats/report'),
+        'attachments' =>
+        array('supported' => array('image/png',
+            'image/jpeg',
+            'image/gif',
+            'image/svg+xml',
+            'audio/mpeg',
+            'audio/x-speex',
+            'application/ogg',
+            'application/pdf',
+            'application/vnd.oasis.opendocument.text',
+            'application/vnd.oasis.opendocument.text-template',
+            'application/vnd.oasis.opendocument.graphics',
+            'application/vnd.oasis.opendocument.graphics-template',
+            'application/vnd.oasis.opendocument.presentation',
+            'application/vnd.oasis.opendocument.presentation-template',
+            'application/vnd.oasis.opendocument.spreadsheet',
+            'application/vnd.oasis.opendocument.spreadsheet-template',
+            'application/vnd.oasis.opendocument.chart',
+            'application/vnd.oasis.opendocument.chart-template',
+            'application/vnd.oasis.opendocument.image',
+            'application/vnd.oasis.opendocument.image-template',
+            'application/vnd.oasis.opendocument.formula',
+            'application/vnd.oasis.opendocument.formula-template',
+            'application/vnd.oasis.opendocument.text-master',
+            'application/vnd.oasis.opendocument.text-web',
+            'application/x-zip',
+            'application/zip',
+            'text/plain',
+            'video/mpeg',
+            'video/mp4',
+            'video/quicktime',
+            'video/mpeg'),
+        'file_quota' => 5000000,
+        'user_quota' => 50000000,
+        'monthly_quota' => 15000000,
+        ),
         );
 
 $config['db'] = &PEAR::getStaticProperty('DB_DataObject','options');
@@ -174,6 +210,7 @@ $config['db'] =
         'require_prefix' => 'classes/',
         'class_prefix' => '',
         'mirror' => null,
+        'utf8' => true,
         'db_driver' => 'DB', # XXX: JanRain libs only work with DB
         'quote_identifiers' => false,
         'type' => 'mysql' );
@@ -223,19 +260,19 @@ if ($_db_name != 'laconica' && !array_key_exists('ini_'.$_db_name, $config['db']
 
 // XXX: how many of these could be auto-loaded on use?
 
-require_once('Validate.php');
-require_once('markdown.php');
+require_once 'Validate.php';
+require_once 'markdown.php';
 
-require_once(INSTALLDIR.'/lib/util.php');
-require_once(INSTALLDIR.'/lib/action.php');
-require_once(INSTALLDIR.'/lib/theme.php');
-require_once(INSTALLDIR.'/lib/mail.php');
-require_once(INSTALLDIR.'/lib/subs.php');
-require_once(INSTALLDIR.'/lib/Shorturl_api.php');
-require_once(INSTALLDIR.'/lib/twitter.php');
+require_once INSTALLDIR.'/lib/util.php';
+require_once INSTALLDIR.'/lib/action.php';
+require_once INSTALLDIR.'/lib/theme.php';
+require_once INSTALLDIR.'/lib/mail.php';
+require_once INSTALLDIR.'/lib/subs.php';
+require_once INSTALLDIR.'/lib/Shorturl_api.php';
+require_once INSTALLDIR.'/lib/twitter.php';
 
-require_once(INSTALLDIR.'/lib/clientexception.php');
-require_once(INSTALLDIR.'/lib/serverexception.php');
+require_once INSTALLDIR.'/lib/clientexception.php';
+require_once INSTALLDIR.'/lib/serverexception.php';
 
 // XXX: other formats here
 
index 637a6284d9f0b1cbbdd92b40f300b98303a1fd8f..a445750f7e00eee711f054a200c7ebc0dbd0f75e 100644 (file)
@@ -38,14 +38,14 @@ require_once INSTALLDIR.'/lib/noticeform.php';
 
 class FacebookAction extends Action
 {
-    
+
     var $facebook = null;
     var $fbuid    = null;
     var $flink    = null;
     var $action   = null;
     var $app_uri  = null;
     var $app_name = null;
-  
+
     /**
      * Constructor
      *
@@ -60,71 +60,71 @@ class FacebookAction extends Action
     function __construct($output='php://output', $indent=true, $facebook=null, $flink=null)
     {
         parent::__construct($output, $indent);
-        
+
         $this->facebook = $facebook;
         $this->flink = $flink;
-        
+
         if ($this->flink) {
-            $this->fbuid = $flink->foreign_id; 
+            $this->fbuid = $flink->foreign_id;
             $this->user = $flink->getUser();
         }
-        
+
         $this->args = array();
     }
-  
+
     function prepare($argarray)
-    {        
+    {
         parent::prepare($argarray);
-          
+
         $this->facebook = getFacebook();
         $this->fbuid = $this->facebook->require_login();
-        
+
         $this->action = $this->trimmed('action');
-        
+
         $app_props = $this->facebook->api_client->Admin_getAppProperties(
                 array('canvas_name', 'application_name'));
-        
+
         $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name'];
         $this->app_name = $app_props['application_name'];
 
         $this->flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
-        
+
         return true;
-        
+
     }
-  
+
     function showStylesheets()
     {
         // Add a timestamp to the file so Facebook cache wont ignore our changes
         $ts = filemtime(INSTALLDIR.'/theme/base/css/display.css');
 
-       $this->element('link', array('rel' => 'stylesheet',
-                      'type' => 'text/css',
-                      'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
-        
+    $this->element('link', array('rel' => 'stylesheet',
+               'type' => 'text/css',
+               'href' => theme_path('css/display.css', 'base') . '?ts=' . $ts));
+
         $theme = common_config('site', 'theme');
-        
+
         $ts = filemtime(INSTALLDIR. '/theme/' . $theme .'/css/display.css');
-                                     
+
         $this->element('link', array('rel' => 'stylesheet',
                                      'type' => 'text/css',
                                      'href' => theme_path('css/display.css', null) . '?ts=' . $ts));
-                                     
+
         $ts = filemtime(INSTALLDIR.'/theme/base/css/facebookapp.css');
-        
+
         $this->element('link', array('rel' => 'stylesheet',
                                      'type' => 'text/css',
                                      'href' => theme_path('css/facebookapp.css', 'base') . '?ts=' . $ts));
     }
-  
+
     function showScripts()
     {
         // Add a timestamp to the file so Facebook cache wont ignore our changes
         $ts = filemtime(INSTALLDIR.'/js/facebookapp.js');
-        
+
         $this->element('script', array('src' => common_path('js/facebookapp.js') . '?ts=' . $ts));
     }
-    
+
     /**
      * Start an Facebook ready HTML document
      *
@@ -138,11 +138,11 @@ class FacebookAction extends Action
      * @return void
      */
 
-    function startHTML($type=null) 
-    {          
+    function startHTML($type=null)
+    {
         $this->showStylesheets();
         $this->showScripts();
-        
+
         $this->elementStart('div', array('class' => 'facebook-page'));
     }
 
@@ -177,18 +177,18 @@ class FacebookAction extends Action
         $this->showFooter();
         $this->elementEnd('div');
     }
-      
+
     function showAside()
     {
     }
 
     function showHead($error, $success)
     {
-        
+
         if ($error) {
             $this->element("h1", null, $error);
         }
-        
+
         if ($success) {
             $this->element("h1", null, $success);
         }
@@ -198,10 +198,10 @@ class FacebookAction extends Action
         $this->element('fb:add-section-button', array('section' => 'profile'));
         $this->elementEnd('span');
         $this->elementEnd('fb:if-section-not-added');
-        
+
     }
 
-    
+
     // Make this into a widget later
     function showLocalNav()
     {
@@ -229,8 +229,8 @@ class FacebookAction extends Action
         $this->elementEnd('li');
 
         $this->elementEnd('ul');
-    }     
-    
+    }
+
     /**
      * Show header of the page.
      *
@@ -245,7 +245,7 @@ class FacebookAction extends Action
         $this->showNoticeForm();
         $this->elementEnd('div');
     }
-    
+
     /**
      * Show page, a template method.
      *
@@ -258,7 +258,7 @@ class FacebookAction extends Action
         $this->showBody();
         $this->endHTML();
     }
-    
+
 
     function showInstructions()
     {
@@ -278,7 +278,7 @@ class FacebookAction extends Action
         $this->element('a',
             array('href' => common_local_url('register')), _('Register'));
         $this->text($loginmsg_part2);
-       $this->elementEnd('p');
+    $this->elementEnd('p');
         $this->elementEnd('dd');
 
         $this->elementEnd('dl');
@@ -317,7 +317,7 @@ class FacebookAction extends Action
         $this->elementEnd('ul');
 
         $this->submit('submit', _('Login'));
-       $this->elementEnd('fieldset');
+    $this->elementEnd('fieldset');
         $this->elementEnd('form');
 
         $this->elementStart('p');
@@ -329,73 +329,73 @@ class FacebookAction extends Action
         $this->elementEnd('div');
 
     }
-    
-    
+
+
     function updateProfileBox($notice)
     {
 
         // Need to include inline CSS for styling the Profile box
 
-       $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
-       $icon_url = $app_props['icon_url'];
+    $app_props = $this->facebook->api_client->Admin_getAppProperties(array('icon_url'));
+    $icon_url = $app_props['icon_url'];
 
         $style = '<style>
-        .entry-title *,
-        .entry-content * {
-        font-size:14px;
-        font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
-        }
-        .entry-title a,
-        .entry-content a {
-        color:#002E6E;
-        }
+     .entry-title *,
+     .entry-content * {
+     font-size:14px;
+     font-family:"Lucida Sans Unicode", "Lucida Grande", sans-serif;
+     }
+     .entry-title a,
+     .entry-content a {
+     color:#002E6E;
+     }
 
          .entry-title .vcard .photo {
          float:left;
          display:inline;
-        margin-right:11px;
-        margin-bottom:11px
+     margin-right:11px;
+     margin-bottom:11px
          }
-        .entry-title {
-        margin-bottom:11px;
-        }
+     .entry-title {
+     margin-bottom:11px;
+     }
          .entry-title p.entry-content {
          display:inline;
-        margin-left:5px;
+     margin-left:5px;
          }
 
-        div.entry-content {
-        clear:both;
-        }
+     div.entry-content {
+     clear:both;
+     }
          div.entry-content dl,
          div.entry-content dt,
          div.entry-content dd {
          display:inline;
-        text-transform:lowercase;
+     text-transform:lowercase;
          }
 
          div.entry-content dd,
-        div.entry-content .device dt {
-        margin-left:0;
-        margin-right:5px;
+     div.entry-content .device dt {
+     margin-left:0;
+     margin-right:5px;
          }
          div.entry-content dl.timestamp dt,
-        div.entry-content dl.response dt {
+     div.entry-content dl.response dt {
          display:none;
          }
          div.entry-content dd a {
          display:inline-block;
          }
 
-        #facebook_laconica_app {
-        text-indent:-9999px;
-        height:16px;
-        width:16px;
-        display:block;
-        background:url('.$icon_url.') no-repeat 0 0;
-        float:right;
-        }
-         </style>';        
+     #facebook_laconica_app {
+     text-indent:-9999px;
+     height:16px;
+     width:16px;
+     display:block;
+     background:url('.$icon_url.') no-repeat 0 0;
+     float:right;
+     }
+         </style>';
 
         $this->xw->openMemory();
 
@@ -407,12 +407,12 @@ class FacebookAction extends Action
 
         $fbml_main = "<fb:narrow>$style " . $this->xw->outputMemory(false) . "</fb:narrow>";
 
-        $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main);  
+        $this->facebook->api_client->profile_setFBML(null, $this->fbuid, $fbml, null, null, $fbml_main);
 
         $this->xw->openURI('php://output');
     }
-    
-    
+
+
     /**
      * Generate pagination links
      *
@@ -457,24 +457,24 @@ class FacebookAction extends Action
             $this->elementEnd('div');
         }
     }
-    
-    function updateFacebookStatus($notice) 
+
+    function updateFacebookStatus($notice)
     {
         $prefix = $this->facebook->api_client->data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $this->fbuid);
         $content = "$prefix $notice->content";
-        
+
         if ($this->facebook->api_client->users_hasAppPermission('status_update', $this->fbuid)) {
             $this->facebook->api_client->users_setStatus($content, $this->fbuid, false, true);
         }
     }
-    
+
     function saveNewNotice()
     {
 
         $user = $this->flink->getUser();
 
         $content = $this->trimmed('status_textarea');
-        
+
         if (!$content) {
             $this->showPage(_('No notice content!'));
             return;
@@ -492,9 +492,9 @@ class FacebookAction extends Action
         $cmd = $inter->handle_command($user, $content_shortened);
 
         if ($cmd) {
-            
+
             // XXX fix this
-            
+
             $cmd->execute(new WebChannel());
             return;
         }
@@ -510,20 +510,20 @@ class FacebookAction extends Action
         }
 
         common_broadcast_notice($notice);
-        
+
         // Also update the user's Facebook status
         $this->updateFacebookStatus($notice);
         $this->updateProfileBox($notice);
-        
+
     }
 
 }
 
-class FacebookNoticeForm extends NoticeForm 
+class FacebookNoticeForm extends NoticeForm
 {
-    
+
     var $post_action = null;
-    
+
     /**
      * Constructor
      *
@@ -532,13 +532,13 @@ class FacebookNoticeForm extends NoticeForm
      * @param string        $content content to pre-fill
      */
 
-    function __construct($out=null, $action=null, $content=null, 
+    function __construct($out=null, $action=null, $content=null,
         $post_action=null, $user=null)
     {
         parent::__construct($out, $action, $content, $user);
         $this->post_action = $post_action;
     }
-    
+
     /**
      * Action of the form
      *
@@ -554,7 +554,7 @@ class FacebookNoticeForm extends NoticeForm
 
 class FacebookNoticeList extends NoticeList
 {
-    
+
     /**
      * constructor
      *
@@ -565,7 +565,7 @@ class FacebookNoticeList extends NoticeList
     {
         parent::__construct($notice, $out);
     }
-    
+
     /**
      * show the list of notices
      *
@@ -619,7 +619,7 @@ class FacebookNoticeList extends NoticeList
 }
 
 class FacebookNoticeListItem extends NoticeListItem
-{    
+{
 
     /**
      * constructor
@@ -646,51 +646,19 @@ class FacebookNoticeListItem extends NoticeListItem
     function show()
     {
         $this->showStart();
+        $this->showNotice();
+        $this->showNoticeInfo();
 
-        $this->out->elementStart('div', 'entry-title');
-        $this->showAuthor();
-        $this->showContent();
-        $this->out->elementEnd('div');
-
-        $this->out->elementStart('div', 'entry-content');
-        $this->showNoticeLink();
-        $this->showNoticeSource();
-        $this->showReplyTo();
-        $this->out->elementEnd('div');
+        // XXX: Need to update to show attachements and controls
 
         $this->showEnd();
     }
 
-    function showNoticeLink()
-    {
-        $noticeurl = common_local_url('shownotice',
-                                      array('notice' => $this->notice->id));
-        // XXX: we need to figure this out better. Is this right?
-        if (strcmp($this->notice->uri, $noticeurl) != 0 &&
-            preg_match('/^http/', $this->notice->uri)) {
-            $noticeurl = $this->notice->uri;
-        }
-
-        $this->out->elementStart('dl', 'timestamp');
-        $this->out->element('dt', null, _('Published'));
-        $this->out->elementStart('dd', null);
-        $this->out->elementStart('a', array('rel' => 'bookmark',
-                                        'href' => $noticeurl));
-        $dt = common_date_iso8601($this->notice->created);
-        $this->out->element('abbr', array('class' => 'published',
-                                     'title' => $dt),
-        common_date_string($this->notice->created));
-        $this->out->elementEnd('a');
-        $this->out->elementEnd('dd');
-        $this->out->elementEnd('dl');
-    }
-
 }
 
-
 class FacebookProfileBoxNotice extends FacebookNoticeListItem
-{    
-    
+{
+
     /**
      * constructor
      *
@@ -703,36 +671,24 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
     {
         parent::__construct($notice, $out);
     }
-    
+
     /**
-     * Recipe function for displaying a single notice in the 
-     * Facebook App's Profile
+     * Recipe function for displaying a single notice in the
+     * Facebook App profile notice box
      *
      * @return void
      */
 
     function show()
     {
-
-        $this->out->elementStart('div', 'entry-title');
-        $this->showAuthor();
-        $this->showContent();
-        $this->out->elementEnd('div');
-
-        $this->out->elementStart('div', 'entry-content');
-
-        $this->showNoticeLink();
-        $this->showNoticeSource();
-        $this->showReplyTo();
-        $this->out->elementEnd('div');
-        
+        $this->showNotice();
+        $this->showNoticeInfo();
         $this->showAppLink();
-
     }
 
-    function showAppLink() 
+    function showAppLink()
     {
-        
+
         $this->facebook = getFacebook();
 
         $app_props = $this->facebook->api_client->Admin_getAppProperties(
@@ -740,7 +696,7 @@ class FacebookProfileBoxNotice extends FacebookNoticeListItem
 
         $this->app_uri = 'http://apps.facebook.com/' . $app_props['canvas_name'];
         $this->app_name = $app_props['application_name'];
-        
+
         $this->out->elementStart('a', array('id' => 'facebook_laconica_app',
                                             'href' => $this->app_uri));
         $this->out->text($this->app_name);
index 5317df4715946e9638a1b32905f603f3652ca299..f872aef0b55066196d54c61456678664736c1d93 100644 (file)
@@ -52,6 +52,8 @@ require_once INSTALLDIR.'/lib/widget.php';
 
 class Form extends Widget
 {
+    var $enctype = null;
+
     /**
      * Show the form
      *
@@ -63,11 +65,15 @@ class Form extends Widget
 
     function show()
     {
-        $this->out->elementStart('form',
-                                 array('id' => $this->id(),
-                                       'class' => $this->formClass(),
-                                       'method' => 'post',
-                                       'action' => $this->action()));
+        $attributes = array('id' => $this->id(),
+            'class' => $this->formClass(),
+            'method' => 'post',
+            'action' => $this->action());
+
+        if (!empty($this->enctype)) {
+            $attributes['enctype'] = $this->enctype;
+        }
+        $this->out->elementStart('form', $attributes);
         $this->out->elementStart('fieldset');
         $this->formLegend();
         $this->sessionToken();
index 27a1d99dcbbd93e80b43e7d815ee27ba3091fe31..4e1f1dbb1d71ccee1d9407c591ae0c135de637c2 100644 (file)
@@ -335,6 +335,7 @@ function mail_broadcast_notice_sms($notice)
                  "FROM $UT JOIN subscription " .
                  "ON $UT.id = subscription.subscriber " .
                  'WHERE subscription.subscribed = ' . $notice->profile_id . ' ' .
+                 'AND subscription.subscribed != subscription.subscriber ' .
                  "AND $UT.smsemail IS NOT null " .
                  "AND $UT.smsnotify = 1 " .
                  'AND subscription.sms = 1 ');
index 606b5d028e7d73826cdd3d0d4a5f757575af2bbf..5d7cf194ee069d102ea701e4650c5bd95684b1a9 100644 (file)
@@ -89,7 +89,8 @@ class NoticeForm extends Form
         } else {
             $this->user = common_current_user();
         }
-        
+
+        $this->enctype = 'multipart/form-data';
     }
 
     /**
@@ -142,17 +143,21 @@ class NoticeForm extends Form
                                               'rows' => 4,
                                               'name' => 'status_textarea'),
                             ($this->content) ? $this->content : '');
-
         $this->out->elementStart('dl', 'form_note');
         $this->out->element('dt', null, _('Available characters'));
         $this->out->element('dd', array('id' => 'notice_text-count'),
                             '140');
         $this->out->elementEnd('dl');
-
+        $this->out->element('label', array('for' => 'notice_data-attach'), _('Attach'));
+        $this->out->element('input', array('id' => 'notice_data-attach',
+                                           'type' => 'file',
+                                           'name' => 'attach',
+                                           'title' => _('Attach a file')));
         if ($this->action) {
             $this->out->hidden('notice_return-to', $this->action, 'returnto');
         }
         $this->out->hidden('notice_in-reply-to', $this->action, 'inreplyto');
+        $this->out->hidden('MAX_FILE_SIZE', common_config('attachments', 'file_quota'));
     }
 
     /**
index 8aab834338228da4c921492d0326de41c0d1ea84..c00942af4998226b3bdee957b29f849486005b1f 100644 (file)
@@ -85,7 +85,7 @@ class NoticeList extends Widget
     {
         $this->out->elementStart('div', array('id' =>'notices_primary'));
         $this->out->element('h2', null, _('Notices'));
-        $this->out->elementStart('ul', array('class' => 'notices'));
+        $this->out->elementStart('ol', array('class' => 'notices xoxo'));
 
         $cnt = 0;
 
@@ -100,7 +100,7 @@ class NoticeList extends Widget
             $item->show();
         }
 
-        $this->out->elementEnd('ul');
+        $this->out->elementEnd('ol');
         $this->out->elementEnd('div');
 
         return $cnt;
@@ -205,6 +205,7 @@ class NoticeListItem extends Widget
         return 'shownotice' !== $this->out->args['action'];
     }
 
+/*
     function attachmentCount($discriminant = true) {
         $file_oembed = new File_oembed;
         $query = "select count(*) as c from file_oembed join file_to_post on file_oembed.file_id = file_to_post.file_id where post_id=" . $this->notice->id;
@@ -212,11 +213,16 @@ class NoticeListItem extends Widget
         $file_oembed->fetch();
         return intval($file_oembed->c);
     }
+*/
+
+    function showWithAttachment() {
+    }
 
     function showNoticeInfo()
     {
         $this->out->elementStart('div', 'entry-content');
         $this->showNoticeLink();
+//        $this->showWithAttachment();
         $this->showNoticeSource();
         $this->showContext();
         $this->out->elementEnd('div');
@@ -357,6 +363,10 @@ class NoticeListItem extends Widget
             // versions (>> 0.4.x)
             $this->out->raw(common_render_content($this->notice->content, $this->notice));
         }
+        $uploaded = $this->notice->getUploadedAttachment();
+        if ($uploaded) {
+            $this->out->element('a', array('href' => $uploaded, 'class' => 'attachment'), $uploaded);
+        }
         $this->out->elementEnd('p');
     }
 
@@ -387,6 +397,7 @@ class NoticeListItem extends Widget
         $this->out->element('abbr', array('class' => 'published',
                                           'title' => $dt),
                             common_date_string($this->notice->created));
+
         $this->out->elementEnd('a');
         $this->out->elementEnd('dd');
         $this->out->elementEnd('dl');
index 37aafdaf6cb2c4753d522d0d94f1dea7edb2d08f..ca14326861b5aaa92ab614bd71fe67eee7d15707 100644 (file)
@@ -52,12 +52,12 @@ class NoticeSection extends Section
     {
         $notices = $this->getNotices();
         $cnt = 0;
-        $this->out->elementStart('ul', 'notices');
+        $this->out->elementStart('ol', 'notices xoxo');
         while ($notices->fetch() && ++$cnt <= NOTICES_PER_SECTION) {
             $this->showNotice($notices);
         }
 
-        $this->out->elementEnd('ul');
+        $this->out->elementEnd('ol');
         return ($cnt > NOTICES_PER_SECTION);
     }
 
index e8e1acc4139b6aa59fe916f1938e6817986ffa19..40cb847dfa8434e96f6ca0a388aa7069e867b8f0 100644 (file)
@@ -159,13 +159,9 @@ function omb_post_notice($notice, $remote_profile, $subscription)
 
 function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
 {
-
-    common_debug('Posting notice ' . $notice->id . ' to ' . $postnoticeurl, __FILE__);
-
     $user = User::staticGet('id', $notice->profile_id);
 
     if (!$user) {
-        common_debug('Failed to get user for notice ' . $notice->id . ', profile = ' . $notice->profile_id, __FILE__);
         return false;
     }
 
@@ -208,8 +204,6 @@ function omb_post_notice_keys($notice, $postnoticeurl, $tk, $secret)
                              $req->to_postdata(),
                              array('User-Agent: Laconica/' . LACONICA_VERSION));
 
-    common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
-
     if ($result->status == 403) { # not authorized, don't send again
         common_debug('403 result, deleting subscription', __FILE__);
         # FIXME: figure out how to delete this
@@ -286,14 +280,10 @@ function omb_update_profile($profile, $remote_profile, $subscription)
 
     $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
 
-    common_debug('request URL = '.$req->get_normalized_http_url(), __FILE__);
-    common_debug('postdata = '.$req->to_postdata(), __FILE__);
     $result = $fetcher->post($req->get_normalized_http_url(),
                              $req->to_postdata(),
                              array('User-Agent: Laconica/' . LACONICA_VERSION));
 
-    common_debug('Got HTTP result "'.print_r($result,true).'"', __FILE__);
-
     if (empty($result) || !$result) {
         common_debug("Unable to contact " . $req->get_normalized_http_url());
     } else if ($result->status == 403) { # not authorized, don't send again
index fc119821b9260a30cc4ccdc64c04c6d9369383f3..456d1793e3245bf333343aae7ee4213245420fda 100644 (file)
@@ -164,6 +164,10 @@ class Router
                     array('action' => 'newnotice'),
                     array('replyto' => '[A-Za-z0-9_-]+'));
 
+        $m->connect('notice/:notice/file', 
+            array('action' => 'file'), 
+            array('notice' => '[0-9]+'));
+        
         $m->connect('notice/:notice',
                     array('action' => 'shownotice'),
                     array('notice' => '[0-9]+'));
index caf8c071639212a4ec8c7eb19dc37509fddd197e..ca8b03cdcd79f6230df08db5bce0495932cd8773 100644 (file)
@@ -54,7 +54,7 @@ class TwitterapiAction extends Action
     /**
      * Overrides XMLOutputter::element to write booleans as strings (true|false).
      * See that method's documentation for more info.
-     * 
+     *
      * @param string $tag     Element type or tagname
      * @param array  $attrs   Array of element attributes, as
      *                        key-value pairs
@@ -70,24 +70,85 @@ class TwitterapiAction extends Action
 
         return parent::element($tag, $attrs, $content);
     }
-    
+
     function twitter_user_array($profile, $get_notice=false)
     {
-
         $twitter_user = array();
 
+        $twitter_user['id'] = intval($profile->id);
         $twitter_user['name'] = $profile->getBestName();
-        $twitter_user['followers_count'] = $this->count_subscriptions($profile);
         $twitter_user['screen_name'] = $profile->nickname;
-        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
         $twitter_user['location'] = ($profile->location) ? $profile->location : null;
-        $twitter_user['id'] = intval($profile->id);
+        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
 
         $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+        $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() :
+            Avatar::defaultImage(AVATAR_STREAM_SIZE);
 
-        $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
-        $twitter_user['protected'] = false; # not supported by Laconica yet
         $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
+        $twitter_user['protected'] = false; # not supported by Laconica yet
+        $twitter_user['followers_count'] = $this->count_subscriptions($profile);
+
+        // To be supported soon...
+        $twitter_user['profile_background_color'] = '';
+        $twitter_user['profile_text_color'] = '';
+        $twitter_user['profile_link_color'] = '';
+        $twitter_user['profile_sidebar_fill_color'] = '';
+        $twitter_user['profile_sidebar_border_color'] = '';
+
+        $subbed = DB_DataObject::factory('subscription');
+        $subbed->subscriber = $profile->id;
+        $subbed_count = (int) $subbed->count() - 1;
+        $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
+
+        $twitter_user['created_at'] = $this->date_twitter($profile->created);
+
+        $faves = DB_DataObject::factory('fave');
+        $faves->user_id = $user->id;
+        $faves_count = (int) $faves->count();
+        $twitter_user['favourites_count'] = $faves_count; // British spelling!
+
+        // Need to pull up the user for some of this
+        $user = User::staticGet($profile->id);
+
+        $timezone = 'UTC';
+
+        if ($user->timezone) {
+            $timezone = $user->timezone;
+        }
+
+        $t = new DateTime;
+        $t->setTimezone(new DateTimeZone($timezone));
+
+        $twitter_user['utc_offset'] = $t->format('Z');
+        $twitter_user['time_zone'] = $timezone;
+
+        // To be supported some day, perhaps
+        $twitter_user['profile_background_image_url'] = '';
+        $twitter_user['profile_background_tile'] = false;
+
+        $notices = DB_DataObject::factory('notice');
+        $notices->profile_id = $profile->id;
+        $notice_count = (int) $notices->count();
+
+        $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
+
+        // Is the requesting user following this user?
+        $twitter_user['following'] = false;
+        $twitter_user['notifications'] = false;
+
+        if (isset($apidata['user'])) {
+
+            $twitter_user['following'] = $apidata['user']->isSubscribed($profile);
+
+            // Notifications on?
+            $sub = Subscription::pkeyGet(array('subscriber' =>
+                $apidata['user']->id, 'subscribed' => $profile->id));
+
+            if ($sub) {
+                $twitter_user['notifications'] = ($sub->jabber || $sub->sms);
+            }
+        }
 
         if ($get_notice) {
             $notice = $profile->getCurrentNotice();
@@ -612,7 +673,27 @@ class TwitterapiAction extends Action
     function get_user($id, $apidata=null)
     {
         if (!$id) {
-            return $apidata['user'];
+            
+            // Twitter supports these other ways of passing the user ID
+            if (is_numeric($this->arg('id'))) {
+                return User::staticGet($this->arg('id'));
+            } else if ($this->arg('id')) {
+                $nickname = common_canonical_nickname($this->arg('id'));
+                return User::staticGet('nickname', $nickname);
+            } else if ($this->arg('user_id')) {
+                // This is to ensure that a non-numeric user_id still 
+                // overrides screen_name even if it doesn't get used
+                if (is_numeric($this->arg('user_id'))) {
+                    return User::staticGet('id', $this->arg('user_id'));
+                }
+            } else if ($this->arg('screen_name')) {
+                $nickname = common_canonical_nickname($this->arg('screen_name'));
+                return User::staticGet('nickname', $nickname);
+            } else {
+                // Fall back to trying the currently authenticated user
+                return $apidata['user'];
+            }
+            
         } else if (is_numeric($id)) {
             return User::staticGet($id);
         } else {
index d56f44f7b45c39a948f8cd4ed488eb081ff783a1..9872d97c4895f8f66cbae97ae4bf565cf817623d 100644 (file)
@@ -499,6 +499,11 @@ function common_linkify($url) {
 
 // if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
 // where ID is the id of the attachment for the given URL.
+//
+// we need a better test telling what can be shown as an attachment
+// we're currently picking up oembeds only.
+// I think the best option is another file_view table in the db
+// and associated dbobject.
     $query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
     $file = new File;
     $file->query($query);
@@ -895,6 +900,34 @@ function common_enqueue_notice($notice)
     return $result;
 }
 
+function common_post_inbox_transports()
+{
+    $transports = array('omb', 'sms');
+
+    if (common_config('xmpp', 'enabled')) {
+        $transports = array_merge($transports, array('jabber', 'public'));
+    }
+
+    return $transports;
+}
+
+function common_enqueue_notice_transport($notice, $transport)
+{
+    $qi = new Queue_item();
+    $qi->notice_id = $notice->id;
+    $qi->transport = $transport;
+    $qi->created = $notice->created;
+    $result = $qi->insert();
+    if (!$result) {
+        $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
+        common_log(LOG_ERR, 'DB error inserting queue item: ' . $last_error->message);
+        throw new ServerException('DB error inserting queue item: ' . $last_error->message);
+>>>>>>> 0.7.x:lib/util.php
+    }
+    common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport);
+    return true;
+}
+
 function common_real_broadcast($notice, $remote=false)
 {
     $success = true;
diff --git a/plugins/FBConnect/FBC_XDReceiver.php b/plugins/FBConnect/FBC_XDReceiver.php
new file mode 100644 (file)
index 0000000..57c98b4
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/*
+ * Generates the cross domain communication channel file
+ * (xd_receiver.html). By generating it we can add some caching
+ * instructions.
+ *
+ * See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
+ */
+class FBC_XDReceiverAction extends Action
+{
+
+    /**
+     * Do we need to write to the database?
+     *
+     * @return boolean true
+     */
+
+    function isReadonly()
+    {
+        return true;
+    }
+
+    /**
+     * Handle a request
+     *
+     * @param array $args Arguments from $_REQUEST
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        // Parent handling, including cache check
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function showPage()
+    {
+        // cache the xd_receiver
+        header('Cache-Control: max-age=225065900');
+        header('Expires:');
+        header('Pragma:');
+
+        $this->startXML('html',
+                        '-//W3C//DTD XHTML 1.0 Strict//EN',
+                        'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
+
+        $language = $this->getLanguage();
+
+        $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
+                                          'xml:lang' => $language,
+                                          'lang' => $language));
+        $this->elementStart('head');
+        $this->element('title', null, 'cross domain receiver page');
+        $this->element('script',
+            array('src' =>
+                'http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js',
+                'type' => 'text/javascript'), '');
+        $this->elementEnd('head');
+        $this->elementStart('body');
+        $this->elementEnd('body');
+
+        $this->elementEnd('html');
+    }
+
+}
+
index 233eb83ab3fbcf57c72657a877f47a7245d17a5f..4699ce636c4405f119f7ae514d83fa3be1e3f17b 100644 (file)
@@ -62,7 +62,28 @@ class FBConnectauthAction extends Action
         parent::handle($args);
 
         if (common_is_real_login()) {
-            $this->clientError(_('Already logged in.'));
+
+            // User is already logged in.  Does she already have a linked Facebook acct?
+            $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
+
+            if ($flink) {
+
+                // User already has a linked Facebook account and shouldn't be here
+                common_debug('There is already a local user (' . $flink->user_id .
+                    ') linked with this Facebook (' . $this->fbuid . ').');
+
+                // We don't want these cookies
+                getFacebook()->clear_cookie_state();
+
+                $this->clientError(_('There is already a local user linked with this Facebook.'));
+
+            } else {
+
+                // User came from the Facebook connect settings tab, and
+                // probably just wants to link/relink their Facebook account
+                $this->connectUser();
+            }
+
         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
 
             $token = $this->trimmed('token');
@@ -78,7 +99,7 @@ class FBConnectauthAction extends Action
                 }
                 $this->createNewUser();
             } else if ($this->arg('connect')) {
-                $this->connectUser();
+                $this->connectNewUser();
             } else {
                 common_debug(print_r($this->args, true), __FILE__);
                 $this->showForm(_('Something weird happened.'),
@@ -259,7 +280,7 @@ class FBConnectauthAction extends Action
                         303);
     }
 
-    function connectUser()
+    function connectNewUser()
     {
         $nickname = $this->trimmed('nickname');
         $password = $this->trimmed('password');
@@ -290,6 +311,23 @@ class FBConnectauthAction extends Action
         $this->goHome($user->nickname);
     }
 
+    function connectUser()
+    {
+        $user = common_current_user();
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_debug("Connected Facebook user $this->fbuid to local user $user->id");
+
+        // Return to Facebook connection settings tab
+        common_redirect(common_local_url('FBConnectSettings'), 303);
+    }
+
     function tryLogin()
     {
         common_debug("Trying Facebook Login...");
index 7989dc854d9469b251c542fa8299631abcc05a71..205086cd8bd4b65fba2ce6384f12781ddc95a5df 100644 (file)
@@ -58,8 +58,6 @@ class FBConnectLoginAction extends Action
     function showContent() {
 
         $this->elementStart('fieldset');
-
-
         $this->element('fb:login-button', array('onlogin' => 'goto_login()',
             'length' => 'long'));
 
index ad5e47e4780bd17449d1ff7d10866ea8e55534cb..a366985be424432c076dadadd8fe54cc922a9088 100644 (file)
@@ -39,6 +39,7 @@ require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
 require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
 require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
 require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
 
 /**
  * Plugin to enable Facebook Connect
@@ -62,27 +63,19 @@ class FBConnectPlugin extends Plugin
         $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
         $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
         $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
+        $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
      }
 
     // Add in xmlns:fb
     function onStartShowHTML($action)
     {
-        $httpaccept = isset($_SERVER['HTTP_ACCEPT']) ?
-        $_SERVER['HTTP_ACCEPT'] : null;
-
-        // XXX: allow content negotiation for RDF, RSS, or XRDS
-
-        $cp = common_accept_to_prefs($httpaccept);
-        $sp = common_accept_to_prefs(PAGE_TYPE_PREFS);
-
-        $type = common_negotiate_type($cp, $sp);
-
-        if (!$type) {
-            throw new ClientException(_('This page is not available in a '.
-                                         'media type you accept'), 406);
-        }
-
-        header('Content-Type: '.$type);
+        // XXX: Horrible hack to make Safari, FF2, and Chrome work with
+        // Facebook Connect. These browser cannot use Facebook's
+        // DOM parsing routines unless the mime type of the page is
+        // text/html even though Facebook Connect uses XHTML.  This is
+        // A bug in Facebook Connect, and this is a temporary solution
+        // until they fix their JavaScript libs.
+        header('Content-Type: text/html');
 
         $action->extraHeaders();
 
@@ -101,20 +94,27 @@ class FBConnectPlugin extends Plugin
         return false;
     }
 
-    function onEndShowLaconicaScripts($action)
-    {
-        $action->element('script',
-            array('type' => 'text/javascript',
-                  'src'  => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
-                  ' ');
+    // Note: this script needs to appear in the <body>
 
+    function onStartShowHeader($action)
+    {
         $apikey = common_config('facebook', 'apikey');
         $plugin_path = common_path('plugins/FBConnect');
 
         $login_url = common_local_url('FBConnectAuth');
         $logout_url = common_local_url('logout');
 
-        $html = sprintf('<script type="text/javascript">FB.init("%s", "%s/xd_receiver.htm");
+        // XXX: Facebook says we don't need this FB_RequireFeatures(),
+        // but we actually do, for IE and Safari. Gar.
+
+        $html = sprintf('<script type="text/javascript">
+                            window.onload = function () {
+                                FB_RequireFeatures(
+                                    ["XFBML"],
+                                        function() {
+                                            FB.Facebook.init("%s", "../xd_receiver.html");
+                                        }
+                                    ); }
 
                             function goto_login() {
                                 window.location = "%s";
@@ -123,11 +123,21 @@ class FBConnectPlugin extends Plugin
                             function goto_logout() {
                                 window.location = "%s";
                             }
+                          </script>', $apikey,
+                              $login_url, $logout_url);
+
+        $action->raw($html);
+    }
 
-                         </script>', $apikey, $plugin_path, $login_url, $logout_url);
+    // Note: this script needs to appear as close as possible to </body>
 
+    function onEndShowFooter($action)
+    {
 
-        $action->raw($html);
+        $action->element('script',
+            array('type' => 'text/javascript',
+                  'src'  => 'http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php'),
+                  '');
     }
 
     function onEndShowLaconicaStyles($action)
@@ -143,24 +153,8 @@ class FBConnectPlugin extends Plugin
 
         if ($user) {
 
-            $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
-                _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
-            $action->menuItem(common_local_url('profilesettings'),
-                _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
-            if (common_config('xmpp', 'enabled')) {
-                $action->menuItem(common_local_url('imsettings'),
-                    _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
-            } else {
-             $action->menuItem(common_local_url('smssettings'),
-                 _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
-            }
-            $action->menuItem(common_local_url('invite'),
-                _('Invite'),
-                sprintf(_('Invite friends and colleagues to join you on %s'),
-                common_config('site', 'name')),
-                false, 'nav_invitecontact');
-
-            $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
+            $flink = Foreign_link::getByUserId($user->id,
+                FACEBOOK_CONNECT_SERVICE);
             $fbuid = 0;
 
             if ($flink) {
@@ -195,9 +189,25 @@ class FBConnectPlugin extends Plugin
                 }
             }
 
-            // Need to override the Logout link to make it do FB stuff
+            $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
+                _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
+            $action->menuItem(common_local_url('profilesettings'),
+                _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
+            if (common_config('xmpp', 'enabled')) {
+                $action->menuItem(common_local_url('imsettings'),
+                    _('Connect'), _('Connect to IM, SMS, Twitter'), false, 'nav_connect');
+            } else {
+             $action->menuItem(common_local_url('smssettings'),
+                 _('Connect'), _('Connect to SMS, Twitter'), false, 'nav_connect');
+            }
+            $action->menuItem(common_local_url('invite'),
+                _('Invite'),
+                sprintf(_('Invite friends and colleagues to join you on %s'),
+                common_config('site', 'name')),
+                false, 'nav_invitecontact');
 
-            if ($fbuid > 0) {
+            // Need to override the Logout link to make it do FB stuff
+            if ($flink && $fbuid > 0) {
 
                 $logout_url = common_local_url('logout');
                 $title =  _('Logout from the site');
@@ -258,21 +268,32 @@ class FBConnectPlugin extends Plugin
         return true;
     }
 
-    function onEndLogout($action)
+    function onStartLogout($action)
     {
-        try {
+        $user = common_current_user();
+
+        $flink = Foreign_link::getByUserId($user->id, FACEBOOK_CONNECT_SERVICE);
+
+        $action->logout();
+
+        if ($flink) {
 
             $facebook = getFacebook();
-            $fbuid = $facebook->get_loggedin_user();
 
-            if ($fbuid > 0) {
-                $facebook->logout(common_local_url('public'));
-            }
+            try {
+                $fbuid = $facebook->get_loggedin_user();
+
+                if ($fbuid > 0) {
+                    $facebook->logout(common_local_url('public'));
+                }
 
-        } catch (Exception $e) {
-            common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
-                $e->getMessage());
+            } catch (Exception $e) {
+                common_log(LOG_WARNING, 'Could\'t logout of Facebook: ' .
+                    $e->getMessage());
+            }
         }
+
+        return true;
     }
 
 }
index 7e255f43a68d0f58d70958e003e48214abe3fa4f..034ecebae21b74df88c349435dd14e208d000ee2 100644 (file)
@@ -78,63 +78,73 @@ class FBConnectSettingsAction extends ConnectSettingsAction
     function showContent()
     {
         $user = common_current_user();
-
         $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
 
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_facebook',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('FBConnectSettings')));
+
         if (!$flink) {
 
-            $this->element('p', 'form_note',
+            $this->element('p', 'instructions',
                 _('There is no Facebook user connected to this account.'));
 
             $this->element('fb:login-button', array('onlogin' => 'goto_login()',
                 'length' => 'long'));
 
-            return;
-        }
+        } else {
 
-        $this->element('p', 'form_note',
-                       _('Connected Facebook user:'));
+            $this->element('p', 'form_note',
+                           _('Connected Facebook user'));
+
+            $this->elementStart('p', array('class' => 'facebook-user-display'));
+            $this->elementStart('fb:profile-pic',
+                array('uid' => $flink->foreign_id,
+                      'size' => 'small',
+                      'linked' => 'true',
+                      'facebook-logo' => 'true'));
+            $this->elementEnd('fb:profile-pic');
+
+            $this->elementStart('fb:name', array('uid' => $flink->foreign_id,
+                                                 'useyou' => 'false'));
+            $this->elementEnd('fb:name');
+            $this->elementEnd('p');
 
-        $this->elementStart('p', array('class' => 'facebook-user-display'));
-        $this->elementStart('fb:profile-pic',
-            array('uid' => $flink->foreign_id,
-                  'size' => 'square',
-                  'linked' => 'true',
-                  'facebook-logo' => 'true'));
-        $this->elementEnd('fb:profile-pic');
+            $this->hidden('token', common_session_token());
 
-        $this->elementStart('fb:name', array('uid' => $flink->foreign_id));
-        $this->elementEnd('fb:name');
-        $this->elementEnd('p');
+            $this->elementStart('fieldset');
 
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'form_settings_facebook',
-                                          'class' => 'form_settings',
-                                          'action' =>
-                                          common_local_url('FBConnectSettings')));
+            $this->element('legend', null, _('Disconnect my account from Facebook'));
 
-        $this->hidden('token', common_session_token());
+            if (!$user->password) {
 
-        $this->elementStart('fieldset');
+                $this->elementStart('p', array('class' => 'form_guide'));
+                $this->text(_('Disconnecting your Faceboook ' .
+                              'would make it impossible to log in! Please '));
+                $this->element('a',
+                    array('href' => common_local_url('passwordsettings')),
+                        _('set a password'));
 
-        $this->element('legend', null, _('Disconnect my account from Facebook'));
+                $this->text(_(' first.'));
+                $this->elementEnd('p');
+            } else {
 
-        if (!$user->password) {
+                $note = 'Keep your %s account but disconnect from Facebook. ' .
+                    'You\'ll use your %s password to log in.';
 
-            $this->elementStart('p', array('class' => 'form_guide'));
-            $this->text(_('Disconnecting your Faceboook ' .
-                          'would make it impossible to log in! Please '));
-            $this->element('a',
-                array('href' => common_local_url('passwordsettings')),
-                    _('set a password'));
+                $site = common_config('site', 'name');
 
-            $this->text(_(' first.'));
-            $this->elementEnd('p');
-        } else {
-            $this->submit('disconnect', _('Disconnect'));
-         }
+                $this->element('p', 'instructions',
+                    sprintf($note, $site, $site));
+
+                $this->submit('disconnect', _('Disconnect'));
+            }
+
+            $this->elementEnd('fieldset');
+        }
 
-        $this->elementEnd('fieldset');
         $this->elementEnd('form');
     }
 
@@ -171,8 +181,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction
 
             try {
 
-                // XXX: not sure what exactly to do here
-
+                // Clear FB Connect cookies out
                 $facebook = getFacebook();
                 $facebook->clear_cookie_state();
 
@@ -182,7 +191,7 @@ class FBConnectSettingsAction extends ConnectSettingsAction
                         $e->getMessage());
             }
 
-            $this->showForm(_('Facebook user disconnected.'), true);
+            $this->showForm(_('You have disconnected from Facebook.'), true);
 
         } else {
             $this->showForm(_('Not sure what you\'re trying to do.'));
diff --git a/plugins/FBConnect/xd_receiver.htm b/plugins/FBConnect/xd_receiver.htm
deleted file mode 100644 (file)
index 43fb2c4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
-   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" >
-<head>
-    <title>cross domain receiver page</title>
-</head>
-<body>
-    <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js" type="text/javascript"></script>
-</body>
-</html>
diff --git a/scripts/fixup_utf8.php b/scripts/fixup_utf8.php
new file mode 100644 (file)
index 0000000..1693760
--- /dev/null
@@ -0,0 +1,368 @@
+#!/usr/bin/env php
+<?php
+/*
+ * Laconica - a distributed open-source microblogging tool
+ * Copyright (C) 2009, Control Yourself, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+# Abort if called from a web server
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+    print "This script must be run from the command line\n";
+    exit(1);
+}
+
+ini_set("max_execution_time", "0");
+ini_set("max_input_time", "0");
+set_time_limit(0);
+mb_internal_encoding('UTF-8');
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('LACONICA', true);
+
+require_once(INSTALLDIR . '/lib/common.php');
+require_once('DB.php');
+
+class UTF8FixerUpper
+{
+    var $dbl = null;
+    var $dbu = null;
+    var $args = array();
+
+    function __construct($args)
+    {
+        $this->args = $args;
+
+        if (array_key_exists('max_date', $args)) {
+            $this->max_date = strftime('%Y-%m-%d %H:%M:%S', strtotime($args['max_date']));
+        } else {
+            $this->max_date = strftime('%Y-%m-%d %H:%M:%S', time());
+        }
+
+        $this->dbl = $this->doConnect('latin1');
+
+        if (empty($this->dbl)) {
+            return;
+        }
+
+        $this->dbu = $this->doConnect('utf8');
+
+        if (empty($this->dbu)) {
+            return;
+        }
+    }
+
+    function doConnect($charset)
+    {
+        $db = DB::connect(common_config('db', 'database'),
+                          array('persistent' => false));
+
+        if (PEAR::isError($db)) {
+            echo "ERROR: " . $db->getMessage() . "\n";
+            return NULL;
+        }
+
+        $conn = $db->connection;
+
+        $succ = mysqli_set_charset($conn, $charset);
+
+        if (!$succ) {
+            echo "ERROR: couldn't set charset\n";
+            $db->disconnect();
+            return NULL;
+        }
+
+        $result = $db->autoCommit(true);
+
+        if (PEAR::isError($result)) {
+            echo "ERROR: " . $result->getMessage() . "\n";
+            $db->disconnect();
+            return NULL;
+        }
+
+        return $db;
+    }
+
+    function fixup()
+    {
+        $this->fixupNotices($this->args['max_notice'],
+                            $this->args['min_notice']);
+        $this->fixupProfiles();
+        $this->fixupGroups();
+        $this->fixupMessages();
+    }
+
+    function fixupNotices($max_id, $min_id) {
+
+        // Do a separate DB connection
+
+        $sth = $this->dbu->prepare("UPDATE notice SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
+
+        if (PEAR::isError($sth)) {
+            echo "ERROR: " . $sth->getMessage() . "\n";
+            return;
+        }
+
+        $sql = 'SELECT id, content, rendered FROM notice ' .
+          'WHERE LENGTH(content) != CHAR_LENGTH(content) '.
+          'AND modified < "'.$this->max_date.'" ';
+
+        if (!empty($max_id)) {
+            $sql .= ' AND id <= ' . $max_id;
+        }
+
+        if (!empty($min_id)) {
+            $sql .= ' AND id >= ' . $min_id;
+        }
+
+        $sql .= ' ORDER BY id DESC';
+
+        $rn = $this->dbl->query($sql);
+
+        if (PEAR::isError($rn)) {
+            echo "ERROR: " . $rn->getMessage() . "\n";
+            return;
+        }
+
+        echo "Number of rows: " . $rn->numRows() . "\n";
+
+        $notice = array();
+
+        while (DB_OK == $rn->fetchInto($notice)) {
+
+            $id = ($notice[0])+0;
+            $content = bin2hex($notice[1]);
+            $rendered = bin2hex($notice[2]);
+
+            echo "$id...";
+
+            $result =& $this->dbu->execute($sth, array($content, $rendered, $id));
+
+            if (PEAR::isError($result)) {
+                echo "ERROR: " . $result->getMessage() . "\n";
+                continue;
+            }
+
+            $cnt = $this->dbu->affectedRows();
+
+            if ($cnt != 1) {
+                echo "ERROR: 0 rows affected\n";
+                continue;
+            }
+
+            $notice = Notice::staticGet('id', $id);
+            $notice->decache();
+            $notice->free();
+
+            echo "OK\n";
+        }
+    }
+
+    function fixupProfiles()
+    {
+        // Do a separate DB connection
+
+        $sth = $this->dbu->prepare("UPDATE profile SET ".
+                                   "fullname = UNHEX(?),".
+                                   "location = UNHEX(?), ".
+                                   "bio = UNHEX(?) ".
+                                   "WHERE id = ?");
+
+        if (PEAR::isError($sth)) {
+            echo "ERROR: " . $sth->getMessage() . "\n";
+            return;
+        }
+
+        $sql = 'SELECT id, fullname, location, bio FROM profile ' .
+          'WHERE (LENGTH(fullname) != CHAR_LENGTH(fullname) '.
+          'OR LENGTH(location) != CHAR_LENGTH(location) '.
+          'OR LENGTH(bio) != CHAR_LENGTH(bio)) '.
+          'AND modified < "'.$this->max_date.'" '.
+          ' ORDER BY modified DESC';
+
+        $rn = $this->dbl->query($sql);
+
+        if (PEAR::isError($rn)) {
+            echo "ERROR: " . $rn->getMessage() . "\n";
+            return;
+        }
+
+        echo "Number of rows: " . $rn->numRows() . "\n";
+
+        $profile = array();
+
+        while (DB_OK == $rn->fetchInto($profile)) {
+
+            $id = ($profile[0])+0;
+            $fullname = bin2hex($profile[1]);
+            $location = bin2hex($profile[2]);
+            $bio = bin2hex($profile[3]);
+
+            echo "$id...";
+
+            $result =& $this->dbu->execute($sth, array($fullname, $location, $bio, $id));
+
+            if (PEAR::isError($result)) {
+                echo "ERROR: " . $result->getMessage() . "\n";
+                continue;
+            }
+
+            $cnt = $this->dbu->affectedRows();
+
+            if ($cnt != 1) {
+                echo "ERROR: 0 rows affected\n";
+                continue;
+            }
+
+            $profile = Profile::staticGet('id', $id);
+            $profile->decache();
+            $profile->free();
+
+            echo "OK\n";
+        }
+    }
+
+    function fixupGroups()
+    {
+        // Do a separate DB connection
+
+        $sth = $this->dbu->prepare("UPDATE user_group SET ".
+                                   "fullname = UNHEX(?),".
+                                   "location = UNHEX(?), ".
+                                   "description = UNHEX(?) ".
+                                   "WHERE id = ?");
+
+        if (PEAR::isError($sth)) {
+            echo "ERROR: " . $sth->getMessage() . "\n";
+            return;
+        }
+
+        $sql = 'SELECT id, fullname, location, description FROM user_group ' .
+          'WHERE LENGTH(fullname) != CHAR_LENGTH(fullname) '.
+          'OR LENGTH(location) != CHAR_LENGTH(location) '.
+          'OR LENGTH(description) != CHAR_LENGTH(description) ';
+          'AND modified < "'.$this->max_date.'" '.
+          'ORDER BY modified DESC';
+
+        $rn = $this->dbl->query($sql);
+
+        if (PEAR::isError($rn)) {
+            echo "ERROR: " . $rn->getMessage() . "\n";
+            return;
+        }
+
+        echo "Number of rows: " . $rn->numRows() . "\n";
+
+        $user_group = array();
+
+        while (DB_OK == $rn->fetchInto($user_group)) {
+
+            $id = ($user_group[0])+0;
+            $fullname = bin2hex($user_group[1]);
+            $location = bin2hex($user_group[2]);
+            $description = bin2hex($user_group[3]);
+
+            echo "$id...";
+
+            $result =& $this->dbu->execute($sth, array($fullname, $location, $description, $id));
+
+            if (PEAR::isError($result)) {
+                echo "ERROR: " . $result->getMessage() . "\n";
+                continue;
+            }
+
+            $cnt = $this->dbu->affectedRows();
+
+            if ($cnt != 1) {
+                echo "ERROR: 0 rows affected\n";
+                continue;
+            }
+
+            $user_group = User_group::staticGet('id', $id);
+            $user_group->decache();
+            $user_group->free();
+
+            echo "OK\n";
+        }
+    }
+
+    function fixupMessages() {
+
+        // Do a separate DB connection
+
+        $sth = $this->dbu->prepare("UPDATE message SET content = UNHEX(?), rendered = UNHEX(?) WHERE id = ?");
+
+        if (PEAR::isError($sth)) {
+            echo "ERROR: " . $sth->getMessage() . "\n";
+            return;
+        }
+
+        $sql = 'SELECT id, content, rendered FROM message ' .
+          'WHERE LENGTH(content) != CHAR_LENGTH(content) '.
+          'AND modified < "'.$this->max_date.'" '.
+          'ORDER BY id DESC';
+
+        $rn = $this->dbl->query($sql);
+
+        if (PEAR::isError($rn)) {
+            echo "ERROR: " . $rn->getMessage() . "\n";
+            return;
+        }
+
+        echo "Number of rows: " . $rn->numRows() . "\n";
+
+        $message = array();
+
+        while (DB_OK == $rn->fetchInto($message)) {
+
+            $id = ($message[0])+0;
+            $content = bin2hex($message[1]);
+            $rendered = bin2hex($message[2]);
+
+            echo "$id...";
+
+            $result =& $this->dbu->execute($sth, array($content, $rendered, $id));
+
+            if (PEAR::isError($result)) {
+                echo "ERROR: " . $result->getMessage() . "\n";
+                continue;
+            }
+
+            $cnt = $this->dbu->affectedRows();
+
+            if ($cnt != 1) {
+                echo "ERROR: 0 rows affected\n";
+                continue;
+            }
+
+            $message = Message::staticGet('id', $id);
+            $message->decache();
+            $message->free();
+
+            echo "OK\n";
+        }
+    }
+}
+
+$max_date = ($argc > 1) ? $argv[1] : null;
+$max_id = ($argc > 2) ? $argv[2] : null;
+$min_id = ($argc > 3) ? $argv[3] : null;
+
+$fixer = new UTF8FixerUpper(array('max_date' => $max_date,
+                                  'max_notice' => $max_id,
+                                  'min_notice' => $min_id));
+
+$fixer->fixup();
+
index a10233e69fcc2ce453a969118eccd867c37c63f7..4e49f9bd4bc776e1ef7b5928dd3deab39972ac89 100755 (executable)
@@ -25,7 +25,6 @@
  * daemon names.
  */
 
-
 # Abort if called from a web server
 if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
     print "This script must be run from the command line\n";
@@ -51,5 +50,4 @@ echo "ombqueuehandler.php ";
 echo "twitterqueuehandler.php ";
 echo "facebookqueuehandler.php ";
 echo "pingqueuehandler.php ";
-echo "inboxqueuehandler.php ";
 echo "smsqueuehandler.php ";
diff --git a/scripts/inboxqueuehandler.php b/scripts/inboxqueuehandler.php
deleted file mode 100755 (executable)
index 73d31e8..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env php
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008,2009 Control Yourself, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// Abort if called from a web server
-
-if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
-    print "This script must be run from the command line\n";
-    exit();
-}
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
-
-require_once(INSTALLDIR . '/lib/common.php');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
-
-set_error_handler('common_error_handler');
-
-class InboxQueueHandler extends QueueHandler
-{
-    function transport()
-    {
-        return 'inbox';
-    }
-
-       function start() {
-               $this->log(LOG_INFO, "INITIALIZE");
-               return true;
-       }
-
-    function handle_notice($notice)
-    {
-        $this->log(LOG_INFO, "Distributing notice to inboxes for $notice->id");
-        $notice->addToInboxes();
-        $notice->blowSubsCache();
-        return true;
-    }
-
-       function finish() {
-       }
-}
-
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
-
-$id = ($argc > 1) ? $argv[1] : null;
-
-$handler = new InboxQueueHandler($id);
-
-$handler->runOnce();
diff --git a/scripts/memcachedqueuehandler.php b/scripts/memcachedqueuehandler.php
deleted file mode 100755 (executable)
index 185b781..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env php
-<?php
-/*
- * Laconica - a distributed open-source microblogging tool
- * Copyright (C) 2008,2009 Control Yourself, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-// Abort if called from a web server
-
-if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
-    print "This script must be run from the command line\n";
-    exit();
-}
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-define('LACONICA', true);
-
-require_once(INSTALLDIR . '/lib/common.php');
-require_once(INSTALLDIR . '/lib/queuehandler.php');
-
-set_error_handler('common_error_handler');
-
-class MemcachedQueueHandler extends QueueHandler
-{
-    function transport()
-    {
-        return 'memcache';
-    }
-
-       function start() {
-               $this->log(LOG_INFO, "INITIALIZE");
-               return true;
-       }
-
-    function handle_notice($notice)
-    {
-        // XXX: fork here
-        $this->log(LOG_INFO, "Blowing memcached for $notice->id");
-        $notice->blowCaches();
-        return true;
-    }
-
-       function finish() {
-       }
-
-}
-
-ini_set("max_execution_time", "0");
-ini_set("max_input_time", "0");
-set_time_limit(0);
-mb_internal_encoding('UTF-8');
-
-$id = ($argc > 1) ? $argv[1] : null;
-
-$handler = new MemcachedQueueHandler($id);
-
-$handler->runOnce();
index 764037e8ff0bb4f06d7d6041d0f06437ab22d748..2134b4ab00f2af90e95750a5a24fb9153d97a917 100755 (executable)
@@ -24,8 +24,7 @@ SDIR=`dirname $0`
 DIR=`php $SDIR/getpiddir.php`
 
 for f in jabberhandler ombhandler publichandler smshandler pinghandler \
-        xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
-         memcachehandler inboxhandler twitterstatusfetcher; do
+        xmppconfirmhandler xmppdaemon twitterhandler facebookhandler; do
 
        FILES="$DIR/$f.*.pid"
        for ff in "$FILES" ; do
index 9bc1417b170954bb38273b0bd13bf87fecdff706..dc275e19f71df226e2917aad3c299ae6e0eee272 100644 (file)
@@ -452,6 +452,21 @@ float:left;
 font-size:1.3em;
 margin-bottom:7px;
 }
+#form_notice label[for=notice_data-attach] {
+text-indent:-9999px;
+}
+#form_notice label[for=notice_data-attach],
+#form_notice #notice_data-attach {
+position:absolute;
+top:25px;
+right:49px;
+width:16px;
+height:16px;
+cursor:pointer;
+}
+#form_notice #notice_data-attach {
+text-indent:-279px;
+}
 #form_notice #notice_submit label {
 display:none;
 }
@@ -855,20 +870,6 @@ display:inline-block;
 text-transform:lowercase;
 }
 
-.notice .attachment {
-position:relative;
-}
-.notice .attachment img {
-position:absolute;
-top:18px;
-left:0;
-z-index:99;
-}
-#shownotice .notice .attachment img {
-position:static;
-}
-
-
 .notice-options {
 position:relative;
 font-size:0.95em;
@@ -936,6 +937,75 @@ padding:0;
 }
 
 
+.notice .attachment {
+position:relative;
+padding-left:16px;
+}
+#attachments .attachment {
+padding-left:0;
+}
+.notice .attachment img {
+position:absolute;
+top:18px;
+left:0;
+z-index:99;
+}
+#shownotice .notice .attachment img {
+position:static;
+}
+
+#attachments {
+clear:both;
+float:left;
+width:100%;
+margin-top:18px;
+}
+#attachments dt {
+font-weight:bold;
+font-size:1.3em;
+margin-bottom:4px;
+}
+
+#attachments ol li {
+margin-bottom:18px;
+list-style-type:decimal;
+float:left;
+clear:both;
+}
+
+#jOverlayContent,
+#jOverlayContent #content,
+#jOverlayContent #content_inner {
+width: auto !important;
+margin-bottom:0;
+}
+#jOverlayContent #content {
+padding:11px;
+min-height:auto;
+}
+#jOverlayContent .external span {
+display:block;
+margin-bottom:11px;
+}
+#jOverlayContent button {
+position:absolute;
+top:0;
+right:0;
+width:29px;
+height:29px;
+text-align:center;
+font-weight:bold;
+padding:0;
+}
+#jOverlayContent h1 {
+max-width:475px;
+}
+#jOverlayContent #content {
+border-radius:7px;
+-moz-border-radius:7px;
+-webkit-border-radius:7px;
+}
+
 #usergroups #new_group {
 float: left;
 margin-right: 2em;
@@ -1182,6 +1252,7 @@ width:33%;
 }
 #settings_design_color .form_data label {
 float:none;
+display:block;
 }
 #settings_design_color .form_data .swatch {
 padding:11px;
index 5d8bea8ae3f5c1992228021f287448212c0ad2b5..8183fee67974d1ea71418ad3914da768ebd23397 100644 (file)
@@ -30,3 +30,12 @@ margin-right:4px;
 .entity_profile {
 width:64%;
 }
+.notice {
+z-index:1;
+}
+.notice:hover {
+z-index:9999;
+}
+.notice .thumbnail img  {
+z-index:9999;
+}
\ No newline at end of file
diff --git a/theme/base/images/icons/twotone/green/clip-01.gif b/theme/base/images/icons/twotone/green/clip-01.gif
new file mode 100644 (file)
index 0000000..f2dee7e
Binary files /dev/null and b/theme/base/images/icons/twotone/green/clip-01.gif differ
diff --git a/theme/base/images/icons/twotone/green/clip-02.gif b/theme/base/images/icons/twotone/green/clip-02.gif
new file mode 100644 (file)
index 0000000..77a7297
Binary files /dev/null and b/theme/base/images/icons/twotone/green/clip-02.gif differ
index 0e37a6ee4e9d211d68d071ca8038adf082e0a679..696fd0645b1c1937f3d62ecc1d5684147b394463 100644 (file)
@@ -446,6 +446,27 @@ float:left;
 font-size:1.3em;
 margin-bottom:7px;
 }
+#form_notice label {
+display:block;
+float:left;
+font-size:1.3em;
+margin-bottom:7px;
+}
+#form_notice label[for=notice_data-attach] {
+text-indent:-9999px;
+}
+#form_notice label[for=notice_data-attach],
+#form_notice #notice_data-attach {
+position:absolute;
+top:25px;
+right:49px;
+width:16px;
+height:16px;
+cursor:pointer;
+}
+#form_notice #notice_data-attach {
+text-indent:-279px;
+}
 #form_notice #notice_submit label {
 display:none;
 }
index 14092d964efc8f63a904528b0b693e24d3b87ad0..3af4c06b91f455a8eb991a4466a71447c6be9152 100644 (file)
@@ -102,6 +102,13 @@ color:#333;
 #form_notice.warning #notice_text-count {
 color:#000;
 }
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
+}
+
 #form_notice.processing #notice_action-submit {
 background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
index 737db7ce9691125103befaac6311e8da62d0cff3..34f6b3b8a6f3791acfc87eb32aed373e29f8bf13 100644 (file)
@@ -82,6 +82,13 @@ color:#333;
 #form_notice.warning #notice_text-count {
 color:#000;
 }
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
+}
+
 #form_notice.processing #notice_action-submit {
 background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
@@ -175,6 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
 }
 
 /* NOTICES */
+.notice .attachment {
+background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+}
+#attachments .attachment {
+background:none;
+}
 .notice-options .notice_reply a,
 .notice-options form input.submit {
 background-color:transparent;
index f7abac482364621572b2da6c541834b6c90c9ea9..8a03a4d7727d514d2449b0478e6c03c821a23302 100644 (file)
@@ -82,6 +82,13 @@ color:#333;
 #form_notice.warning #notice_text-count {
 color:#000;
 }
+#form_notice label[for=notice_data-attach] {
+background:transparent url(../../base/images/icons/twotone/green/clip-01.gif) no-repeat 0 45%;
+}
+#form_notice #notice_data-attach {
+opacity:0;
+}
+
 #form_notice.processing #notice_action-submit {
 background:#fff url(../../base/images/icons/icon_processing.gif) no-repeat 47% 47%;
 cursor:wait;
@@ -175,6 +182,12 @@ background-image:url(../../base/images/icons/twotone/green/shield.gif);
 }
 
 /* NOTICES */
+.notice .attachment {
+background:transparent url(../../base/images/icons/twotone/green/clip-02.gif) no-repeat 0 45%;
+}
+#attachments .attachment {
+background:none;
+}
 .notice-options .notice_reply a,
 .notice-options form input.submit {
 background-color:transparent;