[REALTIME] Reviewed both the superclass and its dist plugins

This commit is contained in:
Diogo Cordeiro 2019-11-03 15:37:49 +00:00 committed by Diogo Peralta Cordeiro
parent aab3584f93
commit 3b01aa31d3
80 changed files with 349 additions and 315 deletions

View File

@ -1,35 +1,31 @@
<?php <?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/** /**
* StatusNet, the distributed open-source microblogging tool
*
* Superclass for plugins that do "real time" updates of timelines using Ajax * Superclass for plugins that do "real time" updates of timelines using Ajax
* *
* 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 Plugin * @category Plugin
* @package StatusNet * @package GNUsocial
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @author Mikael Nordfeldth <mmn@hethane.se> * @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2009 StatusNet, Inc. * @copyright 2009-2019 Free Software Foundation, Inc http://www.fsf.org
* @copyright 2014 Free Software Foundation, Inc. * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/ */
if (!defined('GNUSOCIAL')) { exit(1); } defined('GNUSOCIAL') || die();
/** /**
* Superclass for plugin to do realtime updates * Superclass for plugin to do realtime updates
@ -37,13 +33,12 @@ if (!defined('GNUSOCIAL')) { exit(1); }
* Based on experience with the Comet and Meteor plugins, * Based on experience with the Comet and Meteor plugins,
* this superclass extracts out some of the common functionality * this superclass extracts out some of the common functionality
* *
* Currently depends on Favorite plugin. * Currently depends on the Favorite module.
* *
* @category Plugin * @category Plugin
* @package StatusNet * @package GNUsocial
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://status.net/
*/ */
class RealtimePlugin extends Plugin class RealtimePlugin extends Plugin
{ {
@ -53,15 +48,17 @@ class RealtimePlugin extends Plugin
* When it's time to initialize the plugin, calculate and * When it's time to initialize the plugin, calculate and
* pass the URLs we need. * pass the URLs we need.
*/ */
function onInitializePlugin() public function onInitializePlugin()
{ {
// FIXME: need to find a better way to pass this pattern in // FIXME: need to find a better way to pass this pattern in
$this->showurl = common_local_url('shownotice', $this->showurl = common_local_url(
array('notice' => '0000000000')); 'shownotice',
['notice' => '0000000000']
);
return true; return true;
} }
function onCheckSchema() public function onCheckSchema()
{ {
$schema = Schema::get(); $schema = Schema::get();
$schema->ensureTable('realtime_channel', Realtime_channel::schemaDef()); $schema->ensureTable('realtime_channel', Realtime_channel::schemaDef());
@ -72,20 +69,25 @@ class RealtimePlugin extends Plugin
* Hook for RouterInitialized event. * Hook for RouterInitialized event.
* *
* @param URLMapper $m path-to-action mapper * @param URLMapper $m path-to-action mapper
* @return boolean hook return * @return bool hook return
* @throws Exception
*/ */
public function onRouterInitialized(URLMapper $m) public function onRouterInitialized(URLMapper $m)
{ {
$m->connect('main/channel/:channelkey/keepalive', $m->connect(
['action' => 'keepalivechannel'], 'main/channel/:channelkey/keepalive',
['channelkey' => '[a-z0-9]{32}']); ['action' => 'keepalivechannel'],
$m->connect('main/channel/:channelkey/close', ['channelkey' => '[a-z0-9]{32}']
['action' => 'closechannel'], );
['channelkey' => '[a-z0-9]{32}']); $m->connect(
'main/channel/:channelkey/close',
['action' => 'closechannel'],
['channelkey' => '[a-z0-9]{32}']
);
return true; return true;
} }
function onEndShowScripts($action) public function onEndShowScripts(Action $action)
{ {
$channel = $this->_getChannel($action); $channel = $this->_getChannel($action);
@ -93,7 +95,7 @@ class RealtimePlugin extends Plugin
return true; return true;
} }
$timeline = $this->_pathToChannel(array($channel->channel_key)); $timeline = $this->_pathToChannel([$channel->channel_key]);
// If there's not a timeline on this page, // If there's not a timeline on this page,
// just return true // just return true
@ -125,11 +127,10 @@ class RealtimePlugin extends Plugin
if ($action->boolean('realtime')) { if ($action->boolean('realtime')) {
$realtimeUI = ' RealtimeUpdate.initPopupWindow();'; $realtimeUI = ' RealtimeUpdate.initPopupWindow();';
} } else {
else {
$pluginPath = common_path('plugins/Realtime/'); $pluginPath = common_path('plugins/Realtime/');
$keepalive = common_local_url('keepalivechannel', array('channelkey' => $channel->channel_key)); $keepalive = common_local_url('keepalivechannel', ['channelkey' => $channel->channel_key]);
$close = common_local_url('closechannel', array('channelkey' => $channel->channel_key)); $close = common_local_url('closechannel', ['channelkey' => $channel->channel_key]);
$realtimeUI = ' RealtimeUpdate.initActions('.json_encode($url).', '.json_encode($timeline).', '.json_encode($pluginPath).', '.json_encode($keepalive).', '.json_encode($close).'); '; $realtimeUI = ' RealtimeUpdate.initActions('.json_encode($url).', '.json_encode($timeline).', '.json_encode($pluginPath).', '.json_encode($keepalive).', '.json_encode($close).'); ';
} }
@ -144,15 +145,17 @@ class RealtimePlugin extends Plugin
public function onEndShowStylesheets(Action $action) public function onEndShowStylesheets(Action $action)
{ {
$urlpath = self::staticPath(str_replace('Plugin','',__CLASS__), $urlpath = self::staticPath(
'css/realtimeupdate.css'); str_replace('Plugin', '', __CLASS__),
'css/realtimeupdate.css'
);
$action->cssLink($urlpath, null, 'screen, projection, tv'); $action->cssLink($urlpath, null, 'screen, projection, tv');
return true; return true;
} }
public function onHandleQueuedNotice(Notice $notice) public function onHandleQueuedNotice(Notice $notice)
{ {
$paths = array(); $paths = [];
// Add to the author's timeline // Add to the author's timeline
@ -165,7 +168,7 @@ class RealtimePlugin extends Plugin
try { try {
$user = $profile->getUser(); $user = $profile->getUser();
$paths[] = array('showstream', $user->nickname, null); $paths[] = ['showstream', $user->nickname, null];
} catch (NoSuchUserException $e) { } catch (NoSuchUserException $e) {
// We really should handle the remote profile views too // We really should handle the remote profile views too
$user = null; $user = null;
@ -176,7 +179,7 @@ class RealtimePlugin extends Plugin
$is_local = intval($notice->is_local); $is_local = intval($notice->is_local);
if ($is_local === Notice::LOCAL_PUBLIC || if ($is_local === Notice::LOCAL_PUBLIC ||
($is_local === Notice::REMOTE && !common_config('public', 'localonly'))) { ($is_local === Notice::REMOTE && !common_config('public', 'localonly'))) {
$paths[] = array('public', null, null); $paths[] = ['public', null, null];
} }
// Add to the tags timeline // Add to the tags timeline
@ -185,7 +188,7 @@ class RealtimePlugin extends Plugin
if (!empty($tags)) { if (!empty($tags)) {
foreach ($tags as $tag) { foreach ($tags as $tag) {
$paths[] = array('tag', $tag, null); $paths[] = ['tag', $tag, null];
} }
} }
@ -196,7 +199,7 @@ class RealtimePlugin extends Plugin
foreach (array_keys($ni) as $user_id) { foreach (array_keys($ni) as $user_id) {
$user = User::getKV('id', $user_id); $user = User::getKV('id', $user_id);
$paths[] = array('all', $user->nickname, null); $paths[] = ['all', $user->getNickname(), null];
} }
// Add to the replies timeline // Add to the replies timeline
@ -208,7 +211,7 @@ class RealtimePlugin extends Plugin
while ($reply->fetch()) { while ($reply->fetch()) {
$user = User::getKV('id', $reply->profile_id); $user = User::getKV('id', $reply->profile_id);
if (!empty($user)) { if (!empty($user)) {
$paths[] = array('replies', $user->nickname, null); $paths[] = ['replies', $user->getNickname(), null];
} }
} }
} }
@ -222,12 +225,11 @@ class RealtimePlugin extends Plugin
if ($gi->find()) { if ($gi->find()) {
while ($gi->fetch()) { while ($gi->fetch()) {
$ug = User_group::getKV('id', $gi->group_id); $ug = User_group::getKV('id', $gi->group_id);
$paths[] = array('showgroup', $ug->nickname, null); $paths[] = ['showgroup', $ug->getNickname(), null];
} }
} }
if (count($paths) > 0) { if (count($paths) > 0) {
$json = $this->noticeAsJson($notice); $json = $this->noticeAsJson($notice);
$this->_connect(); $this->_connect();
@ -236,13 +238,14 @@ class RealtimePlugin extends Plugin
// new queue item for each path // new queue item for each path
foreach ($paths as $path) { foreach ($paths as $path) {
list($action, $arg1, $arg2) = $path; list($action, $arg1, $arg2) = $path;
$channels = Realtime_channel::getAllChannels($action, $arg1, $arg2); $channels = Realtime_channel::getAllChannels($action, $arg1, $arg2);
$this->log(LOG_INFO, sprintf(_("%d candidate channels for notice %d"), $this->log(LOG_INFO, sprintf(
count($channels), _("%d candidate channels for notice %d"),
$notice->id)); count($channels),
$notice->id
));
foreach ($channels as $channel) { foreach ($channels as $channel) {
@ -255,14 +258,18 @@ class RealtimePlugin extends Plugin
$profile = Profile::getKV('id', $channel->user_id); $profile = Profile::getKV('id', $channel->user_id);
} }
if ($notice->inScope($profile)) { if ($notice->inScope($profile)) {
$this->log(LOG_INFO, $this->log(
sprintf(_("Delivering notice %d to channel (%s, %s, %s) for user '%s'"), LOG_INFO,
$notice->id, sprintf(
$channel->action, _m("Delivering notice %d to channel (%s, %s, %s) for user '%s'"),
$channel->arg1, $notice->id,
$channel->arg2, $channel->action,
($profile) ? ($profile->nickname) : "<public>")); $channel->arg1,
$timeline = $this->_pathToChannel(array($channel->channel_key)); $channel->arg2,
($profile ? $profile->getNickname() : '<public>')
)
);
$timeline = $this->_pathToChannel([$channel->channel_key]);
$this->_publish($timeline, $json); $this->_publish($timeline, $json);
} }
} }
@ -274,18 +281,23 @@ class RealtimePlugin extends Plugin
return true; return true;
} }
function onStartShowBody($action) public function onStartShowBody(Action $action)
{ {
$realtime = $action->boolean('realtime'); $realtime = $action->boolean('realtime');
if (!$realtime) { if (!$realtime) {
return true; return true;
} }
$action->elementStart('body', $action->elementStart(
(common_current_user()) ? array('id' => $action->trimmed('action'), 'body',
'class' => 'user_in realtime-popup') (common_current_user() ? [
: array('id' => $action->trimmed('action'), 'id' => $action->trimmed('action'),
'class'=> 'realtime-popup')); 'class' => 'user_in realtime-popup',
] : [
'id' => $action->trimmed('action'),
'class'=> 'realtime-popup',
])
);
// XXX hack to deal with JS that tries to get the // XXX hack to deal with JS that tries to get the
// root url from page output // root url from page output
@ -294,14 +306,17 @@ class RealtimePlugin extends Plugin
if (common_config('singleuser', 'enabled')) { if (common_config('singleuser', 'enabled')) {
$user = User::singleUser(); $user = User::singleUser();
$url = common_local_url('showstream', array('nickname' => $user->nickname)); $url = common_local_url('showstream', ['nickname' => $user->nickname]);
} else { } else {
$url = common_local_url('public'); $url = common_local_url('public');
} }
$action->element('a', array('class' => 'url', $action->element(
'href' => $url), 'a',
''); ['class' => 'url',
'href' => $url],
''
);
$action->elementEnd('address'); $action->elementEnd('address');
@ -311,7 +326,7 @@ class RealtimePlugin extends Plugin
return false; // No default processing return false; // No default processing
} }
function noticeAsJson(Notice $notice) public function noticeAsJson(Notice $notice)
{ {
// FIXME: this code should be abstracted to a neutral third // FIXME: this code should be abstracted to a neutral third
// party, like Notice::asJson(). I'm not sure of the ethics // party, like Notice::asJson(). I'm not sure of the ethics
@ -347,7 +362,7 @@ class RealtimePlugin extends Plugin
return $arr; return $arr;
} }
function getNoticeTags(Notice $notice) public function getNoticeTags(Notice $notice)
{ {
$tags = null; $tags = null;
@ -355,7 +370,7 @@ class RealtimePlugin extends Plugin
$nt->notice_id = $notice->id; $nt->notice_id = $notice->id;
if ($nt->find()) { if ($nt->find()) {
$tags = array(); $tags = [];
while ($nt->fetch()) { while ($nt->fetch()) {
$tags[] = $nt->tag; $tags[] = $nt->tag;
} }
@ -367,11 +382,13 @@ class RealtimePlugin extends Plugin
return $tags; return $tags;
} }
function _getScripts() public function _getScripts(): array
{ {
$urlpath = self::staticPath(str_replace('Plugin','',__CLASS__), $urlpath = self::staticPath(
'js/realtimeupdate.js'); str_replace('Plugin', '', __CLASS__),
return array($urlpath); 'js/realtimeupdate.js'
);
return [$urlpath];
} }
/** /**
@ -380,9 +397,10 @@ class RealtimePlugin extends Plugin
* @param Action $action * @param Action $action
* @param array $messages * @param array $messages
* *
* @return boolean hook return value * @return bool hook return value
* @throws Exception
*/ */
function onEndScriptMessages($action, &$messages) public function onEndScriptMessages(Action $action, array &$messages)
{ {
// TRANS: Text label for realtime view "play" button, usually replaced by an icon. // TRANS: Text label for realtime view "play" button, usually replaced by an icon.
$messages['realtime_play'] = _m('BUTTON', 'Play'); $messages['realtime_play'] = _m('BUTTON', 'Play');
@ -400,40 +418,40 @@ class RealtimePlugin extends Plugin
return true; return true;
} }
function _updateInitialize($timeline, $user_id) public function _updateInitialize($timeline, int $user_id)
{ {
return "RealtimeUpdate.init($user_id, \"$this->showurl\"); "; return "RealtimeUpdate.init($user_id, \"$this->showurl\"); ";
} }
function _connect() public function _connect()
{ {
} }
function _publish($timeline, $json) public function _publish($timeline, $json)
{ {
} }
function _disconnect() public function _disconnect()
{ {
} }
function _pathToChannel($path) public function _pathToChannel(array $path): string
{ {
return ''; return '';
} }
function _getTimeline($action) public function _getTimeline(Action $action)
{ {
$channel = $this->_getChannel($action); $channel = $this->_getChannel($action);
if (empty($channel)) { if (empty($channel)) {
return null; return null;
} }
return $this->_pathToChannel(array($channel->channel_key)); return $this->_pathToChannel([$channel->channel_key]);
} }
function _getChannel($action) public function _getChannel(Action $action)
{ {
$timeline = null; $timeline = null;
$arg1 = null; $arg1 = null;
@ -478,15 +496,17 @@ class RealtimePlugin extends Plugin
$user_id = (!empty($user)) ? $user->id : null; $user_id = (!empty($user)) ? $user->id : null;
$channel = Realtime_channel::getChannel($user_id, $channel = Realtime_channel::getChannel(
$action_name, $user_id,
$arg1, $action_name,
$arg2); $arg1,
$arg2
);
return $channel; return $channel;
} }
function onStartReadWriteTables(&$alwaysRW, &$rwdb) public function onStartReadWriteTables(&$alwaysRW, &$rwdb)
{ {
$alwaysRW[] = 'realtime_channel'; $alwaysRW[] = 'realtime_channel';
return true; return true;

View File

@ -1,48 +1,38 @@
<?php <?php
/** // This file is part of GNU social - https://www.gnu.org/software/social
* StatusNet - the distributed open-source microblogging tool //
* Copyright (C) 2011, StatusNet, Inc. // GNU social is free software: you can redistribute it and/or modify
* // it under the terms of the GNU Affero General Public License as published by
* action to close a channel // the Free Software Foundation, either version 3 of the License, or
* // (at your option) any later version.
* PHP version 5 //
* // GNU social is distributed in the hope that it will be useful,
* This program is free software: you can redistribute it and/or modify // but WITHOUT ANY WARRANTY; without even the implied warranty of
* it under the terms of the GNU Affero General Public License as published by // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* the Free Software Foundation, either version 3 of the License, or // GNU Affero General Public License for more details.
* (at your option) any later version. //
* // You should have received a copy of the GNU Affero General Public License
* This program is distributed in the hope that it will be useful, // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
* 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 Realtime
* @package StatusNet
* @author Evan Prodromou <evan@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')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/** /**
* Action to close a channel * Action to close a channel
* *
* @category Realtime * @category Realtime
* @package StatusNet * @package GNUsocial
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc. * @copyright 2011-2019 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://status.net/ */
defined('GNUSOCIAL') || die();
/**
* Action to close a channel
*
* @category Realtime
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
class ClosechannelAction extends Action class ClosechannelAction extends Action
{ {
@ -57,7 +47,7 @@ class ClosechannelAction extends Action
* @return boolean true * @return boolean true
* @throws ClientException * @throws ClientException
*/ */
function prepare(array $args = []) public function prepare(array $args = [])
{ {
parent::prepare($args); parent::prepare($args);
@ -88,7 +78,7 @@ class ClosechannelAction extends Action
* *
* @return void * @return void
*/ */
function handle() public function handle(): void
{ {
$this->channel->decrement(); $this->channel->decrement();
@ -104,9 +94,9 @@ class ClosechannelAction extends Action
* *
* @param array $args other arguments * @param array $args other arguments
* *
* @return boolean is read only action? * @return bool is read only action?
*/ */
function isReadOnly($args) public function isReadOnly($args): bool
{ {
return false; return false;
} }

View File

@ -54,10 +54,10 @@ class KeepalivechannelAction extends Action
* *
* @param array $args misc. arguments * @param array $args misc. arguments
* *
* @return boolean true * @return bool true
* @throws ClientException * @throws ClientException
*/ */
function prepare(array $args = []) public function prepare(array $args = []): bool
{ {
parent::prepare($args); parent::prepare($args);
@ -88,7 +88,7 @@ class KeepalivechannelAction extends Action
* *
* @return void * @return void
*/ */
function handle() public function handle(): void
{ {
$this->channel->touch(); $this->channel->touch();
@ -104,9 +104,9 @@ class KeepalivechannelAction extends Action
* *
* @param array $args other arguments * @param array $args other arguments
* *
* @return boolean is read only action? * @return bool is read only action?
*/ */
function isReadOnly($args) public function isReadOnly($args): bool
{ {
return false; return false;
} }

View File

@ -23,7 +23,7 @@
* @category Realtime * @category Realtime
* @package GNUsocial * @package GNUsocial
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc. * @copyright 2011-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -32,10 +32,9 @@ defined('GNUSOCIAL') || die();
/** /**
* A channel for real-time browser data * A channel for real-time browser data
* *
* @copyright 2011 StatusNet, Inc.
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* *
* @see DB_DataObject * @see DB_DataObject
*/ */
class Realtime_channel extends Managed_DataObject class Realtime_channel extends Managed_DataObject
{ {
@ -57,64 +56,64 @@ class Realtime_channel extends Managed_DataObject
*/ */
public static function schemaDef() public static function schemaDef()
{ {
return array( return [
'description' => 'A channel of realtime notice data', 'description' => 'A channel of realtime notice data',
'fields' => array( 'fields' => [
'user_id' => array('type' => 'int', 'user_id' => ['type' => 'int',
'not null' => false, 'not null' => false,
'description' => 'user viewing page; can be null'), 'description' => 'user viewing page; can be null'],
'action' => array('type' => 'varchar', 'action' => ['type' => 'varchar',
'length' => 191, 'length' => 191,
'not null' => true, 'not null' => true,
'description' => 'page being viewed'), 'description' => 'page being viewed'],
'arg1' => array('type' => 'varchar', 'arg1' => ['type' => 'varchar',
'length' => 191, 'length' => 191,
'not null' => false, 'not null' => false,
'description' => 'page argument, like username or tag'), 'description' => 'page argument, like username or tag'],
'arg2' => array('type' => 'varchar', 'arg2' => ['type' => 'varchar',
'length' => 191, 'length' => 191,
'not null' => false, 'not null' => false,
'description' => 'second page argument, like tag for showstream'), 'description' => 'second page argument, like tag for showstream'],
'channel_key' => array('type' => 'varchar', 'channel_key' => ['type' => 'varchar',
'length' => 32, 'length' => 32,
'not null' => true, 'not null' => true,
'description' => 'shared secret key for this channel'), 'description' => 'shared secret key for this channel'],
'audience' => array('type' => 'int', 'audience' => ['type' => 'int',
'not null' => true, 'not null' => true,
'default' => 0, 'default' => 0,
'description' => 'reference count'), 'description' => 'reference count'],
'created' => array('type' => 'datetime', 'created' => ['type' => 'datetime',
'not null' => true, 'not null' => true,
'description' => 'date this record was created'), 'description' => 'date this record was created'],
'modified' => array('type' => 'datetime', 'modified' => ['type' => 'datetime',
'not null' => true, 'not null' => true,
'description' => 'date this record was modified'), 'description' => 'date this record was modified'],
), ],
'primary key' => array('channel_key'), 'primary key' => ['channel_key'],
'unique keys' => array('realtime_channel_user_page_idx' => array('user_id', 'action', 'arg1', 'arg2')), 'unique keys' => ['realtime_channel_user_page_idx' => ['user_id', 'action', 'arg1', 'arg2']],
'foreign keys' => array( 'foreign keys' => [
'realtime_channel_user_id_fkey' => array('user', array('user_id' => 'id')), 'realtime_channel_user_id_fkey' => ['user', ['user_id' => 'id']],
), ],
'indexes' => array( 'indexes' => [
'realtime_channel_modified_idx' => array('modified'), 'realtime_channel_modified_idx' => ['modified'],
'realtime_channel_page_idx' => array('action', 'arg1', 'arg2') 'realtime_channel_page_idx' => ['action', 'arg1', 'arg2']
), ],
); ];
} }
public static function saveNew($user_id, $action, $arg1, $arg2) public static function saveNew(int $user_id, Action $action, $arg1, $arg2): Realtime_channel
{ {
$channel = new Realtime_channel(); $channel = new Realtime_channel();
$channel->user_id = $user_id; $channel->user_id = $user_id;
$channel->action = $action; $channel->action = $action;
$channel->arg1 = $arg1; $channel->arg1 = $arg1;
$channel->arg2 = $arg2; $channel->arg2 = $arg2;
$channel->audience = 1; $channel->audience = 1;
$channel->channel_key = common_random_hexstr(16); // 128-bit key, 32 hex chars $channel->channel_key = common_random_hexstr(16); // 128-bit key, 32 hex chars
$channel->created = common_sql_now(); $channel->created = common_sql_now();
$channel->modified = $channel->created; $channel->modified = $channel->created;
$channel->insert(); $channel->insert();
@ -122,7 +121,7 @@ class Realtime_channel extends Managed_DataObject
return $channel; return $channel;
} }
public static function getChannel($user_id, $action, $arg1, $arg2) public static function getChannel(int $user_id, Action $action, $arg1, $arg2): Realtime_channel
{ {
$channel = self::fetchChannel($user_id, $action, $arg1, $arg2); $channel = self::fetchChannel($user_id, $action, $arg1, $arg2);
@ -143,7 +142,7 @@ class Realtime_channel extends Managed_DataObject
return $channel; return $channel;
} }
public static function getAllChannels($action, $arg1, $arg2) public static function getAllChannels(Action $action, $arg1, $arg2): array
{ {
$channel = new Realtime_channel(); $channel = new Realtime_channel();
@ -172,7 +171,7 @@ class Realtime_channel extends Managed_DataObject
return $channels; return $channels;
} }
public static function fetchChannel($user_id, $action, $arg1, $arg2) public static function fetchChannel(int $user_id, Action $action, $arg1, $arg2): ?Realtime_channel
{ {
$channel = new Realtime_channel(); $channel = new Realtime_channel();
@ -204,7 +203,7 @@ class Realtime_channel extends Managed_DataObject
} }
} }
public function increment() public function increment(): void
{ {
// XXX: race // XXX: race
$orig = clone($this); $orig = clone($this);
@ -213,7 +212,7 @@ class Realtime_channel extends Managed_DataObject
$this->update($orig); $this->update($orig);
} }
public function touch() public function touch(): void
{ {
// XXX: race // XXX: race
$orig = clone($this); $orig = clone($this);
@ -221,7 +220,7 @@ class Realtime_channel extends Managed_DataObject
$this->update($orig); $this->update($orig);
} }
public function decrement() public function decrement(): void
{ {
// XXX: race // XXX: race
if ($this->audience == 1) { if ($this->audience == 1) {

View File

@ -20,7 +20,7 @@
* *
* @package Realtime * @package Realtime
* @author Mikael Nordfeldth <mmn@hethane.se> * @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2011 StatusNet, Inc. * @copyright 2011-2019 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/ */
@ -28,7 +28,7 @@ define('INSTALLDIR', dirname(__DIR__, 3));
define('PUBLICDIR', INSTALLDIR . DIRECTORY_SEPARATOR . 'public'); define('PUBLICDIR', INSTALLDIR . DIRECTORY_SEPARATOR . 'public');
$shortoptions = 'u'; $shortoptions = 'u';
$longoptions = array('universe'); $longoptions = ['universe'];
$helptext = <<<END_OF_CLEANUPCHANNELS_HELP $helptext = <<<END_OF_CLEANUPCHANNELS_HELP
cleanupchannels.php [options] cleanupchannels.php [options]

View File

@ -26,6 +26,17 @@
defined('GNUSOCIAL') || die(); defined('GNUSOCIAL') || die();
/**
* Class TheFreeNetworkModule
* This module ensures that multiple protocols serving the same purpose won't result in duplicated data.
* This class is not to be extended but a developer implementing a new protocol should be aware of it and notify the
* StartTFNCensus event.
*
* @category Module
* @package GNUsocial
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class TheFreeNetworkModule extends Module class TheFreeNetworkModule extends Module
{ {
const MODULE_VERSION = '0.1.0alpha0'; const MODULE_VERSION = '0.1.0alpha0';

View File

@ -46,6 +46,8 @@ const ACTIVITYPUB_HTTP_CLIENT_HEADERS = [
]; ];
/** /**
* Adds ActivityPub support to GNU social when enabled
*
* @category Plugin * @category Plugin
* @package GNUsocial * @package GNUsocial
* @author Diogo Cordeiro <diogo@fc.up.pt> * @author Diogo Cordeiro <diogo@fc.up.pt>

View File

@ -31,7 +31,7 @@ if (!defined('GNUSOCIAL') && !defined('STATUSNET')) {
exit(1); exit(1);
} }
require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php'; require_once INSTALLDIR . DIRECTORY_SEPARATOR . 'lib/modules/Realtime/RealtimePlugin.php';
/** /**
* Plugin to do realtime updates using Comet * Plugin to do realtime updates using Comet
@ -52,8 +52,12 @@ class CometPlugin extends RealtimePlugin
public $prefix = null; public $prefix = null;
protected $bay = null; protected $bay = null;
function __construct($server=null, $username=null, $password=null, $prefix=null) public function __construct(
{ ?string $server = null,
?string $username = null,
?string $password = null,
?string $prefix = null
) {
$this->server = $server; $this->server = $server;
$this->username = $username; $this->username = $username;
$this->password = $password; $this->password = $password;
@ -62,11 +66,11 @@ class CometPlugin extends RealtimePlugin
parent::__construct(); parent::__construct();
} }
function _getScripts() public function _getScripts(): array
{ {
$scripts = parent::_getScripts(); $scripts = parent::_getScripts();
$ours = array('js/jquery.comet.js', 'js/cometupdate.js'); $ours = ['js/jquery.comet.js', 'js/cometupdate.js'];
foreach ($ours as $script) { foreach ($ours as $script) {
$scripts[] = $this->path($script); $scripts[] = $this->path($script);
@ -75,30 +79,30 @@ class CometPlugin extends RealtimePlugin
return $scripts; return $scripts;
} }
function _updateInitialize($timeline, $user_id) public function _updateInitialize($timeline, int $user_id)
{ {
$script = parent::_updateInitialize($timeline, $user_id); $script = parent::_updateInitialize($timeline, $user_id);
return $script." CometUpdate.init(\"$this->server\", \"$timeline\", $user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->deleteurl\");"; return $script." CometUpdate.init(\"$this->server\", \"$timeline\", $user_id, \"$this->replyurl\", \"$this->favorurl\", \"$this->deleteurl\");";
} }
function _connect() public function _connect(): void
{ {
require_once INSTALLDIR.'/plugins/Comet/extlib/Bayeux/Bayeux.class.php'; require_once __DIR__. DIRECTORY_SEPARATOR . 'extlib/Bayeux/Bayeux.class.php';
// Bayeux? Comet? Huh? These terms confuse me // Bayeux? Comet? Huh? These terms confuse me
$this->bay = new Bayeux($this->server, $this->user, $this->password); $this->bay = new Bayeux($this->server, $this->user, $this->password);
} }
function _publish($timeline, $json) public function _publish($timeline, $json): void
{ {
$this->bay->publish($timeline, $json); $this->bay->publish($timeline, $json);
} }
function _disconnect() public function _disconnect(): void
{ {
unset($this->bay); unset($this->bay);
} }
function _pathToChannel($path) public function _pathToChannel(array $path): string
{ {
if (!empty($this->prefix)) { if (!empty($this->prefix)) {
array_unshift($path, $this->prefix); array_unshift($path, $this->prefix);
@ -108,14 +112,16 @@ class CometPlugin extends RealtimePlugin
public function onPluginVersion(array &$versions): bool public function onPluginVersion(array &$versions): bool
{ {
$versions[] = array('name' => 'Comet', $versions[] = [
'version' => self::PLUGIN_VERSION, 'name' => 'Comet',
'author' => 'Evan Prodromou', 'version' => self::PLUGIN_VERSION,
'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Comet', 'author' => 'Evan Prodromou',
'rawdescription' => 'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Comet',
// TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages 'rawdescription' =>
// TRANS: and Comet is a web application model. // TRANS: Plugin description message. Bayeux is a protocol for transporting asynchronous messages
_m('Plugin to make updates using Comet and Bayeux.')); // TRANS: and Comet is a web application model.
_m('Plugin to make updates using Comet and Bayeux.')
];
return true; return true;
} }
} }

View File

@ -30,36 +30,36 @@ class Bayeux
public $sUrl = ''; public $sUrl = '';
function __construct($sUrl, $sUser='', $sPassword='') public function __construct($sUrl, $sUser='', $sPassword='')
{ {
$this->sUrl = $sUrl; $this->sUrl = $sUrl;
$this->oCurl = curl_init(); $this->oCurl = curl_init();
$aHeaders = array(); $aHeaders = [];
$aHeaders[] = 'Connection: Keep-Alive'; $aHeaders[] = 'Connection: Keep-Alive';
curl_setopt($this->oCurl, CURLOPT_URL, $sUrl); curl_setopt($this->oCurl, CURLOPT_URL, $sUrl);
curl_setopt($this->oCurl, CURLOPT_HTTPHEADER, $aHeaders); curl_setopt($this->oCurl, CURLOPT_HTTPHEADER, $aHeaders);
curl_setopt($this->oCurl, CURLOPT_HEADER, 0); curl_setopt($this->oCurl, CURLOPT_HEADER, 0);
curl_setopt($this->oCurl, CURLOPT_POST, 1); curl_setopt($this->oCurl, CURLOPT_POST, 1);
curl_setopt($this->oCurl, CURLOPT_RETURNTRANSFER,1); curl_setopt($this->oCurl, CURLOPT_RETURNTRANSFER, 1);
if (!is_null($sUser) && mb_strlen($sUser) > 0) { if (!is_null($sUser) && mb_strlen($sUser) > 0) {
curl_setopt($this->oCurl, CURLOPT_USERPWD,"$sUser:$sPassword"); curl_setopt($this->oCurl, CURLOPT_USERPWD, "$sUser:$sPassword");
} }
$this->handShake(); $this->handShake();
} }
function __destruct() public function __destruct()
{ {
$this->disconnect(); $this->disconnect();
} }
function handShake() public function handShake()
{ {
$msgHandshake = array(); $msgHandshake = [];
$msgHandshake['channel'] = '/meta/handshake'; $msgHandshake['channel'] = '/meta/handshake';
$msgHandshake['version'] = "1.0"; $msgHandshake['version'] = "1.0";
$msgHandshake['minimumVersion'] = "0.9"; $msgHandshake['minimumVersion'] = "0.9";
@ -70,8 +70,9 @@ class Bayeux
$data = curl_exec($this->oCurl); $data = curl_exec($this->oCurl);
if(curl_errno($this->oCurl)) if (curl_errno($this->oCurl)) {
die("Error: " . curl_error($this->oCurl)); die("Error: " . curl_error($this->oCurl));
}
$oReturn = json_decode($data); $oReturn = json_decode($data);
@ -81,8 +82,7 @@ class Bayeux
$bSuccessful = ($oReturn->successful) ? true : false; $bSuccessful = ($oReturn->successful) ? true : false;
if($bSuccessful) if ($bSuccessful) {
{
$this->clientId = $oReturn->clientId; $this->clientId = $oReturn->clientId;
$this->connect(); $this->connect();
@ -101,9 +101,9 @@ class Bayeux
$data = curl_exec($this->oCurl); $data = curl_exec($this->oCurl);
} }
function disconnect() public function disconnect()
{ {
$msgHandshake = array(); $msgHandshake = [];
$msgHandshake['channel'] = '/meta/disconnect'; $msgHandshake['channel'] = '/meta/disconnect';
$msgHandshake['id'] = $this->nNextId++; $msgHandshake['id'] = $this->nNextId++;
$msgHandshake['clientId'] = $this->clientId; $msgHandshake['clientId'] = $this->clientId;
@ -115,10 +115,11 @@ class Bayeux
public function publish($sChannel, $oData) public function publish($sChannel, $oData)
{ {
if(!$sChannel || !$oData) if (!$sChannel || !$oData) {
return; return;
}
$aMsg = array(); $aMsg = [];
$aMsg['channel'] = $sChannel; $aMsg['channel'] = $sChannel;
$aMsg['id'] = $this->nNextId++; $aMsg['id'] = $this->nNextId++;

View File

@ -1,46 +1,40 @@
<?php <?php
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social 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.
//
// GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>.
/** /**
* StatusNet, the distributed open-source microblogging tool
*
* Plugin to do "real time" updates using Meteor * Plugin to do "real time" updates using Meteor
* *
* 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 Plugin * @category Plugin
* @package StatusNet * @package GNUsocial
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc. * @copyright 2010-2019 Free Software Foundation, Inc http://www.fsf.org
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://status.net/
*/ */
if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { defined('GNUSOCIAL') || die();
exit(1);
}
require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php'; require_once INSTALLDIR . DIRECTORY_SEPARATOR . 'lib/modules/Realtime/RealtimePlugin.php';
/** /**
* Plugin to do realtime updates using Meteor * Plugin to do realtime updates using Meteor
* *
* @category Plugin * @category Plugin
* @package StatusNet * @package GNUsocial
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
* @link http://status.net/
*/ */
class MeteorPlugin extends RealtimePlugin class MeteorPlugin extends RealtimePlugin
{ {
@ -55,8 +49,14 @@ class MeteorPlugin extends RealtimePlugin
public $persistent = true; public $persistent = true;
protected $_socket = null; protected $_socket = null;
function __construct($webserver=null, $webport=4670, $controlport=4671, $controlserver=null, $channelbase='', $protocol='http') public function __construct(
{ ?string $webserver = null,
int $webport = 4670,
int $controlport = 4671,
?string $controlserver = null,
string $channelbase = '',
string $protocol = 'http'
) {
global $config; global $config;
$this->webserver = (empty($webserver)) ? $config['site']['server'] : $webserver; $this->webserver = (empty($webserver)) ? $config['site']['server'] : $webserver;
@ -64,22 +64,24 @@ class MeteorPlugin extends RealtimePlugin
$this->controlport = $controlport; $this->controlport = $controlport;
$this->controlserver = (empty($controlserver)) ? $webserver : $controlserver; $this->controlserver = (empty($controlserver)) ? $webserver : $controlserver;
$this->channelbase = $channelbase; $this->channelbase = $channelbase;
$this->protocol = $protocol; $this->protocol = $protocol;
parent::__construct(); parent::__construct();
} }
/** /**
* Pull settings from config file/database if set. * Pull settings from config file/database if set.
*/ */
function initialize() public function initialize()
{ {
$settings = array('webserver', $settings = [
'webport', 'webserver',
'controlport', 'webport',
'controlserver', 'controlport',
'channelbase', 'controlserver',
'protocol'); 'channelbase',
'protocol',
];
foreach ($settings as $name) { foreach ($settings as $name) {
$val = common_config('meteor', $name); $val = common_config('meteor', $name);
if ($val !== false) { if ($val !== false) {
@ -90,47 +92,57 @@ class MeteorPlugin extends RealtimePlugin
return parent::initialize(); return parent::initialize();
} }
function _getScripts() public function _getScripts()
{ {
$scripts = parent::_getScripts(); $scripts = parent::_getScripts();
if ($this->protocol == 'https') { if ($this->protocol == 'https') {
$scripts[] = 'https://'.$this->webserver.(($this->webport == 443) ? '':':'.$this->webport).'/meteor.js'; $scripts[] = 'https://' . $this->webserver . (($this->webport == 443) ? '' : ':' . $this->webport) . '/meteor.js';
} else { } else {
$scripts[] = 'http://'.$this->webserver.(($this->webport == 80) ? '':':'.$this->webport).'/meteor.js'; $scripts[] = 'http://' . $this->webserver . (($this->webport == 80) ? '' : ':' . $this->webport) . '/meteor.js';
} }
$scripts[] = $this->path('js/meteorupdater.js'); $scripts[] = $this->path('js/meteorupdater.js');
return $scripts; return $scripts;
} }
function _updateInitialize($timeline, $user_id) public function _updateInitialize($timeline, int $user_id)
{ {
$script = parent::_updateInitialize($timeline, $user_id); $script = parent::_updateInitialize($timeline, $user_id);
$ours = sprintf("MeteorUpdater.init(%s, %s, %s, %s);", $ours = sprintf(
json_encode($this->webserver), "MeteorUpdater.init(%s, %s, %s, %s);",
json_encode($this->webport), json_encode($this->webserver),
json_encode($this->protocol), json_encode($this->webport),
json_encode($timeline)); json_encode($this->protocol),
json_encode($timeline)
);
return $script." ".$ours; return $script." ".$ours;
} }
function _connect() public function _connect()
{ {
$controlserver = (empty($this->controlserver)) ? $this->webserver : $this->controlserver; $controlserver = (empty($this->controlserver)) ? $this->webserver : $this->controlserver;
$errno = $errstr = null; $errno = $errstr = null;
$timeout = 5; $timeout = 5;
$flags = STREAM_CLIENT_CONNECT; $flags = STREAM_CLIENT_CONNECT;
if ($this->persistent) $flags |= STREAM_CLIENT_PERSISTENT; if ($this->persistent) {
$flags |= STREAM_CLIENT_PERSISTENT;
}
// May throw an exception. // May throw an exception.
$this->_socket = stream_socket_client("tcp://{$controlserver}:{$this->controlport}", $errno, $errstr, $timeout, $flags); $this->_socket = stream_socket_client(
"tcp://{$controlserver}:{$this->controlport}",
$errno,
$errstr,
$timeout,
$flags
);
if (!$this->_socket) { if (!$this->_socket) {
// TRANS: Exception. %1$s is the control server, %2$s is the control port. // TRANS: Exception. %1$s is the control server, %2$s is the control port.
throw new Exception(sprintf(_m('Could not connect to %1$s on %2$s.'),$controlserver,$this->controlport)); throw new Exception(sprintf(_m('Could not connect to %1$s on %2$s.'), $controlserver, $this->controlport));
} }
} }
function _publish($channel, $message) public function _publish($channel, $message)
{ {
$message = json_encode($message); $message = json_encode($message);
$message = addslashes($message); $message = addslashes($message);
@ -139,12 +151,12 @@ class MeteorPlugin extends RealtimePlugin
$result = fgets($this->_socket); $result = fgets($this->_socket);
if (preg_match('/^ERR (.*)$/', $result, $matches)) { if (preg_match('/^ERR (.*)$/', $result, $matches)) {
// TRANS: Exception. %s is the Meteor message that could not be added. // TRANS: Exception. %s is the Meteor message that could not be added.
throw new Exception(sprintf(_m('Error adding meteor message "%s".'),$matches[1])); throw new Exception(sprintf(_m('Error adding meteor message "%s".'), $matches[1]));
} }
// TODO: parse and deal with result // TODO: parse and deal with result
} }
function _disconnect() public function _disconnect()
{ {
if (!$this->persistent) { if (!$this->persistent) {
$cnt = fwrite($this->_socket, "QUIT\n"); $cnt = fwrite($this->_socket, "QUIT\n");
@ -154,7 +166,7 @@ class MeteorPlugin extends RealtimePlugin
// Meteord flips out with default '/' separator // Meteord flips out with default '/' separator
function _pathToChannel($path) public function _pathToChannel(array $path): string
{ {
if (!empty($this->channelbase)) { if (!empty($this->channelbase)) {
array_unshift($path, $this->channelbase); array_unshift($path, $this->channelbase);
@ -164,13 +176,15 @@ class MeteorPlugin extends RealtimePlugin
public function onPluginVersion(array &$versions): bool public function onPluginVersion(array &$versions): bool
{ {
$versions[] = array('name' => 'Meteor', $versions[] = [
'version' => self::PLUGIN_VERSION, 'name' => 'Meteor',
'author' => 'Evan Prodromou', 'version' => self::PLUGIN_VERSION,
'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Meteor', 'author' => 'Evan Prodromou',
'rawdescription' => 'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Meteor',
// TRANS: Plugin description. 'rawdescription' =>
_m('Plugin to do "real time" updates using Meteor.')); // TRANS: Plugin description.
_m('Plugin to do "real time" updates using Meteor.')
];
return true; return true;
} }
} }

View File

@ -4,7 +4,7 @@ FriendFeed's "real time" news.
It requires a meteor server. It requires a meteor server.
http://meteorserver.org/ https://github.com/visitsb/meteorserver/
Note that the controller interface needs to be accessible by the Web server, and Note that the controller interface needs to be accessible by the Web server, and
the subscriber interface needs to be accessible by your Web users. You MUST the subscriber interface needs to be accessible by your Web users. You MUST
@ -13,7 +13,7 @@ push any message to your subscribers. Not good!
You can enable the plugin with this line in config.php: You can enable the plugin with this line in config.php:
addPlugin('Meteor', array('webserver' => 'meteor server address')); addPlugin('Meteor', ['webserver' => 'meteor server address']);
Available parameters: Available parameters:
* webserver: Web server address. Defaults to site server. * webserver: Web server address. Defaults to site server.

View File

@ -1,9 +0,0 @@
.fake: all clean
all: realtimeupdate.min.js
clean:
rm -f js/realtimeupdate.min.js
realtimeupdate.min.js: js/realtimeupdate.js
yui-compressor js/realtimeupdate.js > js/realtimeupdate.min.js