diff --git a/backend.php b/backend.php
index 511bb35528245b5b015aa44660372184e731f2e9..8aaf1001657c573c9dd3dabec24405674ccd2bdf 100644
--- a/backend.php
+++ b/backend.php
@@ -126,6 +126,10 @@
 				if ($handler->before($method)) {
 					if ($method && method_exists($handler, $method)) {
 						$handler->$method();
+					} else {
+						if (method_exists($handler, "catchall")) {
+							$handler->catchall($method);
+						}
 					}
 					$handler->after();
 					return;
diff --git a/classes/button.php b/classes/button.php
deleted file mode 100644
index 24d576dae4c7cbfab51d7ccae1765de1b59d935b..0000000000000000000000000000000000000000
--- a/classes/button.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-class Button {
-
-	protected $link;
-
-	function __construct($link) {
-		$this->link = $link;
-	}
-
-}
-?>
diff --git a/classes/button/tweet.php b/classes/button/tweet.php
deleted file mode 100644
index 3157fb779f3ab665159ed01dde013d00794dc338..0000000000000000000000000000000000000000
--- a/classes/button/tweet.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-class Button_Tweet extends Button {
-	function render($article_id) {
-		$rv = "<img src=\"".theme_image($this->link, 'images/art-tweet.png')."\"
-			class='tagsPic' style=\"cursor : pointer\"
-			onclick=\"tweetArticle($article_id)\"
-			title='".__('Share on Twitter')."'>";
-
-		return $rv;
-	}
-
-	function getTweetInfo() {
-		$id = db_escape_string($_REQUEST['id']);
-
-		$result = db_query($this->link, "SELECT title, link
-				FROM ttrss_entries, ttrss_user_entries
-				WHERE id = '$id' AND ref_id = id AND owner_uid = " .$_SESSION['uid']);
-
-		if (db_num_rows($result) != 0) {
-			$title = truncate_string(strip_tags(db_fetch_result($result, 0, 'title')),
-				100, '...');
-			$article_link = db_fetch_result($result, 0, 'link');
-		}
-
-		print json_encode(array("title" => $title, "link" => $article_link,
-				"id" => $id));
-	}
-
-
-}
-?>
diff --git a/classes/feeds.php b/classes/feeds.php
index 49adf38795b8381d0182ab43d08e383a051971f7..a90b1c8ed7059679231df43ac86b32d53972a7cd 100644
--- a/classes/feeds.php
+++ b/classes/feeds.php
@@ -249,7 +249,7 @@ class Feeds extends Handler_Protected {
 
 		$headlines_count = db_num_rows($result);
 
-		if (get_pref($this->link, 'COMBINED_DISPLAY_MODE')) {
+		/* if (get_pref($this->link, 'COMBINED_DISPLAY_MODE')) {
 			$button_plugins = array();
 			foreach (explode(",", ARTICLE_BUTTON_PLUGINS) as $p) {
 				$pclass = "button_" . trim($p);
@@ -259,7 +259,9 @@ class Feeds extends Handler_Protected {
 					array_push($button_plugins, $plugin);
 				}
 			}
-		}
+		} */
+
+		global $pluginhost;
 
 		if (db_num_rows($result) > 0) {
 
@@ -706,8 +708,8 @@ class Feeds extends Handler_Protected {
 
 					//$note_escaped = htmlspecialchars($line['note'], ENT_QUOTES);
 
-					foreach ($button_plugins as $p) {
-						$reply['content'] .= $p->render($id, $line);
+					foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON) as $p) {
+						$reply['content'] .= $p->hook_article_button($line);
 					}
 
 					$reply['content'] .= "<img src=\"images/digest_checkbox.png\"
diff --git a/classes/filter.php b/classes/filter.php
deleted file mode 100644
index 8d6bf6f264cc4fefba8f34a2ef47320d5c3addb1..0000000000000000000000000000000000000000
--- a/classes/filter.php
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-class Filter {
-	protected $link;
-
-	function __construct($link) {
-		$this->link = $link;
-	}
-
-	function filter_article($article) {
-		return $article;
-	}
-
-}
-?>
diff --git a/classes/pluginhandler.php b/classes/pluginhandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..df7058dcd6d4f78241617e277b7aa481b262dd7c
--- /dev/null
+++ b/classes/pluginhandler.php
@@ -0,0 +1,18 @@
+<?php
+class PluginHandler extends Handler_Protected {
+	function csrf_ignore($method) {
+		return true;
+	}
+
+	function catchall($method) {
+		global $pluginhost;
+
+		$plugin = $pluginhost->get_plugin($_REQUEST["plugin"]);
+
+		if (method_exists($plugin, $method)) {
+			$plugin->$method();
+		}
+	}
+}
+
+?>
diff --git a/classes/pluginhost.php b/classes/pluginhost.php
new file mode 100644
index 0000000000000000000000000000000000000000..4274ec37b1b46f6efa6168eb00bdcaf324087ac2
--- /dev/null
+++ b/classes/pluginhost.php
@@ -0,0 +1,71 @@
+<?php
+class PluginHost {
+	private $link;
+	private $hooks = array();
+	private $plugins = array();
+
+	const HOOK_ARTICLE_BUTTON = 1;
+	const HOOK_ARTICLE_FILTER = 2;
+
+	function __construct($link) {
+		$this->link = $link;
+	}
+
+	private function register_plugin($name, $plugin) {
+		//array_push($this->plugins, $plugin);
+		$this->plugins[$name] = $plugin;
+	}
+
+	function get_link() {
+		return $this->link;
+	}
+
+	function get_plugins() {
+		return $this->plugins;
+	}
+
+	function get_plugin($name) {
+		return $this->plugins[$name];
+	}
+
+	function add_hook($type, $sender) {
+		if (!is_array($this->hooks[$type])) {
+			$this->hooks[$type] = array();
+		}
+
+		array_push($this->hooks[$type], $sender);
+	}
+
+	function del_hook($type, $sender) {
+		if (is_array($this->hooks[$type])) {
+			$key = array_Search($this->hooks[$type], $sender);
+			if ($key !== FALSE) {
+				unset($this->hooks[$type][$key]);
+			}
+		}
+	}
+
+	function get_hooks($type) {
+		return $this->hooks[$type];
+	}
+
+	function load($classlist) {
+		$plugins = explode(",", $classlist);
+
+		foreach ($plugins as $class) {
+			$class = trim($class);
+			$class_file = str_replace("_", "/", strtolower(basename($class)));
+			$file = dirname(__FILE__)."/../plugins/$class_file/$class_file.php";
+
+			if (file_exists($file)) require_once $file;
+
+			if (class_exists($class)) {
+				$plugin = new $class($this);
+
+				$this->register_plugin($class, $plugin);
+			}
+		}
+	}
+
+}
+?>
diff --git a/classes/rpc.php b/classes/rpc.php
index 984187915e79e694fe94223a79c48af4d2654b57..2bec8c54a9068bbe514cb2acbbcd5fdbf855ad83 100644
--- a/classes/rpc.php
+++ b/classes/rpc.php
@@ -706,7 +706,7 @@ class RPC extends Handler_Protected {
 		print json_encode(array("status" => $status));
 	}
 
-	function buttonPlugin() {
+	/* function buttonPlugin() {
 		$pclass = "button_" . basename($_REQUEST['plugin']);
 		$method = $_REQUEST['plugin_method'];
 
@@ -716,7 +716,7 @@ class RPC extends Handler_Protected {
 				return $plugin->$method();
 			}
 		}
-	}
+	} */
 
 	function genHash() {
 		$hash = sha1(uniqid(rand(), true));
diff --git a/config.php-dist b/config.php-dist
index 53f5dc87b5cad13f01821fbb3416f88754ba7351..2975d680ae46cbb4609d48ae3db3278c3415d7f1 100644
--- a/config.php-dist
+++ b/config.php-dist
@@ -176,16 +176,13 @@
 	// if you experience weird errors and tt-rss failing to start, blank pages
 	// after login, or content encoding errors, disable it.
 
+	define('PLUGINS', '');
+	// Plugins to load. Check plugins/ directory for additional information.
+
 	define('FEEDBACK_URL', '');
 	// Displays an URL for users to provide feedback or comments regarding
 	// this instance of tt-rss. Can lead to a forum, contact email, etc.
 
-	define('ARTICLE_BUTTON_PLUGINS', 'note,tweet,share,mail');
-	// Comma-separated list of additional article action button plugins
-	// to enable, like tweet button, etc.
-	// The following plugins are available: note, tweet, share, mail
-	// More plugins: http://tt-rss.org/wiki/Plugins
-
 	define('CONFIG_VERSION', 26);
 	// Expected config version. Please update this option in config.php
 	// if necessary (after migrating all new options from this file).
diff --git a/images/art-tweet.png b/images/art-tweet.png
deleted file mode 100644
index ad3c177a2d9e0e17f9a456aeb4bb86fff9e7be97..0000000000000000000000000000000000000000
Binary files a/images/art-tweet.png and /dev/null differ
diff --git a/include/functions.php b/include/functions.php
index 53aaa92b33155c390d2660429c1d8762c4d618ef..da676798636a2db5eedb6b6629a91cb49f244b6e 100644
--- a/include/functions.php
+++ b/include/functions.php
@@ -7,13 +7,6 @@
 	function __autoload($class) {
 		$class_file = str_replace("_", "/", strtolower(basename($class)));
 
-		$file = dirname(__FILE__)."/../plugins/$class_file.php";
-
-		if (file_exists($file)) {
-			require $file;
-			return;
-		}
-
 		$file = dirname(__FILE__)."/../classes/$class_file.php";
 
 		if (file_exists($file)) {
@@ -3265,15 +3258,10 @@
 						onclick=\"postOpenInNewTab(event, $id)\"
 						alt='Zoom' title='".__('Open article in new tab')."'>";
 
-				$button_plugins = explode(",", ARTICLE_BUTTON_PLUGINS);
+				global $pluginhost;
 
-				foreach ($button_plugins as $p) {
-					$pclass = "button_" . trim($p);
-
-					if (class_exists($pclass)) {
-						$plugin = new $pclass($link);
-						$rv['content'] .= $plugin->render($id, $line);
-					}
+				foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_BUTTON) as $p) {
+					$rv['content'] .= $p->hook_article_button($line);
 				}
 
 				$rv['content'] .= "<img src=\"".theme_image($link, 'images/digest_checkbox.png')."\"
@@ -3568,6 +3556,12 @@
 					db_query($link, "SET NAMES " . MYSQL_CHARSET);
 				}
 			}
+
+			global $pluginhost;
+
+			$pluginhost = new PluginHost($link);
+			$pluginhost->load(PLUGINS);
+
 			return true;
 		} else {
 			print "Unable to connect to database:" . db_last_error();
diff --git a/include/rssfuncs.php b/include/rssfuncs.php
index f844aaeb9825befd20a50562adf002b1a0de2373..4a5bd7680f3bff998af546c9c97976659a4a5cf9 100644
--- a/include/rssfuncs.php
+++ b/include/rssfuncs.php
@@ -399,23 +399,6 @@
 				_debug("update_rss_feed: " . count($filters) . " filters loaded.");
 			}
 
-			$filter_plugins = array();
-
-			if (defined('_ARTICLE_FILTER_PLUGINS')) {
-				foreach (explode(",", _ARTICLE_FILTER_PLUGINS) as $p) {
-					$pclass = "filter_" . trim($p);
-
-					if (class_exists($pclass)) {
-						$plugin = new $pclass($link);
-						array_push($filter_plugins, $plugin);
-					}
-				}
-			}
-
-			if ($debug_enabled) {
-				_debug("update_rss_feed: " . count($filter_plugins) . " filter plugins loaded.");
-			}
-
 			if ($use_simplepie) {
 				$iterator = $rss->get_items();
 			} else {
@@ -782,7 +765,9 @@
 				}
 
 				// TODO: less memory-hungry implementation
-				if (count($filter_plugins) > 0) {
+				global $pluginhost;
+
+				foreach ($pluginhost->get_hooks($pluginhost::HOOK_ARTICLE_FILTER) as $p) {
 					if ($debug_enabled) {
 						_debug("update_rss_feed: applying plugin filters...");
 					}
@@ -795,7 +780,7 @@
 						"author" => $entry_author);
 
 					foreach ($filter_plugins as $plugin) {
-						$article = $plugin->filter_article($article);
+						$article = $plugin->hook_article_filter($article);
 					}
 
 					$entry_title = $article["title"];
diff --git a/include/sanity_config.php b/include/sanity_config.php
index c0626243d12a41eac885d726833b1b17a8dc53e5..475f81f1b075188e3b6607b165a128c1416134a4 100644
--- a/include/sanity_config.php
+++ b/include/sanity_config.php
@@ -1,3 +1,3 @@
-<?php # This file has been generated at:  Fri Sep 7 10:20:51 MSK 2012
+<?php # This file has been generated at:  Sun Dec 23 13:56:09 MSK 2012
 define('GENERATED_CONFIG_CHECK', 26);
-$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'SINGLE_USER_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_MODULES', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'DEFAULT_UPDATE_METHOD', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'SPHINX_ENABLED', 'SPHINX_INDEX', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_EXPIRE_TIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_HOST', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'CHECK_FOR_NEW_VERSION', 'ENABLE_GZIP_OUTPUT', 'FEEDBACK_URL', 'ARTICLE_BUTTON_PLUGINS', 'CONFIG_VERSION'); ?>
+$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'SINGLE_USER_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_MODULES', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'DEFAULT_UPDATE_METHOD', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'SPHINX_ENABLED', 'SPHINX_INDEX', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_EXPIRE_TIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_HOST', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'CHECK_FOR_NEW_VERSION', 'ENABLE_GZIP_OUTPUT', 'FEEDBACK_URL', 'CONFIG_VERSION'); ?>
diff --git a/index.php b/index.php
index 445a8fa75e2b5b8da50990b62d3f6f545cb7f907..d958e2b891d76078c4671ce04d4e6db393db3b76 100644
--- a/index.php
+++ b/index.php
@@ -71,10 +71,11 @@
 	<?php
 		require 'lib/jsmin.php';
 
-		foreach (explode(",", ARTICLE_BUTTON_PLUGINS) as $p) {
-			$jsf = "js/".trim($p)."_button.js";
-			if (file_exists($jsf)) {
-				echo JSMin::minify(file_get_contents($jsf));
+		global $pluginhost;
+
+		foreach ($pluginhost->get_plugins() as $n => $p) {
+			if (method_exists($p, "get_js")) {
+				echo JSMin::minify($p->get_js());
 			}
 		}
 
diff --git a/js/tweet_button.js b/js/tweet_button.js
deleted file mode 100644
index d127010ac0aa3e0f74e2c9eda13286658e73a30e..0000000000000000000000000000000000000000
--- a/js/tweet_button.js
+++ /dev/null
@@ -1,31 +0,0 @@
-	function tweetArticle(id) {
-	try {
-		var query = "?op=rpc&method=buttonPlugin&plugin=tweet&plugin_method=getTweetInfo&id=" + param_escape(id);
-
-		console.log(query);
-
-		var d = new Date();
-      var ts = d.getTime();
-
-		var w = window.open('backend.php?op=backend&method=loading', 'ttrss_tweet',
-			"status=0,toolbar=0,location=0,width=500,height=400,scrollbars=1,menubar=0");
-
-		new Ajax.Request("backend.php",	{
-			parameters: query,
-			onComplete: function(transport) {
-				var ti = JSON.parse(transport.responseText);
-
-				var share_url = "http://twitter.com/share?_=" + ts +
-					"&text=" + param_escape(ti.title) +
-					"&url=" + param_escape(ti.link);
-
-				w.location.href = share_url;
-
-			} });
-
-
-	} catch (e) {
-		exception_error("tweetArticle", e);
-	}
-	}
-
diff --git a/plugins/mail/README.txt b/plugins/mail/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d499a5de8477cc7e9c371832ed8694fe25261cf2
--- /dev/null
+++ b/plugins/mail/README.txt
@@ -0,0 +1 @@
+Shares article by email
diff --git a/js/mail_button.js b/plugins/mail/mail.js
similarity index 85%
rename from js/mail_button.js
rename to plugins/mail/mail.js
index 0fcb0d80fb2c1a0dc5bc852ad53204cea9bd6224..39f753cc04ac47f4270f6bd907886256e0127a0e 100644
--- a/js/mail_button.js
+++ b/plugins/mail/mail.js
@@ -14,7 +14,7 @@ function emailArticle(id) {
 		if (dijit.byId("emailArticleDlg"))
 			dijit.byId("emailArticleDlg").destroyRecursive();
 
-		var query = "backend.php?op=rpc&method=buttonPlugin&plugin=mail&plugin_method=emailArticle&param=" + param_escape(id);
+		var query = "backend.php?op=pluginhandler&plugin=mail&method=emailArticle&param=" + param_escape(id);
 
 		dialog = new dijit.Dialog({
 			id: "emailArticleDlg",
@@ -47,7 +47,7 @@ function emailArticle(id) {
 	   	dojo.disconnect(tmph);
 
 		   new Ajax.Autocompleter('emailArticleDlg_destination', 'emailArticleDlg_dst_choices',
-			   "backend.php?op=rpc&method=buttonPlugin&plugin=mail&plugin_method=completeEmails",
+			   "backend.php?op=pluginhandler&plugin=mail&method=completeEmails",
 			   { tokens: '', paramName: "search" });
 		});
 
diff --git a/classes/button/mail.php b/plugins/mail/mail.php
similarity index 90%
rename from classes/button/mail.php
rename to plugins/mail/mail.php
index 309493bbe89bf0086e9f10a35951e45e09b047c2..a633d815f33e1f60c30fdaf1b33b2efb4057eff5 100644
--- a/classes/button/mail.php
+++ b/plugins/mail/mail.php
@@ -1,9 +1,24 @@
 <?php
-class Button_Mail extends Button {
-	function render($article_id) {
-		return "<img src=\"".theme_image($link, 'images/art-email.png')."\"
+class Mail {
+
+	private $link;
+	private $host;
+
+	function __construct($host) {
+		$this->link = $host->get_link();
+		$this->host = $host;
+
+		$host->add_hook($host::HOOK_ARTICLE_BUTTON, $this);
+	}
+
+	function get_js() {
+		return file_get_contents(dirname(__FILE__) . "/mail.js");
+	}
+
+	function hook_article_button($line) {
+		return "<img src=\"".theme_image($link, 'plugins/mail/mail.png')."\"
 					class='tagsPic' style=\"cursor : pointer\"
-					onclick=\"emailArticle($article_id)\"
+					onclick=\"emailArticle(".$line["id"].")\"
 					alt='Zoom' title='".__('Forward by email')."'>";
 	}
 
@@ -16,10 +31,9 @@ class Button_Mail extends Button {
 		$_SESSION['email_secretkey'] = $secretkey;
 
 		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"secretkey\" value=\"$secretkey\">";
-		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"rpc\">";
-		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"buttonPlugin\">";
+		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"pluginhandler\">";
 		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"plugin\" value=\"mail\">";
-		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"plugin_method\" value=\"sendEmail\">";
+		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"sendEmail\">";
 
 		$result = db_query($this->link, "SELECT email, full_name FROM ttrss_users WHERE
 			id = " . $_SESSION["uid"]);
diff --git a/images/art-email.png b/plugins/mail/mail.png
similarity index 100%
rename from images/art-email.png
rename to plugins/mail/mail.png
diff --git a/plugins/note/README.txt b/plugins/note/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1efec8f026ccf7e1fb1ed5e6db6ccbd2882499b7
--- /dev/null
+++ b/plugins/note/README.txt
@@ -0,0 +1 @@
+Support for article notes
diff --git a/js/note_button.js b/plugins/note/note.js
similarity index 89%
rename from js/note_button.js
rename to plugins/note/note.js
index c9347ab1972c9a0a340bf77d36d1771451b39604..022fc88e7e98a95117660d9b98bb126155a155bb 100644
--- a/js/note_button.js
+++ b/plugins/note/note.js
@@ -1,7 +1,7 @@
 function editArticleNote(id) {
 	try {
 
-		var query = "backend.php?op=rpc&method=buttonPlugin&plugin=note&plugin_method=edit&param=" + param_escape(id);
+		var query = "backend.php?op=pluginhandler&plugin=note&method=edit&param=" + param_escape(id);
 
 		if (dijit.byId("editNoteDlg"))
 			dijit.byId("editNoteDlg").destroyRecursive();
diff --git a/classes/button/note.php b/plugins/note/note.php
similarity index 71%
rename from classes/button/note.php
rename to plugins/note/note.php
index d5b6e380c3b76d1aec14f25540e9e46017de1ec2..a856b5ac875e3dad3f5cd11eccde493c54b206d3 100644
--- a/classes/button/note.php
+++ b/plugins/note/note.php
@@ -1,10 +1,25 @@
 <?php
-class Button_Note extends Button {
-	function render($article_id) {
-		return "<img src=\"".theme_image($this->link, "images/art-pub-note.png")."\"
-				style=\"cursor : pointer\" style=\"cursor : pointer\"
-				onclick=\"editArticleNote($article_id)\"
-				class='tagsPic' title='".__('Edit article note')."'>";
+class Note {
+	private $link;
+	private $host;
+
+	function __construct($host) {
+		$this->link = $host->get_link();
+		$this->host = $host;
+
+		$host->add_hook($host::HOOK_ARTICLE_BUTTON, $this);
+	}
+
+	function get_js() {
+		return file_get_contents(dirname(__FILE__) . "/note.js");
+	}
+
+
+	function hook_article_button($line) {
+		return "<img src=\"".theme_image($this->link, "plugins/note/note.png")."\"
+			style=\"cursor : pointer\" style=\"cursor : pointer\"
+			onclick=\"editArticleNote(".$line["id"].")\"
+			class='tagsPic' title='".__('Edit article note')."'>";
 	}
 
 	function edit() {
@@ -16,10 +31,9 @@ class Button_Note extends Button {
 		$note = db_fetch_result($result, 0, "note");
 
 		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"id\" value=\"$param\">";
-		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"rpc\">";
-		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"buttonPlugin\">";
+		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"op\" value=\"pluginhandler\">";
+		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"method\" value=\"setNote\">";
 		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"plugin\" value=\"note\">";
-		print "<input dojoType=\"dijit.form.TextBox\" style=\"display : none\" name=\"plugin_method\" value=\"setNote\">";
 
 		print "<table width='100%'><tr><td>";
 		print "<textarea dojoType=\"dijit.form.SimpleTextarea\"
@@ -50,6 +64,5 @@ class Button_Note extends Button {
 				"raw_length" => mb_strlen($note)));
 	}
 
-
 }
 ?>
diff --git a/images/art-pub-note.png b/plugins/note/note.png
similarity index 100%
rename from images/art-pub-note.png
rename to plugins/note/note.png
diff --git a/plugins/redditimgur/README.txt b/plugins/redditimgur/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3913e264dd12ccc75029123e1e8cf4ff691b650d
--- /dev/null
+++ b/plugins/redditimgur/README.txt
@@ -0,0 +1 @@
+Inline image links in Reddit RSS
diff --git a/classes/filter/redditimgur.php b/plugins/redditimgur/redditimgur.php
similarity index 81%
rename from classes/filter/redditimgur.php
rename to plugins/redditimgur/redditimgur.php
index b9ed24353cb80b572807deee51cda63e4a4661e8..21aaed6e1d511ee76fb635a6b8f4fa5745e9531b 100644
--- a/classes/filter/redditimgur.php
+++ b/plugins/redditimgur/redditimgur.php
@@ -1,7 +1,17 @@
 <?php
-class Filter_RedditImgur {
+class RedditImgur {
 
-	function filter_article($article) {
+	private $link;
+	private $host;
+
+	function __construct($host) {
+		$this->link = $host->get_link();
+		$this->host = $host;
+
+		$host->add_hook($host::HOOK_ARTICLE_FILTER, $this);
+	}
+
+	function hook_article_filter($article) {
 
 		if (strpos($article["link"], "reddit.com/r/") !== FALSE) {
 			if (strpos($article["content"], "i.imgur.com") !== FALSE) {
diff --git a/plugins/share/README.txt b/plugins/share/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..087c9281dda11bec274b210bb7f6c7e5af6dd6bf
--- /dev/null
+++ b/plugins/share/README.txt
@@ -0,0 +1 @@
+Support for sharing articles by URL
diff --git a/js/share_button.js b/plugins/share/share.js
similarity index 73%
rename from js/share_button.js
rename to plugins/share/share.js
index 7645940e7c88da997b98f6acb683f80800289cd2..6752189eabdccf049a940bbfa7276258bf77396f 100644
--- a/js/share_button.js
+++ b/plugins/share/share.js
@@ -3,7 +3,7 @@ function shareArticle(id) {
 		if (dijit.byId("shareArticleDlg"))
 			dijit.byId("shareArticleDlg").destroyRecursive();
 
-		var query = "backend.php?op=rpc&method=buttonPlugin&plugin=share&plugin_method=shareArticle&param=" + param_escape(id);
+		var query = "backend.php?op=pluginhandler&plugin=share&method=shareArticle&param=" + param_escape(id);
 
 		dialog = new dijit.Dialog({
 			id: "shareArticleDlg",
diff --git a/classes/button/share.php b/plugins/share/share.php
similarity index 83%
rename from classes/button/share.php
rename to plugins/share/share.php
index 74d7128d910b86260038db28a1bafbe570808b2b..157c937ea7b1996ace7bd1c8b5697e40a2e39dbc 100644
--- a/classes/button/share.php
+++ b/plugins/share/share.php
@@ -1,6 +1,20 @@
 <?php
-class Button_Share extends Button {
-	function render($article_id, $line) {
+class Share {
+	private $link;
+	private $host;
+
+	function __construct($host) {
+		$this->link = $host->get_link();
+		$this->host = $host;
+
+		$host->add_hook($host::HOOK_ARTICLE_BUTTON, $this);
+	}
+
+	function get_js() {
+		return file_get_contents(dirname(__FILE__) . "/share.js");
+	}
+
+	function hook_article_button($line) {
 		return "<img src=\"".theme_image($this->link, 'images/art-share.png')."\"
 			class='tagsPic' style=\"cursor : pointer\"
 			onclick=\"shareArticle(".$line['int_id'].")\"
diff --git a/images/art-share.png b/plugins/share/share.png
similarity index 100%
rename from images/art-share.png
rename to plugins/share/share.png