diff --git a/classes/Notice.php b/classes/Notice.php index 93d5de7908..ba2227c0a3 100644 --- a/classes/Notice.php +++ b/classes/Notice.php @@ -1,5 +1,5 @@ . + * + * @category Notices + * @package StatusNet + * @author Brenda Wallace + * @author Christopher Vollick + * @author CiaranG + * @author Craig Andrews + * @author Evan Prodromou + * @author Gina Haeussge + * @author Jeffery To + * @author Mike Cochrane + * @author Robin Millette + * @author Sarven Capadisli + * @author Tom Adams + * @license GNU Affero General Public License http://www.gnu.org/licenses/ */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} /** * Table Definition for notice @@ -168,11 +185,11 @@ class Notice extends Memcached_DataObject if (common_config('site', 'dupelimit') > 0 && !Notice::checkDupes($profile_id, $final)) { common_log(LOG_WARNING, 'Dupe posting by profile #' . $profile_id . '; throttled.'); - throw new ClientException(_('Too many duplicate messages too quickly;'. + throw new ClientException(_('Too many duplicate messages too quickly;'. ' take a breather and post again in a few minutes.')); } - $banned = common_config('profile', 'banned'); + $banned = common_config('profile', 'banned'); if ( in_array($profile_id, $banned) || in_array($profile->nickname, $banned)) { common_log(LOG_WARNING, "Attempted post from banned user: $profile->nickname (user id = $profile_id)."); @@ -200,12 +217,12 @@ class Notice extends Memcached_DataObject $notice->created = common_sql_now(); } - $notice->content = $final; - $notice->rendered = common_render_content($final, $notice); - $notice->source = $source; - $notice->uri = $uri; + $notice->content = $final; + $notice->rendered = common_render_content($final, $notice); + $notice->source = $source; + $notice->uri = $uri; - $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); + $notice->reply_to = self::getReplyTo($reply_to, $profile_id, $source, $final); if (!empty($notice->reply_to)) { $reply = Notice::staticGet('id', $notice->reply_to); diff --git a/classes/Profile.php b/classes/Profile.php index 7f0d127583..4a069ee84e 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -476,4 +476,79 @@ class Profile extends Memcached_DataObject $biolimit = self::maxBio(); return ($biolimit > 0 && !empty($bio) && (mb_strlen($bio) > $biolimit)); } + + function delete() + { + $this->_deleteNotices(); + $this->_deleteSubscriptions(); + $this->_deleteMessages(); + $this->_deleteTags(); + $this->_deleteBlocks(); + + $related = array('Avatar', + 'Reply', + 'Group_member', + ); + + foreach ($related as $cls) { + $inst = new $cls(); + $inst->profile_id = $this->id; + $inst->delete(); + } + + parent::delete(); + } + + function _deleteNotices() + { + $notice = new Notice(); + $notice->profile_id = $this->id; + + if ($notice->find()) { + while ($notice->fetch()) { + $other = clone($notice); + $other->delete(); + } + } + } + + function _deleteSubscriptions() + { + $sub = new Subscription(); + $sub->subscriber = $this->id; + $sub->delete(); + + $subd = new Subscription(); + $subd->subscribed = $this->id; + $subd->delete(); + } + + function _deleteMessages() + { + $msg = new Message(); + $msg->from_profile = $this->id; + $msg->delete(); + + $msg = new Message(); + $msg->to_profile = $this->id; + $msg->delete(); + } + + function _deleteTags() + { + $tag = new Profile_tag(); + $tag->tagged = $this->id; + $tag->delete(); + } + + function _deleteBlocks() + { + $block = new Profile_block(); + $block->blocked = $this->id; + $block->delete(); + + $block = new Group_block(); + $block->blocked = $this->id; + $block->delete(); + } } diff --git a/classes/User.php b/classes/User.php index 3f7ed09bb7..48df0cdd77 100644 --- a/classes/User.php +++ b/classes/User.php @@ -740,4 +740,48 @@ class User extends Memcached_DataObject } return $result; } + + function delete() + { + $profile = $this->getProfile(); + $profile->delete(); + + $related = array('Fave', + 'User_openid', + 'Confirm_address', + 'Remember_me', + 'Foreign_link', + 'Invitation', + ); + + if (common_config('inboxes', 'enabled')) { + $related[] = 'Notice_inbox'; + } + + foreach ($related as $cls) { + $inst = new $cls(); + $inst->user_id = $this->id; + $inst->delete(); + } + + $this->_deleteTags(); + $this->_deleteBlocks(); + + parent::delete(); + } + + function _deleteTags() + { + $tag = new Profile_tag(); + $tag->tagger = $this->id; + $tag->delete(); + } + + function _deleteBlocks() + { + $block = new Profile_block(); + $block->blocker = $this->id; + $block->delete(); + // XXX delete group block? Reset blocker? + } } diff --git a/lib/action.php b/lib/action.php index 71ceffe20d..1b2f737521 100644 --- a/lib/action.php +++ b/lib/action.php @@ -120,14 +120,16 @@ class Action extends HTMLOutputter // lawsuit { // XXX: attributes (profile?) $this->elementStart('head'); - $this->showTitle(); - $this->showShortcutIcon(); - $this->showStylesheets(); - $this->showScripts(); - $this->showOpenSearch(); - $this->showFeeds(); - $this->showDescription(); - $this->extraHead(); + if (Event::handle('StartShowHeadElements', array($this))) { + $this->showTitle(); + $this->showShortcutIcon(); + $this->showStylesheets(); + $this->showOpenSearch(); + $this->showFeeds(); + $this->showDescription(); + $this->extraHead(); + Event::handle('EndShowHeadElements', array($this)); + } $this->elementEnd('head'); } @@ -352,6 +354,7 @@ class Action extends HTMLOutputter // lawsuit Event::handle('EndShowFooter', array($this)); } $this->elementEnd('div'); + $this->showScripts(); $this->elementEnd('body'); } diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index c70f965376..ce83295fb3 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -427,16 +427,12 @@ class HTMLOutputter extends XMLOutputter function autofocus($id) { $this->elementStart('script', array('type' => 'text/javascript')); - $this->raw(' - - '); + $this->raw('/**/'); $this->elementEnd('script'); } } diff --git a/plugins/Orbited/OrbitedPlugin.php b/plugins/Orbited/OrbitedPlugin.php new file mode 100644 index 0000000000..ba87b266a0 --- /dev/null +++ b/plugins/Orbited/OrbitedPlugin.php @@ -0,0 +1,154 @@ +. + * + * @category Plugin + * @package Laconica + * @author Evan Prodromou + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +require_once INSTALLDIR.'/plugins/Realtime/RealtimePlugin.php'; + +/** + * Plugin to do realtime updates using Orbited + STOMP + * + * This plugin pushes data to a STOMP server which is then served to the + * browser by the Orbited server. + * + * @category Plugin + * @package Laconica + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +class OrbitedPlugin extends RealtimePlugin +{ + public $webserver = null; + public $webport = null; + public $channelbase = null; + public $stompserver = null; + public $stompport = null; + public $username = null; + public $password = null; + public $webuser = null; + public $webpass = null; + + protected $con = null; + + function onStartShowHeadElements($action) + { + // See http://orbited.org/wiki/Deployment#Cross-SubdomainDeployment + $action->element('script', null, ' document.domain = document.domain; '); + } + + function _getScripts() + { + $scripts = parent::_getScripts(); + + $port = (is_null($this->webport)) ? 8000 : $this->webport; + + $server = (is_null($this->webserver)) ? common_config('site', 'server') : $this->webserver; + + $root = 'http://'.$server.(($port == 80) ? '':':'.$port); + + $scripts[] = $root.'/static/Orbited.js'; + $scripts[] = common_path('plugins/Orbited/orbitedextra.js'); + $scripts[] = $root.'/static/protocols/stomp/stomp.js'; + $scripts[] = common_path('plugins/Orbited/orbitedupdater.js'); + + return $scripts; + } + + function _updateInitialize($timeline, $user_id) + { + $script = parent::_updateInitialize($timeline, $user_id); + + $server = $this->_getStompServer(); + $port = $this->_getStompPort(); + + return $script." OrbitedUpdater.init(\"$server\", $port, ". + "\"{$timeline}\", \"{$this->webuser}\", \"{$this->webpass}\");"; + } + + function _connect() + { + require_once(INSTALLDIR.'/extlib/Stomp.php'); + + $url = $this->_getStompUrl(); + + $this->con = new Stomp($url); + + if ($this->con->connect($this->username, $this->password)) { + $this->log(LOG_INFO, "Connected."); + } else { + $this->log(LOG_ERR, 'Failed to connect to queue server'); + throw new ServerException('Failed to connect to queue server'); + } + } + + function _publish($channel, $message) + { + $result = $this->con->send($channel, + json_encode($message)); + + return $result; + // TODO: parse and deal with result + } + + function _disconnect() + { + $this->con->disconnect(); + } + + function _pathToChannel($path) + { + if (!empty($this->channelbase)) { + array_unshift($path, $this->channelbase); + } + return '/' . implode('/', $path); + } + + function _getStompServer() + { + return (!is_null($this->stompserver)) ? $this->stompserver : + (!is_null($this->webserver)) ? $this->webserver : + common_config('site', 'server'); + } + + function _getStompPort() + { + return (!is_null($this->stompport)) ? $this->stompport : 61613; + } + + function _getStompUrl() + { + $server = $this->_getStompServer(); + $port = $this->_getStompPort(); + return "tcp://$server:$port/"; + } +} diff --git a/plugins/Orbited/orbitedextra.js b/plugins/Orbited/orbitedextra.js new file mode 100644 index 0000000000..47e5c0c80e --- /dev/null +++ b/plugins/Orbited/orbitedextra.js @@ -0,0 +1,2 @@ +TCPSocket = Orbited.TCPSocket; + diff --git a/plugins/Orbited/orbitedupdater.js b/plugins/Orbited/orbitedupdater.js new file mode 100644 index 0000000000..8c5ab3b732 --- /dev/null +++ b/plugins/Orbited/orbitedupdater.js @@ -0,0 +1,24 @@ +// Update the local timeline from a Orbited server + +var OrbitedUpdater = function() +{ + return { + + init: function(server, port, timeline, username, password) + { + // set up stomp client. + stomp = new STOMPClient(); + + stomp.onmessageframe = function(frame) { + RealtimeUpdate.receive(JSON.parse(frame.body)); + }; + + stomp.onconnectedframe = function() { + stomp.subscribe(timeline); + } + + stomp.connect(server, port, username, password); + } + } +}(); + diff --git a/plugins/Realtime/RealtimePlugin.php b/plugins/Realtime/RealtimePlugin.php index 0f0d0f9f42..1819279686 100644 --- a/plugins/Realtime/RealtimePlugin.php +++ b/plugins/Realtime/RealtimePlugin.php @@ -230,6 +230,7 @@ class RealtimePlugin extends Plugin } $action->showContentBlock(); + $action->showScripts(); $action->elementEnd('body'); return false; // No default processing } diff --git a/scripts/deleteuser.php b/scripts/deleteuser.php new file mode 100644 index 0000000000..52389123c5 --- /dev/null +++ b/scripts/deleteuser.php @@ -0,0 +1,68 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); + +$shortoptions = 'i::n::y'; +$longoptions = array('id::nickname::yes'); + +$helptext = <<nickname}' ({$user->id}). Are you sure? [y/N] "; + $response = fgets(STDIN); + if (strtolower(trim($response)) != 'y') { + print "Aborting.\n"; + exit(0); + } +} + +print "Deleting..."; +$user->delete(); +print "DONE.\n";