diff --git a/config/services.yml b/config/services.yml
index 76a247f4..cc4fa873 100644
--- a/config/services.yml
+++ b/config/services.yml
@@ -78,3 +78,13 @@ services:
- @dbal.conn
- @board3.portal.modules_helper
- @user
+
+ board3.portal.listener:
+ class: board3\portal\event\listener
+ arguments:
+ - @controller.helper
+ - @template
+ - @user
+ - %core.php_ext%
+ tags:
+ - { name: event.listener }
diff --git a/event/listener.php b/event/listener.php
new file mode 100644
index 00000000..9dfe1fdf
--- /dev/null
+++ b/event/listener.php
@@ -0,0 +1,101 @@
+controller_helper = $controller_helper;
+ $this->template = $template;
+ $this->user = $user;
+ $this->php_ext = $php_ext;
+ }
+
+ /**
+ * Assign functions defined in this class to event listeners in the core
+ *
+ * @return array
+ */
+ static public function getSubscribedEvents()
+ {
+ return array(
+ 'core.user_setup' => 'load_portal_language',
+ 'core.viewonline_overwrite_location' => 'viewonline_page',
+ 'core.page_header' => 'add_portal_link',
+ );
+ }
+
+ /**
+ * Load portal language during user setup
+ *
+ * @param object $event The event object
+ * @return null
+ */
+ public function load_portal_language($event)
+ {
+ $lang_set_ext = $event['lang_set_ext'];
+ $lang_set_ext[] = array(
+ 'ext_name' => 'board3/portal',
+ 'lang_set' => 'portal',
+ );
+ $event['lang_set_ext'] = $lang_set_ext;
+ }
+
+ /**
+ * Show users as viewing the portals on Who Is Online page
+ *
+ * @param object $event The event object
+ * @return null
+ */
+ public function viewonline_page($event)
+ {
+ if ($event['on_page'][1] == 'app' && strrpos($event['row']['session_page'], 'app.' . $this->php_ext . '/portal') === 0)
+ {
+ $event['location'] = $this->user->lang('VIEWING_PORTAL');
+ $event['location_url'] = $this->controller_helper->route('board3_controller');
+ }
+ }
+
+ /**
+ * Add portal link
+ *
+ * @param object $event The event object
+ * @return null
+ */
+ public function add_portal_link($event)
+ {
+ $this->template->assign_vars(array(
+ 'U_PORTAL' => $this->controller_helper->route('board3_controller'),
+ ));
+ }
+}
diff --git a/styles/prosilver/template/event/overall_footer_breadcrumb_prepend.html b/styles/prosilver/template/event/overall_footer_breadcrumb_prepend.html
new file mode 100644
index 00000000..2753a6c6
--- /dev/null
+++ b/styles/prosilver/template/event/overall_footer_breadcrumb_prepend.html
@@ -0,0 +1 @@
+{L_PORTAL}
diff --git a/styles/prosilver/template/event/overall_header_breadcrumb_prepend.html b/styles/prosilver/template/event/overall_header_breadcrumb_prepend.html
new file mode 100644
index 00000000..2753a6c6
--- /dev/null
+++ b/styles/prosilver/template/event/overall_header_breadcrumb_prepend.html
@@ -0,0 +1 @@
+{L_PORTAL}
diff --git a/styles/subsilver2/template/event/overall_footer_breadcrumb_prepend.html b/styles/subsilver2/template/event/overall_footer_breadcrumb_prepend.html
new file mode 100644
index 00000000..a6e98a45
--- /dev/null
+++ b/styles/subsilver2/template/event/overall_footer_breadcrumb_prepend.html
@@ -0,0 +1 @@
+{L_PORTAL} »
diff --git a/styles/subsilver2/template/event/overall_header_breadcrumb_prepend.html b/styles/subsilver2/template/event/overall_header_breadcrumb_prepend.html
new file mode 100644
index 00000000..a6e98a45
--- /dev/null
+++ b/styles/subsilver2/template/event/overall_header_breadcrumb_prepend.html
@@ -0,0 +1 @@
+{L_PORTAL} »
diff --git a/tests/unit/event/config/routing.yml b/tests/unit/event/config/routing.yml
new file mode 100644
index 00000000..a7232a78
--- /dev/null
+++ b/tests/unit/event/config/routing.yml
@@ -0,0 +1,3 @@
+board3_controller:
+ pattern: /portal
+ defaults: { _controller: board3.portal.main:handle }
diff --git a/tests/unit/event/listener_test.php b/tests/unit/event/listener_test.php
new file mode 100644
index 00000000..411d16a4
--- /dev/null
+++ b/tests/unit/event/listener_test.php
@@ -0,0 +1,135 @@
+setup_listener();
+
+ global $phpbb_dispatcher;
+
+ $phpbb_dispatcher = new \phpbb\event\dispatcher(new \phpbb_mock_container_builder());
+ $this->phpbb_dispatcher = $phpbb_dispatcher;
+ }
+
+ public function setup_listener()
+ {
+ $this->user = $this->getMock('\phpbb\user');
+ $this->user->expects($this->any())
+ ->method('lang')
+ ->will($this->returnValue('foo'));
+
+ $manager = new \phpbb_mock_extension_manager(dirname(__FILE__) . '/', array());
+ $finder = new \phpbb\finder(
+ new \phpbb\filesystem(),
+ dirname(__FILE__) . '/',
+ new \phpbb_mock_cache()
+ );
+ $finder->set_extensions(array_keys($manager->all_enabled()));
+
+ $this->config = new \phpbb\config\config(array('enable_mod_rewrite' => '1'));
+ $provider = new \phpbb\controller\provider();
+ $provider->find_routing_files($finder);
+ $provider->find(dirname(__FILE__) . '/');
+ $this->controller_helper = new \phpbb_mock_controller_helper($this->template, $this->user, $this->config, $provider, $manager, '', 'php', dirname(__FILE__) . '/');
+
+ $this->listener = new \board3\portal\event\listener(
+ $this->controller_helper,
+ $this->template,
+ $this->user,
+ 'php'
+ );
+ }
+
+ public function test_construct()
+ {
+ $this->setup_listener();
+ $this->assertInstanceOf('\Symfony\Component\EventDispatcher\EventSubscriberInterface', $this->listener);
+ }
+
+ public function test_getSubscribedEvents()
+ {
+ $this->assertEquals(array(
+ 'core.user_setup',
+ 'core.viewonline_overwrite_location',
+ 'core.page_header',
+ ), array_keys(\board3\portal\event\listener::getSubscribedEvents()));
+ }
+
+ public function test_viewonline_page()
+ {
+ $this->phpbb_dispatcher->addListener('core.viewonline_overwrite_location', array($this->listener, 'viewonline_page'));
+ $on_page = array(
+ 'foobar',
+ 'app',
+ );
+ $row = array(
+ 'session_page' => 'app.php/portal',
+ );
+ $location = 'foo';
+ $location_url = 'bar.php';
+
+ $vars = array(
+ 'on_page',
+ 'row',
+ 'location',
+ 'location_url',
+ );
+
+ $result = $this->phpbb_dispatcher->trigger_event('core.viewonline_overwrite_location', compact($vars));
+
+ $this->assertEquals('foo', $location);
+ }
+
+ public function test_add_portal_link()
+ {
+ $this->phpbb_dispatcher->addListener('core.page_header', array($this->listener, 'add_portal_link'));
+
+ $vars = array();
+
+ $result = $this->phpbb_dispatcher->trigger_event('core.page_header', compact($vars));
+
+ $this->assertEmpty($result);
+ }
+
+ public function test_load_portal_language()
+ {
+ $this->phpbb_dispatcher->addListener('core.user_setup', array($this->listener, 'load_portal_language'));
+
+ $lang_set_ext = array(array(
+ 'ext_name' => 'foo/bar',
+ 'lang_set' => 'bar',
+ ));
+ $vars = array('lang_set_ext');
+
+ $result = $this->phpbb_dispatcher->trigger_event('core.user_setup', compact($vars));
+
+ $this->assertEquals(array(
+ array(
+ 'ext_name' => 'foo/bar',
+ 'lang_set' => 'bar',
+ ),
+ array(
+ 'ext_name' => 'board3/portal',
+ 'lang_set' => 'portal',
+ )), $result['lang_set_ext']);
+ }
+}