Merge branch 'testing'
This commit is contained in:
		
							
								
								
									
										208
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										208
									
								
								README
									
									
									
									
									
								
							| @@ -2,8 +2,8 @@ | ||||
| README | ||||
| ------ | ||||
|  | ||||
| Laconica 0.7.4 ("Can't Get There From Here") | ||||
| 29 May 2009 | ||||
| Laconica 0.8.0 ("Shiny Happy People") | ||||
| 8 July 2009 | ||||
|  | ||||
| This is the README file for Laconica, the Open Source microblogging | ||||
| platform. It includes installation instructions, descriptions of | ||||
| @@ -71,29 +71,52 @@ for additional terms. | ||||
| New this version | ||||
| ================ | ||||
|  | ||||
| This is a minor bug-fix and feature release since version 0.7.3, | ||||
| released Apr 4 2009. Notable changes this version: | ||||
| This is a major feature release since version 0.7.4, released May 31 | ||||
| 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. | ||||
| - Support for a hosted service (status network). Multiple sites can | ||||
|   share the same codebase but use different databases. | ||||
| - OEmbed. Links to pages that support OEmbed (http://www.oembed.com/) | ||||
|   become popup links, and the media are shown in a special lightbox. | ||||
| - File attachments. Users can attach files of the size and type approved | ||||
|   by an administrator, and a shortened link will be included in the | ||||
|   notice. | ||||
| - Related notices are organized into conversations, with each reply a | ||||
|   branch in a tree. Conversations have pages and are linked to from each | ||||
|   notice in the conversation. | ||||
| - User designs. Users can specify colours and backgrounds | ||||
|   for their profile pages and other "personal" pages. | ||||
| - Group designs. Group administrators can specify similar designs for | ||||
|   group profiles and related pages. | ||||
| - Site designs. Site authors can specify a design (background and | ||||
|   colors) for the site. | ||||
| - New themes. Five new themes are added to the base release; these show | ||||
|   off the flexibility of Laconica's theming system. | ||||
| - Statistics. Public sites will periodically send usage statistics, | ||||
|   configuration options, and dependency information to Laconica dev site. | ||||
|   This will help us understand how the software is used and plan future | ||||
|   versions of the software. | ||||
| - Additional hooks. The hooks and plugins system introduced in 0.7.x was | ||||
|   expanded with additional points of access. | ||||
| - Facebook Connect. A new plugin allows logging in with Facebook Connect | ||||
|   (http://developers.facebook.com/connect.php). | ||||
| - A session handler. A new optional session handler class to manage PHP | ||||
|   sessions reliably and quickly for large sites. | ||||
| - STOMP queuing. Queue management for offline daemons has been | ||||
|   abstracted with three concrete instances. A new interface that should | ||||
|   work with STOMP servers like ActiveMQ and RabbitMQ is available, which | ||||
|   should make things scale better. | ||||
| - Group block. Group admins can block users from joining or posting to | ||||
|   a group. | ||||
| - Group aliases. Groups can be referred to with aliases, additional | ||||
|   names. For example, "!yul" and "!montreal" can be the same group. | ||||
| - Bidirectional Twitter bridge. Users can read the tweets their Twitter | ||||
|   friends post on Twitter. | ||||
| - Adaptation of WordPress.com Terms of Service (http://en.wordpress.com/tos/) | ||||
|   as default TOS for Laconica sites. | ||||
| - Better command-line handling for scripts, including standard options | ||||
|   and ability to set hostname and path from the command line. | ||||
| - Many, many bug fixes. | ||||
|  | ||||
| Prerequisites | ||||
| ============= | ||||
| @@ -198,9 +221,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.4.tar.gz | ||||
|    	   tar zxf laconica-0.8.0.tar.gz | ||||
|  | ||||
|    ...which will make a laconica-0.7.4 subdirectory in your current | ||||
|    ...which will make a laconica-0.8.0 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.) | ||||
| @@ -208,7 +231,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.4 /var/www/mublog | ||||
|    	   mv laconica-0.8.0 /var/www/mublog | ||||
|  | ||||
|    This will make your Laconica instance available in the mublog path of | ||||
|    your server, like "http://example.net/mublog". "microblog" or | ||||
| @@ -512,6 +535,11 @@ All the daemons write their process IDs (pids) to /var/run/ by | ||||
| default. This can be useful for starting, stopping, and monitoring the | ||||
| daemons. | ||||
|  | ||||
| With version 0.8.0, it's now possible to use a STOMP server instead of | ||||
| our kind of hacky home-grown DB-based queue solution. See the "queues" | ||||
| config section below for how to configure to use STOMP. As of this | ||||
| writing, the software has been tested with ActiveMQ ( | ||||
|  | ||||
| Twitter Friends Syncing | ||||
| ----------------------- | ||||
|  | ||||
| @@ -702,11 +730,11 @@ 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.4. Try these step-by-step instructions; read | ||||
| to the end first before trying them. | ||||
| If you've been using Laconica 0.7, 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.8.0. 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 | ||||
|    doing a new install. | ||||
| @@ -726,20 +754,31 @@ to the end first before trying them. | ||||
| 5. Once all writing processes to your site are turned off, make a | ||||
|    final backup of the Web directory and database. | ||||
| 6. Move your Laconica directory to a backup spot, like "mublog.bak". | ||||
| 7. Unpack your Laconica 0.6 tarball and move it to "mublog" or | ||||
| 7. Unpack your Laconica 0.8.0 tarball and move it to "mublog" or | ||||
|    wherever your code used to be. | ||||
| 8. Copy the config.php file and avatar directory from your old | ||||
|    directory to your new directory. | ||||
| 9. Copy htaccess.sample to .htaccess in the new directory. Change the | ||||
|    RewriteBase to use the correct path. | ||||
| 10. Rebuild the database. For MySQL, go to your Laconica directory and | ||||
|     run the rebuilddb.sh script like this: | ||||
| 10. Rebuild the database. NOTE: this step is destructive and cannot be | ||||
|     reversed. YOU CAN EASILY DESTROY YOUR SITE WITH THIS STEP. Don't | ||||
|     do it without a known-good backup! | ||||
|  | ||||
|     If your database is at version 0.7.4, you can run a special upgrade | ||||
|     script: | ||||
|  | ||||
|     mysql -u<rootuser> -p<rootpassword> <database> db/074to080.sql | ||||
|  | ||||
|     Otherwise, go to your Laconica directory and AFTER YOU MAKE A | ||||
|     BACKUP run the rebuilddb.sh script like this: | ||||
|  | ||||
|     ./scripts/rebuilddb.sh rootuser rootpassword database db/laconica.sql | ||||
|  | ||||
|     Here, rootuser and rootpassword are the username and password for a | ||||
|     user who can drop and create databases as well as tables; typically | ||||
|     that's _not_ the user Laconica runs as. | ||||
|     that's _not_ the user Laconica runs as. Note that rebuilddb.sh drops | ||||
|     your database and rebuilds it; if there is an error you have no | ||||
|     database. Make sure you have a backup. | ||||
|     For PostgreSQL databases there is an equivalent, rebuilddb_psql.sh, | ||||
|     which operates slightly differently. Read the documentation in that | ||||
|     script before running it. | ||||
| @@ -791,6 +830,9 @@ problem. | ||||
| 3. When fixup_inboxes is finished, you can set the enabled flag to | ||||
|    'true'. | ||||
|  | ||||
| NOTE: we will drop support for non-inboxed sites in the 0.9.x version | ||||
| of Laconica. It's time to switch now! | ||||
|  | ||||
| UTF-8 Database | ||||
| -------------- | ||||
|  | ||||
| @@ -817,7 +859,7 @@ what to do. | ||||
| Configuration options | ||||
| ===================== | ||||
|  | ||||
| The sole configuration file for Laconica (excepting configurations for | ||||
| The main configuration file for Laconica (excepting configurations for | ||||
| dependency software) is config.php in your Laconica directory. If you | ||||
| edit any other file in the directory, like lib/common.php (where most | ||||
| of the defaults are defined), you will lose your configuration options | ||||
| @@ -855,6 +897,8 @@ fancy: whether or not your site uses fancy URLs (see Fancy URLs | ||||
| logfile: full path to a file for Laconica to save logging | ||||
| 	 information to. You may want to use this if you don't have | ||||
| 	 access to syslog. | ||||
| logdebug: whether to log additional debug info like backtraces on | ||||
|           hard errors. Default false. | ||||
| locale_path: full path to the directory for locale data. Unless you | ||||
| 	     store all your locale data in one place, you probably | ||||
| 	     don't need to use this. | ||||
| @@ -892,8 +936,6 @@ notice: A plain string that will appear on every page. A good place | ||||
| 	to put introductory information about your service, or info about | ||||
| 	upgrades and outages, or other community info. Any HTML will | ||||
|         be escaped. | ||||
| dupelimit: Time in which it's not OK for the same person to post the | ||||
|            same notice; default = 60 seconds. | ||||
| logo: URL of an image file to use as the logo for the site. Overrides | ||||
|       the logo in the theme, if any. | ||||
| ssl: Whether to use SSL and https:// URLs for some or all pages. | ||||
| @@ -909,6 +951,12 @@ sslserver: use an alternate server name for SSL URLs, like | ||||
| shorturllength: Length of URL at which URLs in a message exceeding 140 | ||||
|                 characters will be sent to the user's chosen | ||||
|                 shortening service. | ||||
| design: a default design (colors and background) for the site. | ||||
|         Sub-items are: backgroundcolor, contentcolor, sidebarcolor, | ||||
|         textcolor, linkcolor, backgroundimage, disposition. | ||||
| dupelimit: minimum time allowed for one person to say the same thing | ||||
|            twice. Default 60s. Anything lower is considered a user | ||||
|            or UI error. | ||||
|  | ||||
| db | ||||
| -- | ||||
| @@ -959,6 +1007,10 @@ appname: The name that Laconica uses to log messages. By default it's | ||||
| 	 "laconica", but if you have more than one installation on the | ||||
| 	 server, you may want to change the name for each instance so | ||||
| 	 you can track log messages more easily. | ||||
| priority: level to log at. Currently ignored. | ||||
| facility: what syslog facility to used. Defaults to LOG_USER, only | ||||
|           reset if you know what syslog is and have a good reason | ||||
|           to change it. | ||||
|  | ||||
| queue | ||||
| ----- | ||||
| @@ -968,7 +1020,19 @@ sending out SMS email or XMPP messages, for off-line processing. See | ||||
| 'Queues and daemons' above for how to set this up. | ||||
|  | ||||
| enabled: Whether to uses queues. Defaults to false. | ||||
|  | ||||
| subsystem: Which kind of queueserver to use. Values include "db" for | ||||
|            our hacked-together database queuing (no other server | ||||
|            required) and "stomp" for a stomp server. | ||||
| stomp_server: "broker URI" for stomp server. Something like | ||||
|               "tcp://hostname:61613". More complicated ones are | ||||
|               possible; see your stomp server's documentation for | ||||
|               details. | ||||
| queue_basename: a root name to use for queues (stomp only). Typically | ||||
|                 something like '/queue/sitename/' makes sense. | ||||
| stomp_username: username for connecting to the stomp server; defaults | ||||
|                 to null. | ||||
| stomp_password: password for connecting to the stomp server; defaults | ||||
|                 to null. | ||||
| license | ||||
| ------- | ||||
|  | ||||
| @@ -1038,6 +1102,8 @@ localonly: If set to true, only messages posted by users of this | ||||
| blacklist: An array of IDs of users to hide from the public stream. | ||||
| 	   Useful if you have someone making excessive Twitterfeed posts | ||||
| 	   to the site, other kinds of automated posts, testing bots, etc. | ||||
| autosource: Sources of notices that are from automatic posters, and thus | ||||
|             should be kept off the public timeline. Default empty. | ||||
|  | ||||
| theme | ||||
| ----- | ||||
| @@ -1100,6 +1166,15 @@ dropoff: Decay factor for tag listing, in seconds. | ||||
| 	 Defaults to exponential decay over ten days; you can twiddle | ||||
| 	 with it to try and get better results for your site. | ||||
|  | ||||
| popular | ||||
| ------- | ||||
|  | ||||
| Settings for the "popular" section of the site. | ||||
|  | ||||
| dropoff: Decay factor for popularity listing, in seconds. | ||||
| 	 Defaults to exponential decay over ten days; you can twiddle | ||||
| 	 with it to try and get better results for your site. | ||||
|  | ||||
| daemon | ||||
| ------ | ||||
|  | ||||
| @@ -1153,6 +1228,7 @@ source: The name to use for the source of posts to Twitter. Defaults | ||||
| 	Twitter <http://twitter.com/help/request_source>, you can use | ||||
| 	that here instead. Status updates on Twitter will then have | ||||
| 	links to your site. | ||||
| taguri: base for tag:// URIs. Defaults to site-server + ',2009'. | ||||
|  | ||||
| inboxes | ||||
| ------- | ||||
| @@ -1239,7 +1315,7 @@ 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 | ||||
|            setup your server to properly recognize the types you want to | ||||
|            support. | ||||
| uploads:   false to disable uploading files with notices (true by default). | ||||
| filecommand: The required MIME_Type library may need to use the 'file' | ||||
| @@ -1260,6 +1336,17 @@ user_quota: total size in bytes a user can store on this server. Each user | ||||
|             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. | ||||
| dir: directory accessible to the Web process where uploads should go. | ||||
|      Defaults to the 'file' subdirectory of the install directory, which | ||||
|      should be writeable by the Web user. | ||||
| server: server name to use when creating URLs for uploaded files. | ||||
|         Defaults to null, meaning to use the default Web server. Using | ||||
|         a virtual server here can speed up Web performance. | ||||
| path: URL path, relative to the server, to find files. Defaults to | ||||
|       main path + '/file/'. | ||||
| filecommand: command to use for determining the type of a file. May be | ||||
|              skipped if fileinfo extension is installed. Defaults to | ||||
|              '/usr/bin/file'. | ||||
|  | ||||
| group | ||||
| ----- | ||||
| @@ -1300,6 +1387,38 @@ handle: boolean. Whether we should register our own PHP session-handling | ||||
| debug: whether to output debugging info for session storage. Can help | ||||
|        with weird session bugs, sometimes. Default false. | ||||
|  | ||||
| background | ||||
| ---------- | ||||
|  | ||||
| Users can upload backgrounds for their pages; this section defines | ||||
| their use. | ||||
|  | ||||
| server: the server to use for background. Using a separate (even | ||||
|         virtual) server for this can speed up load times. Default is | ||||
|         null; same as site server. | ||||
| dir: directory to write backgrounds too. Default is '/background/' | ||||
|      subdir of install dir. | ||||
| path: path to backgrounds. Default is sub-path of install path; note | ||||
|       that you may need to change this if you change site-path too. | ||||
|  | ||||
| twitterbridge | ||||
| ------------- | ||||
|  | ||||
| A bi-direction bridge to Twitter (http://twitter.com/). | ||||
|  | ||||
| enabled: default false. If true, will show user's Twitter friends' | ||||
|          notices in their inbox and faves pages, only to the user. You | ||||
|          must also run the twitterstatusfetcher.php script. | ||||
|  | ||||
| ping | ||||
| ---- | ||||
|  | ||||
| Using the "XML-RPC Ping" method initiated by weblogs.com, the site can | ||||
| notify third-party servers of updates. | ||||
|  | ||||
| notify: an array of URLs for ping endpoints. Default is the empty | ||||
|         array (no notification). | ||||
|  | ||||
| Troubleshooting | ||||
| =============== | ||||
|  | ||||
| @@ -1396,7 +1515,7 @@ if anyone's been overlooked in error. | ||||
| * Ori Avtalion | ||||
| * Meitar Moscovitz | ||||
| * Ken Sheppardson (Trac server, man-about-town) | ||||
| * Tiago 'gouki' Faria (i18n managerx) | ||||
| * Tiago 'gouki' Faria (i18n manager) | ||||
| * Sean Murphy | ||||
| * Leslie Michael Orchard | ||||
| * Eric Helgeson | ||||
| @@ -1405,6 +1524,11 @@ if anyone's been overlooked in error. | ||||
| * Tobias Diekershoff | ||||
| * Dan Moore | ||||
| * Fil | ||||
| * Jeff Mitchell | ||||
| * Brenda Wallace | ||||
| * Jeffery To | ||||
| * Federico Marani | ||||
| * Craig Andrews | ||||
|  | ||||
| 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, | ||||
|   | ||||
| @@ -75,14 +75,14 @@ class ApiAction extends Action | ||||
|             } | ||||
|         } else { | ||||
|  | ||||
| 			# Caller might give us a username even if not required | ||||
| 			if (isset($_SERVER['PHP_AUTH_USER'])) { | ||||
| 				$user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']); | ||||
| 				if ($user) { | ||||
| 					$this->user = $user; | ||||
| 				} | ||||
| 				# Twitter doesn't throw an error if the user isn't found | ||||
| 			} | ||||
|             // Caller might give us a username even if not required | ||||
|             if (isset($_SERVER['PHP_AUTH_USER'])) { | ||||
|                 $user = User::staticGet('nickname', $_SERVER['PHP_AUTH_USER']); | ||||
|                 if ($user) { | ||||
|                     $this->user = $user; | ||||
|                 } | ||||
|                 # Twitter doesn't throw an error if the user isn't found | ||||
|             } | ||||
|  | ||||
|             $this->process_command(); | ||||
|         } | ||||
| @@ -117,7 +117,7 @@ class ApiAction extends Action | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     # Whitelist of API methods that don't need authentication | ||||
|     // Whitelist of API methods that don't need authentication | ||||
|     function requires_auth() | ||||
|     { | ||||
|         static $noauth = array( 'statuses/public_timeline', | ||||
| @@ -127,7 +127,8 @@ class ApiAction extends Action | ||||
|                                 'help/downtime_schedule', | ||||
|                                 'laconica/version', | ||||
|                                 'laconica/config', | ||||
|                                 'laconica/wadl'); | ||||
|                                 'laconica/wadl', | ||||
|                                 'groups/timeline'); | ||||
|  | ||||
|         static $bareauth = array('statuses/user_timeline', | ||||
|                                  'statuses/friends_timeline', | ||||
| @@ -135,28 +136,61 @@ class ApiAction extends Action | ||||
|                                  'statuses/replies', | ||||
|                                  'statuses/mentions', | ||||
|                                  'statuses/followers', | ||||
|                                  'favorites/favorites'); | ||||
|                                  'favorites/favorites', | ||||
|                                  'friendships/show'); | ||||
|  | ||||
|         $fullname = "$this->api_action/$this->api_method"; | ||||
|  | ||||
|         // If the site is "private", all API methods except laconica/config | ||||
|         // need authentication | ||||
|  | ||||
|         if (common_config('site', 'private')) { | ||||
|             return $fullname != 'laconica/config' || false; | ||||
|         } | ||||
|  | ||||
|         // bareauth: only needs auth if without an argument or query param specifying user | ||||
|  | ||||
|         if (in_array($fullname, $bareauth)) { | ||||
|             # 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')) { | ||||
|  | ||||
|             // Special case: friendships/show only needs auth if source_id or | ||||
|             // source_screen_name is not specified as a param | ||||
|  | ||||
|             if ($fullname == 'friendships/show') { | ||||
|  | ||||
|                 $source_id          = $this->arg('source_id'); | ||||
|                 $source_screen_name = $this->arg('source_screen_name'); | ||||
|  | ||||
|                 if (empty($source_id) && empty($source_screen_name)) { | ||||
|                     return true; | ||||
|                 } | ||||
|  | ||||
|                 return false; | ||||
|             } else { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             // if all of these are empty, auth is required | ||||
|  | ||||
|             $id          = $this->arg('id'); | ||||
|             $user_id     = $this->arg('user_id'); | ||||
|             $screen_name = $this->arg('screen_name'); | ||||
|  | ||||
|             if (empty($this->api_arg) && | ||||
|                 empty($id)            && | ||||
|                 empty($user_id)       && | ||||
|                 empty($screen_name)) { | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|         } else if (in_array($fullname, $noauth)) { | ||||
|             # noauth: never needs auth | ||||
|  | ||||
|             // noauth: never needs auth | ||||
|  | ||||
|             return false; | ||||
|         } else { | ||||
|             # everybody else needs auth | ||||
|  | ||||
|             // everybody else needs auth | ||||
|  | ||||
|             return true; | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -57,7 +57,7 @@ class FacebookhomeAction extends FacebookAction | ||||
|  | ||||
|             // If this is the first time the user has started the app | ||||
|             // prompt for Facebook status update permission | ||||
|             if (!$this->facebook->api_client->users_hasAppPermission('status_update')) { | ||||
|             if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) { | ||||
|  | ||||
|                  if ($this->facebook->api_client->data_getUserPreference( | ||||
|                     FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') { | ||||
| @@ -203,7 +203,7 @@ class FacebookhomeAction extends FacebookAction | ||||
|         $api_key = common_config('facebook', 'apikey'); | ||||
|  | ||||
|         $auth_url = 'http://www.facebook.com/authorize.php?api_key=' . | ||||
|             $api_key . '&v=1.0&ext_perm=status_update&next=' . $next . | ||||
|             $api_key . '&v=1.0&ext_perm=publish_stream&next=' . $next . | ||||
|             '&next_cancel=' . $next . '&submit=skip'; | ||||
|  | ||||
|         $this->elementStart('span', array('class' => 'facebook-button')); | ||||
|   | ||||
| @@ -31,7 +31,7 @@ class FacebookinviteAction extends FacebookAction | ||||
|         $this->error = $error; | ||||
|          | ||||
|         if ($this->flink) { | ||||
|             if (!$this->facebook->api_client->users_hasAppPermission('status_update') && | ||||
|             if (!$this->facebook->api_client->users_hasAppPermission('publish_stream') && | ||||
|                 $this->facebook->api_client->data_getUserPreference( | ||||
|                      FACEBOOK_PROMPTED_UPDATE_PREF) == 'true') { | ||||
|      | ||||
| @@ -60,7 +60,7 @@ class FacebookinviteAction extends FacebookAction | ||||
|  | ||||
|             // If this is the first time the user has started the app | ||||
|              // prompt for Facebook status update permission | ||||
|              if (!$this->facebook->api_client->users_hasAppPermission('status_update')) { | ||||
|              if (!$this->facebook->api_client->users_hasAppPermission('publish_stream')) { | ||||
|  | ||||
|                  if ($this->facebook->api_client->data_getUserPreference( | ||||
|                          FACEBOOK_PROMPTED_UPDATE_PREF) != 'true') { | ||||
|   | ||||
| @@ -78,7 +78,7 @@ class FacebooksettingsAction extends FacebookAction | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if ($this->facebook->api_client->users_hasAppPermission('status_update')) { | ||||
|         if ($this->facebook->api_client->users_hasAppPermission('publish_stream')) { | ||||
|  | ||||
|             $this->elementStart('form', array('method' => 'post', | ||||
|                                                'id' => 'facebook_settings')); | ||||
| @@ -131,7 +131,7 @@ class FacebooksettingsAction extends FacebookAction | ||||
|  | ||||
|             $this->elementStart('ul', array('id' => 'fb-permissions-list')); | ||||
|             $this->elementStart('li', array('id' => 'fb-permissions-item')); | ||||
|             $this->elementStart('fb:prompt-permission', array('perms' => 'status_update', | ||||
|             $this->elementStart('fb:prompt-permission', array('perms' => 'publish_stream', | ||||
|                 'next_fbjs' => 'document.setLocation(\'' . "$this->app_uri/settings.php" . '\')')); | ||||
|             $this->element('span', array('class' => 'facebook-button'), | ||||
|                 sprintf(_('Allow %s to update my Facebook status'), common_config('site', 'name'))); | ||||
|   | ||||
| @@ -194,7 +194,7 @@ class FavoritedAction extends Action | ||||
|         $qry = 'SELECT notice.*, '. | ||||
|           $weightexpr . ' as weight ' . | ||||
|           'FROM notice JOIN fave ON notice.id = fave.notice_id ' . | ||||
|           'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source ' . | ||||
|           'GROUP BY id,profile_id,uri,content,rendered,url,created,notice.modified,reply_to,is_local,source,notice.conversation ' . | ||||
|           'ORDER BY weight DESC'; | ||||
|  | ||||
|         $offset = ($this->page - 1) * NOTICES_PER_PAGE; | ||||
|   | ||||
| @@ -312,36 +312,4 @@ class GroupDesignSettingsAction extends DesignSettingsAction | ||||
|         $this->showForm(_('Design preferences saved.'), true); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handle input and output a page (overrided) | ||||
|      * | ||||
|      * @param array $args $_REQUEST arguments | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|  | ||||
|     function handle($args) | ||||
|     { | ||||
|         parent::handle($args); | ||||
|         if (!common_logged_in()) { | ||||
|             $this->clientError(_('Not logged in.')); | ||||
|             return; | ||||
|         } else if (!common_is_real_login()) { | ||||
|             // Cookie theft means that automatic logins can't | ||||
|             // change important settings or see private info, and | ||||
|             // _all_ our settings are important | ||||
|             common_set_returnto($this->selfUrl()); | ||||
|             $user = common_current_user(); | ||||
|             if ($user->hasOpenID()) { | ||||
|                 common_redirect(common_local_url('openidlogin'), 303); | ||||
|             } else { | ||||
|                 common_redirect(common_local_url('login'), 303); | ||||
|             } | ||||
|         } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { | ||||
|             $this->handlePost(); | ||||
|         } else { | ||||
|             $this->showForm(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -135,7 +135,7 @@ class NewnoticeAction extends Action | ||||
|  | ||||
|     function isRespectsQuota($user) { | ||||
|         $file = new File; | ||||
|         $ret = $file->isRespectsQuota($user); | ||||
|         $ret = $file->isRespectsQuota($user,$_FILES['attach']['size']); | ||||
|         if (true === $ret) return true; | ||||
|         $this->clientError($ret); | ||||
|     } | ||||
|   | ||||
| @@ -83,14 +83,12 @@ class OthersettingsAction extends AccountSettingsAction | ||||
|     { | ||||
|         $user = common_current_user(); | ||||
|  | ||||
|  | ||||
|         $this->elementStart('form', array('method' => 'post', | ||||
|                                           'id' => 'form_settings_other', | ||||
|                                           'class' => 'form_settings', | ||||
|                                           'action' => | ||||
|                                           common_local_url('othersettings'))); | ||||
|         $this->elementStart('fieldset'); | ||||
|         $this->element('legend', null, _('URL Auto-shortening')); | ||||
|         $this->hidden('token', common_session_token()); | ||||
|  | ||||
|         // I18N | ||||
| @@ -109,10 +107,14 @@ class OthersettingsAction extends AccountSettingsAction | ||||
|  | ||||
|         $this->elementStart('ul', 'form_data'); | ||||
|         $this->elementStart('li'); | ||||
|         $this->dropdown('urlshorteningservice', _('Service'), | ||||
|         $this->dropdown('urlshorteningservice', _('Shorten URLs with'), | ||||
|                         $services, _('Automatic shortening service to use.'), | ||||
|                         false, $user->urlshorteningservice); | ||||
|         $this->elementEnd('li'); | ||||
|         $this->elementStart('li'); | ||||
|         $this->checkbox('viewdesigns', _('View profile designs'), | ||||
|                         $user->viewdesigns, _('Show or hide profile designs.')); | ||||
|         $this->elementEnd('li'); | ||||
|         $this->elementEnd('ul'); | ||||
|         $this->submit('save', _('Save')); | ||||
|         $this->elementEnd('fieldset'); | ||||
| @@ -145,6 +147,8 @@ class OthersettingsAction extends AccountSettingsAction | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $viewdesigns = $this->boolean('viewdesigns'); | ||||
|  | ||||
|         $user = common_current_user(); | ||||
|  | ||||
|         assert(!is_null($user)); // should already be checked | ||||
| @@ -154,6 +158,7 @@ class OthersettingsAction extends AccountSettingsAction | ||||
|         $original = clone($user); | ||||
|  | ||||
|         $user->urlshorteningservice = $urlshorteningservice; | ||||
|         $user->viewdesigns          = $viewdesigns; | ||||
|  | ||||
|         $result = $user->update($original); | ||||
|  | ||||
|   | ||||
| @@ -317,8 +317,25 @@ class ShowgroupAction extends GroupDesignAction | ||||
|           common_local_url('grouprss', | ||||
|                            array('nickname' => $this->group->nickname)); | ||||
|  | ||||
|         return array(new Feed(Feed::RSS1, $url, sprintf(_('Notice feed for %s group'), | ||||
|                                                         $this->group->nickname))); | ||||
|         return array(new Feed(Feed::RSS1, | ||||
|                               common_local_url('grouprss', | ||||
|                                                array('nickname' => $this->group->nickname)), | ||||
|                               sprintf(_('Notice feed for %s group (RSS 1.0)'), | ||||
|                                       $this->group->nickname)), | ||||
|                      new Feed(Feed::RSS2, | ||||
|                               common_local_url('api', | ||||
|                                                array('apiaction' => 'groups', | ||||
|                                                      'method' => 'timeline', | ||||
|                                                      'argument' => $this->group->nickname.'.rss')), | ||||
|                               sprintf(_('Notice feed for %s group (RSS 2.0)'), | ||||
|                                       $this->group->nickname)), | ||||
|                      new Feed(Feed::ATOM, | ||||
|                               common_local_url('api', | ||||
|                                                array('apiaction' => 'groups', | ||||
|                                                      'method' => 'timeline', | ||||
|                                                      'argument' => $this->group->nickname.'.atom')), | ||||
|                               sprintf(_('Notice feed for %s group (Atom)'), | ||||
|                                       $this->group->nickname))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -466,4 +483,4 @@ class GroupAdminSection extends ProfileSection | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										114
									
								
								actions/twitapigroups.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								actions/twitapigroups.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Laconica, the distributed open-source microblogging tool | ||||
|  * | ||||
|  * Laconica extensions to the Twitter-like API for groups | ||||
|  * | ||||
|  * PHP version 5 | ||||
|  * | ||||
|  * LICENCE: This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * @category  Twitter | ||||
|  * @package   Laconica | ||||
|  * @author    Craig Andrews | ||||
|  * @author    Zach Copley <zach@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| if (!defined('LACONICA')) { | ||||
|     exit(1); | ||||
| } | ||||
|  | ||||
| require_once INSTALLDIR.'/lib/twitterapi.php'; | ||||
|  | ||||
| /** | ||||
|  * Group-specific API methods | ||||
|  * | ||||
|  * This class handles Laconica group API methods. | ||||
|  * | ||||
|  * @category  Twitter | ||||
|  * @package   Laconica | ||||
|  * @author    Craig Andrews | ||||
|  * @author    Zach Copley <zach@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
|  class TwitapigroupsAction extends TwitterapiAction | ||||
|  { | ||||
|  | ||||
|      function timeline($args, $apidata) | ||||
|      { | ||||
|          parent::handle($args); | ||||
|  | ||||
|          common_debug("in groups api action"); | ||||
|  | ||||
|          $this->auth_user = $apidata['user']; | ||||
|          $group = $this->get_group($apidata['api_arg'], $apidata); | ||||
|  | ||||
|          if (empty($group)) { | ||||
|              $this->clientError('Not Found', 404, $apidata['content-type']); | ||||
|              return; | ||||
|          } | ||||
|  | ||||
|          $sitename   = common_config('site', 'name'); | ||||
|          $title      = sprintf(_("%s timeline"), $group->nickname); | ||||
|          $taguribase = common_config('integration', 'taguri'); | ||||
|          $id         = "tag:$taguribase:GroupTimeline:".$group->id; | ||||
|          $link       = common_local_url('showgroup', | ||||
|              array('nickname' => $group->nickname)); | ||||
|          $subtitle   = sprintf(_('Updates from %1$s on %2$s!'), | ||||
|              $group->nickname, $sitename); | ||||
|  | ||||
|          $page     = (int)$this->arg('page', 1); | ||||
|          $count    = (int)$this->arg('count', 20); | ||||
|          $max_id   = (int)$this->arg('max_id', 0); | ||||
|          $since_id = (int)$this->arg('since_id', 0); | ||||
|          $since    = $this->arg('since'); | ||||
|  | ||||
|          $notice = $group->getNotices(($page-1)*$count, | ||||
|              $count, $since_id, $max_id, $since); | ||||
|  | ||||
|          switch($apidata['content-type']) { | ||||
|           case 'xml': | ||||
|              $this->show_xml_timeline($notice); | ||||
|              break; | ||||
|           case 'rss': | ||||
|              $this->show_rss_timeline($notice, $title, $link, | ||||
|                  $subtitle, $suplink); | ||||
|              break; | ||||
|           case 'atom': | ||||
|              if (isset($apidata['api_arg'])) { | ||||
|                  $selfuri = common_root_url() . | ||||
|                      'api/statuses/groups/timeline/' . | ||||
|                          $apidata['api_arg'] . '.atom'; | ||||
|              } else { | ||||
|                  $selfuri = common_root_url() . | ||||
|                   'api/statuses/groups/timeline.atom'; | ||||
|              } | ||||
|              $this->show_atom_timeline($notice, $title, $id, $link, | ||||
|                  $subtitle, $suplink, $selfuri); | ||||
|              break; | ||||
|           case 'json': | ||||
|              $this->show_json_timeline($notice); | ||||
|              break; | ||||
|           default: | ||||
|              $this->clientError(_('API method not found!'), $code = 404); | ||||
|          } | ||||
|      } | ||||
|  | ||||
| } | ||||
| @@ -373,9 +373,19 @@ class TwitapistatusesAction extends TwitterapiAction | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 'id' is an undocumented parameter in Twitter's API. Several | ||||
|         // clients make use of it, so we support it too. | ||||
|  | ||||
|         // show.json?id=12345 takes precedence over /show/12345.json | ||||
|  | ||||
|         $this->auth_user = $apidata['user']; | ||||
|         $notice_id       = $apidata['api_arg']; | ||||
|         $notice          = Notice::staticGet($notice_id); | ||||
|         $notice_id       = $this->trimmed('id'); | ||||
|  | ||||
|         if (empty($notice_id)) { | ||||
|             $notice_id   = $apidata['api_arg']; | ||||
|         } | ||||
|  | ||||
|         $notice          = Notice::staticGet((int)$notice_id); | ||||
|  | ||||
|         if ($notice) { | ||||
|             if ($apidata['content-type'] == 'xml') { | ||||
| @@ -389,7 +399,6 @@ class TwitapistatusesAction extends TwitterapiAction | ||||
|             $this->clientError(_('No status with that ID found.'), | ||||
|                 404, $apidata['content-type']); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     function destroy($args, $apidata) | ||||
|   | ||||
| @@ -37,24 +37,24 @@ class TwitapiusersAction extends TwitterapiAction | ||||
|  | ||||
|         $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']; | ||||
|         } else { | ||||
|             $user = $this->get_user($apidata['api_arg'], $apidata); | ||||
|         } | ||||
|  | ||||
|         if (empty($user)) { | ||||
|             $this->client_error(_('Not found.'), 404, $apidata['content-type']); | ||||
|             $this->clientError(_('Not found.'), 404, $apidata['content-type']); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         $profile = $user->getProfile(); | ||||
|  | ||||
|         if (!$profile) { | ||||
|             common_server_error(_('User has no profile.')); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -122,17 +122,17 @@ class File extends Memcached_DataObject | ||||
|         return $x; | ||||
|     } | ||||
|  | ||||
|     function isRespectsQuota($user) { | ||||
|         if ($_FILES['attach']['size'] > common_config('attachments', 'file_quota')) { | ||||
|     function isRespectsQuota($user,$fileSize) { | ||||
|         if ($fileSize > 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']); | ||||
|                            common_config('attachments', 'file_quota'), $fileSize); | ||||
|         } | ||||
|  | ||||
|         $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']; | ||||
|         $total = $this->total + $fileSize; | ||||
|         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')); | ||||
|         } | ||||
| @@ -140,7 +140,7 @@ class File extends Memcached_DataObject | ||||
|         $query .= ' month(modified) = month(now()) and year(modified) = year(now())'; | ||||
|         $this->query($query); | ||||
|         $this->fetch(); | ||||
|         $total = $this->total + $_FILES['attach']['size']; | ||||
|         $total = $this->total + $fileSize; | ||||
|         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')); | ||||
|         } | ||||
|   | ||||
| @@ -33,41 +33,21 @@ class Memcached_DataObject extends DB_DataObject | ||||
|             $k = $keys[0]; | ||||
|             unset($i); | ||||
|         } | ||||
|         $i = self::getcached($cls, $k, $v); | ||||
|         $i = Memcached_DataObject::getcached($cls, $k, $v); | ||||
|         if ($i) { | ||||
|             return $i; | ||||
|         } else { | ||||
|             $i = DB_DataObject::staticGet($cls, $k, $v); | ||||
|             if ($i) { | ||||
|                 $i->encache(); | ||||
|             } else { | ||||
|                 self::cachenull($cls, $k, $v); | ||||
|             } | ||||
|             return $i; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function cachenull($cls, $k, $v) | ||||
|     { | ||||
|         $c = self::memcache(); | ||||
|         if (empty($c)) { | ||||
|             return; | ||||
|         } | ||||
|         $c->set(self::cacheKey($cls, $k, $v), null); | ||||
|     } | ||||
|  | ||||
|     function multicachenull($cls, $kv) | ||||
|     { | ||||
|         $c = self::memcache(); | ||||
|         if (empty($c)) { | ||||
|             return; | ||||
|         } | ||||
|         $c->set(self::multicachekey($cls, $kv), null); | ||||
|     } | ||||
|  | ||||
|     function &pkeyGet($cls, $kv) | ||||
|     { | ||||
|         $i = self::multicache($cls, $kv); | ||||
|         $i = Memcached_DataObject::multicache($cls, $kv); | ||||
|         if ($i) { | ||||
|             return $i; | ||||
|         } else { | ||||
| @@ -78,7 +58,6 @@ class Memcached_DataObject extends DB_DataObject | ||||
|             if ($i->find(true)) { | ||||
|                 $i->encache(); | ||||
|             } else { | ||||
|                 self::multicachenull($cls, $kv); | ||||
|                 $i = null; | ||||
|             } | ||||
|             return $i; | ||||
| @@ -88,9 +67,6 @@ class Memcached_DataObject extends DB_DataObject | ||||
|     function insert() | ||||
|     { | ||||
|         $result = parent::insert(); | ||||
|         if ($result) { | ||||
|             $this->encache(); | ||||
|         } | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
| @@ -121,11 +97,11 @@ class Memcached_DataObject extends DB_DataObject | ||||
|     } | ||||
|  | ||||
|     static function getcached($cls, $k, $v) { | ||||
|         $c = self::memcache(); | ||||
|         $c = Memcached_DataObject::memcache(); | ||||
|         if (!$c) { | ||||
|             return false; | ||||
|         } else { | ||||
|             return $c->get(self::cacheKey($cls, $k, $v)); | ||||
|             return $c->get(Memcached_DataObject::cacheKey($cls, $k, $v)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -192,23 +168,17 @@ class Memcached_DataObject extends DB_DataObject | ||||
|  | ||||
|     function multicache($cls, $kv) | ||||
|     { | ||||
|         $c = self::memcache(); | ||||
|         ksort($kv); | ||||
|         $c = Memcached_DataObject::memcache(); | ||||
|         if (!$c) { | ||||
|             return false; | ||||
|         } else { | ||||
|             return $c->get(self::multicachekey($cls, $kv)); | ||||
|             $pkeys = implode(',', array_keys($kv)); | ||||
|             $pvals = implode(',', array_values($kv)); | ||||
|             return $c->get(Memcached_DataObject::cacheKey($cls, $pkeys, $pvals)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function multicachekey($cls, $kv) | ||||
|     { | ||||
|         ksort($kv); | ||||
|         $pkeys = implode(',', array_keys($kv)); | ||||
|         $pvals = implode(',', array_values($kv)); | ||||
|  | ||||
|         return self::cacheKey($cls, $pkeys, $pvals); | ||||
|     } | ||||
|  | ||||
|     function getSearchEngine($table) | ||||
|     { | ||||
|         require_once INSTALLDIR.'/lib/search_engines.php'; | ||||
| @@ -241,7 +211,7 @@ class Memcached_DataObject extends DB_DataObject | ||||
|  | ||||
|     static function cachedQuery($cls, $qry, $expiry=3600) | ||||
|     { | ||||
|         $c = self::memcache(); | ||||
|         $c = Memcached_DataObject::memcache(); | ||||
|         if (!$c) { | ||||
|             $inst = new $cls(); | ||||
|             $inst->query($qry); | ||||
|   | ||||
| @@ -356,6 +356,8 @@ class Notice extends Memcached_DataObject | ||||
|         $this->blowTagCache($blowLast); | ||||
|         $this->blowGroupCache($blowLast); | ||||
|         $this->blowConversationCache($blowLast); | ||||
|         $profile = Profile::staticGet($this->profile_id); | ||||
|         $profile->blowNoticeCount(); | ||||
|     } | ||||
|  | ||||
|     function blowConversationCache($blowLast=false) | ||||
| @@ -1164,6 +1166,18 @@ class Notice extends Memcached_DataObject | ||||
|         } | ||||
|         $tag->free(); | ||||
|  | ||||
|         # Enclosures | ||||
|         $attachments = $this->attachments(); | ||||
|         if($attachments){ | ||||
|             foreach($attachments as $attachment){ | ||||
|                 $attributes = array('rel'=>'enclosure','href'=>$attachment->url,'type'=>$attachment->mimetype,'length'=>$attachment->size); | ||||
|                 if($attachment->title){ | ||||
|                     $attributes['title']=$attachment->title; | ||||
|                 } | ||||
|                 $xs->element('link', $attributes, null); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $xs->elementEnd('entry'); | ||||
|  | ||||
|         return $xs->getString(); | ||||
| @@ -1210,7 +1224,7 @@ class Notice extends Memcached_DataObject | ||||
|             $window = explode(',', $laststr); | ||||
|             $last_id = $window[0]; | ||||
|             $new_ids = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW, | ||||
|                                                                           $last_id, 0, null, $tag))); | ||||
|                                                                           $last_id, 0, null))); | ||||
|  | ||||
|             $new_window = array_merge($new_ids, $window); | ||||
|  | ||||
| @@ -1225,7 +1239,7 @@ class Notice extends Memcached_DataObject | ||||
|         } | ||||
|  | ||||
|         $window = call_user_func_array($fn, array_merge($args, array(0, NOTICE_CACHE_WINDOW, | ||||
|                                                                      0, 0, null, $tag))); | ||||
|                                                                      0, 0, null))); | ||||
|  | ||||
|         $windowstr = implode(',', $window); | ||||
|  | ||||
|   | ||||
| @@ -337,4 +337,132 @@ class Profile extends Memcached_DataObject | ||||
|  | ||||
|         return $profile; | ||||
|     } | ||||
|  | ||||
|     function subscriptionCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|  | ||||
|         if (!empty($c)) { | ||||
|             $cnt = $c->get(common_cache_key('profile:subscription_count:'.$this->id)); | ||||
|             if (is_integer($cnt)) { | ||||
|                 return (int) $cnt; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $sub = new Subscription(); | ||||
|         $sub->subscriber = $this->id; | ||||
|  | ||||
|         $cnt = (int) $sub->count('distinct subscribed'); | ||||
|  | ||||
|         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt; | ||||
|  | ||||
|         if (!empty($c)) { | ||||
|             $c->set(common_cache_key('profile:subscription_count:'.$this->id), $cnt); | ||||
|         } | ||||
|  | ||||
|         common_debug("subscriptionCount == $cnt"); | ||||
|         return $cnt; | ||||
|     } | ||||
|  | ||||
|     function subscriberCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|         if (!empty($c)) { | ||||
|             $cnt = $c->get(common_cache_key('profile:subscriber_count:'.$this->id)); | ||||
|             if (is_integer($cnt)) { | ||||
|                 return (int) $cnt; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $sub = new Subscription(); | ||||
|         $sub->subscribed = $this->id; | ||||
|  | ||||
|         $cnt = (int) $sub->count('distinct subscriber'); | ||||
|  | ||||
|         $cnt = ($cnt > 0) ? $cnt - 1 : $cnt; | ||||
|  | ||||
|         if (!empty($c)) { | ||||
|             $c->set(common_cache_key('profile:subscriber_count:'.$this->id), $cnt); | ||||
|         } | ||||
|  | ||||
|         common_debug("subscriberCount == $cnt"); | ||||
|         return $cnt; | ||||
|     } | ||||
|  | ||||
|     function faveCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|         if (!empty($c)) { | ||||
|             $cnt = $c->get(common_cache_key('profile:fave_count:'.$this->id)); | ||||
|             if (is_integer($cnt)) { | ||||
|                 return (int) $cnt; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $faves = new Fave(); | ||||
|         $faves->user_id = $this->id; | ||||
|         $cnt = (int) $faves->count('distinct notice_id'); | ||||
|  | ||||
|         if (!empty($c)) { | ||||
|             $c->set(common_cache_key('profile:fave_count:'.$this->id), $cnt); | ||||
|         } | ||||
|  | ||||
|         common_debug("faveCount == $cnt"); | ||||
|         return $cnt; | ||||
|     } | ||||
|  | ||||
|     function noticeCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|  | ||||
|         if (!empty($c)) { | ||||
|             $cnt = $c->get(common_cache_key('profile:notice_count:'.$this->id)); | ||||
|             if (is_integer($cnt)) { | ||||
|                 return (int) $cnt; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $notices = new Notice(); | ||||
|         $notices->profile_id = $this->id; | ||||
|         $cnt = (int) $notices->count('distinct id'); | ||||
|  | ||||
|         if (!empty($c)) { | ||||
|             $c->set(common_cache_key('profile:notice_count:'.$this->id), $cnt); | ||||
|         } | ||||
|  | ||||
|         common_debug("noticeCount == $cnt"); | ||||
|         return $cnt; | ||||
|     } | ||||
|  | ||||
|     function blowSubscriberCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|         if (!empty($c)) { | ||||
|             $c->delete(common_cache_key('profile:subscriber_count:'.$this->id)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function blowSubscriptionCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|         if (!empty($c)) { | ||||
|             $c->delete(common_cache_key('profile:subscription_count:'.$this->id)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function blowFaveCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|         if (!empty($c)) { | ||||
|             $c->delete(common_cache_key('profile:fave_count:'.$this->id)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function blowNoticeCount() | ||||
|     { | ||||
|         $c = common_memcache(); | ||||
|         if (!empty($c)) { | ||||
|             $c->delete(common_cache_key('profile:notice_count:'.$this->id)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  */ | ||||
| require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; | ||||
|  | ||||
| class Queue_item extends Memcached_DataObject  | ||||
| class Queue_item extends Memcached_DataObject | ||||
| { | ||||
|     ###START_AUTOCODE | ||||
|     /* the code below is auto generated do not remove the above tag */ | ||||
| @@ -13,7 +13,7 @@ class Queue_item extends Memcached_DataObject | ||||
|     public $notice_id;                       // int(4)  primary_key not_null | ||||
|     public $transport;                       // varchar(8)  primary_key not_null | ||||
|     public $created;                         // datetime()   not_null | ||||
|     public $claimed;                         // datetime()   | ||||
|     public $claimed;                         // datetime() | ||||
|  | ||||
|     /* Static get */ | ||||
|     function staticGet($k,$v=null) | ||||
| @@ -24,7 +24,7 @@ class Queue_item extends Memcached_DataObject | ||||
|  | ||||
|     function sequenceKey() | ||||
|     { return array(false, false); } | ||||
|      | ||||
|  | ||||
|     static function top($transport) { | ||||
|  | ||||
|         $qi = new Queue_item(); | ||||
| @@ -54,4 +54,9 @@ class Queue_item extends Memcached_DataObject | ||||
|         $qi = null; | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     function &pkeyGet($kv) | ||||
|     { | ||||
|         return Memcached_DataObject::pkeyGet('Queue_item', $kv); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -494,6 +494,8 @@ class User extends Memcached_DataObject | ||||
|             $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id)); | ||||
|             $cache->delete(common_cache_key('fave:ids_by_user_own:'.$this->id.';last')); | ||||
|         } | ||||
|         $profile = $this->getProfile(); | ||||
|         $profile->blowFaveCount(); | ||||
|     } | ||||
|  | ||||
|     function getSelfTags() | ||||
|   | ||||
| @@ -116,7 +116,9 @@ create table notice ( | ||||
|     modified timestamp /* comment 'date this record was modified' */, | ||||
|     reply_to integer /* comment 'notice replied to (usually a guess)' */ references notice (id) , | ||||
|     is_local integer default 0 /* comment 'notice was generated by a user' */, | ||||
|     source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */ | ||||
|     source varchar(32) /* comment 'source of comment, like "web", "im", or "clientname"' */, | ||||
|     conversation integer /*id of root notice in this conversation' */ references notice (id) | ||||
|  | ||||
|  | ||||
| /*    FULLTEXT(content) */ | ||||
| ); | ||||
| @@ -172,7 +174,7 @@ create table token ( | ||||
|     tok char(32) not null /* comment 'identifying value' */, | ||||
|     secret char(32) not null /* comment 'secret value' */, | ||||
|     type integer not null default 0 /* comment 'request or access' */, | ||||
|     state integer default 0 /* comment 'for requests; 0 = initial, 1 = authorized, 2 = used' */, | ||||
|     state integer default 0 /* comment 'for requests 0 = initial, 1 = authorized, 2 = used' */, | ||||
|  | ||||
|     created timestamp not null default CURRENT_TIMESTAMP /* comment 'date this record was created' */, | ||||
|     modified timestamp /* comment 'date this record was modified' */, | ||||
| @@ -346,7 +348,7 @@ create table notice_inbox ( | ||||
|     user_id integer not null /* comment 'user receiving the message' */ references "user" (id), | ||||
|     notice_id integer not null /* comment 'notice received' */ references notice (id), | ||||
|     created timestamp not null default CURRENT_TIMESTAMP /* comment 'date the notice was created' */, | ||||
|     source integer default 1 /* comment 'reason it is in the inbox; 1=subscription' */, | ||||
|     source integer default 1 /* comment 'reason it is in the inbox: 1=subscription' */, | ||||
|  | ||||
|     primary key (user_id, notice_id) | ||||
| ); | ||||
| @@ -436,8 +438,8 @@ create table file ( | ||||
|     mimetype varchar(50),  | ||||
|     size integer,  | ||||
|     title varchar(255),  | ||||
|     date integer(11),  | ||||
|     protected integer(1) | ||||
|     date integer,  | ||||
|     protected integer | ||||
| ); | ||||
|  | ||||
| create sequence file_oembed_seq; | ||||
| @@ -454,7 +456,7 @@ create table file_oembed ( | ||||
|     title varchar(255), | ||||
|     author_name varchar(50),  | ||||
|     author_url varchar(255),  | ||||
|     url varchar(255),  | ||||
|     url varchar(255)  | ||||
| ); | ||||
|  | ||||
| create sequence file_redirection_seq; | ||||
| @@ -484,6 +486,18 @@ create table file_to_post ( | ||||
|     unique(file_id, post_id) | ||||
| ); | ||||
|  | ||||
| create sequence design_seq; | ||||
| create table design ( | ||||
|     id bigint default nextval('design_seq') /* comment 'design ID'*/, | ||||
|     backgroundcolor integer /* comment 'main background color'*/ , | ||||
|     contentcolor integer /*comment 'content area background color'*/ , | ||||
|     sidebarcolor integer /*comment 'sidebar background color'*/ , | ||||
|     textcolor integer /*comment 'text color'*/ , | ||||
|     linkcolor integer /*comment 'link color'*/, | ||||
|     backgroundimage varchar(255) /*comment 'background image, if any'*/, | ||||
|     disposition int default 1 /*comment 'bit 1 = hide background image, bit 2 = display background image, bit 4 = tile background image'*/, | ||||
|     primary key (id) | ||||
| ); | ||||
|  | ||||
| /* Textsearch stuff */ | ||||
|  | ||||
|   | ||||
							
								
								
									
										300
									
								
								doc-src/tos
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								doc-src/tos
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,300 @@ | ||||
| The gist | ||||
| -------- | ||||
|  | ||||
| We (the folks at [%%site.broughtby%%](%%site.broughtbyurl%%)) run a | ||||
| service called %%site.name%% and would love for you to use it. Our | ||||
| service is designed to give you as much control and ownership over | ||||
| what goes in your notice stream as possible and encourage you to | ||||
| express yourself freely. However, be responsible in what you post. In | ||||
| particular, make sure that none of the prohibited items listed below | ||||
| appear in your notice stream or get linked to from your notice stream (things | ||||
| like spam, viruses, or hate content). | ||||
|  | ||||
| You can review our [Public Stream](%%action.public%%) to get a sense | ||||
| of the types of notices that are welcome on our service (or not!). If | ||||
| you find a %%site.name%% account that you believe violates our terms | ||||
| of service, please check our [Contact](%%doc.contact%%) documentation. | ||||
|  | ||||
| (Note: Automattic, Inc., original creators of the below Terms of | ||||
| Service, decided to make them available under a Creative Commons | ||||
| Sharealike license, which means you’re more than welcome to steal it | ||||
| and repurpose it for your own use. Just make sure to replace | ||||
| references to us with ones to you. They’d appreciate a link to | ||||
| [WordPress.com](http://www.wordpress.com/) somewhere on your site. | ||||
| They spent a lot of money and time on the below, and other people | ||||
| shouldn’t need to do the same. (We didn't!)) | ||||
|  | ||||
| Terms of Service | ||||
| ---------------- | ||||
|  | ||||
| The following terms and conditions govern all use of the %%site.name%% | ||||
| website and all content, services and products available at or through | ||||
| the website (taken together, the Website). The Website is owned and | ||||
| operated by %%site.broughtby%% (“Operator”). The Website is offered | ||||
| subject to your acceptance without modification of all of the terms | ||||
| and conditions contained herein and all other operating rules, | ||||
| policies (including, without limitation, Operator’s [Privacy | ||||
| Policy](%%doc.privacy%%)) | ||||
| and procedures that may be published from time to time on this Site by | ||||
| Operator (collectively, the “Agreement”). | ||||
|  | ||||
| Please read this Agreement carefully before accessing or using the | ||||
| Website. By accessing or using any part of the web site, you agree to | ||||
| become bound by the terms and conditions of this agreement. If you do | ||||
| not agree to all the terms and conditions of this agreement, then you | ||||
| may not access the Website or use any services. If these terms and | ||||
| conditions are considered an offer by Operator, acceptance is | ||||
| expressly limited to these terms. The Website is available only to | ||||
| individuals who are at least 13 years old. | ||||
|  | ||||
| <ol> | ||||
|  | ||||
| <li><strong>Your %%site.name%% Account and Site.</strong> If you | ||||
| create a notice stream on the Website, you are responsible for | ||||
| maintaining the security of your account and notice stream, and you | ||||
| are fully responsible for all activities that occur under the account | ||||
| and any other actions taken in connection with the notice stream. You | ||||
| must not describe or assign keywords to your notice stream in a | ||||
| misleading or unlawful manner, including in a manner intended to trade | ||||
| on the name or reputation of others, and Operator may change or remove | ||||
| any description or keyword that it considers inappropriate or | ||||
| unlawful, or otherwise likely to cause Operator liability. You must | ||||
| immediately notify Operator of any unauthorized uses of your notice | ||||
| stream, your account or any other breaches of security. Operator will | ||||
| not be liable for any acts or omissions by You, including any damages | ||||
| of any kind incurred as a result of such acts or omissions.</li> | ||||
|  | ||||
| <li><strong>Responsibility of Contributors.</strong> If you operate a | ||||
| notice stream, comment on a notice stream, post material to the | ||||
| Website, post links on the Website, or otherwise make (or allow any | ||||
| third party to make) material available by means of the Website (any | ||||
| such material, “Content”), You are entirely responsible for the | ||||
| content of, and any harm resulting from, that Content. That is the | ||||
| case regardless of whether the Content in question constitutes text, | ||||
| graphics, an audio file, or computer software. By making Content | ||||
| available, you represent and warrant that: | ||||
|  | ||||
| <ul> | ||||
|  | ||||
| <li>the downloading, copying and use of the Content will not infringe | ||||
| the proprietary rights, including but not limited to the copyright, | ||||
| patent, trademark or trade secret rights, of any third party;</li> | ||||
|  | ||||
| <li>if your employer has rights to intellectual property you create, | ||||
| you have either (i) received permission from your employer to post or | ||||
| make available the Content, including but not limited to any software, | ||||
| or (ii) secured from your employer a waiver as to all rights in or to | ||||
| the Content;</li> | ||||
|  | ||||
| <li>you have fully complied with any third-party licenses | ||||
| relating to the Content, and have done all things necessary to | ||||
| successfully pass through to end users any required terms;</li> | ||||
|  | ||||
| <li>the Content does not contain or install any viruses, worms, malware, | ||||
| Trojan horses or other harmful or destructive content;</li> | ||||
|  | ||||
| <li>the Content is not spam, and does not contain unethical or | ||||
| unwanted commercial content designed to drive traffic to third party | ||||
| sites or boost the search engine rankings of third party sites, or to | ||||
| further unlawful acts (such as phishing) or mislead recipients as to | ||||
| the source of the material (such as spoofing);</li> | ||||
|  | ||||
| <li>if the Content is machine- or randomly-generated, it is for | ||||
| purposes of direct entertainment, information and/or utility for you | ||||
| or other users, and not for spam,</li> | ||||
|  | ||||
| <li>the Content is not libelous or defamatory (more info on | ||||
| what that means), does not contain threats or incite violence towards | ||||
| individuals or entities, and does not violate the privacy or publicity | ||||
| rights of any third party;</li> | ||||
|  | ||||
| <li>your notice stream is not getting advertised via unwanted electronic | ||||
| messages such as spam links on newsgroups, email lists, other notice streams | ||||
| and web sites, and similar unsolicited promotional methods;</li> | ||||
|  | ||||
| <li>your notice stream is not named in a manner that misleads your | ||||
| readers into thinking that you are another person or company. For | ||||
| example, your notice stream’s URL or name is not the name of a person other | ||||
| than yourself or company other than your own; and</li> | ||||
|  | ||||
| <li>you have, in the case of Content that includes computer code, | ||||
| accurately categorized and/or described the type, nature, uses and | ||||
| effects of the materials, whether requested to do so by Operator or | ||||
| otherwise.</li> | ||||
|  | ||||
| </ul> | ||||
|  | ||||
| <p>By submitting Content to Operator for inclusion on your Website, you | ||||
| grant Operator a world-wide, royalty-free, and non-exclusive license | ||||
| to reproduce, modify, adapt and publish the Content solely for the | ||||
| purpose of displaying, distributing and promoting your notice | ||||
| stream.</p> | ||||
|  | ||||
| <p>By submitting Content to Operator for inclusion on your Website, | ||||
| you grant all readers the right to use, re-use, modify and/or | ||||
| re-distribute the Content under the terms of the <a | ||||
| href="%%license.url%%">%%license.title%%</a>.</p> | ||||
|  | ||||
| <p>If you delete Content, Operator will use reasonable efforts to remove it from | ||||
| the Website, but you acknowledge that caching or references to the | ||||
| Content may not be made immediately unavailable.</p> | ||||
|  | ||||
| <p>Without limiting any of those representations or warranties, Operator | ||||
| has the right (though not the obligation) to, in Operator’s sole | ||||
| discretion (i) refuse or remove any content that, in Operator’s | ||||
| reasonable opinion, violates any Operator policy or is in any way | ||||
| harmful or objectionable, or (ii) terminate or deny access to and use | ||||
| of the Website to any individual or entity for any reason, in | ||||
| Operator’s sole discretion.</p> | ||||
| </li> | ||||
|  | ||||
| <li><strong>Responsibility of Website Visitors.</strong> Operator has not reviewed, | ||||
| and cannot review, all of the material, including computer software, | ||||
| posted to the Website, and cannot therefore be responsible for that | ||||
| material’s content, use or effects. By operating the Website, | ||||
| Operator does not represent or imply that it endorses the material | ||||
| there posted, or that it believes such material to be accurate, useful | ||||
| or non-harmful. You are responsible for taking precautions as | ||||
| necessary to protect yourself and your computer systems from viruses, | ||||
| worms, Trojan horses, and other harmful or destructive content. The | ||||
| Website may contain content that is offensive, indecent, or otherwise | ||||
| objectionable, as well as content containing technical inaccuracies, | ||||
| typographical mistakes, and other errors. The Website may also contain | ||||
| material that violates the privacy or publicity rights, or infringes | ||||
| the intellectual property and other proprietary rights, of third | ||||
| parties, or the downloading, copying or use of which is subject to | ||||
| additional terms and conditions, stated or unstated. Operator | ||||
| disclaims any responsibility for any harm resulting from the use by | ||||
| visitors of the Website, or from any downloading by those visitors of | ||||
| content there posted.</li> | ||||
|  | ||||
| <li><strong>Content Posted on Other Websites.</strong> We have not reviewed, and | ||||
| cannot review, all of the material, including computer software, made | ||||
| available through the websites and webpages to which %%site.name%% | ||||
| links, and that link to %%site.name%%. Operator does not have any | ||||
| control over those external websites and webpages, and is not | ||||
| responsible for their contents or their use. By linking to a | ||||
| external website or webpage, Operator does not represent or | ||||
| imply that it endorses such website or webpage. You are responsible | ||||
| for taking precautions as necessary to protect yourself and your | ||||
| computer systems from viruses, worms, Trojan horses, and other harmful | ||||
| or destructive content. Operator disclaims any responsibility for | ||||
| any harm resulting from your use of external websites and | ||||
| webpages.</li> | ||||
|  | ||||
| <li><strong>Copyright Infringement and DMCA Policy.</strong> As Operator asks | ||||
| others to respect its intellectual property rights, it respects the | ||||
| intellectual property rights of others. If you believe that material | ||||
| located on or linked to by %%site.name%% violates your copyright, you | ||||
| are encouraged to notify Operator in accordance with Operator’s | ||||
| Digital Millennium Copyright Act (”DMCA”) Policy. Operator will | ||||
| respond to all such notices, including as required or appropriate by | ||||
| removing the infringing material or disabling all links to the | ||||
| infringing material. In the case of a visitor who may infringe or | ||||
| repeatedly infringes the copyrights or other intellectual property | ||||
| rights of Operator or others, Operator may, in its discretion, | ||||
| terminate or deny access to and use of the Website. In the case of | ||||
| such termination, Operator will have no obligation to provide a | ||||
| refund of any amounts previously paid to Operator.</li> | ||||
|  | ||||
| <li><strong>Intellectual Property.</strong> This Agreement does not | ||||
| transfer from Operator to you any Operator or third party intellectual | ||||
| property, and all right, title and interest in and to such property | ||||
| will remain (as between the parties) solely with Operator. | ||||
| %%site.name%%, the %%site.name%% logo, and all other trademarks, | ||||
| service marks, graphics and logos used in connection with | ||||
| %%site.name%%, or the Website are trademarks or registered trademarks | ||||
| of Operator or Operator’s licensors. Other trademarks, service marks, | ||||
| graphics and logos used in connection with the Website may be the | ||||
| trademarks of other third parties. Your use of the Website grants you | ||||
| no right or license to reproduce or otherwise use any Operator or | ||||
| third-party trademarks.</li> | ||||
|  | ||||
| <li><strong>Changes.</strong> Operator reserves the right, at its sole | ||||
| discretion, to modify or replace any part of this Agreement. It is | ||||
| your responsibility to check this Agreement periodically for changes. | ||||
| Your continued use of or access to the Website following the posting | ||||
| of any changes to this Agreement constitutes acceptance of those | ||||
| changes. Operator may also, in the future, offer new services and/or | ||||
| features through the Website (including, the release of new tools and | ||||
| resources). Such new features and/or services shall be subject to the | ||||
| terms and conditions of this Agreement.</li> | ||||
|  | ||||
| <li><strong>Termination.</strong> Operator may terminate your access | ||||
| to all or any part of the Website at any time, with or without cause, | ||||
| with or without notice, effective immediately. If you wish to | ||||
| terminate this Agreement or your %%site.name%% account (if you have | ||||
| one), you may simply discontinue using the Website. All provisions of | ||||
| this Agreement which by their nature should survive termination shall | ||||
| survive termination, including, without limitation, ownership | ||||
| provisions, warranty disclaimers, indemnity and limitations of | ||||
| liability.</li> | ||||
|  | ||||
| <li><strong>Disclaimer of Warranties.</strong> The Website is provided | ||||
| “as is”. Operator and its suppliers and licensors hereby disclaim all | ||||
| warranties of any kind, express or implied, including, without | ||||
| limitation, the warranties of merchantability, fitness for a | ||||
| particular purpose and non-infringement. Neither Operator nor its | ||||
| suppliers and licensors, makes any warranty that the Website will be | ||||
| error free or that access thereto will be continuous or uninterrupted. | ||||
| If you’re actually reading this, here’s a treat. You understand that | ||||
| you download from, or otherwise obtain content or services through, | ||||
| the Website at your own discretion and risk.</li> | ||||
|  | ||||
| <li><strong>Limitation of Liability.</strong> In no event will | ||||
| Operator, or its suppliers or licensors, be liable with respect to any | ||||
| subject matter of this agreement under any contract, negligence, | ||||
| strict liability or other legal or equitable theory for: (i) any | ||||
| special, incidental or consequential damages; (ii) the cost of | ||||
| procurement or substitute products or services; (iii) for interruption | ||||
| of use or loss or corruption of data; or (iv) for any amounts that | ||||
| exceed the fees paid by you to Operator under this agreement during | ||||
| the twelve (12) month period prior to the cause of action. Operator | ||||
| shall have no liability for any failure or delay due to matters beyond | ||||
| their reasonable control. The foregoing shall not apply to the extent | ||||
| prohibited by applicable law.</li> | ||||
|  | ||||
| <li><strong>General Representation and Warranty.</strong> You | ||||
| represent and warrant that (i) your use of the Website will be in | ||||
| strict accordance with the Operator Privacy Policy, with this | ||||
| Agreement and with all applicable laws and regulations (including | ||||
| without limitation any local laws or regulations in your country, | ||||
| state, city, or other governmental area, regarding online conduct and | ||||
| acceptable content, and including all applicable laws regarding the | ||||
| transmission of technical data exported from the United States or the | ||||
| country in which you reside) and (ii) your use of the Website will not | ||||
| infringe or misappropriate the intellectual property rights of any | ||||
| third party.</li> | ||||
|  | ||||
| <li><strong>Indemnification.</strong> You agree to indemnify and hold | ||||
| harmless Operator, its contractors, and its licensors, and their | ||||
| respective directors, officers, employees and agents from and against | ||||
| any and all claims and expenses, including attorneys’ fees, arising | ||||
| out of your use of the Website, including but not limited to out of | ||||
| your violation this Agreement.</li> | ||||
|  | ||||
| <li><strong>Miscellaneous.</strong> This Agreement constitutes the | ||||
| entire agreement between Operator and you concerning the subject | ||||
| matter hereof, and they may only be modified by a written amendment | ||||
| signed by an authorized executive of Operator, or by the posting by | ||||
| Operator of a revised version. If any part of this Agreement is held | ||||
| invalid or unenforceable, that part will be construed to reflect the | ||||
| parties’ original intent, and the remaining portions will remain in | ||||
| full force and effect. A waiver by either party of any term or | ||||
| condition of this Agreement or any breach thereof, in any one | ||||
| instance, will not waive such term or condition or any subsequent | ||||
| breach thereof. You may assign your rights under this Agreement to any | ||||
| party that consents to, and agrees to be bound by, its terms and | ||||
| conditions; Operator may assign its rights under this Agreement | ||||
| without condition. This Agreement will be binding upon and will inure | ||||
| to the benefit of the parties, their successors and permitted | ||||
| assigns.</li> </ol> | ||||
|  | ||||
| *Originally published by Automattic, Inc. as the [WordPress.com Terms | ||||
| of Service](http://en.wordpress.com/tos/) and made available by them | ||||
| under the [Creative Commons Attribution-ShareAlike 3.0 | ||||
| License](http://creativecommons.org/licenses/by-sa/3.0/). | ||||
| Modifications to remove reference to "VIP services", rename "blog" to | ||||
| "notice stream", remove the choice-of-venue clause, and add variables | ||||
| specific to instances of this software made by Control Yourself, Inc. | ||||
| and made available under the terms of the same license.* | ||||
| @@ -223,6 +223,7 @@ $(document).ready(function(){ | ||||
| 													} | ||||
| 													$("#notice_data-text").val(""); | ||||
|     												$("#notice_data-attach").val(""); | ||||
|     												$("#notice_in-reply-to").val(""); | ||||
|                                                     $('#notice_data-attach_selected').remove(); | ||||
|                                                     counter(); | ||||
| 												} | ||||
| @@ -282,7 +283,7 @@ function NoticeAttachments() { | ||||
|         }, | ||||
|         timeout : 0, | ||||
|         autoHide : true, | ||||
|         css : {'max-width':'502px', 'top':'22.5%', 'left':'32.5%'} | ||||
|         css : {'max-width':'542px', 'top':'22.5%', 'left':'32.5%'} | ||||
|     }; | ||||
|  | ||||
|     $('#content .notice a.attachment').click(function() { | ||||
|   | ||||
| @@ -439,8 +439,6 @@ class Action extends HTMLOutputter // lawsuit | ||||
|                     $this->menuItem(common_local_url('register'), | ||||
|                                     _('Register'), _('Create an account'), false, 'nav_register'); | ||||
|                 } | ||||
|                 $this->menuItem(common_local_url('openidlogin'), | ||||
|                                 _('OpenID'), _('Login with OpenID'), false, 'nav_openid'); | ||||
|                 $this->menuItem(common_local_url('login'), | ||||
|                                 _('Login'), _('Login to the site'), false, 'nav_login'); | ||||
|             } | ||||
| @@ -708,6 +706,11 @@ class Action extends HTMLOutputter // lawsuit | ||||
|                             _('About')); | ||||
|             $this->menuItem(common_local_url('doc', array('title' => 'faq')), | ||||
|                             _('FAQ')); | ||||
|             $bb = common_config('site', 'broughtby'); | ||||
|             if (!empty($bb)) { | ||||
|                 $this->menuItem(common_local_url('doc', array('title' => 'tos')), | ||||
|                                 _('TOS')); | ||||
|             } | ||||
|             $this->menuItem(common_local_url('doc', array('title' => 'privacy')), | ||||
|                             _('Privacy')); | ||||
|             $this->menuItem(common_local_url('doc', array('title' => 'source')), | ||||
| @@ -769,7 +772,9 @@ class Action extends HTMLOutputter // lawsuit | ||||
|         $this->elementStart('p'); | ||||
|         $this->element('img', array('id' => 'license_cc', | ||||
|                                     'src' => common_config('license', 'image'), | ||||
|                                     'alt' => common_config('license', 'title'))); | ||||
|                                     'alt' => common_config('license', 'title'), | ||||
|                                     'width' => '80', | ||||
|                                     'height' => '15')); | ||||
|         //TODO: This is dirty: i18n | ||||
|         $this->text(_('All '.common_config('site', 'name').' content and data are available under the ')); | ||||
|         $this->element('a', array('class' => 'license', | ||||
|   | ||||
| @@ -97,18 +97,11 @@ class StatsCommand extends Command | ||||
| { | ||||
|     function execute($channel) | ||||
|     { | ||||
|         $profile = $this->user->getProfile(); | ||||
|  | ||||
|         $subs = new Subscription(); | ||||
|         $subs->subscriber = $this->user->id; | ||||
|         $subs_count = (int) $subs->count() - 1; | ||||
|  | ||||
|         $subbed = new Subscription(); | ||||
|         $subbed->subscribed = $this->user->id; | ||||
|         $subbed_count = (int) $subbed->count() - 1; | ||||
|  | ||||
|         $notices = new Notice(); | ||||
|         $notices->profile_id = $this->user->id; | ||||
|         $notice_count = (int) $notices->count(); | ||||
|         $subs_count   = $profile->subscriptionCount(); | ||||
|         $subbed_count = $profile->subscriberCount(); | ||||
|         $notice_count = $profile->noticeCount(); | ||||
|  | ||||
|         $channel->output($this->user, sprintf(_("Subscriptions: %1\$s\n". | ||||
|                                    "Subscribers: %2\$s\n". | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|  | ||||
| if (!defined('LACONICA')) { exit(1); } | ||||
|  | ||||
| define('LACONICA_VERSION', '0.8.0dev'); | ||||
| define('LACONICA_VERSION', '0.8.0'); | ||||
|  | ||||
| define('AVATAR_PROFILE_SIZE', 96); | ||||
| define('AVATAR_STREAM_SIZE', 48); | ||||
| @@ -206,7 +206,7 @@ $config = | ||||
|         'inboxes' => | ||||
|         array('enabled' => true), # on by default for new sites | ||||
|         'newuser' => | ||||
|         array('subscribe' => null, | ||||
|         array('default' => null, | ||||
|               'welcome' => null), | ||||
|         'snapshot' => | ||||
|         array('run' => 'web', | ||||
| @@ -282,6 +282,39 @@ if (function_exists('date_default_timezone_set')) { | ||||
|     date_default_timezone_set('UTC'); | ||||
| } | ||||
|  | ||||
| function addPlugin($name, $attrs = null) | ||||
| { | ||||
|     $name = ucfirst($name); | ||||
|     $pluginclass = "{$name}Plugin"; | ||||
|  | ||||
|     if (!class_exists($pluginclass)) { | ||||
|  | ||||
|         $files = array("local/plugins/{$pluginclass}.php", | ||||
|                        "local/plugins/{$name}/{$pluginclass}.php", | ||||
|                        "local/{$pluginclass}.php", | ||||
|                        "local/{$name}/{$pluginclass}.php", | ||||
|                        "plugins/{$pluginclass}.php", | ||||
|                        "plugins/{$name}/{$pluginclass}.php"); | ||||
|  | ||||
|         foreach ($files as $file) { | ||||
|             $fullpath = INSTALLDIR.'/'.$file; | ||||
|             if (@file_exists($fullpath)) { | ||||
|                 include_once($fullpath); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     $inst = new $pluginclass(); | ||||
|  | ||||
|     if (!empty($attrs)) { | ||||
|         foreach ($attrs as $aname => $avalue) { | ||||
|             $inst->$aname = $avalue; | ||||
|         } | ||||
|     } | ||||
|     return $inst; | ||||
| } | ||||
|  | ||||
| // From most general to most specific: | ||||
| // server-wide, then vhost-wide, then for a path, | ||||
| // finally for a dir (usually only need one of the last two). | ||||
|   | ||||
| @@ -53,14 +53,19 @@ class CurrentUserDesignAction extends Action | ||||
|       * | ||||
|       * @return nothing | ||||
|       */ | ||||
|  | ||||
|      function showStylesheets() | ||||
|      { | ||||
|          parent::showStylesheets(); | ||||
|  | ||||
|          $design = $this->getDesign(); | ||||
|          $user = common_current_user(); | ||||
|  | ||||
|          if (!empty($design)) { | ||||
|              $design->showCSS($this); | ||||
|          if (empty($user) || $user->viewdesigns) { | ||||
|              $design = $this->getDesign(); | ||||
|  | ||||
|              if (!empty($design)) { | ||||
|                  $design->showCSS($this); | ||||
|              } | ||||
|          } | ||||
|      } | ||||
|  | ||||
| @@ -84,5 +89,4 @@ class CurrentUserDesignAction extends Action | ||||
|         return $cur->getDesign(); | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
							
								
								
									
										166
									
								
								lib/dbqueuemanager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								lib/dbqueuemanager.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Laconica, the distributed open-source microblogging tool | ||||
|  * | ||||
|  * Simple-minded queue manager for storing items in the database | ||||
|  * | ||||
|  * PHP version 5 | ||||
|  * | ||||
|  * LICENCE: This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * @category  QueueManager | ||||
|  * @package   Laconica | ||||
|  * @author    Evan Prodromou <evan@controlyourself.ca> | ||||
|  * @author    Sarven Capadisli <csarven@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| class DBQueueManager extends QueueManager | ||||
| { | ||||
|     var $qis = array(); | ||||
|  | ||||
|     function enqueue($object, $queue) | ||||
|     { | ||||
|         $notice = $object; | ||||
|  | ||||
|         $qi = new Queue_item(); | ||||
|  | ||||
|         $qi->notice_id = $notice->id; | ||||
|         $qi->transport = $queue; | ||||
|         $qi->created   = $notice->created; | ||||
|         $result        = $qi->insert(); | ||||
|  | ||||
|         if (!$result) { | ||||
|             common_log_db_error($qi, 'INSERT', __FILE__); | ||||
|             throw new ServerException('DB error inserting queue item'); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function service($queue, $handler) | ||||
|     { | ||||
|         while (true) { | ||||
|             $this->_log(LOG_DEBUG, 'Checking for notices...'); | ||||
|             $notice = $this->_nextItem($queue, null); | ||||
|             if (empty($notice)) { | ||||
|                 $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); | ||||
|                 // Nothing in the queue. Do you | ||||
|                 // have other tasks, like servicing your | ||||
|                 // XMPP connection, to do? | ||||
|                 $handler->idle(QUEUE_HANDLER_MISS_IDLE); | ||||
|             } else { | ||||
|                 $this->_log(LOG_INFO, 'Got notice '. $notice->id); | ||||
|                 // Yay! Got one! | ||||
|                 if ($handler->handle_notice($notice)) { | ||||
|                     $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id); | ||||
|                     $this->_done($notice, $queue); | ||||
|                 } else { | ||||
|                     $this->_log(LOG_INFO, 'Failed to handle notice '. $notice->id); | ||||
|                     $this->_fail($notice, $queue); | ||||
|                 } | ||||
|                 // Chance to e.g. service your XMPP connection | ||||
|                 $this->_log(LOG_DEBUG, 'Idling after success.'); | ||||
|                 $handler->idle(QUEUE_HANDLER_HIT_IDLE); | ||||
|             } | ||||
|             // XXX: when do we give up? | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _nextItem($queue, $timeout=null) | ||||
|     { | ||||
|         $start = time(); | ||||
|         $result = null; | ||||
|  | ||||
|         do { | ||||
|             $qi = Queue_item::top($queue); | ||||
|             if (!empty($qi)) { | ||||
|                 $notice = Notice::staticGet('id', $qi->notice_id); | ||||
|                 if (!empty($notice)) { | ||||
|                     $result = $notice; | ||||
|                 } else { | ||||
|                     $this->_log(LOG_INFO, 'dequeued non-existent notice ' . $notice->id); | ||||
|                     $qi->delete(); | ||||
|                     $qi->free(); | ||||
|                     $qi = null; | ||||
|                 } | ||||
|             } | ||||
|         } while (empty($result) && (is_null($timeout) || (time() - $start) < $timeout)); | ||||
|  | ||||
|         return $result; | ||||
|     } | ||||
|  | ||||
|     function _done($object, $queue) | ||||
|     { | ||||
|         // XXX: right now, we only handle notices | ||||
|  | ||||
|         $notice = $object; | ||||
|  | ||||
|         $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, | ||||
|                                         'transport' => $queue)); | ||||
|  | ||||
|         if (empty($qi)) { | ||||
|             $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue); | ||||
|         } else { | ||||
|             if (empty($qi->claimed)) { | ||||
|                 $this->_log(LOG_WARNING, 'Reluctantly releasing unclaimed queue item '. | ||||
|                            'for '.$notice->id.', queue '.$queue); | ||||
|             } | ||||
|             $qi->delete(); | ||||
|             $qi->free(); | ||||
|             $qi = null; | ||||
|         } | ||||
|  | ||||
|         $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id); | ||||
|  | ||||
|         $notice->free(); | ||||
|         $notice = null; | ||||
|     } | ||||
|  | ||||
|     function _fail($object, $queue) | ||||
|     { | ||||
|         // XXX: right now, we only handle notices | ||||
|  | ||||
|         $notice = $object; | ||||
|  | ||||
|         $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, | ||||
|                                         'transport' => $queue)); | ||||
|  | ||||
|         if (empty($qi)) { | ||||
|             $this->_log(LOG_INFO, 'Cannot find queue item for notice '.$notice->id.', queue '.$queue); | ||||
|         } else { | ||||
|             if (empty($qi->claimed)) { | ||||
|                 $this->_log(LOG_WARNING, 'Ignoring failure for unclaimed queue item '. | ||||
|                            'for '.$notice->id.', queue '.$queue); | ||||
|             } else { | ||||
|                 $orig = clone($qi); | ||||
|                 $qi->claimed = null; | ||||
|                 $qi->update($orig); | ||||
|                 $qi = null; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->_log(LOG_INFO, 'done with notice ID = ' . $notice->id); | ||||
|  | ||||
|         $notice->free(); | ||||
|         $notice = null; | ||||
|     } | ||||
|  | ||||
|     function _log($level, $msg) | ||||
|     { | ||||
|         common_log($level, 'DBQueueManager: '.$msg); | ||||
|     } | ||||
| } | ||||
| @@ -460,16 +460,6 @@ class FacebookAction extends Action | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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() | ||||
|     { | ||||
|  | ||||
| @@ -504,7 +494,7 @@ class FacebookAction extends Action | ||||
|         $replyto = $this->trimmed('inreplyto'); | ||||
|  | ||||
|         $notice = Notice::saveNew($user->id, $content, | ||||
|             'Facebook', 1, ($replyto == 'false') ? null : $replyto); | ||||
|             'web', 1, ($replyto == 'false') ? null : $replyto); | ||||
|  | ||||
|         if (is_string($notice)) { | ||||
|             $this->showPage($notice); | ||||
| @@ -514,8 +504,7 @@ class FacebookAction extends Action | ||||
|         common_broadcast_notice($notice); | ||||
|  | ||||
|         // Also update the user's Facebook status | ||||
|         $this->updateFacebookStatus($notice); | ||||
|         $this->updateProfileBox($notice); | ||||
|         facebookBroadcastNotice($notice); | ||||
|  | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -51,6 +51,10 @@ function updateProfileBox($facebook, $flink, $notice) { | ||||
|  | ||||
| function isFacebookBound($notice, $flink) { | ||||
|  | ||||
|     if (empty($flink)) { | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // If the user does not want to broadcast to Facebook, move along | ||||
|     if (!($flink->noticesync & FOREIGN_NOTICE_SEND == FOREIGN_NOTICE_SEND)) { | ||||
|         common_log(LOG_INFO, "Skipping notice $notice->id " . | ||||
| @@ -82,14 +86,18 @@ function isFacebookBound($notice, $flink) { | ||||
|  | ||||
|             // Check to see if the user has given the FB app status update perms | ||||
|             $result = $facebook->api_client-> | ||||
|                 users_hasAppPermission('status_update', $fbuid); | ||||
|                 users_hasAppPermission('publish_stream', $fbuid); | ||||
|  | ||||
|             if ($result != 1) { | ||||
|                 $result = $facebook->api_client-> | ||||
|                     users_hasAppPermission('status_update', $fbuid); | ||||
|             } | ||||
|             if ($result != 1) { | ||||
|                 $user = $flink->getUser(); | ||||
|                 $msg = "Can't send notice $notice->id to Facebook " . | ||||
|                 $msg = "Not sending notice $notice->id to Facebook " . | ||||
|                     "because user $user->nickname hasn't given the " . | ||||
|                     'Facebook app \'status_update\' permission.'; | ||||
|                 common_log(LOG_INFO, $msg); | ||||
|                     'Facebook app \'status_update\' or \'publish_stream\' permission.'; | ||||
|                 common_debug($msg); | ||||
|                 $success = false; | ||||
|             } | ||||
|  | ||||
| @@ -108,13 +116,16 @@ function facebookBroadcastNotice($notice) | ||||
| { | ||||
|     $facebook = getFacebook(); | ||||
|     $flink = Foreign_link::getByUserID($notice->profile_id, FACEBOOK_SERVICE); | ||||
|     $fbuid = $flink->foreign_id; | ||||
|  | ||||
|     if (isFacebookBound($notice, $flink)) { | ||||
|  | ||||
|         $status = null; | ||||
|         $fbuid = $flink->foreign_id; | ||||
|  | ||||
|         $user = $flink->getUser(); | ||||
|  | ||||
|         // Get the status 'verb' (prefix) the user has set | ||||
|  | ||||
|         try { | ||||
|             $prefix = $facebook->api_client-> | ||||
|                 data_getUserPreference(FACEBOOK_NOTICE_PREFIX, $fbuid); | ||||
| @@ -122,23 +133,128 @@ function facebookBroadcastNotice($notice) | ||||
|             $status = "$prefix $notice->content"; | ||||
|  | ||||
|         } catch(FacebookRestClientException $e) { | ||||
|             common_log(LOG_ERR, $e->getMessage()); | ||||
|             return false; | ||||
|             common_log(LOG_WARNING, $e->getMessage()); | ||||
|             common_log(LOG_WARNING, | ||||
|                 'Unable to get the status verb setting from Facebook ' . | ||||
|                 "for $user->nickname (user id: $user->id)."); | ||||
|         } | ||||
|  | ||||
|         // Okay, we're good to go! | ||||
|         // Okay, we're good to go, update the FB status | ||||
|  | ||||
|         try { | ||||
|             $facebook->api_client->users_setStatus($status, $fbuid, false, true); | ||||
|             updateProfileBox($facebook, $flink, $notice); | ||||
|             $result = $facebook->api_client-> | ||||
|                 users_hasAppPermission('publish_stream', $fbuid); | ||||
|             if($result == 1){ | ||||
|                 // authorized to use the stream api, so use it | ||||
|                 $fbattachment = null; | ||||
|                 $attachments = $notice->attachments(); | ||||
|                 if($attachments){ | ||||
|                     $fbattachment=array(); | ||||
|                     $fbattachment['media']=array(); | ||||
|                     //facebook only supports one attachment per item | ||||
|                     $attachment = $attachments[0]; | ||||
|                     $fbmedia=array(); | ||||
|                     if(strncmp($attachment->mimetype,'image/',strlen('image/'))==0){ | ||||
|                         $fbmedia['type']='image'; | ||||
|                         $fbmedia['src']=$attachment->url; | ||||
|                         $fbmedia['href']=$attachment->url; | ||||
|                         $fbattachment['media'][]=$fbmedia; | ||||
| /* Video doesn't seem to work. The notice never makes it to facebook, and no error is reported. | ||||
|                     }else if(strncmp($attachment->mimetype,'video/',strlen('image/'))==0 || $attachment->mimetype="application/ogg"){ | ||||
|                         $fbmedia['type']='video'; | ||||
|                         $fbmedia['video_src']=$attachment->url; | ||||
|                         // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29 | ||||
|                         // says that preview_img is required... but we have no value to put in it | ||||
|                         // $fbmedia['preview_img']=$attachment->url; | ||||
|                         if($attachment->title){ | ||||
|                             $fbmedia['video_title']=$attachment->title; | ||||
|                         } | ||||
|                         $fbmedia['video_type']=$attachment->mimetype; | ||||
|                         $fbattachment['media'][]=$fbmedia; | ||||
| */ | ||||
|                     }else if($attachment->mimetype=='audio/mpeg'){ | ||||
|                         $fbmedia['type']='mp3'; | ||||
|                         $fbmedia['src']=$attachment->url; | ||||
|                         $fbattachment['media'][]=$fbmedia; | ||||
|                     }else if($attachment->mimetype=='application/x-shockwave-flash'){ | ||||
|                         $fbmedia['type']='flash'; | ||||
|                         // http://wiki.developers.facebook.com/index.php/Attachment_%28Streams%29 | ||||
|                         // says that imgsrc is required... but we have no value to put in it | ||||
|                         // $fbmedia['imgsrc']=''; | ||||
|                         $fbmedia['swfsrc']=$attachment->url; | ||||
|                         $fbattachment['media'][]=$fbmedia; | ||||
|                     }else{ | ||||
|                         $fbattachment['name']=($attachment->title?$attachment->title:$attachment->url); | ||||
|                         $fbattachment['href']=$attachment->url; | ||||
|                     } | ||||
|                 } | ||||
|                 $facebook->api_client->stream_publish($status, $fbattachment, null, null, $fbuid); | ||||
|             }else{ | ||||
|                 $facebook->api_client->users_setStatus($status, $fbuid, false, true); | ||||
|             } | ||||
|         } catch(FacebookRestClientException $e) { | ||||
|             common_log(LOG_ERR, $e->getMessage()); | ||||
|             return false; | ||||
|             common_log(LOG_ERR, | ||||
|                 'Unable to update Facebook status for ' . | ||||
|                 "$user->nickname (user id: $user->id)!"); | ||||
|  | ||||
|              // Should we remove flink if this fails? | ||||
|             $code = $e->getCode(); | ||||
|  | ||||
|             if ($code >= 200) { | ||||
|  | ||||
|                 // 200 The application does not have permission to operate on the passed in uid parameter. | ||||
|                 // 250 Updating status requires the extended permission status_update or publish_stream. | ||||
|                 // see: http://wiki.developers.facebook.com/index.php/Users.setStatus#Example_Return_XML | ||||
|  | ||||
|                 remove_facebook_app($flink); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         // Now try to update the profile box | ||||
|  | ||||
|         try { | ||||
|             updateProfileBox($facebook, $flink, $notice); | ||||
|         } catch(FacebookRestClientException $e) { | ||||
|             common_log(LOG_WARNING, $e->getMessage()); | ||||
|             common_log(LOG_WARNING, | ||||
|                 'Unable to update Facebook profile box for ' . | ||||
|                 "$user->nickname (user id: $user->id)."); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function remove_facebook_app($flink) | ||||
| { | ||||
|  | ||||
|     $user = $flink->getUser(); | ||||
|  | ||||
|     common_log(LOG_INFO, 'Removing Facebook App Foreign link for ' . | ||||
|         "user $user->nickname (user id: $user->id)."); | ||||
|  | ||||
|     $result = $flink->delete(); | ||||
|  | ||||
|     if (empty($result)) { | ||||
|         common_log(LOG_ERR, 'Could not remove Facebook App ' . | ||||
|             "Foreign_link for $user->nickname (user id: $user->id)!"); | ||||
|         common_log_db_error($flink, 'DELETE', __FILE__); | ||||
|     } | ||||
|  | ||||
|     // Notify the user that we are removing their FB app access | ||||
|  | ||||
|     $result = mail_facebook_app_removed($user); | ||||
|  | ||||
|     if (!$result) { | ||||
|  | ||||
|         $msg = 'Unable to send email to notify ' . | ||||
|             "$user->nickname (user id: $user->id) " . | ||||
|             'that their Facebook app link was ' . | ||||
|             'removed!'; | ||||
|  | ||||
|         common_log(LOG_WARNING, $msg); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -34,7 +34,7 @@ if (!defined('LACONICA')) { | ||||
| /** | ||||
|  * Base class for actions that use a group's design | ||||
|  * | ||||
|  * Pages related to groups can be themed with a design.  | ||||
|  * Pages related to groups can be themed with a design. | ||||
|  * This superclass returns that design. | ||||
|  * | ||||
|  * @category Action | ||||
| @@ -48,7 +48,7 @@ class GroupDesignAction extends Action { | ||||
|  | ||||
|     /** The group in question */ | ||||
|     var $group = null; | ||||
|      | ||||
|  | ||||
|     /** | ||||
|       * Show the groups's design stylesheet | ||||
|       * | ||||
| @@ -58,10 +58,14 @@ class GroupDesignAction extends Action { | ||||
|      { | ||||
|          parent::showStylesheets(); | ||||
|  | ||||
|          $design = $this->getDesign(); | ||||
|          $user = common_current_user(); | ||||
|  | ||||
|          if (!empty($design)) { | ||||
|              $design->showCSS($this); | ||||
|          if (empty($user) || $user->viewdesigns) { | ||||
|              $design = $this->getDesign(); | ||||
|  | ||||
|              if (!empty($design)) { | ||||
|                  $design->showCSS($this); | ||||
|              } | ||||
|          } | ||||
|      } | ||||
|  | ||||
| @@ -76,12 +80,10 @@ class GroupDesignAction extends Action { | ||||
|  | ||||
|     function getDesign() | ||||
|     { | ||||
|  | ||||
|         if (empty($this->group)) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         return $this->group->getDesign(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -77,6 +77,14 @@ function jabber_daemon_address() | ||||
|     return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server'); | ||||
| } | ||||
|  | ||||
| class Sharing_XMPP extends XMPPHP_XMPP | ||||
| { | ||||
|     function getSocket() | ||||
|     { | ||||
|         return $this->socket; | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * connect the configured Jabber account to the configured server | ||||
|  * | ||||
| @@ -89,7 +97,7 @@ function jabber_connect($resource=null) | ||||
| { | ||||
|     static $conn = null; | ||||
|     if (!$conn) { | ||||
|         $conn = new XMPPHP_XMPP(common_config('xmpp', 'host') ? | ||||
|         $conn = new Sharing_XMPP(common_config('xmpp', 'host') ? | ||||
|                                 common_config('xmpp', 'host') : | ||||
|                                 common_config('xmpp', 'server'), | ||||
|                                 common_config('xmpp', 'port'), | ||||
|   | ||||
							
								
								
									
										72
									
								
								lib/mail.php
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								lib/mail.php
									
									
									
									
									
								
							| @@ -625,3 +625,75 @@ function mail_notify_attn($user, $notice) | ||||
|     common_init_locale(); | ||||
|     mail_to_user($user, $subject, $body); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Send a mail message to notify a user that her Twitter bridge link | ||||
|  * has stopped working, and therefore has been removed.  This can | ||||
|  * happen when the user changes her Twitter password, or otherwise | ||||
|  * revokes access. | ||||
|  * | ||||
|  * @param User $user   user whose Twitter bridge link has been removed | ||||
|  * | ||||
|  * @return boolean success flag | ||||
|  */ | ||||
|  | ||||
| function mail_twitter_bridge_removed($user) | ||||
| { | ||||
|     common_init_locale($user->language); | ||||
|  | ||||
|     $profile = $user->getProfile(); | ||||
|  | ||||
|     $subject = sprintf(_('Your Twitter bridge has been disabled.')); | ||||
|  | ||||
|     $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that your " . | ||||
|         'link to Twitter has been disabled. Your Twitter credentials ' . | ||||
|         'have either changed (did you recently change your Twitter ' . | ||||
|         'password?) or you have otherwise revoked our access to your ' . | ||||
|         "Twitter account.\n\n" . | ||||
|         'You can re-enable your Twitter bridge by visiting your ' . | ||||
|         "Twitter settings page:\n\n\t%2\$s\n\n" . | ||||
|         "Regards,\n%3\$s\n"), | ||||
|         $profile->getBestName(), | ||||
|         common_local_url('twittersettings'), | ||||
|         common_config('site', 'name')); | ||||
|  | ||||
|     common_init_locale(); | ||||
|     return mail_to_user($user, $subject, $body); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Send a mail message to notify a user that her Facebook Application | ||||
|  * access has been removed. | ||||
|  * | ||||
|  * @param User $user   user whose Facebook app link has been removed | ||||
|  * | ||||
|  * @return boolean success flag | ||||
|  */ | ||||
|  | ||||
| function mail_facebook_app_removed($user) | ||||
| { | ||||
|     common_init_locale($user->language); | ||||
|  | ||||
|     $profile = $user->getProfile(); | ||||
|  | ||||
|     $site_name = common_config('site', 'name'); | ||||
|  | ||||
|     $subject = sprintf( | ||||
|         _('Your %s Facebook application access has been disabled.', | ||||
|             $site_name)); | ||||
|  | ||||
|     $body = sprintf(_("Hi, %1\$s. We're sorry to inform you that we are " . | ||||
|         'unable to update your Facebook status from %s, and have disabled ' . | ||||
|         'the Facebook application for your account. This may be because ' . | ||||
|         'you have removed the Facebook application\'s authorization, or ' . | ||||
|         'have deleted your Facebook account.  You can re-enable the ' . | ||||
|         'Facebook application and automatic status updating by ' . | ||||
|         "re-installing the %1\$s Facebook application.\n\nRegards,\n\n%1\$s"), | ||||
|         $site_name); | ||||
|  | ||||
|     common_init_locale(); | ||||
|     return mail_to_user($user, $subject, $body); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -61,11 +61,15 @@ class OwnerDesignAction extends Action { | ||||
|      { | ||||
|          parent::showStylesheets(); | ||||
|  | ||||
|          $design = $this->getDesign(); | ||||
|          $user = common_current_user(); | ||||
|  | ||||
|          if (!empty($design)) { | ||||
|              $design->showCSS($this); | ||||
|         } | ||||
|          if (empty($user) || $user->viewdesigns) { | ||||
|              $design = $this->getDesign(); | ||||
|  | ||||
|              if (!empty($design)) { | ||||
|                  $design->showCSS($this); | ||||
|              } | ||||
|          } | ||||
|      } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -59,7 +59,7 @@ function ping_broadcast_notice($notice) { | ||||
|  | ||||
|             $response = xmlrpc_decode($file); | ||||
|  | ||||
|             if (xmlrpc_is_fault($response)) { | ||||
|             if (is_array($response) && xmlrpc_is_fault($response)) { | ||||
|                 common_log(LOG_WARNING, | ||||
|                            "XML-RPC error for ping ($notify_url, $notice->id) ". | ||||
|                            "$response[faultString] ($response[faultCode])"); | ||||
|   | ||||
| @@ -68,7 +68,7 @@ class PopularNoticeSection extends NoticeSection | ||||
|         } | ||||
|         $qry .= ' GROUP BY notice.id,notice.profile_id,notice.content,notice.uri,' . | ||||
|                 'notice.rendered,notice.url,notice.created,notice.modified,' . | ||||
|                 'notice.reply_to,notice.is_local,notice.source ' . | ||||
|                 'notice.reply_to,notice.is_local,notice.source,notice.conversation ' . | ||||
|                 'ORDER BY weight DESC'; | ||||
|  | ||||
|         $offset = 0; | ||||
|   | ||||
| @@ -163,18 +163,9 @@ class ProfileAction extends OwnerDesignAction | ||||
|  | ||||
|     function showStatistics() | ||||
|     { | ||||
|         // XXX: WORM cache this | ||||
|         $subs = new Subscription(); | ||||
|         $subs->subscriber = $this->profile->id; | ||||
|         $subs_count = (int) $subs->count() - 1; | ||||
|  | ||||
|         $subbed = new Subscription(); | ||||
|         $subbed->subscribed = $this->profile->id; | ||||
|         $subbed_count = (int) $subbed->count() - 1; | ||||
|  | ||||
|         $notices = new Notice(); | ||||
|         $notices->profile_id = $this->profile->id; | ||||
|         $notice_count = (int) $notices->count(); | ||||
|         $subs_count   = $this->profile->subscriptionCount(); | ||||
|         $subbed_count = $this->profile->subscriberCount(); | ||||
|         $notice_count = $this->profile->noticeCount(); | ||||
|  | ||||
|         $this->elementStart('div', array('id' => 'entity_statistics', | ||||
|                                          'class' => 'section')); | ||||
| @@ -199,7 +190,7 @@ class ProfileAction extends OwnerDesignAction | ||||
|                                                              array('nickname' => $this->profile->nickname))), | ||||
|                        _('Subscriptions')); | ||||
|         $this->elementEnd('dt'); | ||||
|         $this->element('dd', null, (is_int($subs_count)) ? $subs_count : '0'); | ||||
|         $this->element('dd', null, $subs_count); | ||||
|         $this->elementEnd('dl'); | ||||
|  | ||||
|         $this->elementStart('dl', 'entity_subscribers'); | ||||
| @@ -208,12 +199,12 @@ class ProfileAction extends OwnerDesignAction | ||||
|                                                              array('nickname' => $this->profile->nickname))), | ||||
|                        _('Subscribers')); | ||||
|         $this->elementEnd('dt'); | ||||
|         $this->element('dd', 'subscribers', (is_int($subbed_count)) ? $subbed_count : '0'); | ||||
|         $this->element('dd', 'subscribers', $subbed_count); | ||||
|         $this->elementEnd('dl'); | ||||
|  | ||||
|         $this->elementStart('dl', 'entity_notices'); | ||||
|         $this->element('dt', null, _('Notices')); | ||||
|         $this->element('dd', null, (is_int($notice_count)) ? $notice_count : '0'); | ||||
|         $this->element('dd', null, $notice_count); | ||||
|         $this->elementEnd('dl'); | ||||
|  | ||||
|         $this->elementEnd('div'); | ||||
|   | ||||
| @@ -94,8 +94,8 @@ class ProfileSection extends Section | ||||
|                                     $profile->fullname : | ||||
|                                     $profile->nickname)); | ||||
|         $this->out->element('span', 'fn nickname', $profile->nickname); | ||||
|         $this->out->elementEnd('span'); | ||||
|         $this->out->elementEnd('a'); | ||||
|         $this->out->elementEnd('span'); | ||||
|         $this->out->elementEnd('td'); | ||||
|         if ($profile->value) { | ||||
|             $this->out->element('td', 'value', $profile->value); | ||||
|   | ||||
| @@ -17,14 +17,16 @@ | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| define('CLAIM_TIMEOUT', 1200); | ||||
|  | ||||
| if (!defined('LACONICA')) { exit(1); } | ||||
|  | ||||
| require_once(INSTALLDIR.'/lib/daemon.php'); | ||||
| require_once(INSTALLDIR.'/classes/Queue_item.php'); | ||||
| require_once(INSTALLDIR.'/classes/Notice.php'); | ||||
|  | ||||
| define('CLAIM_TIMEOUT', 1200); | ||||
| define('QUEUE_HANDLER_MISS_IDLE', 10); | ||||
| define('QUEUE_HANDLER_HIT_IDLE', 0); | ||||
|  | ||||
| class QueueHandler extends Daemon | ||||
| { | ||||
|     var $_id = 'generic'; | ||||
| @@ -38,6 +40,11 @@ class QueueHandler extends Daemon | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function timeout() | ||||
|     { | ||||
|         return 60; | ||||
|     } | ||||
|  | ||||
|     function class_name() | ||||
|     { | ||||
|         return ucfirst($this->transport()) . 'Handler'; | ||||
| @@ -76,110 +83,21 @@ class QueueHandler extends Daemon | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function db_dispatch() { | ||||
|         do { | ||||
|             $qi = Queue_item::top($this->transport()); | ||||
|             if ($qi) { | ||||
|                 $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($qi->created)); | ||||
|                 $notice = Notice::staticGet($qi->notice_id); | ||||
|                 if ($notice) { | ||||
|                     $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id); | ||||
|                     # XXX: what to do if broadcast fails? | ||||
|                     $result = $this->handle_notice($notice); | ||||
|                     if (!$result) { | ||||
|                         $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id); | ||||
|                         $orig = $qi; | ||||
|                         $qi->claimed = null; | ||||
|                         $qi->update($orig); | ||||
|                         $this->log(LOG_WARNING, 'Abandoned claim for notice ID = ' . $notice->id); | ||||
|                         continue; | ||||
|                     } | ||||
|                     $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id); | ||||
|                     $notice->free(); | ||||
|                     unset($notice); | ||||
|                     $notice = null; | ||||
|                 } else { | ||||
|                     $this->log(LOG_WARNING, 'queue item for notice that does not exist'); | ||||
|                 } | ||||
|                 $qi->delete(); | ||||
|                 $qi->free(); | ||||
|                 unset($qi); | ||||
|                 $this->idle(0); | ||||
|             } else { | ||||
|                 $this->clear_old_claims(); | ||||
|                 $this->idle(5); | ||||
|             } | ||||
|         } while (true); | ||||
|     } | ||||
|  | ||||
|     function stomp_dispatch() { | ||||
|  | ||||
|         // use an external message queue system via STOMP | ||||
|         require_once("Stomp.php"); | ||||
|  | ||||
|         $server = common_config('queue','stomp_server'); | ||||
|         $username = common_config('queue', 'stomp_username'); | ||||
|         $password = common_config('queue', 'stomp_password'); | ||||
|  | ||||
|         $con = new Stomp($server); | ||||
|  | ||||
|         if (!$con->connect($username, $password)) { | ||||
|             $this->log(LOG_ERR, 'Failed to connect to queue server'); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $queue_basename = common_config('queue','queue_basename'); | ||||
|         // subscribe to the relevant queue (format: basename-transport) | ||||
|         $con->subscribe('/queue/'.$queue_basename.'-'.$this->transport()); | ||||
|  | ||||
|         do { | ||||
|             $frame = $con->readFrame(); | ||||
|             if ($frame) { | ||||
|                 $this->log(LOG_INFO, 'Got item enqueued '.common_exact_date($frame->headers['created'])); | ||||
|  | ||||
|                 // XXX: Now the queue handler receives only the ID of the | ||||
|                 // notice, and it has to get it from the DB | ||||
|                 // A massive improvement would be avoid DB query by transmitting | ||||
|                 // all the notice details via queue server... | ||||
|                 $notice = Notice::staticGet($frame->body); | ||||
|  | ||||
|                 if ($notice) { | ||||
|                     $this->log(LOG_INFO, 'broadcasting notice ID = ' . $notice->id); | ||||
|                     $result = $this->handle_notice($notice); | ||||
|                     if ($result) { | ||||
|                         // if the msg has been handled positively, ack it | ||||
|                         // and the queue server will remove it from the queue | ||||
|                         $con->ack($frame); | ||||
|                         $this->log(LOG_INFO, 'finished broadcasting notice ID = ' . $notice->id); | ||||
|                     } | ||||
|                     else { | ||||
|                         // no ack | ||||
|                         $this->log(LOG_WARNING, 'Failed broadcast for notice ID = ' . $notice->id); | ||||
|                     } | ||||
|                     $notice->free(); | ||||
|                     unset($notice); | ||||
|                     $notice = null; | ||||
|                 } else { | ||||
|                     $this->log(LOG_WARNING, 'queue item for notice that does not exist'); | ||||
|                 } | ||||
|             } | ||||
|         } while (true); | ||||
|  | ||||
|         $con->disconnect(); | ||||
|     } | ||||
|  | ||||
|     function run() | ||||
|     { | ||||
|         if (!$this->start()) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         $this->log(LOG_INFO, 'checking for queued notices'); | ||||
|         if (common_config('queue','subsystem') == 'stomp') { | ||||
|             $this->stomp_dispatch(); | ||||
|         } | ||||
|         else { | ||||
|             $this->db_dispatch(); | ||||
|         } | ||||
|  | ||||
|         $queue   = $this->transport(); | ||||
|         $timeout = $this->timeout(); | ||||
|  | ||||
|         $qm = QueueManager::get(); | ||||
|  | ||||
|         $qm->service($queue, $this); | ||||
|  | ||||
|         if (!$this->finish()) { | ||||
|             return false; | ||||
|         } | ||||
| @@ -188,24 +106,19 @@ class QueueHandler extends Daemon | ||||
|  | ||||
|     function idle($timeout=0) | ||||
|     { | ||||
|         if ($timeout>0) { | ||||
|         if ($timeout > 0) { | ||||
|             sleep($timeout); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function clear_old_claims() | ||||
|     { | ||||
|         $qi = new Queue_item(); | ||||
|         $qi->transport = $this->transport(); | ||||
|         $qi->whereAdd('now() - claimed > '.CLAIM_TIMEOUT); | ||||
|         $qi->update(DB_DATAOBJECT_WHEREADD_ONLY); | ||||
|         $qi->free(); | ||||
|         unset($qi); | ||||
|     } | ||||
|  | ||||
|     function log($level, $msg) | ||||
|     { | ||||
|         common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg); | ||||
|     } | ||||
|  | ||||
|     function getSockets() | ||||
|     { | ||||
|         return array(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										74
									
								
								lib/queuemanager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								lib/queuemanager.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Laconica, the distributed open-source microblogging tool | ||||
|  * | ||||
|  * Abstract class for queue managers | ||||
|  * | ||||
|  * PHP version 5 | ||||
|  * | ||||
|  * LICENCE: This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * @category  QueueManager | ||||
|  * @package   Laconica | ||||
|  * @author    Evan Prodromou <evan@controlyourself.ca> | ||||
|  * @author    Sarven Capadisli <csarven@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| class QueueManager | ||||
| { | ||||
|     static $qm = null; | ||||
|  | ||||
|     static function get() | ||||
|     { | ||||
|         if (empty(self::$qm)) { | ||||
|  | ||||
|             if (Event::handle('StartNewQueueManager', array(&self::$qm))) { | ||||
|  | ||||
|                 $enabled = common_config('queue', 'enabled'); | ||||
|                 $type = common_config('queue', 'subsystem'); | ||||
|  | ||||
|                 if (!$enabled) { | ||||
|                     // does everything immediately | ||||
|                     self::$qm = new UnQueueManager(); | ||||
|                 } else { | ||||
|                     switch ($type) { | ||||
|                      case 'db': | ||||
|                         self::$qm = new DBQueueManager(); | ||||
|                         break; | ||||
|                      case 'stomp': | ||||
|                         self::$qm = new StompQueueManager(); | ||||
|                         break; | ||||
|                      default: | ||||
|                         throw new ServerException("No queue manager class for type '$type'"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return self::$qm; | ||||
|     } | ||||
|  | ||||
|     function enqueue($object, $queue) | ||||
|     { | ||||
|         throw ServerException("Unimplemented function 'enqueue' called"); | ||||
|     } | ||||
|  | ||||
|     function service($queue, $handler) | ||||
|     { | ||||
|         throw ServerException("Unimplemented function 'service' called"); | ||||
|     } | ||||
| } | ||||
| @@ -261,12 +261,12 @@ class Router | ||||
|         $m->connect('api/statuses/:method', | ||||
|                     array('action' => 'api', | ||||
|                           'apiaction' => 'statuses'), | ||||
|                     array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|friends|followers|featured)(\.(atom|rss|xml|json))?')); | ||||
|                     array('method' => '(public_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); | ||||
|  | ||||
|         $m->connect('api/statuses/:method/:argument', | ||||
|                     array('action' => 'api', | ||||
|                           'apiaction' => 'statuses'), | ||||
|                     array('method' => '(user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); | ||||
|                     array('method' => '(|user_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); | ||||
|  | ||||
|         // users | ||||
|  | ||||
| @@ -394,6 +394,15 @@ class Router | ||||
|                     array('action' => 'api', | ||||
|                           'apiaction' => 'laconica')); | ||||
|  | ||||
|         // Groups | ||||
|         $m->connect('api/laconica/groups/:method/:argument', | ||||
|                     array('action' => 'api', | ||||
|                           'apiaction' => 'groups')); | ||||
|  | ||||
|         $m->connect('api/laconica/groups/:method', | ||||
|                     array('action' => 'api', | ||||
|                           'apiaction' => 'groups')); | ||||
|  | ||||
|         // search | ||||
|         $m->connect('api/search.atom', array('action' => 'twitapisearchatom')); | ||||
|         $m->connect('api/search.json', array('action' => 'twitapisearchjson')); | ||||
|   | ||||
| @@ -216,6 +216,13 @@ class Rss10Action extends Action | ||||
|             $replyurl = common_local_url('shownotice', array('notice' => $notice->reply_to)); | ||||
|             $this->element('sioc:reply_of', array('rdf:resource' => $replyurl)); | ||||
|         } | ||||
|         $attachments = $notice->attachments(); | ||||
|         if($attachments){ | ||||
|             foreach($attachments as $attachment){ | ||||
|                 $this->element('enc:enclosure', array('rdf:resource'=>$attachment->url,'enc:type'=>$attachment->mimetype,'enc:length'=>$attachment->size), null); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->elementEnd('item'); | ||||
|         $this->creators[$creator_uri] = $profile; | ||||
|     } | ||||
| @@ -251,6 +258,8 @@ class Rss10Action extends Action | ||||
|                                               'http://creativecommons.org/ns#', | ||||
|                                               'xmlns:content' => | ||||
|                                               'http://purl.org/rss/1.0/modules/content/', | ||||
|                                               'xmlns:enc' => | ||||
|                                               'http://purl.oclc.org/net/rss_2.0/enc#', | ||||
|                                               'xmlns:foaf' => | ||||
|                                               'http://xmlns.com/foaf/0.1/', | ||||
|                                               'xmlns:sioc' => | ||||
|   | ||||
							
								
								
									
										169
									
								
								lib/stompqueuemanager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								lib/stompqueuemanager.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Laconica, the distributed open-source microblogging tool | ||||
|  * | ||||
|  * Abstract class for queue managers | ||||
|  * | ||||
|  * PHP version 5 | ||||
|  * | ||||
|  * LICENCE: This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * @category  QueueManager | ||||
|  * @package   Laconica | ||||
|  * @author    Evan Prodromou <evan@controlyourself.ca> | ||||
|  * @author    Sarven Capadisli <csarven@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| require_once 'Stomp.php'; | ||||
|  | ||||
| class LiberalStomp extends Stomp | ||||
| { | ||||
|     function getSocket() | ||||
|     { | ||||
|         return $this->_socket; | ||||
|     } | ||||
| } | ||||
|  | ||||
| class StompQueueManager | ||||
| { | ||||
|     var $server = null; | ||||
|     var $username = null; | ||||
|     var $password = null; | ||||
|     var $base = null; | ||||
|     var $con = null; | ||||
|  | ||||
|     function __construct() | ||||
|     { | ||||
|         $this->server   = common_config('queue', 'stomp_server'); | ||||
|         $this->username = common_config('queue', 'stomp_username'); | ||||
|         $this->password = common_config('queue', 'stomp_password'); | ||||
|         $this->base     = common_config('queue', 'queue_basename'); | ||||
|     } | ||||
|  | ||||
|     function _connect() | ||||
|     { | ||||
|         if (empty($this->con)) { | ||||
|             $this->_log(LOG_INFO, "Connecting to '$this->server' as '$this->username'..."); | ||||
|             $this->con = new LiberalStomp($this->server); | ||||
|  | ||||
|             if ($this->con->connect($this->username, $this->password)) { | ||||
|                 $this->_log(LOG_INFO, "Connected."); | ||||
|             } else { | ||||
|                 $this->_log(LOG_ERR, 'Failed to connect to queue server'); | ||||
|                 throw new ServerException('Failed to connect to queue server'); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function enqueue($object, $queue) | ||||
|     { | ||||
|         $notice = $object; | ||||
|  | ||||
|         $this->_connect(); | ||||
|  | ||||
|         // XXX: serialize and send entire notice | ||||
|  | ||||
|         $result = $this->con->send($this->_queueName($queue), | ||||
|                                    $notice->id, 		// BODY of the message | ||||
|                                    array ('created' => $notice->created)); | ||||
|  | ||||
|         if (!$result) { | ||||
|             common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' | ||||
|                    . $notice->id . ' for ' . $queue); | ||||
|     } | ||||
|  | ||||
|     function service($queue, $handler) | ||||
|     { | ||||
|         $result = null; | ||||
|  | ||||
|         $this->_connect(); | ||||
|  | ||||
|         $this->con->setReadTimeout($handler->timeout()); | ||||
|  | ||||
|         $this->con->subscribe($this->_queueName($queue)); | ||||
|  | ||||
|         while (true) { | ||||
|  | ||||
|             // Wait for something on one of our sockets | ||||
|  | ||||
|             $stompsock = $this->con->getSocket(); | ||||
|  | ||||
|             $handsocks = $handler->getSockets(); | ||||
|  | ||||
|             $socks = array_merge(array($stompsock), $handsocks); | ||||
|  | ||||
|             $read = $socks; | ||||
|             $write = array(); | ||||
|             $except = array(); | ||||
|  | ||||
|             $ready = stream_select($read, $write, $except, $handler->timeout(), 0); | ||||
|  | ||||
|             if ($ready === false) { | ||||
|                 $this->_log(LOG_ERR, "Error selecting on sockets"); | ||||
|             } else if ($ready > 0) { | ||||
|                 if (in_array($stompsock, $read)) { | ||||
|                     $this->_handleNotice($queue, $handler); | ||||
|                 } | ||||
|                 $handler->idle(QUEUE_HANDLER_HIT_IDLE); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->con->unsubscribe($this->_queueName($queue)); | ||||
|     } | ||||
|  | ||||
|     function _handleNotice($queue, $handler) | ||||
|     { | ||||
|         $frame = $this->con->readFrame(); | ||||
|  | ||||
|         if (!empty($frame)) { | ||||
|             $notice = Notice::staticGet('id', $frame->body); | ||||
|  | ||||
|             if (empty($notice)) { | ||||
|                 $this->_log(LOG_WARNING, 'Got ID '. $frame->body .' for non-existent notice in queue '. $queue); | ||||
|                 $this->con->ack($frame); | ||||
|             } else { | ||||
|                 if ($handler->handle_notice($notice)) { | ||||
|                     $this->_log(LOG_INFO, 'Successfully handled notice '. $notice->id .' posted at ' . $frame->headers['created'] . ' in queue '. $queue); | ||||
|                     $this->con->ack($frame); | ||||
|                 } else { | ||||
|                     $this->_log(LOG_WARNING, 'Failed handling notice '. $notice->id .' posted at ' . $frame->headers['created']  . ' in queue '. $queue); | ||||
|                     // FIXME we probably shouldn't have to do | ||||
|                     // this kind of queue management ourselves | ||||
|                     $this->con->ack($frame); | ||||
|                     $this->enqueue($notice, $queue); | ||||
|                 } | ||||
|                 unset($notice); | ||||
|             } | ||||
|  | ||||
|             unset($frame); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _queueName($queue) | ||||
|     { | ||||
|         return common_config('queue', 'queue_basename') . $queue; | ||||
|     } | ||||
|  | ||||
|     function _log($level, $msg) | ||||
|     { | ||||
|         common_log($level, 'StompQueueManager: '.$msg); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										13
									
								
								lib/subs.php
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								lib/subs.php
									
									
									
									
									
								
							| @@ -44,7 +44,6 @@ function subs_subscribe_user($user, $other_nickname) | ||||
|  | ||||
| function subs_subscribe_to($user, $other) | ||||
| { | ||||
|  | ||||
|     if ($user->isSubscribed($other)) { | ||||
|         return _('Already subscribed!.'); | ||||
|     } | ||||
| @@ -60,12 +59,16 @@ function subs_subscribe_to($user, $other) | ||||
|  | ||||
|     subs_notify($other, $user); | ||||
|  | ||||
|         $cache = common_memcache(); | ||||
|     $cache = common_memcache(); | ||||
|  | ||||
|     if ($cache) { | ||||
|         $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); | ||||
| 	} | ||||
|  | ||||
|     $profile = $user->getProfile(); | ||||
|  | ||||
|     $profile->blowSubscriptionsCount(); | ||||
|     $other->blowSubscribersCount(); | ||||
|  | ||||
|     if ($other->autosubscribe && !$other->isSubscribed($user) && !$user->hasBlocked($other)) { | ||||
|         if (!$other->subscribeTo($user)) { | ||||
| @@ -117,7 +120,6 @@ function subs_unsubscribe_user($user, $other_nickname) | ||||
|  | ||||
| function subs_unsubscribe_to($user, $other) | ||||
| { | ||||
|  | ||||
|     if (!$user->isSubscribed($other)) | ||||
|         return _('Not subscribed!.'); | ||||
|  | ||||
| @@ -139,6 +141,11 @@ function subs_unsubscribe_to($user, $other) | ||||
|         $cache->delete(common_cache_key('user:notices_with_friends:' . $user->id)); | ||||
| 	} | ||||
|  | ||||
|     $profile = $user->getProfile(); | ||||
|  | ||||
|     $profile->blowSubscriptionsCount(); | ||||
|     $other->blowSubscribersCount(); | ||||
|  | ||||
|     return true; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -360,14 +360,11 @@ function is_twitter_bound($notice, $flink) { | ||||
|  | ||||
| function broadcast_twitter($notice) | ||||
| { | ||||
|     $success = true; | ||||
|  | ||||
|     $flink = Foreign_link::getByUserID($notice->profile_id, | ||||
|         TWITTER_SERVICE); | ||||
|  | ||||
|     // XXX: Not sure WHERE to check whether a notice should go to | ||||
|     // Twitter. Should we even put in the queue if it shouldn't? --Zach | ||||
|     if (!is_null($flink) && is_twitter_bound($notice, $flink)) { | ||||
|     if (is_twitter_bound($notice, $flink)) { | ||||
|  | ||||
|         $fuser = $flink->getForeignUser(); | ||||
|         $twitter_user = $fuser->nickname; | ||||
| @@ -401,33 +398,99 @@ function broadcast_twitter($notice) | ||||
|         curl_setopt_array($ch, $options); | ||||
|         $data = curl_exec($ch); | ||||
|         $errmsg = curl_error($ch); | ||||
|         $errno = curl_errno($ch); | ||||
|  | ||||
|         if ($errmsg) { | ||||
|             common_debug("cURL error: $errmsg - " . | ||||
|         if (!empty($errmsg)) { | ||||
|             common_debug("cURL error ($errno): $errmsg - " . | ||||
|                 "trying to send notice for $twitter_user.", | ||||
|                          __FILE__); | ||||
|             $success = false; | ||||
|  | ||||
|             $user = $flink->getUser(); | ||||
|  | ||||
|             if ($errmsg == 'The requested URL returned error: 401') { | ||||
|                 common_debug(sprintf('User %s (user id: %s) ' . | ||||
|                     'has bad Twitter credentials!', | ||||
|                     $user->nickname, $user->id)); | ||||
|  | ||||
|                     // Bad credentials we need to delete the foreign_link | ||||
|                     // to Twitter and inform the user. | ||||
|  | ||||
|                     remove_twitter_link($flink); | ||||
|  | ||||
|                     return true; | ||||
|  | ||||
|             } else { | ||||
|  | ||||
|                 // Some other error happened, so we should try to | ||||
|                 // send again later | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         curl_close($ch); | ||||
|  | ||||
|         if (!$data) { | ||||
|         if (empty($data)) { | ||||
|             common_debug("No data returned by Twitter's " . | ||||
|                 "API trying to send update for $twitter_user", | ||||
|                          __FILE__); | ||||
|             $success = false; | ||||
|         } | ||||
|  | ||||
|         // Twitter should return a status | ||||
|         $status = json_decode($data); | ||||
|             // XXX: Not sure this represents a failure to send, but it | ||||
|             // probably does | ||||
|  | ||||
|         if (!$status->id) { | ||||
|             common_debug("Unexpected data returned by Twitter " . | ||||
|                 " API trying to send update for $twitter_user", | ||||
|                          __FILE__); | ||||
|             $success = false; | ||||
|             return false; | ||||
|  | ||||
|         } else { | ||||
|  | ||||
|             // Twitter should return a status | ||||
|             $status = json_decode($data); | ||||
|  | ||||
|             if (empty($status)) { | ||||
|                 common_debug("Unexpected data returned by Twitter " . | ||||
|                     " API trying to send update for $twitter_user", | ||||
|                         __FILE__); | ||||
|  | ||||
|                 // XXX: Again, this could represent a failure posting | ||||
|                 // or the Twitter API might just be behaving flakey. | ||||
|                 // We're treating it as a failure to post. | ||||
|  | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return $success; | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function remove_twitter_link($flink) | ||||
| { | ||||
|     $user = $flink->getUser(); | ||||
|  | ||||
|     common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' . | ||||
|         "user $user->nickname (user id: $user->id)."); | ||||
|  | ||||
|     $result = $flink->delete(); | ||||
|  | ||||
|     if (empty($result)) { | ||||
|         common_log(LOG_ERR, 'Could not remove Twitter bridge ' . | ||||
|             "Foreign_link for $user->nickname (user id: $user->id)!"); | ||||
|         common_log_db_error($flink, 'DELETE', __FILE__); | ||||
|     } | ||||
|  | ||||
|     // Notify the user that her Twitter bridge is down | ||||
|  | ||||
|     $result = mail_twitter_bridge_removed($user); | ||||
|  | ||||
|     if (!$result) { | ||||
|  | ||||
|         $msg = 'Unable to send email to notify ' . | ||||
|             "$user->nickname (user id: $user->id) " . | ||||
|             'that their Twitter bridge link was ' . | ||||
|             'removed!'; | ||||
|  | ||||
|         common_log(LOG_WARNING, $msg); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -89,7 +89,7 @@ class TwitterapiAction extends Action | ||||
|  | ||||
|         $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); | ||||
|         $twitter_user['followers_count'] = $profile->subscriberCount(); | ||||
|  | ||||
|         // To be supported soon... | ||||
|         $twitter_user['profile_background_color'] = ''; | ||||
| @@ -98,17 +98,11 @@ class TwitterapiAction extends Action | ||||
|         $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['friends_count'] = $profile->subscriptionCount(); | ||||
|  | ||||
|         $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! | ||||
|         $twitter_user['favourites_count'] = $profile->faveCount(); // British spelling! | ||||
|  | ||||
|         // Need to pull up the user for some of this | ||||
|         $user = User::staticGet($profile->id); | ||||
| @@ -129,11 +123,7 @@ class TwitterapiAction extends Action | ||||
|         $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; | ||||
|         $twitter_user['statuses_count'] = $profile->noticeCount(); | ||||
|  | ||||
|         // Is the requesting user following this user? | ||||
|         $twitter_user['following'] = false; | ||||
| @@ -207,7 +197,6 @@ class TwitterapiAction extends Action | ||||
|  | ||||
|     function twitter_rss_entry_array($notice) | ||||
|     { | ||||
|  | ||||
|         $profile = $notice->getProfile(); | ||||
|         $entry = array(); | ||||
|  | ||||
| @@ -224,6 +213,19 @@ class TwitterapiAction extends Action | ||||
|         $entry['updated'] = $entry['published']; | ||||
|         $entry['author'] = $profile->getBestName(); | ||||
|  | ||||
|         # Enclosure | ||||
|         $attachments = $notice->attachments(); | ||||
|         if($attachments){ | ||||
|             $entry['enclosures']=array(); | ||||
|             foreach($attachments as $attachment){ | ||||
|                 $enclosure=array(); | ||||
|                 $enclosure['url']=$attachment->url; | ||||
|                 $enclosure['mimetype']=$attachment->mimetype; | ||||
|                 $enclosure['size']=$attachment->size; | ||||
|                 $entry['enclosures'][]=$enclosure; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         # RSS Item specific | ||||
|         $entry['description'] = $entry['content']; | ||||
|         $entry['pubDate'] = common_date_rfc2822($notice->created); | ||||
| @@ -378,6 +380,13 @@ class TwitterapiAction extends Action | ||||
|         $this->element('pubDate', null, $entry['pubDate']); | ||||
|         $this->element('guid', null, $entry['guid']); | ||||
|         $this->element('link', null, $entry['link']); | ||||
|  | ||||
|         # RSS only supports 1 enclosure per item | ||||
|         if($entry['enclosures']){ | ||||
|             $enclosure = $entry['enclosures'][0]; | ||||
|             $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); | ||||
|         } | ||||
|  | ||||
|         $this->elementEnd('item'); | ||||
|     } | ||||
|  | ||||
| @@ -765,6 +774,34 @@ class TwitterapiAction extends Action | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function get_group($id, $apidata=null) | ||||
|     { | ||||
|         if (empty($id)) { | ||||
|  | ||||
|             if (is_numeric($this->arg('id'))) { | ||||
|                 return User_group::staticGet($this->arg('id')); | ||||
|             } else if ($this->arg('id')) { | ||||
|                 $nickname = common_canonical_nickname($this->arg('id')); | ||||
|                 return User_group::staticGet('nickname', $nickname); | ||||
|             } else if ($this->arg('group_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('group_id'))) { | ||||
|                     return User_group::staticGet('id', $this->arg('group_id')); | ||||
|                 } | ||||
|             } else if ($this->arg('group_name')) { | ||||
|                 $nickname = common_canonical_nickname($this->arg('group_name')); | ||||
|                 return User_group::staticGet('nickname', $nickname); | ||||
|             } | ||||
|  | ||||
|         } else if (is_numeric($id)) { | ||||
|             return User_group::staticGet($id); | ||||
|         } else { | ||||
|             $nickname = common_canonical_nickname($id); | ||||
|             return User_group::staticGet('nickname', $nickname); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function get_profile($id) | ||||
|     { | ||||
|         if (is_numeric($id)) { | ||||
|   | ||||
							
								
								
									
										85
									
								
								lib/unqueuemanager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								lib/unqueuemanager.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| <?php | ||||
| /** | ||||
|  * Laconica, the distributed open-source microblogging tool | ||||
|  * | ||||
|  * A queue manager interface for just doing things immediately | ||||
|  * | ||||
|  * PHP version 5 | ||||
|  * | ||||
|  * LICENCE: This program is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as published by | ||||
|  * the Free Software Foundation, either version 3 of the License, or | ||||
|  * (at your option) any later version. | ||||
|  * | ||||
|  * This program is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  * @category  QueueManager | ||||
|  * @package   Laconica | ||||
|  * @author    Evan Prodromou <evan@controlyourself.ca> | ||||
|  * @author    Sarven Capadisli <csarven@controlyourself.ca> | ||||
|  * @copyright 2009 Control Yourself, Inc. | ||||
|  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 | ||||
|  * @link      http://laconi.ca/ | ||||
|  */ | ||||
|  | ||||
| class UnQueueManager | ||||
| { | ||||
|     function enqueue($object, $queue) | ||||
|     { | ||||
|         $notice = $object; | ||||
|  | ||||
|         switch ($queue) | ||||
|         { | ||||
|          case 'omb': | ||||
|             if ($this->_isLocal($notice)) { | ||||
|                 require_once(INSTALLDIR.'/lib/omb.php'); | ||||
|                 omb_broadcast_remote_subscribers($notice); | ||||
|             } | ||||
|             break; | ||||
|          case 'public': | ||||
|             if ($this->_isLocal($notice)) { | ||||
|                 require_once(INSTALLDIR.'/lib/jabber.php'); | ||||
|                 jabber_public_notice($notice); | ||||
|             } | ||||
|             break; | ||||
|          case 'twitter': | ||||
|             if ($this->_isLocal($notice)) { | ||||
|                 broadcast_twitter($notice); | ||||
|             } | ||||
|             break; | ||||
|          case 'facebook': | ||||
|             if ($this->_isLocal($notice)) { | ||||
|                 require_once INSTALLDIR . '/lib/facebookutil.php'; | ||||
|                 return facebookBroadcastNotice($notice); | ||||
|             } | ||||
|             break; | ||||
|          case 'ping': | ||||
|             if ($this->_isLocal($notice)) { | ||||
|                 require_once INSTALLDIR . '/lib/ping.php'; | ||||
|                 return ping_broadcast_notice($notice); | ||||
|             } | ||||
|          case 'sms': | ||||
|             require_once(INSTALLDIR.'/lib/mail.php'); | ||||
|             mail_broadcast_notice_sms($notice); | ||||
|             break; | ||||
|          case 'jabber': | ||||
|             require_once(INSTALLDIR.'/lib/jabber.php'); | ||||
|             jabber_broadcast_notice($notice); | ||||
|             break; | ||||
|          default: | ||||
|             throw ServerException("UnQueueManager: Unknown queue: $type"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function _isLocal($notice) | ||||
|     { | ||||
|         return ($notice->is_local == NOTICE_LOCAL_PUBLIC || | ||||
|                 $notice->is_local == NOTICE_LOCAL_NONPUBLIC); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										163
									
								
								lib/util.php
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								lib/util.php
									
									
									
									
									
								
							| @@ -862,165 +862,45 @@ function common_redirect($url, $code=307) | ||||
|  | ||||
| function common_broadcast_notice($notice, $remote=false) | ||||
| { | ||||
|     if (common_config('queue', 'enabled')) { | ||||
|         // Do it later! | ||||
|         return common_enqueue_notice($notice); | ||||
|     } else { | ||||
|         return common_real_broadcast($notice, $remote); | ||||
|     } | ||||
|     return common_enqueue_notice($notice); | ||||
| } | ||||
|  | ||||
| // Stick the notice on the queue | ||||
|  | ||||
| function common_enqueue_notice($notice) | ||||
| { | ||||
|     $transports = array('omb', 'sms', 'public', 'twitter', 'facebook', 'ping'); | ||||
|     static $localTransports = array('omb', | ||||
|                                     'twitter', | ||||
|                                     'facebook', | ||||
|                                     'ping'); | ||||
|     static $allTransports = array('sms'); | ||||
|  | ||||
|     if (common_config('xmpp', 'enabled')) | ||||
|     { | ||||
|     $transports = $allTransports; | ||||
|  | ||||
|     $xmpp = common_config('xmpp', 'enabled'); | ||||
|  | ||||
|     if ($xmpp) { | ||||
|         $transports[] = 'jabber'; | ||||
|     } | ||||
|  | ||||
|     if (common_config('queue','subsystem') == 'stomp') { | ||||
|         common_enqueue_notice_stomp($notice, $transports); | ||||
|     } | ||||
|     else { | ||||
|         common_enqueue_notice_db($notice, $transports); | ||||
|     } | ||||
|     return $result; | ||||
| } | ||||
|  | ||||
| function common_enqueue_notice_stomp($notice, $transports) | ||||
| { | ||||
|     // use an external message queue system via STOMP | ||||
|     require_once("Stomp.php"); | ||||
|  | ||||
|     $server = common_config('queue','stomp_server'); | ||||
|     $username = common_config('queue', 'stomp_username'); | ||||
|     $password = common_config('queue', 'stomp_password'); | ||||
|  | ||||
|     $con = new Stomp($server); | ||||
|  | ||||
|     if (!$con->connect($username, $password)) { | ||||
|         common_log(LOG_ERR, 'Failed to connect to queue server'); | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     $queue_basename = common_config('queue','queue_basename'); | ||||
|  | ||||
|     foreach ($transports as $transport) { | ||||
|         $result = $con->send('/queue/'.$queue_basename.'-'.$transport, // QUEUE | ||||
|                              $notice->id, 		// BODY of the message | ||||
|                              array ('created' => $notice->created)); | ||||
|         if (!$result) { | ||||
|             common_log(LOG_ERR, 'Error sending to '.$transport.' queue'); | ||||
|             return false; | ||||
|         } | ||||
|         common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' . $notice->id . ' for ' . $transport); | ||||
|     } | ||||
|  | ||||
|     //send tags as headers, so they can be used as JMS selectors | ||||
|     common_log(LOG_DEBUG, 'searching for tags ' . $notice->id); | ||||
|     $tags = array(); | ||||
|     $tag = new Notice_tag(); | ||||
|     $tag->notice_id = $notice->id; | ||||
|     if ($tag->find()) { | ||||
|         while ($tag->fetch()) { | ||||
|             common_log(LOG_DEBUG, 'tag found = ' . $tag->tag); | ||||
|             array_push($tags,$tag->tag); | ||||
|     if ($notice->is_local == NOTICE_LOCAL_PUBLIC || | ||||
|         $notice->is_local == NOTICE_LOCAL_NONPUBLIC) { | ||||
|         $transports = array_merge($transports, $localTransports); | ||||
|         if ($xmpp) { | ||||
|             $transports[] = 'public'; | ||||
|         } | ||||
|     } | ||||
|     $tag->free(); | ||||
|  | ||||
|     $con->send('/topic/laconica.'.$notice->profile_id, | ||||
|                $notice->content, | ||||
|                array( | ||||
|                      'profile_id' => $notice->profile_id, | ||||
|                      'created' => $notice->created, | ||||
|                      'tags' => implode($tags,' - ') | ||||
|                      ) | ||||
|                ); | ||||
|     common_log(LOG_DEBUG, 'sent to personal topic ' . $notice->id); | ||||
|     $con->send('/topic/laconica.allusers', | ||||
|                $notice->content, | ||||
|                array( | ||||
|                      'profile_id' => $notice->profile_id, | ||||
|                      'created' => $notice->created, | ||||
|                      'tags' => implode($tags,' - ') | ||||
|                      ) | ||||
|                ); | ||||
|     common_log(LOG_DEBUG, 'sent to catch-all topic ' . $notice->id); | ||||
|     $result = true; | ||||
| } | ||||
|     $qm = QueueManager::get(); | ||||
|  | ||||
| function common_enqueue_notice_db($notice, $transports) | ||||
| { | ||||
|     // in any other case, 'internal' | ||||
|     foreach ($transports as $transport) { | ||||
|         common_enqueue_notice_transport($notice, $transport); | ||||
|     foreach ($transports as $transport) | ||||
|     { | ||||
|         $qm->enqueue($notice, $transport); | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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); | ||||
|     } | ||||
|     common_log(LOG_DEBUG, 'complete queueing notice ID = ' . $notice->id . ' for ' . $transport); | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| function common_real_broadcast($notice, $remote=false) | ||||
| { | ||||
|     $success = true; | ||||
|     if (!$remote) { | ||||
|         // Make sure we have the OMB stuff | ||||
|         require_once(INSTALLDIR.'/lib/omb.php'); | ||||
|         $success = omb_broadcast_remote_subscribers($notice); | ||||
|         if (!$success) { | ||||
|             common_log(LOG_ERR, 'Error in OMB broadcast for notice ' . $notice->id); | ||||
|         } | ||||
|     } | ||||
|     if ($success) { | ||||
|         require_once(INSTALLDIR.'/lib/jabber.php'); | ||||
|         $success = jabber_broadcast_notice($notice); | ||||
|         if (!$success) { | ||||
|             common_log(LOG_ERR, 'Error in jabber broadcast for notice ' . $notice->id); | ||||
|         } | ||||
|     } | ||||
|     if ($success) { | ||||
|         require_once(INSTALLDIR.'/lib/mail.php'); | ||||
|         $success = mail_broadcast_notice_sms($notice); | ||||
|         if (!$success) { | ||||
|             common_log(LOG_ERR, 'Error in sms broadcast for notice ' . $notice->id); | ||||
|         } | ||||
|     } | ||||
|     if ($success) { | ||||
|         $success = jabber_public_notice($notice); | ||||
|         if (!$success) { | ||||
|             common_log(LOG_ERR, 'Error in public broadcast for notice ' . $notice->id); | ||||
|         } | ||||
|     } | ||||
|     if ($success) { | ||||
|         $success = broadcast_twitter($notice); | ||||
|         if (!$success) { | ||||
|             common_log(LOG_ERR, 'Error in Twitter broadcast for notice ' . $notice->id); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // XXX: Do a real-time FB broadcast here? | ||||
|  | ||||
|     // XXX: broadcast notices to other IM | ||||
|     return $success; | ||||
| } | ||||
|  | ||||
| function common_broadcast_profile($profile) | ||||
| { | ||||
|     // XXX: optionally use a queue system like http://code.google.com/p/microapps/wiki/NQDQ | ||||
| @@ -1148,6 +1028,9 @@ function common_log_objstring(&$object) | ||||
|     if (is_null($object)) { | ||||
|         return "null"; | ||||
|     } | ||||
|     if (!($object instanceof DB_DataObject)) { | ||||
|         return "(unknown)"; | ||||
|     } | ||||
|     $arr = $object->toArray(); | ||||
|     $fields = array(); | ||||
|     foreach ($arr as $k => $v) { | ||||
|   | ||||
| @@ -21,6 +21,8 @@ if (!defined('LACONICA')) { exit(1); } | ||||
|  | ||||
| require_once(INSTALLDIR.'/lib/queuehandler.php'); | ||||
|  | ||||
| define('PING_INTERVAL', 120); | ||||
|  | ||||
| /** | ||||
|  * Common superclass for all XMPP-using queue handlers. They all need to | ||||
|  * service their message queues on idle, and forward any incoming messages | ||||
| @@ -30,6 +32,9 @@ require_once(INSTALLDIR.'/lib/queuehandler.php'); | ||||
|  | ||||
| class XmppQueueHandler extends QueueHandler | ||||
| { | ||||
|     var $pingid = 0; | ||||
|     var $lastping = null; | ||||
|  | ||||
|     function start() | ||||
|     { | ||||
|         # Low priority; we don't want to receive messages | ||||
| @@ -44,6 +49,11 @@ class XmppQueueHandler extends QueueHandler | ||||
|         return !is_null($this->conn); | ||||
|     } | ||||
|  | ||||
|     function timeout() | ||||
|     { | ||||
|         return 10; | ||||
|     } | ||||
|  | ||||
|     function handle_reconnect(&$pl) | ||||
|     { | ||||
|         $this->conn->processUntil('session_start'); | ||||
| @@ -55,7 +65,13 @@ class XmppQueueHandler extends QueueHandler | ||||
|         # Process the queue for as long as needed | ||||
|         try { | ||||
|             if ($this->conn) { | ||||
|                 $this->log(LOG_DEBUG, "Servicing the XMPP queue."); | ||||
|                 $this->conn->processTime($timeout); | ||||
|                 $now = time(); | ||||
|                 if (empty($this->lastping) || $now - $this->lastping > PING_INTERVAL) { | ||||
|                     $this->sendPing(); | ||||
|                     $this->lastping = $now; | ||||
|                 } | ||||
|             } | ||||
|         } catch (XMPPHP_Exception $e) { | ||||
|             $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); | ||||
| @@ -63,6 +79,22 @@ class XmppQueueHandler extends QueueHandler | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function sendPing() | ||||
|     { | ||||
|         $jid = jabber_daemon_address().'/'.$this->_id.$this->transport(); | ||||
|         $server = common_config('xmpp', 'server'); | ||||
|  | ||||
|         if (!isset($this->pingid)) { | ||||
|             $this->pingid = 0; | ||||
|         } else { | ||||
|             $this->pingid++; | ||||
|         } | ||||
|  | ||||
|         $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}"); | ||||
|  | ||||
| 		$this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>"); | ||||
|     } | ||||
|  | ||||
|     function forward_message(&$pl) | ||||
|     { | ||||
|         if ($pl['type'] != 'chat') { | ||||
| @@ -91,7 +123,12 @@ class XmppQueueHandler extends QueueHandler | ||||
|         if (common_config('xmpp', 'listener')) { | ||||
|             return common_config('xmpp', 'listener'); | ||||
|         } else { | ||||
|             return jabber_daemon_address() . '/' . common_config('xmpp','resource') . '-listener'; | ||||
|             return jabber_daemon_address() . '/' . common_config('xmpp','resource') . 'daemon'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function getSockets() | ||||
|     { | ||||
|         return array($this->conn->getSocket()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -313,8 +313,6 @@ class FBConnectPlugin extends Plugin | ||||
|                  $action->menuItem(common_local_url('register'), | ||||
|                      _('Register'), _('Create an account'), false, 'nav_register'); | ||||
|              } | ||||
|              $action->menuItem(common_local_url('openidlogin'), | ||||
|                  _('OpenID'), _('Login with OpenID'), false, 'nav_openid'); | ||||
|              $action->menuItem(common_local_url('login'), | ||||
|                  _('Login'), _('Login to the site'), false, 'nav_login'); | ||||
|          } | ||||
|   | ||||
| @@ -42,11 +42,11 @@ class MailerDaemon | ||||
|  | ||||
|     function handle_message($fname='php://stdin') | ||||
|     { | ||||
|         list($from, $to, $msg) = $this->parse_message($fname); | ||||
|         list($from, $to, $msg, $attachments) = $this->parse_message($fname); | ||||
|         if (!$from || !$to || !$msg) { | ||||
|             $this->error(null, _('Could not parse message.')); | ||||
|         } | ||||
|         common_log(LOG_INFO, "Mail from $from to $to: " .substr($msg, 0, 20)); | ||||
|         common_log(LOG_INFO, "Mail from $from to $to with ".count($attachments) .' attachment(s): ' .substr($msg, 0, 20)); | ||||
|         $user = $this->user_from($from); | ||||
|         if (!$user) { | ||||
|             $this->error($from, _('Not a registered user.')); | ||||
| @@ -65,7 +65,47 @@ class MailerDaemon | ||||
|             return true; | ||||
|         } | ||||
|         $msg = $this->cleanup_msg($msg); | ||||
|         $err = $this->add_notice($user, $msg); | ||||
|         $msg = common_shorten_links($msg); | ||||
|         if (mb_strlen($msg) > 140) { | ||||
|             $this->error($from,_('That\'s too long. '. | ||||
|                 'Max notice size is 140 chars.')); | ||||
|         } | ||||
|         $fileRecords = array(); | ||||
|         foreach($attachments as $attachment){ | ||||
|             $mimetype = $this->getUploadedFileType($attachment); | ||||
|             $stream  = stream_get_meta_data($attachment); | ||||
|             if (!$this->isRespectsQuota($user,filesize($stream['uri']))) { | ||||
|                 die('error() should trigger an exception before reaching here.'); | ||||
|             } | ||||
|             $filename = $this->saveFile($user, $attachment,$mimetype); | ||||
|              | ||||
|             fclose($attachment); | ||||
|              | ||||
|             if (empty($filename)) { | ||||
|                 $this->error($from,_('Couldn\'t save file.')); | ||||
|             } | ||||
|  | ||||
|             $fileRecord = $this->storeFile($filename, $mimetype); | ||||
|             $fileRecords[] = $fileRecord; | ||||
|             $fileurl = common_local_url('attachment', | ||||
|                 array('attachment' => $fileRecord->id)); | ||||
|  | ||||
|             // not sure this is necessary -- Zach | ||||
|             $this->maybeAddRedir($fileRecord->id, $fileurl); | ||||
|  | ||||
|             $short_fileurl = common_shorten_url($fileurl); | ||||
|             $msg .= ' ' . $short_fileurl; | ||||
|  | ||||
|             if (mb_strlen($msg) > 140) { | ||||
|                 $this->deleteFile($filename); | ||||
|                 $this->error($from,_('Max notice size is 140 chars, including attachment URL.')); | ||||
|             } | ||||
|  | ||||
|             // Also, not sure this is necessary -- Zach | ||||
|             $this->maybeAddRedir($fileRecord->id, $short_fileurl); | ||||
|         } | ||||
|  | ||||
|         $err = $this->add_notice($user, $msg, $fileRecords); | ||||
|         if (is_string($err)) { | ||||
|             $this->error($from, $err); | ||||
|             return false; | ||||
| @@ -74,6 +114,89 @@ class MailerDaemon | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function saveFile($user, $attachment, $mimetype) { | ||||
|  | ||||
|         $filename = File::filename($user->getProfile(), "email", $mimetype); | ||||
|  | ||||
|         $filepath = File::path($filename); | ||||
|  | ||||
|         $stream  = stream_get_meta_data($attachment); | ||||
|         if (copy($stream['uri'], $filepath) && chmod($filepath,0664)) { | ||||
|             return $filename; | ||||
|         } else {    | ||||
|             $this->error(null,_('File could not be moved to destination directory.' . $stream['uri'] . ' ' . $filepath)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function storeFile($filename, $mimetype) { | ||||
|  | ||||
|         $file = new File; | ||||
|         $file->filename = $filename; | ||||
|  | ||||
|         $file->url = File::url($filename); | ||||
|  | ||||
|         $filepath = File::path($filename); | ||||
|  | ||||
|         $file->size = filesize($filepath); | ||||
|         $file->date = time(); | ||||
|         $file->mimetype = $mimetype; | ||||
|  | ||||
|         $file_id = $file->insert(); | ||||
|  | ||||
|         if (!$file_id) { | ||||
|             common_log_db_error($file, "INSERT", __FILE__); | ||||
|             $this->error(null,_('There was a database error while saving your file. Please try again.')); | ||||
|         } | ||||
|  | ||||
|         return $file; | ||||
|     } | ||||
|  | ||||
|     function maybeAddRedir($file_id, $url) | ||||
|     {    | ||||
|         $file_redir = File_redirection::staticGet('url', $url); | ||||
|  | ||||
|         if (empty($file_redir)) { | ||||
|             $file_redir = new File_redirection; | ||||
|             $file_redir->url = $url; | ||||
|             $file_redir->file_id = $file_id; | ||||
|  | ||||
|             $result = $file_redir->insert(); | ||||
|  | ||||
|             if (!$result) { | ||||
|                 common_log_db_error($file_redir, "INSERT", __FILE__); | ||||
|                 $this->error(null,_('There was a database error while saving your file. Please try again.')); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function getUploadedFileType($fileHandle) { | ||||
|         require_once 'MIME/Type.php'; | ||||
|  | ||||
|         $cmd = &PEAR::getStaticProperty('MIME_Type', 'fileCmd'); | ||||
|         $cmd = common_config('attachments', 'filecommand'); | ||||
|  | ||||
|         $stream  = stream_get_meta_data($fileHandle); | ||||
|         $filetype = MIME_Type::autoDetect($stream['uri']); | ||||
|         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->error(null,sprintf( | ||||
|             _('%s is not a supported filetype on this server.'), $filetype) . $hint); | ||||
|     } | ||||
|  | ||||
|     function isRespectsQuota($user,$fileSize) { | ||||
|         $file = new File; | ||||
|         $ret = $file->isRespectsQuota($user,$fileSize); | ||||
|         if (true === $ret) return true; | ||||
|         $this->error(null,$ret); | ||||
|     } | ||||
|  | ||||
|     function error($from, $msg) | ||||
|     { | ||||
|         file_put_contents("php://stderr", $msg . "\n"); | ||||
| @@ -133,19 +256,30 @@ class MailerDaemon | ||||
|         common_log($level, 'MailDaemon: '.$msg); | ||||
|     } | ||||
|  | ||||
|     function add_notice($user, $msg) | ||||
|     function add_notice($user, $msg, $fileRecords) | ||||
|     { | ||||
|         $notice = Notice::saveNew($user->id, $msg, 'mail'); | ||||
|         if (is_string($notice)) { | ||||
|             $this->log(LOG_ERR, $notice); | ||||
|             return $notice; | ||||
|         } | ||||
|         foreach($fileRecords as $fileRecord){ | ||||
|             $this->attachFile($notice, $fileRecord); | ||||
|         } | ||||
|         common_broadcast_notice($notice); | ||||
|         $this->log(LOG_INFO, | ||||
|                    'Added notice ' . $notice->id . ' from user ' . $user->nickname); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     function attachFile($notice, $filerec) | ||||
|     {    | ||||
|         File_to_post::processNew($filerec->id, $notice->id); | ||||
|  | ||||
|         $this->maybeAddRedir($filerec->id, | ||||
|             common_local_url('file', array('notice' => $notice->id))); | ||||
|     } | ||||
|  | ||||
|     function parse_message($fname) | ||||
|     { | ||||
|         $contents = file_get_contents($fname); | ||||
| @@ -163,12 +297,19 @@ class MailerDaemon | ||||
|  | ||||
|         $type = $parsed->ctype_primary . '/' . $parsed->ctype_secondary; | ||||
|  | ||||
|         $attachments = array(); | ||||
|  | ||||
|         if ($parsed->ctype_primary == 'multipart') { | ||||
|             foreach ($parsed->parts as $part) { | ||||
|                 if ($part->ctype_primary == 'text' && | ||||
|                     $part->ctype_secondary == 'plain') { | ||||
|                     $msg = $part->body; | ||||
|                     break; | ||||
|                 }else{ | ||||
|                     if ($part->body) { | ||||
| 			$attachment = tmpfile(); | ||||
| 			fwrite($attachment, $part->body); | ||||
|                         $attachments[] = $attachment; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else if ($type == 'text/plain') { | ||||
| @@ -176,8 +317,7 @@ class MailerDaemon | ||||
|         } else { | ||||
|             $this->unsupported_type($type); | ||||
|         } | ||||
|  | ||||
|         return array($from, $to, $msg); | ||||
|         return array($from, $to, $msg, $attachments); | ||||
|     } | ||||
|  | ||||
|     function unsupported_type($type) | ||||
|   | ||||
| @@ -1,66 +1,97 @@ | ||||
| #!/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'); | ||||
|  | ||||
| // Master Laconica .pot file location (created by update_pot.sh) | ||||
| $laconica_pot = INSTALLDIR . '/locale/laconica.po'; | ||||
|  | ||||
| set_time_limit(60); | ||||
| chdir(dirname(__FILE__) . '/..'); | ||||
|  | ||||
| /* Languages to pull */ | ||||
| $languages = array( | ||||
| 	'da_DK' => 'http://laconi.ca/translate/download.php?file_id=93', | ||||
| 	'nl_NL' => 'http://laconi.ca/translate/download.php?file_id=97', | ||||
| 	'en_NZ' => 'http://laconi.ca/translate/download.php?file_id=87', | ||||
| 	'eo'    => 'http://laconi.ca/translate/download.php?file_id=88', | ||||
| 	'fr_FR' => 'http://laconi.ca/translate/download.php?file_id=99', | ||||
| 	'de_DE' => 'http://laconi.ca/translate/download.php?file_id=100', | ||||
| 	'it_IT' => 'http://laconi.ca/translate/download.php?file_id=101', | ||||
| 	'ko'    => 'http://laconi.ca/translate/download.php?file_id=102', | ||||
| 	'no_NB' => 'http://laconi.ca/translate/download.php?file_id=104', | ||||
| 	'pt'    => 'http://laconi.ca/translate/download.php?file_id=106', | ||||
| 	'pt_BR' => 'http://laconi.ca/translate/download.php?file_id=107', | ||||
| 	'ru_RU' => 'http://laconi.ca/translate/download.php?file_id=109', | ||||
| 	'es'    => 'http://laconi.ca/translate/download.php?file_id=110', | ||||
| 	'tr_TR' => 'http://laconi.ca/translate/download.php?file_id=114', | ||||
| 	'uk_UA' => 'http://laconi.ca/translate/download.php?file_id=115', | ||||
| 	'he_IL' => 'http://laconi.ca/translate/download.php?file_id=116', | ||||
| 	'mk_MK' => 'http://laconi.ca/translate/download.php?file_id=103', | ||||
| 	'ja_JP' => 'http://laconi.ca/translate/download.php?file_id=117', | ||||
| 	'cs_CZ' => 'http://laconi.ca/translate/download.php?file_id=96', | ||||
| 	'ca_ES' => 'http://laconi.ca/translate/download.php?file_id=95', | ||||
| 	'pl_PL' => 'http://laconi.ca/translate/download.php?file_id=105', | ||||
| 	'sv_SE' => 'http://laconi.ca/translate/download.php?file_id=128' | ||||
| ); | ||||
| $languages = get_all_languages(); | ||||
|  | ||||
| /* Update the languages */ | ||||
| foreach ($languages as $code => $file) { | ||||
|  | ||||
| 	$lcdir='locale/'.$code; | ||||
| 	$msgdir=$lcdir.'/LC_MESSAGES'; | ||||
| 	$pofile=$msgdir.'/laconica.po'; | ||||
| 	$mofile=$msgdir.'/laconica.mo'; | ||||
| foreach ($languages as $language) { | ||||
|  | ||||
| 	/* Check for an existing */ | ||||
| 	if (!is_dir($msgdir)) { | ||||
| 		mkdir($lcdir); | ||||
| 		mkdir($msgdir); | ||||
| 		$existingSHA1 = ''; | ||||
| 	} else { | ||||
| 		$existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : ''; | ||||
| 	} | ||||
|     $code = $language['lang']; | ||||
|     $file_url = 'http://laconi.ca/pootle/' . $code . | ||||
|         '/laconica/LC_MESSAGES/laconica.po'; | ||||
|     $lcdir = INSTALLDIR . '/locale/' . $code; | ||||
|     $msgdir = "$lcdir/LC_MESSAGES"; | ||||
|     $pofile = "$msgdir/laconica.po"; | ||||
|     $mofile = "$msgdir/laconica.mo"; | ||||
|  | ||||
| 	/* Get the remote one */ | ||||
| 	$newFile = file_get_contents($file); | ||||
|     /* Check for an existing */ | ||||
|     if (!is_dir($msgdir)) { | ||||
|         mkdir($lcdir); | ||||
|         mkdir($msgdir); | ||||
|         $existingSHA1 = ''; | ||||
|     } else { | ||||
|         $existingSHA1 = file_exists($pofile) ? sha1_file($pofile) : ''; | ||||
|     } | ||||
|  | ||||
| 	// Update if the local .po file is different to the one downloaded, or | ||||
| 	// if the .mo file is not present. | ||||
| 	if(sha1($newFile)!=$existingSHA1 || !file_exists($mofile)) { | ||||
| 		echo "Updating ".$code."\n"; | ||||
| 		file_put_contents($pofile, $newFile); | ||||
| 		$prevdir = getcwd(); | ||||
| 		chdir($msgdir); | ||||
| 		system('msgmerge -U laconica.po ../../laconica.pot'); | ||||
| 		system('msgfmt -f -o laconica.mo laconica.po'); | ||||
| 		chdir($prevdir); | ||||
| 	} else { | ||||
| 		echo "Unchanged - ".$code."\n"; | ||||
| 	} | ||||
|     /* Get the remote one */ | ||||
|     $new_file = curl_get_file($file_url); | ||||
|  | ||||
|     if ($new_file === FALSE) { | ||||
|         echo "Couldn't retrieve .po file for $code: $file_url\n"; | ||||
|         continue; | ||||
|     } | ||||
|  | ||||
|     // Update if the local .po file is different to the one downloaded, or | ||||
|     // if the .mo file is not present. | ||||
|     if (sha1($new_file) != $existingSHA1 || !file_exists($mofile)) { | ||||
|         echo "Updating ".$code."\n"; | ||||
|         file_put_contents($pofile, $new_file); | ||||
|         system(sprintf('msgmerge -U %s %s', $pofile, $laconica_pot)); | ||||
|         system(sprintf('msgfmt -f -o %s %s', $mofile, $pofile)); | ||||
|     } else { | ||||
|         echo "Unchanged - ".$code."\n"; | ||||
|     } | ||||
| } | ||||
|  | ||||
| echo "Finished\n"; | ||||
|  | ||||
|  | ||||
| function curl_get_file($url) | ||||
| { | ||||
|     $c = curl_init(); | ||||
|     curl_setopt($c, CURLOPT_RETURNTRANSFER, 1); | ||||
|     curl_setopt($c, CURLOPT_URL, $url); | ||||
|     $contents = curl_exec($c); | ||||
|     curl_close($c); | ||||
|  | ||||
|     if (!empty($contents)) { | ||||
|         return $contents; | ||||
|     } | ||||
|  | ||||
|     return FALSE; | ||||
| } | ||||
|   | ||||
| @@ -60,7 +60,9 @@ class XMPPDaemon extends Daemon | ||||
|             $this->resource = common_config('xmpp', 'resource') . 'daemon'; | ||||
|         } | ||||
|  | ||||
|         $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->user}@{$this->server}/{$this->resource}"); | ||||
|         $this->jid = $this->user.'@'.$this->server.'/'.$this->resource; | ||||
|  | ||||
|         $this->log(LOG_INFO, "INITIALIZE XMPPDaemon {$this->jid}"); | ||||
|     } | ||||
|  | ||||
|     function connect() | ||||
| @@ -106,10 +108,25 @@ class XMPPDaemon extends Daemon | ||||
|  | ||||
|             $this->log(LOG_DEBUG, "Beginning processing loop."); | ||||
|  | ||||
|             $this->conn->process(); | ||||
|             while ($this->conn->processTime(60)) { | ||||
|                 $this->sendPing(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function sendPing() | ||||
|     { | ||||
|         if (!isset($this->pingid)) { | ||||
|             $this->pingid = 0; | ||||
|         } else { | ||||
|             $this->pingid++; | ||||
|         } | ||||
|  | ||||
|         $this->log(LOG_DEBUG, "Sending ping #{$this->pingid}"); | ||||
|  | ||||
| 		$this->conn->send("<iq from='{$this->jid}' to='{$this->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>"); | ||||
|     } | ||||
|  | ||||
|     function handle_reconnect(&$pl) | ||||
|     { | ||||
|         $this->log(LOG_DEBUG, "Got reconnection callback."); | ||||
|   | ||||
| @@ -275,7 +275,7 @@ margin-bottom:18px; | ||||
|  | ||||
| #anon_notice { | ||||
| float:left; | ||||
| width:43.2%; | ||||
| width:42.4%; | ||||
| padding:1.1%; | ||||
| border-radius:7px; | ||||
| -moz-border-radius:7px; | ||||
| @@ -396,7 +396,7 @@ margin-bottom:1em; | ||||
| } | ||||
|  | ||||
| #content { | ||||
| width:64.009%; | ||||
| width:63.311%; | ||||
| min-height:259px; | ||||
| padding:1.795%; | ||||
| float:left; | ||||
| @@ -422,7 +422,7 @@ float:left; | ||||
| width:27.917%; | ||||
| min-height:259px; | ||||
| float:left; | ||||
| margin-left:0.5%; | ||||
| margin-left:0.699%; | ||||
| padding:1.795%; | ||||
| border-radius:7px; | ||||
| -moz-border-radius:7px; | ||||
| @@ -432,7 +432,7 @@ border-style:solid; | ||||
| } | ||||
|  | ||||
| #form_notice { | ||||
| width:45.664%; | ||||
| width:45%; | ||||
| float:left; | ||||
| position:relative; | ||||
| line-height:1; | ||||
| @@ -471,12 +471,12 @@ cursor:pointer; | ||||
| } | ||||
| #form_notice label[for=notice_data-attach] { | ||||
| text-indent:-9999px; | ||||
| left:394px; | ||||
| left:86%; | ||||
| width:16px; | ||||
| height:16px; | ||||
| } | ||||
| #form_notice #notice_data-attach { | ||||
| left:183px; | ||||
| left:40.6%; | ||||
| padding:0; | ||||
| height:16px; | ||||
| } | ||||
| @@ -783,8 +783,8 @@ list-style-type:none; | ||||
| } | ||||
| .notices .notices { | ||||
| margin-top:7px; | ||||
| margin-left:5%; | ||||
| width:95%; | ||||
| margin-left:2%; | ||||
| width:98%; | ||||
| float:left; | ||||
| } | ||||
|  | ||||
| @@ -1020,20 +1020,21 @@ font-weight:bold; | ||||
| padding:0; | ||||
| } | ||||
| #jOverlayContent h1 { | ||||
| max-width:475px; | ||||
| max-width:425px; | ||||
| } | ||||
| #jOverlayContent #content { | ||||
| border-radius:7px; | ||||
| -moz-border-radius:7px; | ||||
| -webkit-border-radius:7px; | ||||
| } | ||||
| #jOverlayContent #content img { | ||||
| max-width:480px; | ||||
| } | ||||
| #jOverlayLoading { | ||||
| top:22.5%; | ||||
| left:40%; | ||||
| } | ||||
| #attachment_view img { | ||||
| max-width:480px; | ||||
| max-height:480px; | ||||
| } | ||||
| #attachment_view #oembed_info { | ||||
| margin-top:11px; | ||||
| } | ||||
| @@ -1278,6 +1279,7 @@ margin-bottom:0; | ||||
|  | ||||
| #form_settings_design #settings_design_background-image img { | ||||
| max-width:480px; | ||||
| max-height:480px; | ||||
| } | ||||
|  | ||||
| #form_settings_design #settings_design_color .form_data, | ||||
|   | ||||
| @@ -9,7 +9,7 @@ width:78%; | ||||
| #form_notice .form_note + label { | ||||
| position:absolute; | ||||
| top:25px; | ||||
| left:380px; | ||||
| left:83%; | ||||
| text-indent:-9999px; | ||||
| height:16px; | ||||
| width:16px; | ||||
| @@ -25,10 +25,6 @@ width:78.5%; | ||||
| #form_notice #notice_data-attach_selected button { | ||||
| padding:0 4px; | ||||
| } | ||||
| #anon_notice { | ||||
| max-width:39%; | ||||
| } | ||||
|  | ||||
| .notice-options input.submit { | ||||
| font-size:0; | ||||
| margin-top:3px; | ||||
| @@ -49,6 +45,6 @@ z-index:1; | ||||
| .notice:hover { | ||||
| z-index:9999; | ||||
| } | ||||
| .notice .thumbnail img  { | ||||
| .notice .thumbnail img { | ||||
| z-index:9999; | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user