Merge branch '1.0.x' into testing
This commit is contained in:
commit
575ecd9f4c
86
README
86
README
|
@ -2,19 +2,19 @@
|
|||
README
|
||||
------
|
||||
|
||||
StatusNet 0.9.7 "World Leader Pretend"
|
||||
17 March 2011
|
||||
StatusNet 1.0.0beta2
|
||||
2 August 2011
|
||||
|
||||
This is the README file for StatusNet, the Open Source microblogging
|
||||
platform. It includes installation instructions, descriptions of
|
||||
options you can set, warnings, tips, and general info for
|
||||
administrators. Information on using StatusNet can be found in the
|
||||
This is the README file for StatusNet, the Open Source social
|
||||
networking platform. It includes installation instructions,
|
||||
descriptions of options you can set, warnings, tips, and general info
|
||||
for administrators. Information on using StatusNet can be found in the
|
||||
"doc" subdirectory or in the "help" section on-line.
|
||||
|
||||
About
|
||||
=====
|
||||
|
||||
StatusNet is a Free and Open Source microblogging platform. It helps
|
||||
StatusNet is a Free and Open Source social networking platform. It helps
|
||||
people in a community, company or group to exchange short (140
|
||||
characters, by default) messages over the Web. Users can choose which
|
||||
people to "follow" and receive only their friends' or colleagues'
|
||||
|
@ -96,47 +96,27 @@ for additional terms.
|
|||
New this version
|
||||
================
|
||||
|
||||
This is a security, bug and feature release since version 0.9.6 released on
|
||||
23 October 2010.
|
||||
|
||||
For best compatibility with client software and site federation, and a
|
||||
lot of bug fixes, it is highly recommended that all public sites
|
||||
upgrade to the new version. Upgrades require new database indexes for
|
||||
best performance; see Upgrade below.
|
||||
This is a security release since version 0.9.7 released on 11 March
|
||||
2011. It fixes security bug #3260. All sites running version 0.9.7 or
|
||||
below are recommended to upgrade to 0.9.9 immediately.
|
||||
|
||||
Notable changes this version:
|
||||
|
||||
- GroupPrivateMessage plugin lets users send private messages
|
||||
to a group. (Similar to "private groups" on Yammer.)
|
||||
- Support for Twitter streaming API in Twitter bridge plugin
|
||||
- Support for a new Activity Streams-based API using AtomPub, allowing
|
||||
richer API data. See http://status.net/wiki/AtomPub for details.
|
||||
- Unified Facebook plugin, replacing previous Facebook application
|
||||
and Facebook Connect plugin.
|
||||
- A plugin to send out a daily summary email to network users.
|
||||
- In-line thumbnails of some attachments (video, images) and oEmbed objects.
|
||||
- Local copies of remote profiles to let moderators manage OStatus users.
|
||||
- Upgrade upstream JS, minify everything.
|
||||
- Allow pushing plugin JS, CSS, and static files to a CDN.
|
||||
- Configurable nickname rules.
|
||||
- Better support for bit.ly URL shortener.
|
||||
- InProcessCache plugin for additional caching on top of memcached.
|
||||
- Support for Activity Streams JSON feeds on many streams.
|
||||
- User-initiated backup and restore of account data in Activity Streams
|
||||
format.
|
||||
- Bookmark plugin for making del.icio.us-like social bookmarking sites,
|
||||
including del.icio.us backup file import. Supports OStatus.
|
||||
- SQLProfile plugin to tune SQL queries.
|
||||
- Better sorting on timelines to support restored or imported data.
|
||||
- Hundreds of translations from http://translatewiki.net/
|
||||
- Hundreds of performance tunings, bug fixes, and UI improvements.
|
||||
- Remove deprecated data from Activity Streams Atom output, to the
|
||||
extent possible.
|
||||
- NewMenu plugin for new layout of menu items.
|
||||
- Experimental support for moving an account from one server to
|
||||
another, using new AtomPub API.
|
||||
- Fix bug #3260, a cross-site scripting (XSS) bug that allows an
|
||||
attacker to inject JavaScript into a page with a carefully structured URL.
|
||||
- Updated code for Google Analytics to reflect new API.
|
||||
- Various fixes for Bookmark plugin.
|
||||
- Updates to reCAPTCHA plugin based on changes to API.
|
||||
- New plugin to move the site notice to the sidebar.
|
||||
- Add rss.me to notice source list.
|
||||
- Updates to data backup/restore.
|
||||
- Correct use of "likes" in Facebook plugin.
|
||||
- Ignore failures in Twitter plugin.
|
||||
|
||||
A full changelog is available at http://status.net/wiki/StatusNet_0.9.7.
|
||||
A full changelog is available at http://status.net/wiki/StatusNet_0.9.9.
|
||||
|
||||
NOTE: The short-lived StatusNet 0.9.8 ("Letter Never Sent") did not
|
||||
adequately fix bug #3260 as originally thought; thus this new release.
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
@ -246,9 +226,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 statusnet-0.9.7.tar.gz
|
||||
tar zxf statusnet-0.9.9.tar.gz
|
||||
|
||||
...which will make a statusnet-0.9.7 subdirectory in your current
|
||||
...which will make a statusnet-0.9.9 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.)
|
||||
|
@ -256,7 +236,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 statusnet-0.9.7 /var/www/statusnet
|
||||
mv statusnet-0.9.9 /var/www/statusnet
|
||||
|
||||
This will make your StatusNet instance available in the statusnet path of
|
||||
your server, like "http://example.net/statusnet". "microblog" or
|
||||
|
@ -494,7 +474,7 @@ off of amd64 to another server.
|
|||
Public feed
|
||||
-----------
|
||||
|
||||
You can send *all* messages from your microblogging site to a
|
||||
You can send *all* messages from your social networking site to a
|
||||
third-party service using XMPP. This can be useful for providing
|
||||
search, indexing, bridging, or other cool services.
|
||||
|
||||
|
@ -634,7 +614,7 @@ Private
|
|||
|
||||
The administrator can set the "private" flag for a site so that it's
|
||||
not visible to non-logged-in users. This might be useful for
|
||||
workgroups who want to share a microblogging site for project
|
||||
workgroups who want to share a social networking site for project
|
||||
management, but host it on a public server.
|
||||
|
||||
Total privacy is not guaranteed or ensured. Also, privacy is
|
||||
|
@ -671,7 +651,7 @@ with this situation.
|
|||
If you've been using StatusNet 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 StatusNet 0.9.7. Try these step-by-step
|
||||
upgrade procedure in StatusNet 0.9.9. Try these step-by-step
|
||||
instructions; read to the end first before trying them.
|
||||
|
||||
0. Download StatusNet and set up all the prerequisites as if you were
|
||||
|
@ -692,7 +672,7 @@ instructions; read 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 StatusNet directory to a backup spot, like "statusnet.bak".
|
||||
7. Unpack your StatusNet 0.9.7 tarball and move it to "statusnet" or
|
||||
7. Unpack your StatusNet 0.9.9 tarball and move it to "statusnet" or
|
||||
wherever your code used to be.
|
||||
8. Copy the config.php file and the contents of the avatar/, background/,
|
||||
file/, and local/ subdirectories from your old directory to your new
|
||||
|
@ -1753,8 +1733,8 @@ There are several ways to get more information about StatusNet.
|
|||
Feedback
|
||||
========
|
||||
|
||||
* Microblogging messages to http://support.status.net/ are very welcome.
|
||||
* The microblogging group http://identi.ca/group/statusnet is a good
|
||||
* Messages to http://support.status.net/ are very welcome.
|
||||
* The group http://identi.ca/group/statusnet is a good
|
||||
place to discuss the software.
|
||||
* StatusNet has a bug tracker for any defects you may find, or ideas for
|
||||
making things better. http://status.net/bugs
|
||||
|
|
|
@ -27,6 +27,11 @@ class Avatar extends Memcached_DataObject
|
|||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
static function pivotGet($keyCol, $keyVals, $otherCols)
|
||||
{
|
||||
return Memcached_DataObject::pivotGet('Avatar', $keyCol, $keyVals, $otherCols);
|
||||
}
|
||||
|
||||
// We clean up the file, too
|
||||
|
||||
function delete()
|
||||
|
|
|
@ -76,46 +76,7 @@ class Memcached_DataObject extends Safe_DataObject
|
|||
*/
|
||||
function multiGet($cls, $keyCol, $keyVals, $skipNulls=true)
|
||||
{
|
||||
$result = array_fill_keys($keyVals, null);
|
||||
|
||||
$toFetch = array();
|
||||
|
||||
foreach ($keyVals as $keyVal) {
|
||||
$i = self::getcached($cls, $keyCol, $keyVal);
|
||||
if ($i !== false) {
|
||||
$result[$keyVal] = $i;
|
||||
} else if (!empty($keyVal)) {
|
||||
$toFetch[] = $keyVal;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($toFetch) > 0) {
|
||||
$i = DB_DataObject::factory($cls);
|
||||
if (empty($i)) {
|
||||
throw new Exception(_('Cannot instantiate class ' . $cls));
|
||||
}
|
||||
$i->whereAddIn($keyCol, $toFetch, $i->columnType($keyCol));
|
||||
if ($i->find()) {
|
||||
while ($i->fetch()) {
|
||||
$copy = clone($i);
|
||||
$copy->encache();
|
||||
$result[$i->$keyCol] = $copy;
|
||||
}
|
||||
}
|
||||
|
||||
// Save state of DB misses
|
||||
|
||||
foreach ($toFetch as $keyVal) {
|
||||
if (empty($result[$keyVal])) {
|
||||
// save the fact that no such row exists
|
||||
$c = self::memcache();
|
||||
if (!empty($c)) {
|
||||
$ck = self::cachekey($cls, $keyCol, $keyVal);
|
||||
$c->set($ck, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$result = self::pivotGet($cls, $keyCol, $keyVals);
|
||||
|
||||
$values = array_values($result);
|
||||
|
||||
|
@ -131,6 +92,70 @@ class Memcached_DataObject extends Safe_DataObject
|
|||
|
||||
return new ArrayWrapper($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple items from the database by key
|
||||
*
|
||||
* @param string $cls Class to fetch
|
||||
* @param string $keyCol name of column for key
|
||||
* @param array $keyVals key values to fetch
|
||||
* @param boolean $otherCols Other columns to hold fixed
|
||||
*
|
||||
* @return array Array mapping $keyVals to objects, or null if not found
|
||||
*/
|
||||
static function pivotGet($cls, $keyCol, $keyVals, $otherCols = array())
|
||||
{
|
||||
$result = array_fill_keys($keyVals, null);
|
||||
|
||||
$toFetch = array();
|
||||
|
||||
foreach ($keyVals as $keyVal) {
|
||||
|
||||
$kv = array_merge($otherCols, array($keyCol => $keyVal));
|
||||
|
||||
$i = self::multicache($cls, $kv);
|
||||
|
||||
if ($i !== false) {
|
||||
$result[$keyVal] = $i;
|
||||
} else if (!empty($keyVal)) {
|
||||
$toFetch[] = $keyVal;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($toFetch) > 0) {
|
||||
$i = DB_DataObject::factory($cls);
|
||||
if (empty($i)) {
|
||||
throw new Exception(_('Cannot instantiate class ' . $cls));
|
||||
}
|
||||
foreach ($otherCols as $otherKeyCol => $otherKeyVal) {
|
||||
$i->$otherKeyCol = $otherKeyVal;
|
||||
}
|
||||
$i->whereAddIn($keyCol, $toFetch, $i->columnType($keyCol));
|
||||
if ($i->find()) {
|
||||
while ($i->fetch()) {
|
||||
$copy = clone($i);
|
||||
$copy->encache();
|
||||
$result[$i->$keyCol] = $copy;
|
||||
}
|
||||
}
|
||||
|
||||
// Save state of DB misses
|
||||
|
||||
foreach ($toFetch as $keyVal) {
|
||||
if (empty($result[$keyVal])) {
|
||||
$kv = array_merge($otherCols, array($keyCol => $keyVal));
|
||||
// save the fact that no such row exists
|
||||
$c = self::memcache();
|
||||
if (!empty($c)) {
|
||||
$ck = self::multicacheKey($cls, $kv);
|
||||
$c->set($ck, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function columnType($columnName)
|
||||
{
|
||||
|
|
|
@ -106,7 +106,7 @@ class Notice extends Memcached_DataObject
|
|||
function getProfile()
|
||||
{
|
||||
if (is_int($this->_profile) && $this->_profile == -1) {
|
||||
$this->_profile = Profile::staticGet('id', $this->profile_id);
|
||||
$this->_setProfile(Profile::staticGet('id', $this->profile_id));
|
||||
|
||||
if (empty($this->_profile)) {
|
||||
// TRANS: Server exception thrown when a user profile for a notice cannot be found.
|
||||
|
@ -117,6 +117,11 @@ class Notice extends Memcached_DataObject
|
|||
|
||||
return $this->_profile;
|
||||
}
|
||||
|
||||
function _setProfile($profile)
|
||||
{
|
||||
$this->_profile = $profile;
|
||||
}
|
||||
|
||||
function delete()
|
||||
{
|
||||
|
@ -1366,17 +1371,11 @@ class Notice extends Memcached_DataObject
|
|||
*/
|
||||
function getReplyProfiles()
|
||||
{
|
||||
$ids = $this->getReplies();
|
||||
$profiles = array();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$profile = Profile::staticGet('id', $id);
|
||||
if (!empty($profile)) {
|
||||
$profiles[] = $profile;
|
||||
}
|
||||
}
|
||||
$ids = $this->getReplies();
|
||||
|
||||
return $profiles;
|
||||
$profiles = Profile::multiGet('id', $ids);
|
||||
|
||||
return $profiles->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1433,25 +1432,14 @@ class Notice extends Memcached_DataObject
|
|||
|
||||
$gi->notice_id = $this->id;
|
||||
|
||||
if ($gi->find()) {
|
||||
while ($gi->fetch()) {
|
||||
$ids[] = $gi->group_id;
|
||||
}
|
||||
}
|
||||
|
||||
$ids = $gi->fetchAll('group_id');
|
||||
|
||||
self::cacheSet($keypart, implode(',', $ids));
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$group = User_group::staticGet('id', $id);
|
||||
if ($group) {
|
||||
$groups[] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
return $groups;
|
||||
$groups = User_group::multiGet('id', $ids);
|
||||
|
||||
return $groups->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2382,11 +2370,10 @@ class Notice extends Memcached_DataObject
|
|||
|
||||
if ($this->scope & Notice::ADDRESSEE_SCOPE) {
|
||||
|
||||
// XXX: just query for the single reply
|
||||
|
||||
$replies = $this->getReplies();
|
||||
|
||||
if (!in_array($profile->id, $replies)) {
|
||||
$repl = Reply::pkeyGet(array('notice_id' => $this->id,
|
||||
'profile_id' => $profile->id));
|
||||
|
||||
if (empty($repl)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -2492,4 +2479,28 @@ class Notice extends Memcached_DataObject
|
|||
return $scope;
|
||||
}
|
||||
|
||||
static function fillProfiles($notices)
|
||||
{
|
||||
$map = self::getProfiles($notices);
|
||||
|
||||
foreach ($notices as $notice) {
|
||||
if (array_key_exists($notice->profile_id, $map)) {
|
||||
$notice->_setProfile($map[$notice->profile_id]);
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($map);
|
||||
}
|
||||
|
||||
static function getProfiles(&$notices)
|
||||
{
|
||||
$ids = array();
|
||||
foreach ($notices as $notice) {
|
||||
$ids[] = $notice->profile_id;
|
||||
}
|
||||
|
||||
$ids = array_unique($ids);
|
||||
|
||||
return Memcached_DataObject::pivotGet('Profile', 'id', $ids);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,11 @@ class Profile extends Memcached_DataObject
|
|||
return Memcached_DataObject::staticGet('Profile',$k,$v);
|
||||
}
|
||||
|
||||
function multiGet($keyCol, $keyVals, $skipNulls=true)
|
||||
{
|
||||
return parent::multiGet('Profile', $keyCol, $keyVals, $skipNulls);
|
||||
}
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
|
@ -63,12 +68,18 @@ class Profile extends Memcached_DataObject
|
|||
return $this->_user;
|
||||
}
|
||||
|
||||
protected $_avatars = array();
|
||||
|
||||
function getAvatar($width, $height=null)
|
||||
{
|
||||
if (is_null($height)) {
|
||||
$height = $width;
|
||||
}
|
||||
|
||||
if (array_key_exists($width, $this->_avatars)) {
|
||||
return $this->_avatars[$width];
|
||||
}
|
||||
|
||||
$avatar = null;
|
||||
|
||||
if (Event::handle('StartProfileGetAvatar', array($this, $width, &$avatar))) {
|
||||
|
@ -78,9 +89,16 @@ class Profile extends Memcached_DataObject
|
|||
Event::handle('EndProfileGetAvatar', array($this, $width, &$avatar));
|
||||
}
|
||||
|
||||
$this->_avatars[$width] = $avatar;
|
||||
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
function _fillAvatar($width, $avatar)
|
||||
{
|
||||
$this->_avatars[$width] = $avatar;
|
||||
}
|
||||
|
||||
function getOriginalAvatar()
|
||||
{
|
||||
$avatar = DB_DataObject::factory('avatar');
|
||||
|
@ -225,9 +243,14 @@ class Profile extends Memcached_DataObject
|
|||
|
||||
function isMember($group)
|
||||
{
|
||||
$gm = Group_member::pkeyGet(array('profile_id' => $this->id,
|
||||
'group_id' => $group->id));
|
||||
return (!empty($gm));
|
||||
$groups = $this->getGroups(0, null);
|
||||
$gs = $groups->fetchAll();
|
||||
foreach ($gs as $g) {
|
||||
if ($group->id == $g->id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAdmin($group)
|
||||
|
@ -268,16 +291,7 @@ class Profile extends Memcached_DataObject
|
|||
self::cacheSet($keypart, implode(',', $ids));
|
||||
}
|
||||
|
||||
$groups = array();
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$group = User_group::staticGet('id', $id);
|
||||
if (!empty($group)) {
|
||||
$groups[] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayWrapper($groups);
|
||||
return User_group::multiGet('id', $ids);
|
||||
}
|
||||
|
||||
function isTagged($peopletag)
|
||||
|
@ -1357,7 +1371,22 @@ class Profile extends Memcached_DataObject
|
|||
function __sleep()
|
||||
{
|
||||
$vars = parent::__sleep();
|
||||
$skip = array('_user');
|
||||
$skip = array('_user', '_avatars');
|
||||
return array_diff($vars, $skip);
|
||||
}
|
||||
|
||||
static function fillAvatars(&$profiles, $width)
|
||||
{
|
||||
$ids = array();
|
||||
foreach ($profiles as $profile) {
|
||||
$ids[] = $profile->id;
|
||||
}
|
||||
|
||||
$avatars = Avatar::pivotGet('profile_id', $ids, array('width' => $width,
|
||||
'height' => $width));
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
$profile->_fillAvatar($width, $avatars[$profile->id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,11 @@ class Reply extends Memcached_DataObject
|
|||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
||||
function pkeyGet($kv)
|
||||
{
|
||||
return Memcached_DataObject::pkeyGet('Reply',$kv);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for record insertion to update related caches
|
||||
*/
|
||||
|
|
|
@ -33,6 +33,11 @@ class User_group extends Memcached_DataObject
|
|||
function staticGet($k,$v=NULL) {
|
||||
return Memcached_DataObject::staticGet('User_group',$k,$v);
|
||||
}
|
||||
|
||||
function multiGet($keyCol, $keyVals, $skipNulls=true)
|
||||
{
|
||||
return parent::multiGet('User_group', $keyCol, $keyVals, $skipNulls);
|
||||
}
|
||||
|
||||
/* the code above is auto generated do not remove the tag below */
|
||||
###END_AUTOCODE
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
|
||||
|
||||
define('STATUSNET_BASE_VERSION', '1.0.0');
|
||||
define('STATUSNET_LIFECYCLE', 'beta1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
|
||||
define('STATUSNET_LIFECYCLE', 'beta2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
|
||||
define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . STATUSNET_LIFECYCLE);
|
||||
|
||||
define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
|
||||
|
@ -156,4 +156,5 @@ function PEAR_ErrorToPEAR_Exception($err)
|
|||
}
|
||||
throw new PEAR_Exception($err->getMessage());
|
||||
}
|
||||
|
||||
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception');
|
||||
|
|
|
@ -83,17 +83,16 @@ class NoticeList extends Widget
|
|||
$this->out->elementStart('div', array('id' =>'notices_primary'));
|
||||
$this->out->elementStart('ol', array('class' => 'notices xoxo'));
|
||||
|
||||
$cnt = 0;
|
||||
|
||||
while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
|
||||
$cnt++;
|
||||
|
||||
if ($cnt > NOTICES_PER_PAGE) {
|
||||
break;
|
||||
}
|
||||
$notices = $this->notice->fetchAll();
|
||||
$total = count($notices);
|
||||
$notices = array_slice($notices, 0, NOTICES_PER_PAGE);
|
||||
|
||||
self::prefill($notices);
|
||||
|
||||
foreach ($notices as $notice) {
|
||||
|
||||
try {
|
||||
$item = $this->newListItem($this->notice);
|
||||
$item = $this->newListItem($notice);
|
||||
$item->show();
|
||||
} catch (Exception $e) {
|
||||
// we log exceptions and continue
|
||||
|
@ -105,7 +104,7 @@ class NoticeList extends Widget
|
|||
$this->out->elementEnd('ol');
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
return $cnt;
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -122,4 +121,24 @@ class NoticeList extends Widget
|
|||
{
|
||||
return new NoticeListItem($notice, $this->out);
|
||||
}
|
||||
|
||||
static function prefill(&$notices, $avatarSize=AVATAR_STREAM_SIZE)
|
||||
{
|
||||
// Prefill the profiles
|
||||
$profiles = Notice::fillProfiles($notices);
|
||||
// Prefill the avatars
|
||||
Profile::fillAvatars($profiles, $avatarSize);
|
||||
|
||||
$p = Profile::current();
|
||||
|
||||
$ids = array();
|
||||
|
||||
foreach ($notices as $notice) {
|
||||
$ids[] = $notice->id;
|
||||
}
|
||||
|
||||
if (!empty($p)) {
|
||||
Memcached_DataObject::pivotGet('Fave', 'notice_id', $ids, array('user_id' => $p->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,17 +76,18 @@ class ThreadedNoticeList extends NoticeList
|
|||
$this->out->element('h2', null, _m('HEADER','Notices'));
|
||||
$this->out->elementStart('ol', array('class' => 'notices threaded-notices xoxo'));
|
||||
|
||||
$cnt = 0;
|
||||
$notices = $this->notice->fetchAll();
|
||||
$total = count($notices);
|
||||
$notices = array_slice($notices, 0, NOTICES_PER_PAGE);
|
||||
|
||||
self::prefill($notices);
|
||||
|
||||
$conversations = array();
|
||||
while ($this->notice->fetch() && $cnt <= NOTICES_PER_PAGE) {
|
||||
$cnt++;
|
||||
|
||||
if ($cnt > NOTICES_PER_PAGE) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($notices as $notice) {
|
||||
|
||||
// Collapse repeats into their originals...
|
||||
$notice = $this->notice;
|
||||
|
||||
if ($notice->repeat_of) {
|
||||
$orig = Notice::staticGet('id', $notice->repeat_of);
|
||||
if ($orig) {
|
||||
|
@ -119,7 +120,7 @@ class ThreadedNoticeList extends NoticeList
|
|||
$this->out->elementEnd('ol');
|
||||
$this->out->elementEnd('div');
|
||||
|
||||
return $cnt;
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -223,6 +224,7 @@ class ThreadedNoticeListItem extends NoticeListItem
|
|||
$item = new ThreadedNoticeListMoreItem($moreCutoff, $this->out, count($notices));
|
||||
$item->show();
|
||||
}
|
||||
NoticeList::prefill($notices, AVATAR_MINI_SIZE);
|
||||
foreach (array_reverse($notices) as $notice) {
|
||||
if (Event::handle('StartShowThreadedNoticeSub', array($this, $this->notice, $notice))) {
|
||||
$item = new ThreadedNoticeListSubItem($notice, $this->notice, $this->out);
|
||||
|
|
15
lib/util.php
15
lib/util.php
|
@ -1127,8 +1127,11 @@ function common_tag_link($tag)
|
|||
|
||||
function common_canonical_tag($tag)
|
||||
{
|
||||
// only alphanum
|
||||
$tag = preg_replace('/[^\pL\pN]/u', '', $tag);
|
||||
$tag = mb_convert_case($tag, MB_CASE_LOWER, "UTF-8");
|
||||
return str_replace(array('-', '_', '.'), '', $tag);
|
||||
$tag = substr($tag, 0, 64);
|
||||
return $tag;
|
||||
}
|
||||
|
||||
function common_valid_profile_tag($str)
|
||||
|
@ -1501,16 +1504,18 @@ function common_enqueue_notice($notice)
|
|||
}
|
||||
|
||||
/**
|
||||
* Broadcast profile updates to remote subscribers.
|
||||
* Legacy function to broadcast profile updates to OMB remote subscribers.
|
||||
*
|
||||
* XXX: This probably needs killing, but there are several bits of code
|
||||
* that broadcast profile changes that need to be dealt with. AFAIK
|
||||
* this function is only used for OMB. -z
|
||||
*
|
||||
* Since this may be slow with a lot of subscribers or bad remote sites,
|
||||
* this is run through the background queues if possible.
|
||||
*/
|
||||
function common_broadcast_profile(Profile $profile)
|
||||
{
|
||||
$qm = QueueManager::get();
|
||||
$qm->enqueue($profile, "profile");
|
||||
return true;
|
||||
Event::handle('BroadcastProfile', array($profile));
|
||||
}
|
||||
|
||||
function common_profile_url($nickname)
|
||||
|
|
|
@ -82,6 +82,7 @@ class EventPlugin extends MicroappPlugin
|
|||
case 'CancelrsvpAction':
|
||||
case 'ShoweventAction':
|
||||
case 'ShowrsvpAction':
|
||||
case 'TimelistAction':
|
||||
include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
|
||||
return false;
|
||||
case 'EventListItem':
|
||||
|
@ -89,6 +90,7 @@ class EventPlugin extends MicroappPlugin
|
|||
case 'EventForm':
|
||||
case 'RSVPForm':
|
||||
case 'CancelRSVPForm':
|
||||
case 'EventTimeList':
|
||||
include_once $dir . '/'.strtolower($cls).'.php';
|
||||
break;
|
||||
case 'Happening':
|
||||
|
@ -121,6 +123,8 @@ class EventPlugin extends MicroappPlugin
|
|||
$m->connect('rsvp/:id',
|
||||
array('action' => 'showrsvp'),
|
||||
array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
|
||||
$m->connect('main/event/updatetimes',
|
||||
array('action' => 'timelist'));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -345,7 +349,7 @@ class EventPlugin extends MicroappPlugin
|
|||
|
||||
function onEndShowScripts($action)
|
||||
{
|
||||
$action->inlineScript('$(document).ready(function() { $("#event-startdate").datepicker(); $("#event-enddate").datepicker(); });');
|
||||
$action->script($this->path('event.js'));
|
||||
}
|
||||
|
||||
function onEndShowStyles($action)
|
||||
|
|
|
@ -6,3 +6,11 @@
|
|||
.event-title { margin-left: 0px; }
|
||||
#content .event .entry-title { margin-left: 0px; }
|
||||
#content .event .entry-content { margin-left: 0px; }
|
||||
.ui-autocomplete {
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
/* prevent horizontal scrollbar */
|
||||
overflow-x: hidden;
|
||||
/* add padding to account for vertical scrollbar */
|
||||
padding-right: 20px;
|
||||
}
|
73
plugins/Event/event.js
Normal file
73
plugins/Event/event.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
$(document).ready(function() {
|
||||
|
||||
var today = new Date();
|
||||
|
||||
$("#event-startdate").datepicker({
|
||||
// Don't let the user set a crazy start date
|
||||
minDate: today,
|
||||
onClose: function(dateText, picker) {
|
||||
// Don't let the user set a crazy end date
|
||||
var newStartDate = new Date(dateText);
|
||||
var endDate = new Date($("#event-startdate").val());
|
||||
if (endDate < newStartDate) {
|
||||
$("#event-enddate").val(dateText);
|
||||
}
|
||||
if (dateText !== null) {
|
||||
$("#event-enddate").datepicker('option', 'minDate', new Date(dateText));
|
||||
}
|
||||
},
|
||||
onSelect: function() {
|
||||
var startd = $("#event-startdate").val();
|
||||
var endd = $("#event-enddate").val();
|
||||
var sdate = new Date(startd);
|
||||
var edate = new Date(endd);
|
||||
if (sdate !== edate) {
|
||||
updateTimes();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$("#event-enddate").datepicker({
|
||||
minDate: today,
|
||||
onSelect: function() {
|
||||
var startd = $("#event-startdate").val();
|
||||
var endd = $("#event-enddate").val();
|
||||
var sdate = new Date(startd);
|
||||
var edate = new Date(endd);
|
||||
if (sdate !== edate) {
|
||||
updateTimes();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateTimes() {
|
||||
var startd = $("#event-startdate").val();
|
||||
var endd = $("#event-enddate").val();
|
||||
|
||||
var startt = $("#event-starttime option:selected").val();
|
||||
var endt = $("#event-endtime option:selected").val();
|
||||
|
||||
var sdate = new Date(startd + " " + startt);
|
||||
var edate = new Date(endd + " " + endt);
|
||||
var duration = (startd === endd);
|
||||
|
||||
$.getJSON($('#timelist_action_url').val(),
|
||||
{ start: startt, ajax: true, duration: duration },
|
||||
function(data) {
|
||||
var times = [];
|
||||
$.each(data, function(key, val) {
|
||||
times.push('<option value="' + key + '">' + val + '</option>');
|
||||
});
|
||||
|
||||
$("#event-endtime").html(times.join(''));
|
||||
if (startt < endt) {
|
||||
$("#event-endtime").val(endt).attr("selected", "selected");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$("#event-starttime").change(function(e) {
|
||||
updateTimes();
|
||||
});
|
||||
|
||||
});
|
|
@ -84,6 +84,17 @@ class EventForm extends Form
|
|||
function formData()
|
||||
{
|
||||
$this->out->elementStart('fieldset', array('id' => 'new_event_data'));
|
||||
|
||||
// Passing in the URL of the Ajax action that the .js for this form hits
|
||||
// when selecting event start and end times. JavaScript will try to
|
||||
// use a relative path, unless explicitely told where an action is,
|
||||
// and that's a bit difficult to calculate since the event form is on
|
||||
// so many pages with different paths. It might be worth solving this
|
||||
// globally by putting the base site path in the Identifier-URL meta tag
|
||||
// or something similar, so it would be easy to calculate the exact path
|
||||
// for actions and other things in JavaScripts. -z
|
||||
$this->out->hidden('timelist_action_url', common_local_url('timelist'));
|
||||
|
||||
$this->out->elementStart('ul', 'form_data');
|
||||
|
||||
$this->li();
|
||||
|
@ -97,49 +108,71 @@ class EventForm extends Form
|
|||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
|
||||
$today = new DateTime('today');
|
||||
$today->setTimezone(new DateTimeZone(common_timezone()));
|
||||
|
||||
$this->out->input('event-startdate',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','Start date'),
|
||||
null,
|
||||
$today->format('m/d/Y'),
|
||||
// TRANS: Field title on event form.
|
||||
_m('Date the event starts.'),
|
||||
'startdate');
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('event-starttime',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','Start time'),
|
||||
null,
|
||||
// TRANS: Field title on event form.
|
||||
_m('Time the event starts.'),
|
||||
'starttime');
|
||||
|
||||
$times = EventTimeList::getTimes();
|
||||
|
||||
$this->out->dropdown(
|
||||
'event-starttime',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','Start time'),
|
||||
$times,
|
||||
// TRANS: Field title on event form.
|
||||
_m('Time the event starts.'),
|
||||
false,
|
||||
null
|
||||
);
|
||||
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('event-enddate',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','End date'),
|
||||
null,
|
||||
$today->format('m/d/Y'),
|
||||
// TRANS: Field title on event form.
|
||||
_m('Date the event ends.'),
|
||||
'enddate');
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('event-endtime',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','End time'),
|
||||
null,
|
||||
// TRANS: Field title on event form.
|
||||
_m('Time the event ends.'),
|
||||
'endtime');
|
||||
|
||||
// XXX: Initial end time should be at least 30 mins out? We could do
|
||||
// every 15 minute instead -z
|
||||
$keys = array_keys($times);
|
||||
$endStr = date('m/d/y', strtotime('now')) . " {$keys[0]}";
|
||||
$end = new DateTime($endStr);
|
||||
$end->modify('+30');
|
||||
|
||||
$this->out->dropdown(
|
||||
'event-endtime',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','End time'),
|
||||
EventTimeList::getTimes($end->format('c'), true),
|
||||
// TRANS: Field title on event form.
|
||||
_m('Time the event ends.'),
|
||||
false,
|
||||
null
|
||||
);
|
||||
$this->unli();
|
||||
|
||||
$this->li();
|
||||
$this->out->input('event-location',
|
||||
// TRANS: Field label on event form.
|
||||
_m('LABEL','Location'),
|
||||
_m('LABEL','Where?'),
|
||||
null,
|
||||
// TRANS: Field title on event form.
|
||||
_m('Event location.'),
|
||||
|
|
|
@ -83,13 +83,33 @@ class EventListItem extends NoticeListItemAdapter
|
|||
|
||||
$out->elementEnd('h3'); // VEVENT/H3 OUT
|
||||
|
||||
$startDate = strftime("%x", strtotime($event->start_time));
|
||||
$startTime = strftime("%R", strtotime($event->start_time));
|
||||
$now = new DateTime();
|
||||
$startDate = new DateTime($event->start_time);
|
||||
$endDate = new DateTime($event->end_time);
|
||||
$userTz = new DateTimeZone(common_timezone());
|
||||
|
||||
$endDate = strftime("%x", strtotime($event->end_time));
|
||||
$endTime = strftime("%R", strtotime($event->end_time));
|
||||
// Localize the time for the observer
|
||||
$now->setTimeZone($userTz);
|
||||
$startDate->setTimezone($userTz);
|
||||
$endDate->setTimezone($userTz);
|
||||
|
||||
// FIXME: better dates
|
||||
$thisYear = $now->format('Y');
|
||||
$startYear = $startDate->format('Y');
|
||||
$endYear = $endDate->format('Y');
|
||||
|
||||
$dateFmt = 'D, F j, '; // e.g.: Mon, Aug 31
|
||||
|
||||
if ($startYear != $thisYear || $endYear != $thisYear) {
|
||||
$dateFmt .= 'Y,'; // append year if we need to think about years
|
||||
}
|
||||
|
||||
$startDateStr = $startDate->format($dateFmt);
|
||||
$endDateStr = $endDate->format($dateFmt);
|
||||
|
||||
$timeFmt = 'g:ia';
|
||||
|
||||
$startTimeStr = $startDate->format($timeFmt);
|
||||
$endTimeStr = $endDate->format("{$timeFmt} (T)");
|
||||
|
||||
$out->elementStart('div', 'event-times'); // VEVENT/EVENT-TIMES IN
|
||||
|
||||
|
@ -98,16 +118,16 @@ class EventListItem extends NoticeListItemAdapter
|
|||
|
||||
$out->element('abbr', array('class' => 'dtstart',
|
||||
'title' => common_date_iso8601($event->start_time)),
|
||||
$startDate . ' ' . $startTime);
|
||||
$out->text(' - ');
|
||||
if ($startDate == $endDate) {
|
||||
$startDateStr . ' ' . $startTimeStr);
|
||||
$out->text(' – ');
|
||||
if ($startDateStr == $endDateStr) {
|
||||
$out->element('span', array('class' => 'dtend',
|
||||
'title' => common_date_iso8601($event->end_time)),
|
||||
$endTime);
|
||||
$endTimeStr);
|
||||
} else {
|
||||
$out->element('span', array('class' => 'dtend',
|
||||
'title' => common_date_iso8601($event->end_time)),
|
||||
$endDate . ' ' . $endTime);
|
||||
$endDateStr . ' ' . $endTimeStr);
|
||||
}
|
||||
|
||||
$out->elementEnd('div'); // VEVENT/EVENT-TIMES OUT
|
||||
|
|
119
plugins/Event/eventtimelist.php
Normal file
119
plugins/Event/eventtimelist.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
/**
|
||||
* Helper class for calculating and displaying event times
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* @category Data
|
||||
* @package StatusNet
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
|
||||
* @link http://status.net/
|
||||
*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2011, StatusNet, 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to get fancy times for the dropdowns on the new event form
|
||||
*/
|
||||
class EventTimeList {
|
||||
|
||||
/**
|
||||
* Round up to the nearest half hour
|
||||
*
|
||||
* @param string $time the time to round (date/time string)
|
||||
* @return DateTime the rounded time
|
||||
*/
|
||||
public static function nearestHalfHour($time)
|
||||
{
|
||||
$start = strtotime($time);
|
||||
|
||||
$minutes = date('i', $start);
|
||||
$hour = date('H', $start);
|
||||
|
||||
if ($minutes >= 30) {
|
||||
$minutes = '00';
|
||||
$hour++;
|
||||
} else {
|
||||
$minutes = '30';
|
||||
}
|
||||
|
||||
$newTimeStr = date('m/d/y', $start) . " {$hour}:{$minutes}:00";
|
||||
return new DateTime($newTimeStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a list of times in half-hour intervals
|
||||
*
|
||||
* @param string $start Time to start with (date/time string)
|
||||
* @param boolean $duration Whether to include the duration of the event
|
||||
* (from the start)
|
||||
* @return array $times (UTC time string => localized time string)
|
||||
*/
|
||||
public static function getTimes($start = 'now', $duration = false)
|
||||
{
|
||||
$newTime = self::nearestHalfHour($start);
|
||||
|
||||
$newTime->setTimezone(new DateTimeZone(common_timezone()));
|
||||
$times = array();
|
||||
$len = 0;
|
||||
|
||||
for ($i = 0; $i < 48; $i++) {
|
||||
|
||||
// make sure we store the time as UTC
|
||||
$newTime->setTimezone(new DateTimeZone('UTC'));
|
||||
$utcTime = $newTime->format('H:i:s');
|
||||
|
||||
// localize time for user
|
||||
$newTime->setTimezone(new DateTimeZone(common_timezone()));
|
||||
$localTime = $newTime->format('g:ia');
|
||||
|
||||
// pretty up the end-time option list a bit
|
||||
if ($duration) {
|
||||
$len += 30;
|
||||
$hours = $len / 60;
|
||||
// for i18n
|
||||
$hourStr = _m('hour');
|
||||
$hoursStr = _m('hrs');
|
||||
$minStr = _m('mins');
|
||||
switch ($hours) {
|
||||
case 0:
|
||||
$total = " (0 {$minStr})";
|
||||
break;
|
||||
case .5:
|
||||
$total = " (30 {$minStr})";
|
||||
break;
|
||||
case 1:
|
||||
$total = " (1 {$hourStr})";
|
||||
break;
|
||||
default:
|
||||
$total = " ({$hours} " . $hoursStr . ')';
|
||||
break;
|
||||
}
|
||||
$localTime .= $total;
|
||||
}
|
||||
|
||||
$times[$utcTime] = $localTime;
|
||||
$newTime->modify('+30min'); // 30 min intervals
|
||||
}
|
||||
|
||||
return $times;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -52,8 +52,8 @@ class NeweventAction extends Action
|
|||
protected $title = null;
|
||||
protected $location = null;
|
||||
protected $description = null;
|
||||
protected $startTime = null;
|
||||
protected $endTime = null;
|
||||
protected $startTime = null;
|
||||
protected $endTime = null;
|
||||
|
||||
/**
|
||||
* Returns the title of the action
|
||||
|
@ -89,67 +89,78 @@ class NeweventAction extends Action
|
|||
$this->checkSessionToken();
|
||||
}
|
||||
|
||||
$this->title = $this->trimmed('title');
|
||||
try {
|
||||
|
||||
if (empty($this->title)) {
|
||||
// TRANS: Client exception thrown when trying to post an event without providing a title.
|
||||
throw new ClientException(_m('Title required.'));
|
||||
}
|
||||
$this->title = $this->trimmed('title');
|
||||
|
||||
$this->location = $this->trimmed('location');
|
||||
$this->url = $this->trimmed('url');
|
||||
$this->description = $this->trimmed('description');
|
||||
if (empty($this->title)) {
|
||||
// TRANS: Client exception thrown when trying to post an event without providing a title.
|
||||
throw new ClientException(_m('Title required.'));
|
||||
}
|
||||
|
||||
$startDate = $this->trimmed('startdate');
|
||||
$this->location = $this->trimmed('location');
|
||||
$this->url = $this->trimmed('url');
|
||||
$this->description = $this->trimmed('description');
|
||||
|
||||
if (empty($startDate)) {
|
||||
// TRANS: Client exception thrown when trying to post an event without providing a start date.
|
||||
throw new ClientException(_m('Start date required.'));
|
||||
}
|
||||
$startDate = $this->trimmed('startdate');
|
||||
|
||||
$startTime = $this->trimmed('starttime');
|
||||
if (empty($startDate)) {
|
||||
// TRANS: Client exception thrown when trying to post an event without providing a start date.
|
||||
throw new ClientException(_m('Start date required.'));
|
||||
}
|
||||
|
||||
if (empty($startTime)) {
|
||||
$startTime = '00:00';
|
||||
}
|
||||
$startTime = $this->trimmed('event-starttime');
|
||||
|
||||
$endDate = $this->trimmed('enddate');
|
||||
if (empty($startTime)) {
|
||||
$startTime = '00:00';
|
||||
}
|
||||
|
||||
if (empty($endDate)) {
|
||||
// TRANS: Client exception thrown when trying to post an event without providing an end date.
|
||||
throw new ClientException(_m('End date required.'));
|
||||
}
|
||||
$endDate = $this->trimmed('enddate');
|
||||
|
||||
$endTime = $this->trimmed('endtime');
|
||||
if (empty($endDate)) {
|
||||
// TRANS: Client exception thrown when trying to post an event without providing an end date.
|
||||
throw new ClientException(_m('End date required.'));
|
||||
}
|
||||
|
||||
if (empty($endTime)) {
|
||||
$endTime = '00:00';
|
||||
}
|
||||
$endTime = $this->trimmed('event-endtime');
|
||||
|
||||
$start = $startDate . ' ' . $startTime;
|
||||
if (empty($endTime)) {
|
||||
$endTime = '00:00';
|
||||
}
|
||||
|
||||
common_debug("Event start: '$start'");
|
||||
$start = $startDate . ' ' . $startTime;
|
||||
|
||||
$end = $endDate . ' ' . $endTime;
|
||||
common_debug("Event start: '$start'");
|
||||
|
||||
common_debug("Event start: '$end'");
|
||||
$end = $endDate . ' ' . $endTime;
|
||||
|
||||
$this->startTime = strtotime($start);
|
||||
$this->endTime = strtotime($end);
|
||||
common_debug("Event start: '$end'");
|
||||
|
||||
if ($this->startTime == 0) {
|
||||
// TRANS: Client exception thrown when trying to post an event with a date that cannot be processed.
|
||||
// TRANS: %s is the data that could not be processed.
|
||||
throw new Exception(sprintf(_m('Could not parse date "%s".'),
|
||||
$start));
|
||||
}
|
||||
$this->startTime = strtotime($start);
|
||||
$this->endTime = strtotime($end);
|
||||
|
||||
if ($this->startTime == 0) {
|
||||
// TRANS: Client exception thrown when trying to post an event with a date that cannot be processed.
|
||||
// TRANS: %s is the data that could not be processed.
|
||||
throw new ClientException(sprintf(_m('Could not parse date "%s".'),
|
||||
$start));
|
||||
}
|
||||
|
||||
if ($this->endTime == 0) {
|
||||
// TRANS: Client exception thrown when trying to post an event with a date that cannot be processed.
|
||||
// TRANS: %s is the data that could not be processed.
|
||||
throw new Exception(sprintf(_m('Could not parse date "%s".'),
|
||||
$end));
|
||||
if ($this->endTime == 0) {
|
||||
// TRANS: Client exception thrown when trying to post an event with a date that cannot be processed.
|
||||
// TRANS: %s is the data that could not be processed.
|
||||
throw new ClientException(sprintf(_m('Could not parse date "%s".'),
|
||||
$end));
|
||||
}
|
||||
} catch (ClientException $ce) {
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->outputAjaxError($ce->getMessage());
|
||||
return false;
|
||||
} else {
|
||||
$this->error = $ce->getMessage();
|
||||
$this->showPage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -220,9 +231,13 @@ class NeweventAction extends Action
|
|||
RSVP::saveNew($profile, $event, RSVP::POSITIVE);
|
||||
|
||||
} catch (ClientException $ce) {
|
||||
$this->error = $ce->getMessage();
|
||||
$this->showPage();
|
||||
return;
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->outputAjaxError($ce->getMessage());
|
||||
} else {
|
||||
$this->error = $ce->getMessage();
|
||||
$this->showPage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
|
@ -242,6 +257,23 @@ class NeweventAction extends Action
|
|||
}
|
||||
}
|
||||
|
||||
// @todo factor this out into a base class
|
||||
function outputAjaxError($msg)
|
||||
{
|
||||
header('Content-Type: text/xml;charset=utf-8');
|
||||
$this->xw->startDocument('1.0', 'UTF-8');
|
||||
$this->elementStart('html');
|
||||
$this->elementStart('head');
|
||||
// TRANS: Page title after an AJAX error occurs
|
||||
$this->element('title', null, _('Ajax Error'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$this->element('p', array('id' => 'error'), $msg);
|
||||
$this->elementEnd('body');
|
||||
$this->elementEnd('html');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the event form
|
||||
*
|
||||
|
|
106
plugins/Event/timelist.php
Normal file
106
plugins/Event/timelist.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
/**
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2011, StatusNet, 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/>.
|
||||
*
|
||||
* @category Event
|
||||
* @package StatusNet
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @copyright 2011 StatusNet, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
|
||||
* @link http://status.net/
|
||||
*/
|
||||
|
||||
if (!defined('STATUSNET')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback handler to populate end time dropdown
|
||||
*/
|
||||
class TimelistAction extends Action {
|
||||
|
||||
private $start;
|
||||
private $duration;
|
||||
|
||||
/**
|
||||
* Get ready
|
||||
*
|
||||
* @param array $args misc. arguments
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
function prepare($args) {
|
||||
parent::prepare($args);
|
||||
$this->start = $this->arg('start');
|
||||
$this->duration = $this->boolean('duration', false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle input and ouput something
|
||||
*
|
||||
* @param array $args $_REQUEST arguments
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
if (!common_logged_in()) {
|
||||
// TRANS: Error message displayed when trying to perform an action that requires a logged in user.
|
||||
$this->clientError(_('Not logged in.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($this->start)) {
|
||||
$times = EventTimeList::getTimes($this->start, $this->duration);
|
||||
} else {
|
||||
$this->clientError(_m('Unexpected form submission.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
print json_encode($times);
|
||||
} else {
|
||||
$this->clientError(_m('This action is AJAX only.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the regular error handler to show something more
|
||||
* ajaxy
|
||||
*
|
||||
* @param string $msg error message
|
||||
* @param int $code error code
|
||||
*/
|
||||
function clientError($msg, $code = 400) {
|
||||
if ($this->boolean('ajax')) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
print json_encode(
|
||||
array(
|
||||
'success' => false,
|
||||
'code' => $code,
|
||||
'message' => $msg
|
||||
)
|
||||
);
|
||||
} else {
|
||||
parent::clientError($msg, $code);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -103,7 +103,11 @@ class MeteorPlugin extends RealtimePlugin
|
|||
function _updateInitialize($timeline, $user_id)
|
||||
{
|
||||
$script = parent::_updateInitialize($timeline, $user_id);
|
||||
return $script." MeteorUpdater.init(\"$this->webserver\", $this->webport, \"{$timeline}\");";
|
||||
$ours = sprintf("MeteorUpdater.init(%s, %s, %s);",
|
||||
json_encode($this->webserver),
|
||||
json_encode($this->webport),
|
||||
json_encode($timeline));
|
||||
return $script." ".$ours;
|
||||
}
|
||||
|
||||
function _connect()
|
||||
|
|
|
@ -369,6 +369,18 @@ class OMBPlugin extends Plugin
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a profile over OMB
|
||||
*
|
||||
* @param Profile $profile to broadcast
|
||||
* @return false
|
||||
*/
|
||||
function onBroadcastProfile($profile) {
|
||||
$qm = QueueManager::get();
|
||||
$qm->enqueue($profile, "profile");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin version info
|
||||
*
|
||||
|
|
|
@ -1171,9 +1171,19 @@ td.entity_profile {
|
|||
width: auto;
|
||||
}
|
||||
|
||||
#event-startdate, #event-starttime, #event-enddate, #event-endtime {
|
||||
width: 120px;
|
||||
label[for=event-starttime], label[for=event-endtime] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#event-starttime, #event-endtime {
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
#event-startdate, #event-enddate {
|
||||
margin-right: 20px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
/* Limited-scope specific styles */
|
||||
|
|
Loading…
Reference in New Issue
Block a user