diff --git a/cms/pages/modules/cms_galleries.php b/cms/pages/modules_custom/cms_galleries.php
index 6590edd9..af5c6f24 100644
--- a/cms/pages/modules/cms_galleries.php
+++ b/cms/pages/modules_custom/cms_galleries.php
@@ -903,7 +903,7 @@ class Module_cms_galleries extends standard_aed_module
         * @param  boolean                      Whether this form will be used for adding a new image
         * @return array                                A pair: the tempcode for the visible fields, and the tempcode for the hidden fields
         */
-       function get_form_fields($title='',$cat='',$comments='',$url='',$thumb_url='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$notes='',$adding=true)
+       function get_form_fields($id = null, $title='',$cat='',$comments='',$url='',$thumb_url='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$notes='',$adding=true)
        {
                list($allow_rating,$allow_comments,$allow_trackbacks)=$this->choose_feedback_fields_statistically($allow_rating,$allow_comments,$allow_trackbacks);
 
@@ -963,6 +963,9 @@ class Module_cms_galleries extends standard_aed_module
                if (has_some_cat_specific_permission(get_member(),'bypass_validation_'.$this->permissions_require.'range_content',NULL,$this->permissions_cat_require))
                        $fields->attach(form_input_tick(do_lang_tempcode('VALIDATED'),do_lang_tempcode('DESCRIPTION_VALIDATED'),'validated',$validated==1));
 
+               require_code('related_content');
+               $fields->attach(form_input_related_content('image', $id));
+
                $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER',array('SECTION_HIDDEN'=>true,'TITLE'=>do_lang_tempcode('ADVANCED'))));
                if (get_option('is_on_gd')=='1')
                {
@@ -1033,7 +1036,7 @@ class Module_cms_galleries extends standard_aed_module
                        $delete_fields=form_input_radio(do_lang_tempcode('DELETE_STATUS'),do_lang_tempcode('DESCRIPTION_DELETE_STATUS'),'delete',$radios);
                } else $delete_fields=new ocp_tempcode();
 
-               list($fields,$hidden)=$this->get_form_fields(get_translated_text($myrow['title']),$cat,$comments,$myrow['url'],$myrow['thumb_url'],$validated,$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],$myrow['notes'],false);
+               list($fields,$hidden)=$this->get_form_fields($id, get_translated_text($myrow['title']),$cat,$comments,$myrow['url'],$myrow['thumb_url'],$validated,$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],$myrow['notes'],false);
 
                return array($fields,$hidden,$delete_fields,'',true);
        }
@@ -1074,6 +1077,9 @@ class Module_cms_galleries extends standard_aed_module
 
                $id=add_image($title,$cat,$comments,$urls[0],$urls[1],$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes);
 
+               require_code('related_content');
+               save_related_content('image', strval($id));
+
                if (($validated==1) || (!addon_installed('unvalidated')))
                {
                        if ((has_actual_page_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'galleries')) && (has_category_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'galleries',$cat)))
@@ -1139,6 +1145,9 @@ class Module_cms_galleries extends standard_aed_module
 
                edit_image($id,$title,$cat,$comments,$url,$thumb_url,$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,post_param('meta_keywords',''),post_param('meta_description',''));
 
+               require_code('related_content');
+               save_related_content('image', strval($id));
+
                if ((has_edit_permission('cat_mid',get_member(),get_member_id_from_gallery_name($cat),'cms_galleries',array('galleries',$cat))) && (post_param_integer('rep_image',0)==1))
                {
                        $GLOBALS['SITE_DB']->query_update('galleries',array('rep_image'=>$thumb_url),array('name'=>$cat),'',1);
@@ -1159,6 +1168,9 @@ class Module_cms_galleries extends standard_aed_module
                $this->donext_type=post_param('cat');
 
                delete_image($id,$delete_status=='2');
+
+               require_code('related_content');
+               delete_related_content('image', strval($id));
        }
 
        /**
@@ -1321,7 +1333,7 @@ class Module_cms_galleries_alt extends standard_aed_module
         * @param  ?integer                     The height of the video (NULL: not yet added, so not yet known)
         * @return array                                A pair: the tempcode for the visible fields, and the tempcode for the hidden fields
         */
-       function get_form_fields($title='',$cat='',$comments='',$url='',$thumb_url='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$notes='',$video_length=NULL,$video_width=NULL,$video_height=NULL)
+       function get_form_fields($id = null, $title='',$cat='',$comments='',$url='',$thumb_url='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$notes='',$video_length=NULL,$video_width=NULL,$video_height=NULL)
        {
                list($allow_rating,$allow_comments,$allow_trackbacks)=$this->choose_feedback_fields_statistically($allow_rating,$allow_comments,$allow_trackbacks);
 
@@ -1371,6 +1383,10 @@ class Module_cms_galleries_alt extends standard_aed_module
                if ($no_thumb_needed)
                {
                        $fields->attach($description_field);
+
+                       require_code('related_content');
+                       $fields->attach(form_input_related_content('video', strval($id)));
+
                        $fields->attach($validated_field);
                        $temp=do_template('FORM_SCREEN_FIELD_SPACER',array('TITLE'=>do_lang_tempcode('ADVANCED'),'SECTION_HIDDEN'=>true));
                        $fields->attach($temp);
@@ -1397,6 +1413,9 @@ class Module_cms_galleries_alt extends standard_aed_module
                if (!$no_thumb_needed)
                {
                        $fields->attach($validated_field);
+
+                       require_code('related_content');
+                       $fields->attach(form_input_related_content('video', strval($id)));
                }
 
                require_code('feedback2');
@@ -1460,7 +1479,7 @@ class Module_cms_galleries_alt extends standard_aed_module
                        $delete_fields=form_input_radio(do_lang_tempcode('DELETE_STATUS'),do_lang_tempcode('DESCRIPTION_DELETE_STATUS'),'delete',$radios);
                } else $delete_fields=new ocp_tempcode();
 
-               list($fields,$hidden)=$this->get_form_fields(get_translated_text($myrow['title']),$cat,$comments,$url,$myrow['thumb_url'],$validated,$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],$myrow['notes'],$myrow['video_length'],$myrow['video_width'],$myrow['video_height']);
+               list($fields,$hidden)=$this->get_form_fields($id, get_translated_text($myrow['title']),$cat,$comments,$url,$myrow['thumb_url'],$validated,$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],$myrow['notes'],$myrow['video_length'],$myrow['video_width'],$myrow['video_height']);
 
                return array($fields,$hidden,$delete_fields,'',true);
        }
@@ -1506,6 +1525,9 @@ class Module_cms_galleries_alt extends standard_aed_module
 
                $id=add_video($title,$cat,$comments,$urls[0],$urls[1],$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,$video_length,$video_width,$video_height);
 
+               require_code('related_content');
+               save_related_content('video', strval($id));
+
                if (($validated==1) || (!addon_installed('unvalidated')))
                {
                        if ((has_actual_page_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'galleries')) && (has_category_access($GLOBALS['FORUM_DRIVER']->get_guest_id(),'galleries',$cat)))
@@ -1579,6 +1601,9 @@ class Module_cms_galleries_alt extends standard_aed_module
                }
 
                edit_video($id,$title,$cat,$comments,$url,$thumb_url,$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,$video_length,$video_width,$video_height,post_param('meta_keywords',''),post_param('meta_description',''));
+
+               require_code('related_content');
+               save_related_content('video', strval($id));
        }
 
        /**
@@ -1595,6 +1620,9 @@ class Module_cms_galleries_alt extends standard_aed_module
                $this->donext_type=post_param('cat');
 
                delete_video($id,$delete_status=='2');
+
+               require_code('related_content');
+               delete_related_content('video', strval($id));
        }
 
        /**
@@ -1699,6 +1727,10 @@ class Module_cms_galleries_cat extends standard_aed_module
                        $fields->attach(form_input_tree_list(do_lang_tempcode('PARENT'),do_lang_tempcode('DESCRIPTION_PARENT'),'parent_id',NULL,'choose_gallery',array('filter'=>'only_conventional_galleries','purity'=>true),true,$parent_id));
                $fields->attach(form_input_various_ticks(array(array(do_lang_tempcode('ACCEPT_IMAGES'),'accept_images',$accept_images==1,do_lang_tempcode('DESCRIPTION_ACCEPT_IMAGES')),array(do_lang_tempcode('ACCEPT_VIDEOS'),'accept_videos',$accept_videos==1,do_lang_tempcode('DESCRIPTION_ACCEPT_VIDEOS'))),new ocp_tempcode(),NULL,do_lang_tempcode('ACCEPTED_MEDIA_TYPES')));
                $fields->attach(form_input_tick(do_lang_tempcode('FLOW_MODE_INTERFACE'),do_lang_tempcode('DESCRIPTION_FLOW_MODE_INTERFACE'),'flow_mode_interface',$flow_mode_interface==1));
+
+               require_code('related_content');
+               $fields->attach(form_input_related_content('gallery', ($name == '') ? null : $name));
+
                $fields->attach(do_template('FORM_SCREEN_FIELD_SPACER',array('SECTION_HIDDEN'=>($rep_image=='') && ($teaser=='') && ($is_member_synched==0),'TITLE'=>do_lang_tempcode('ADVANCED'))));
                $fields->attach(form_input_upload(do_lang_tempcode('REPRESENTATIVE_IMAGE'),do_lang_tempcode('DESCRIPTION_REPRESENTATIVE_IMAGE_GALLERY'),'rep_image',false,$rep_image,NULL,true,str_replace(' ','',get_option('valid_images'))));
 
@@ -1815,6 +1847,9 @@ class Module_cms_galleries_cat extends standard_aed_module
                add_gallery($name,$fullname,$description,$teaser,$notes,$parent_id,$accept_images,$accept_videos,$is_member_synched,$flow_mode_interface,$url,$watermark_top_left[0],$watermark_top_right[0],$watermark_bottom_left[0],$watermark_bottom_right[0],$allow_rating,$allow_comments,false,time(),$g_owner);
                $this->set_permissions($name);
 
+               require_code('related_content');
+               save_related_content('gallery', $name);
+
                return $name;
        }
 
@@ -1865,6 +1900,9 @@ class Module_cms_galleries_cat extends standard_aed_module
 
                edit_gallery($id,$name,post_param('fullname'),post_param('description',STRING_MAGIC_NULL),post_param('teaser',STRING_MAGIC_NULL),post_param('notes',STRING_MAGIC_NULL),$parent_id,$accept_images,$accept_videos,$is_member_synched,$flow_mode_interface,$url,$watermark_top_left[0],$watermark_top_right[0],$watermark_bottom_left[0],$watermark_bottom_right[0],post_param('meta_keywords',STRING_MAGIC_NULL),post_param('meta_description',STRING_MAGIC_NULL),$allow_rating,$allow_comments,$g_owner);
 
+               require_code('related_content');
+               save_related_content('gallery', $name);
+
                $this->new_id=$name;
 
                if (!fractional_edit())
@@ -1892,6 +1930,9 @@ class Module_cms_galleries_cat extends standard_aed_module
        function delete_actualisation($id)
        {
                delete_gallery($id);
+
+               require_code('related_content');
+               delete_related_content('gallery', $id);
        }
 
        /**
diff --git a/cms/pages/modules/cms_blogs.php b/cms/pages/modules_custom/cms_blogs.php
index 247938ce..edd0dd19 100644
--- a/cms/pages/modules/cms_blogs.php
+++ b/cms/pages/modules_custom/cms_blogs.php
@@ -191,7 +191,7 @@ class Module_cms_blogs extends standard_aed_module
         * @param  ?array                               Scheduled go-live time (NULL: N/A)
         * @return array                                A tuple of lots of info (fields, hidden fields, trailing fields)
         */
-       function get_form_fields($main_news_category=NULL,$news_category=NULL,$title='',$news='',$author='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$send_trackbacks=1,$notes='',$image='',$scheduled=NULL)
+       function get_form_fields($id = null, $main_news_category=NULL,$news_category=NULL,$title='',$news='',$author='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$send_trackbacks=1,$notes='',$image='',$scheduled=NULL)
        {
                list($allow_rating,$allow_comments,$allow_trackbacks)=$this->choose_feedback_fields_statistically($allow_rating,$allow_comments,$allow_trackbacks);
 
@@ -237,6 +237,10 @@ class Module_cms_blogs extends standard_aed_module
                if (has_some_cat_specific_permission(get_member(),'bypass_validation_'.$this->permissions_require.'range_content','cms_news',$this->permissions_cat_require))
                        if (addon_installed('unvalidated'))
                                $fields2->attach(form_input_tick(do_lang_tempcode('VALIDATED'),do_lang_tempcode('DESCRIPTION_VALIDATED'),'validated',$validated==1));
+
+               require_code('related_content');
+               $fields2->attach(form_input_related_content('news', strval($id)));
+
                if ($cats1->is_empty()) warn_exit(do_lang_tempcode('NO_CATEGORIES'));
                if (addon_installed('authors'))
                {
@@ -334,7 +338,7 @@ class Module_cms_blogs extends standard_aed_module
                        $scheduled=NULL;
                }
 
-               list($fields,$hidden,,,,,$fields2)=$this->get_form_fields($cat,$categories,get_translated_text($myrow['title']),get_translated_text($myrow['news']),$myrow['author'],$myrow['validated'],$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],0,$myrow['notes'],$myrow['news_image'],$scheduled);
+               list($fields,$hidden,,,,,$fields2)=$this->get_form_fields($id, $cat,$categories,get_translated_text($myrow['title']),get_translated_text($myrow['news']),$myrow['author'],$myrow['validated'],$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],0,$myrow['notes'],$myrow['news_image'],$scheduled);
 
                return array($fields,$hidden,new ocp_tempcode(),'',false,get_translated_text($myrow['news_article']),$fields2,get_translated_tempcode($myrow['news_article']));
        }
@@ -391,6 +395,9 @@ class Module_cms_blogs extends standard_aed_module
                $time=$add_time;
                $id=add_news($title,$news,$author,$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,$news_article,$main_news_category,$news_category,$time,NULL,0,NULL,NULL,$url);
 
+               require_code('related_content');
+               save_related_content('news', strval($id));
+
                $main_news_category=$GLOBALS['SITE_DB']->query_value('news','news_category',array('id'=>$id));
                $this->donext_type=$main_news_category;
 
@@ -508,6 +515,9 @@ class Module_cms_blogs extends standard_aed_module
                }
 
                edit_news(intval($id),$title,post_param('news',STRING_MAGIC_NULL),post_param('author',STRING_MAGIC_NULL),$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,$news_article,$main_news_category,$news_category,post_param('meta_keywords',STRING_MAGIC_NULL),post_param('meta_description',STRING_MAGIC_NULL),$url,$add_time);
+
+               require_code('related_content');
+               save_related_content('news', strval($id));
        }
 
        /**
@@ -520,6 +530,9 @@ class Module_cms_blogs extends standard_aed_module
                $id=intval($_id);
 
                delete_news($id);
+
+               require_code('related_content');
+               delete_related_content('news', strval($id));
        }
 
        /**
diff --git a/cms/pages/modules/cms_news.php b/cms/pages/modules_custom/cms_news.php
index 7f60f8b0..3b3f2201 100644
--- a/cms/pages/modules/cms_news.php
+++ b/cms/pages/modules_custom/cms_news.php
@@ -221,7 +221,7 @@ class Module_cms_news extends standard_aed_module
         * @param  ?array                               Scheduled go-live time (NULL: N/A)
         * @return array                                A tuple of lots of info (fields, hidden fields, trailing fields, tabindex for posting form)
         */
-       function get_form_fields($main_news_category=NULL,$news_category=NULL,$title='',$news='',$author='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$send_trackbacks=1,$notes='',$image='',$scheduled=NULL)
+       function get_form_fields($id = null, $main_news_category=NULL,$news_category=NULL,$title='',$news='',$author='',$validated=1,$allow_rating=NULL,$allow_comments=NULL,$allow_trackbacks=NULL,$send_trackbacks=1,$notes='',$image='',$scheduled=NULL)
        {
                list($allow_rating,$allow_comments,$allow_trackbacks)=$this->choose_feedback_fields_statistically($allow_rating,$allow_comments,$allow_trackbacks);
 
@@ -294,6 +294,9 @@ class Module_cms_news extends standard_aed_module
                        if (addon_installed('unvalidated'))
                                $fields2->attach(form_input_tick(do_lang_tempcode('VALIDATED'),do_lang_tempcode('DESCRIPTION_VALIDATED'),'validated',$validated==1));
 
+               require_code('related_content');
+               $fields2->attach(form_input_related_content('news', strval($id)));
+
                $fields2->attach(do_template('FORM_SCREEN_FIELD_SPACER',array('SECTION_HIDDEN'=>$news=='' && $image=='' && (is_null($scheduled)) && (is_null($news_category) || $news_category==array()),'TITLE'=>do_lang_tempcode('ADVANCED'))));
                $fields2->attach(form_input_text_comcode(do_lang_tempcode('NEWS_SUMMARY'),do_lang_tempcode('DESCRIPTION_NEWS_SUMMARY'),'news',$news,false));
                $fields2->attach(form_input_multi_list(do_lang_tempcode('SECONDARY_CATEGORIES'),do_lang_tempcode('DESCRIPTION_SECONDARY_CATEGORIES'),'news_category',$cats2));
@@ -376,7 +379,7 @@ class Module_cms_news extends standard_aed_module
                        $scheduled=NULL;
                }
 
-               list($fields,$hidden,,,,,$fields2)=$this->get_form_fields($cat,$categories,get_translated_text($myrow['title']),get_translated_text($myrow['news']),$myrow['author'],$myrow['validated'],$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],0,$myrow['notes'],$myrow['news_image'],$scheduled);
+               list($fields,$hidden,,,,,$fields2)=$this->get_form_fields($id, $cat,$categories,get_translated_text($myrow['title']),get_translated_text($myrow['news']),$myrow['author'],$myrow['validated'],$myrow['allow_rating'],$myrow['allow_comments'],$myrow['allow_trackbacks'],0,$myrow['notes'],$myrow['news_image'],$scheduled);
 
                return array($fields,$hidden,new ocp_tempcode(),'',false,get_translated_text($myrow['news_article']),$fields2,get_translated_tempcode($myrow['news_article']));
        }
@@ -433,6 +436,9 @@ class Module_cms_news extends standard_aed_module
                $time=$add_time;
                $id=add_news($title,$news,$author,$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,$news_article,$main_news_category,$news_category,$time,NULL,0,NULL,NULL,$url);
 
+               require_code('related_content');
+               save_related_content('news', strval($id));
+
                $main_news_category=$GLOBALS['SITE_DB']->query_value('news','news_category',array('id'=>$id));
                $this->donext_type=$main_news_category;
 
@@ -552,6 +558,9 @@ class Module_cms_news extends standard_aed_module
                }
 
                edit_news($id,$title,post_param('news',STRING_MAGIC_NULL),post_param('author',STRING_MAGIC_NULL),$validated,$allow_rating,$allow_comments,$allow_trackbacks,$notes,$news_article,$main_news_category,$news_category,post_param('meta_keywords',STRING_MAGIC_NULL),post_param('meta_description',STRING_MAGIC_NULL),$url,$add_time);
+
+               require_code('related_content');
+               save_related_content('news', strval($id));
        }
 
        /**
@@ -564,6 +573,9 @@ class Module_cms_news extends standard_aed_module
                $id=intval($_id);
 
                delete_news($id);
+
+               require_code('related_content');
+               delete_related_content('news', strval($id));
        }
 
        /**
diff --git a/data_custom/related_content_search.php b/data_custom/related_content_search.php
new file mode 100644
index 00000000..cc2b9273
--- /dev/null
+++ b/data_custom/related_content_search.php
@@ -0,0 +1,39 @@
+<?php
+
+// Find ocPortal base directory, and chdir into it
+global $FILE_BASE,$RELATIVE_PATH;
+$FILE_BASE=(strpos(__FILE__,'./')===false)?__FILE__:realpath(__FILE__);
+if (substr($FILE_BASE,-4)=='.php')
+{
+	$a=strrpos($FILE_BASE,'/');
+	if ($a===false) $a=0;
+		$b=strrpos($FILE_BASE,'\\');
+	if ($b===false) $b=0;
+		$FILE_BASE=dirname($FILE_BASE);
+}
+if (!file_exists($FILE_BASE.'/sources/global.php'))
+{
+	$a=strrpos($FILE_BASE,'/');
+	if ($a===false) $a=0;
+		$b=strrpos($FILE_BASE,'\\');
+	if ($b===false) $b=0;
+		$RELATIVE_PATH=basename($FILE_BASE);
+	$FILE_BASE=dirname($FILE_BASE);
+} else
+{
+	$RELATIVE_PATH='';
+}
+@chdir($FILE_BASE);
+
+global $NON_PAGE_SCRIPT;
+$NON_PAGE_SCRIPT=1;
+global $FORCE_INVISIBLE_GUEST;
+$FORCE_INVISIBLE_GUEST=0;
+global $KNOWN_UTF8;
+$KNOWN_UTF8=true;
+if (!file_exists($FILE_BASE.'/sources/global.php')) exit('<!DOCTYPE html>'.chr(10).'<html lang="EN"><head><title>Critical startup error</title></head><body><h1>ocPortal startup error</h1><p>The second most basic ocPortal startup file, sources/global.php, could not be located. This is almost always due to an incomplete upload of the ocPortal system, so please check all files are uploaded correctly.</p><p>Once all ocPortal files are in place, ocPortal must actually be installed by running the installer. You must be seeing this message either because your system has become corrupt since installation, or because you have uploaded some but not all files from our manual installer package: the quick installer is easier, so you might consider using that instead.</p><p>ocProducts maintains full documentation for all procedures and tools, especially those for installation. These may be found on the <a href="http://ocportal.com">ocPortal website</a>. If you are unable to easily solve this problem, we may be contacted from our website and can help resolve it for you.</p><hr /><p style="font-size: 0.8em">ocPortal is a website engine created by ocProducts.</p></body></html>'); require($FILE_BASE.'/sources/global.php');
+
+require_code('related_content');
+related_content_search_script();
+
+
diff --git a/forum/pages/modules_custom/topics.php b/forum/pages/modules_custom/topics.php
index 6cfb43c9..7662bcd0 100644
--- a/forum/pages/modules_custom/topics.php
+++ b/forum/pages/modules_custom/topics.php
@@ -1445,6 +1445,11 @@ class Module_topics
 			$text->attach(paragraph(do_lang_tempcode('WILL_NEED_VALIDATING')));
 		}
 
+		if (!$private_topic) {
+			require_code('related_content');
+			$specialisation2->attach(form_input_related_content('topic', ''));
+		}
+
 		// Awards?
 		if (addon_installed('awards'))
 		{
@@ -1970,6 +1975,10 @@ class Module_topics
 			} else // New topic
 			{
 				$topic_id=ocf_make_topic($forum_id,post_param('description',''),post_param('emoticon',''),$topic_validated,post_param_integer('open',0),post_param_integer('pinned',0),$sunk,post_param_integer('cascading',0));
+
+				require_code('related_content');
+				save_related_content('topic', strval($topic_id));
+
 				$_title=get_screen_title('ADD_TOPIC');
 
 				if (addon_installed('awards'))
@@ -2885,6 +2894,11 @@ END;
 		$fields->attach(form_input_various_ticks($options,''));
 		if (count($moderation_options)!=0) $fields->attach(form_input_various_ticks($moderation_options,'',NULL,do_lang_tempcode('MODERATION_OPTIONS')));
 
+		if (!$private_topic) {
+			require_code('related_content');
+			$fields->attach(form_input_related_content('topic', strval($topic_id)));
+		}
+
 		require_code('fields');
 		if (has_tied_catalogue('topic'))
 		{
@@ -2923,6 +2937,13 @@ END;
 
 		ocf_edit_topic($topic_id,post_param('description',STRING_MAGIC_NULL),post_param('emoticon',STRING_MAGIC_NULL),$validated,$open,$pinned,$sunk,$cascading,post_param('reason',STRING_MAGIC_NULL),$title);
 
+		$forum_id = $GLOBALS['FORUM_DB']->query_value('f_topics', 't_forum_id', array('id' => $topic_id));
+
+		if ($forum_id !== null) {
+			require_code('related_content');
+			save_related_content('topic', strval($topic_id));
+		}
+
 		require_code('fields');
 		if (has_tied_catalogue('topic'))
 		{
@@ -2996,6 +3017,9 @@ END;
 		require_code('ocf_topics_action2');
 		$forum_id=ocf_delete_topic($topic_id,post_param('reason'),$post_target_topic_id);
 
+		require_code('related_content');
+		delete_related_content('topic', strval($topic_id));
+
 		require_code('fields');
 		if (has_tied_catalogue('topic'))
 		{
diff --git a/sources_custom/hooks/systems/symbols/FIRST_IMAGE_EXTRACTOR.php b/sources_custom/hooks/systems/symbols/FIRST_IMAGE_EXTRACTOR.php
new file mode 100644
index 00000000..0ceac5eb
--- /dev/null
+++ b/sources_custom/hooks/systems/symbols/FIRST_IMAGE_EXTRACTOR.php
@@ -0,0 +1,10 @@
+<?php
+
+class Hook_symbol_FIRST_IMAGE_EXTRACTOR
+{
+	function run($param)
+	{
+		require_code('images');
+		return first_image_extractor($param);
+	}
+}
diff --git a/sources_custom/images.php b/sources_custom/images.php
new file mode 100644
index 00000000..54b633fb
--- /dev/null
+++ b/sources_custom/images.php
@@ -0,0 +1,28 @@
+<?php
+
+function first_image_extractor($image_scan_sources)
+{
+	foreach ($image_scan_sources as $x) {
+		if (is_object($x)) {
+			$x = $x->evaluate();
+		}
+
+		if (trim($x) == '') {
+			continue;
+		}
+
+		if (looks_like_url($x)) {
+			return $x;
+		}
+		$matches = array();
+		if (preg_match('#<img[^<>]*\ssrc="([^"]*)"#', $x, $matches) != 0) {
+			$x = $matches[1];
+
+			if (strpos($x, 'emoticon') === false) {
+				return $x;
+			}
+		}
+	}
+
+	return '';
+}
diff --git a/sources_custom/miniblocks/related_content.php b/sources_custom/miniblocks/related_content.php
new file mode 100644
index 00000000..88a53252
--- /dev/null
+++ b/sources_custom/miniblocks/related_content.php
@@ -0,0 +1,42 @@
+<?php
+
+require_code('related_content');
+
+$content_type = $map['content_type'];
+$id = $map['content_id'];
+
+$include_reverse = !empty($map['include_reverse']);
+
+$allow_fallback = (!isset($map['allow_fallback']) || $map['allow_fallback'] == '1');
+
+$rows = load_related_content($content_type, $id, $include_reverse, true, RELATED_CONTENT__DEFINED);
+
+if (empty($rows)) {
+	// Fallback
+	if (!empty($map['timestamp'])) {
+		$timestamp = intval($map['timestamp']);
+		if ($timestamp > 1632766716/*HACKHACK*/) {
+			$rows = load_related_content($content_type, $id, $include_reverse, true, RELATED_CONTENT__LATEST);
+		}
+	}
+}
+
+$all_content_types = get_content_type_labels();
+
+$content = array();
+foreach ($rows as $row) {
+	$content[] = array(
+		'URL' => $row['url'],
+		'LABEL' => $row['label'],
+		'CONTENT' => $row['content'],
+		'CONTENT_TYPE' => $row['content_type'],
+		'SUBMITTER' => strval($row['submitter']),
+		'ADD_DATE' => strval($row['add_date']),
+		'CONTENT_TYPE_LABEL' => $all_content_types[$row['content_type']],
+		'REP_IMAGE' => $row['rep_image'],
+		'IMAGE' => $row['image'],
+	);
+}
+
+$out = do_template('RELATED_CONTENT', array('CONTENT' => $content));
+$out->evaluate_echo();
diff --git a/sources_custom/related_content.php b/sources_custom/related_content.php
new file mode 100644
index 00000000..1a07f0ea
--- /dev/null
+++ b/sources_custom/related_content.php
@@ -0,0 +1,490 @@
+<?php
+
+function init__related_content()
+{
+	define('MAX_WITH_REVERSE_ASSOCIATIONS', 8);
+
+	define('RELATED_CONTENT__DEFINED', 1);
+	define('RELATED_CONTENT__LATEST', 2);
+}
+
+function install_related_content_table()
+{
+	$GLOBALS['SITE_DB']->create_table('related_content', array(
+		'from_content_type' => '*ID_TEXT',
+		'from_content_id' => '*ID_TEXT',
+		'to_content_type' => '*ID_TEXT',
+		'to_content_id' => '*ID_TEXT',
+	));
+
+	$GLOBALS['SITE_DB']->create_index('related_content','search_from',array('from_content_type', 'from_content_id'));
+	$GLOBALS['SITE_DB']->create_index('related_content','search_to',array('to_content_type', 'to_content_id'));
+}
+
+function has_related_content_spec_access()
+{
+	if ($GLOBALS['FORUM_DRIVER']->is_staff(get_member())) {
+		return true;
+	}
+
+	$groups = $GLOBALS['FORUM_DRIVER']->get_members_groups(get_member());
+	if (in_array(38, $groups)) { // Contributor
+		return true;
+	}
+	if (in_array(42, $groups)) { // Staff writer
+		return true;
+	}
+
+	return false;
+}
+
+function form_input_related_content($content_type, $id = null)
+{
+	if (!has_related_content_spec_access()) {
+		return new ocp_tempcode();
+	}
+
+	require_code('form_templates');
+
+	if ($id !== null) {
+		$rows = load_related_content($content_type, $id, false, false);
+
+		$content = array();
+		foreach ($rows as $row) {
+			$content[] = array(
+				'CONTENT_TYPE' => $row['content_type'],
+				'CONTENT_ID' => $row['content_id'],
+				'LABEL' => $row['label'],
+			);
+		}
+	} else {
+		$content = array();
+	}
+
+	$input = do_template('RELATED_CONTENT_INPUT', array('RELATED_CONTENT' => $content));
+
+	return _form_input('related_content[]', 'Related content', 'Select other content to relate to this. You can select recent content, or type to do a search and select content from the results.', $input, false);
+}
+
+function get_content_type_labels_plural()
+{
+	return array(
+		'news' => 'News Articles',
+		'gallery' => 'Galleries',
+		'image' => 'Gallery Images',
+		'video' => 'Gallery Videos',
+		'topic' => 'Forum Topics',
+	);
+}
+
+function get_content_type_labels()
+{
+	return array(
+		'news' => 'News Article',
+		'gallery' => 'Gallery',
+		'image' => 'Gallery Image',
+		'video' => 'Gallery Video',
+		'topic' => 'Forum Topic',
+	);
+}
+
+function related_content_search_script()
+{
+	require_code('character_sets');
+
+	header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
+	header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
+
+	header('Content-type:  application/json; charset='.get_charset());
+
+	$search = get_param('term', '');
+	if ($search == '') {
+		$search = null;
+	}
+
+	$max = 20;
+	$start = 0;
+
+	if (get_param('_type') == 'query_append') {
+		$start = (get_param_integer('page') - 1) * $max;
+	}
+
+	$all_content_types = get_content_type_labels_plural();
+
+	$content_groups = array();
+	foreach ($all_content_types as $content_type => $group_label) {
+		if ($search === null) {
+			$group_label = 'Recent ' . $group_label;
+		} else {
+			$group_label = 'Search Results from ' . $group_label;
+		}
+		$results = find_matching_content($content_type, $max, $start, $search, null, null, true, true);
+		if (!empty($results)) {
+			$_results = array();
+			foreach ($results as $i => $result) {
+				$result['text'] = convert_to_internal_encoding($result['text'], get_charset(), 'utf-8');
+				$_results[] = array(
+					'id' => $result['id'],
+					'text' => $result['text'],
+					'image' => $result['image'],
+				);
+			}
+
+			$content_groups[] = array(
+				'text' => $group_label,
+				'children' => $_results,
+			);
+		}
+	}
+
+	echo json_encode($content_groups, defined('JSON_INVALID_UTF8_SUBSTITUTE') ? JSON_INVALID_UTF8_SUBSTITUTE : 0);
+}
+
+function load_related_content($content_type, $id, $include_reverse = false, $consider_validation = true, $mode = 1)
+{
+	$rows = array();
+
+	if (($id === null) || ($mode == RELATED_CONTENT__LATEST)) {
+		$rows_filtered = find_matching_content($content_type, 4, 0, null, null, $id, true, true, true);
+	} else {
+		$_rows = $GLOBALS['SITE_DB']->query_select('related_content', array('to_content_type AS content_type', 'to_content_id AS content_id'), array('from_content_type' => $content_type, 'from_content_id' => $id));
+		foreach ($_rows as $row) {
+			$key = $row['content_type'] . ':' . $row['content_id'];
+			$rows[$key] = $row;
+		}
+
+		if ($include_reverse) {
+			if (count($rows) < MAX_WITH_REVERSE_ASSOCIATIONS) {
+				$max = MAX_WITH_REVERSE_ASSOCIATIONS - count($rows);
+				$_rows = $GLOBALS['SITE_DB']->query_select('related_content', array('from_content_type AS content_type', 'from_content_id AS content_id'), array('to_content_type' => $content_type, 'to_content_id' => $id), '', $max);
+				foreach ($_rows as $row) {
+					$key = $row['content_type'] . ':' . $row['content_id'];
+					if (!array_key_exists($key, $rows)) {
+						$rows[$key] = $row;
+					}
+				}
+			}
+		}
+
+		$rows_filtered = array();
+
+		foreach ($rows as $row) {
+			$details = find_content_details($row['content_type'], $row['content_id'], $consider_validation, true, true);
+			if ($details !== null) {
+				$row += $details;
+				$rows_filtered[] = $row;
+			}
+		}
+	}
+
+	global $M_SORT_KEY;
+	$M_SORT_KEY = '!add_date';
+	usort($rows_filtered, 'multi_sort');
+
+	return $rows_filtered;
+}
+
+function delete_related_content($content_type, $id)
+{
+	$GLOBALS['SITE_DB']->query_delete('related_content', array('from_content_type' => $content_type, 'from_content_id' => $id));
+}
+
+function save_related_content($content_type, $id)
+{
+	if (!has_related_content_spec_access()) {
+		return;
+	}
+
+	$to = array();
+	if (isset($_POST['related_content'])) {
+		foreach ($_POST['related_content'] as $r) {
+			if (strpos($r, ':') !== false) {
+				list($to_content_type, $to_content_id) = explode(':', $r, 2);
+				$to[] = array(
+					'to_content_type' => $to_content_type,
+					'to_content_id' => $to_content_id,
+				);
+			}
+		}
+	}
+	_save_related_content($content_type, $id, $to);
+}
+
+function _save_related_content($content_type, $id, $to)
+{
+	$GLOBALS['SITE_DB']->query_delete('related_content', array('from_content_type' => $content_type, 'from_content_id' => $id));
+	foreach ($to as $row) {
+		$GLOBALS['SITE_DB']->query_insert('related_content', $row + array('from_content_type' => $content_type, 'from_content_id' => $id));
+	}
+}
+
+function find_content_details($content_type, $id, $consider_validation = true)
+{
+	$results = find_matching_content($content_type, 1, 0, null, $id, null, $consider_validation, true, true);
+	if (!empty($results)) {
+		return $results[0];
+	}
+	return null;
+}
+
+function find_matching_content($content_type, $limit, $start = 0, $search = null, $id_search = null, $id_skip = null, $consider_validation = true, $get_content_field = false, $get_url = false)
+{
+	$db = $GLOBALS['SITE_DB'];
+	$prefix = $db->get_table_prefix();
+	$lang_fields = null;
+	$where = null;
+	$order_by = null;
+
+	$url = null;
+	$content = '';
+
+	switch ($content_type) {
+		case 'news':
+			$select = 'r.id,r.title,r.date_and_time,r.submitter,r.date_and_time AS add_date';
+			if ($get_content_field) {
+				$select .= ',r.news_article AS content';
+			}
+			$query = "SELECT " . $select . " FROM {$prefix}news r";
+			if ($consider_validation) {
+				$where = "WHERE validated=1";
+			} else {
+				$where = "WHERE 1=1";
+			}
+			if ($search !== null) {
+				$query .= " JOIN {$prefix}translate t ON t.id=r.title";
+				$where .= " AND (MATCH(t.text_original) AGAINST('" . addslashes($search) . "')";
+				if (preg_match('#^\d+$#', $search) != 0) {
+					$where .= ' OR r.id=' . $search;
+				}
+				$where .= ')';
+			} else {
+				$order_by = "ORDER BY r.date_and_time DESC";
+			}
+			if ($id_search !== null) {
+				$where .= ' AND r.id=' . strval(intval($id_search));
+			}
+			if ($id_skip !== null) {
+				$where .= ' AND r.id<>' . strval(intval($id_skip));
+			}
+			$lang_fields = array('title' => 'SHORT_TRANS');
+
+			break;
+
+		case 'gallery':
+			$select = "r.name AS id,r.fullname AS title,r.add_date AS date_and_time,r.g_owner AS submitter,r.add_date AS add_date,rep_image AS rep_image,(SELECT url FROM {$prefix}images i WHERE i.cat=r.name ORDER BY add_date LIMIT 1) AS rep_image_2";
+			if ($get_content_field) {
+				$select .= ',r.description AS content';
+			}
+			$query = "SELECT " . $select . " FROM {$prefix}galleries r";
+			if ($search !== null) {
+				$query .= " JOIN {$prefix}translate t ON t.id=r.fullname";
+				$where = "WHERE (MATCH(t.text_original) AGAINST('" . addslashes($search) . "')";
+				if (preg_match('#^[^ ]+$#', $search) != 0) {
+					$where .= ' OR ' . db_string_equal_to('r.name', $search);
+				}
+				$where .= ')';
+			} else {
+				$order_by = "ORDER BY r.add_date DESC";
+			}
+			if ($id_search !== null) {
+				$where .= (($where === null) ? 'WHERE' : ' AND') . ' ' . db_string_equal_to('r.name', $id_search);
+			}
+			if ($id_skip !== null) {
+				$where .= (($where === null) ? 'WHERE' : ' AND') . ' ' . db_string_not_equal_to('r.name', $id_skip);
+			}
+			$lang_fields = array('fullname' => 'SHORT_TRANS');
+
+			break;
+
+		case 'image':
+			$select = 'r.id,r.title,r.add_date AS date_and_time,r.submitter,r.add_date AS add_date,url AS rep_image';
+			if ($get_content_field) {
+				$select .= ',r.comments AS content';
+			}
+			$query = "SELECT " . $select . " FROM {$prefix}images r";
+			if ($consider_validation) {
+				$where = "WHERE validated=1";
+			} else {
+				$where = "WHERE 1=1";
+			}
+			if ($search !== null) {
+				$query .= " JOIN {$prefix}translate t ON t.id=r.title";
+				$where .= " AND (MATCH(t.text_original) AGAINST('" . addslashes($search) . "')";
+				if (preg_match('#^\d+$#', $search) != 0) {
+					$where .= ' OR r.id=' . $search;
+				}
+				$where .= ')';
+			} else {
+				$order_by = "ORDER BY r.add_date DESC";
+			}
+			if ($id_search !== null) {
+				$where .= ' AND r.id=' . strval(intval($id_search));
+			}
+			if ($id_skip !== null) {
+				$where .= ' AND r.id<>' . strval(intval($id_skip));
+			}
+			$lang_fields = array('title' => 'SHORT_TRANS');
+
+			break;
+
+		case 'video':
+			$select = 'r.id,r.title,r.add_date AS date_and_time,r.submitter,r.add_date AS add_date,thumb_url AS rep_image';
+			if ($get_content_field) {
+				$select .= ',r.comments AS content';
+			}
+			$query = "SELECT " . $select . " FROM {$prefix}videos r";
+			if ($consider_validation) {
+				$where = "WHERE validated=1";
+			} else {
+				$where = "WHERE 1=1";
+			}
+			if ($search !== null) {
+				$query .= " JOIN {$prefix}translate t ON t.id=r.title";
+				$where .= " AND (MATCH(t.text_original) AGAINST('" . addslashes($search) . "')";
+				if (preg_match('#^\d+$#', $search) != 0) {
+					$where .= ' OR r.id=' . $search;
+				}
+				$where .= ')';
+			} else {
+				$order_by = "ORDER BY r.add_date DESC";
+			}
+			if ($id_search !== null) {
+				$where .= ' AND r.id=' . strval(intval($id_search));
+			}
+			if ($id_skip !== null) {
+				$where .= ' AND r.id<>' . strval(intval($id_skip));
+			}
+			$lang_fields = array('title' => 'SHORT_TRANS');
+
+			break;
+
+		case 'topic':
+			$db = $GLOBALS['FORUM_DB'];
+			$prefix = $db->get_table_prefix();
+			$cf = $GLOBALS['FORUM_DRIVER']->forum_id_from_name(get_option('comments_forum_name'));
+			$select = 'r.id,r.t_cache_first_title AS title,r.t_cache_first_time AS date_and_time,r.t_cache_first_member_id AS submitter,r.t_cache_first_time AS add_date';
+			if ($get_content_field) {
+				$select .= ',r.t_cache_first_post AS content';
+			}
+			$query = "SELECT " . $select . " FROM {$prefix}f_topics r";
+			$where = "WHERE ";
+			if ($consider_validation) {
+				$where .= "r.t_validated=1 AND ";
+			}
+			$where .= "r.t_forum_id IS NOT NULL AND r.t_forum_id<>" . strval($cf);
+			if ($search !== null) {
+				$where .= " AND (r.t_cache_first_title LIKE '%" . addslashes($search) . "%'";
+				if (preg_match('#^\d+$#', $search) != 0) {
+					$where .= ' OR r.id=' . $search;
+				}
+				$where .= ')';
+			} else {
+				$order_by = "ORDER BY r.t_cache_first_time DESC";
+			}
+			if ($id_search === null) {
+				$where .= ' AND t_description NOT LIKE \'Comment: #%\'';
+			} else {
+				$where .= ' AND r.id=' . strval(intval($id_search));
+			}
+			if ($id_skip !== null) {
+				$where .= ' AND r.id<>' . strval(intval($id_skip));
+			}
+
+			break;
+
+		default:
+			exit('Error: Unrecognised content type ' . $content_type);
+	}
+
+	if ($where !== null) {
+		$query .= ' ' . $where;
+	}
+	if ($order_by !== null) {
+		$query .= ' ' . $order_by;
+	}
+
+	$raw_results = $db->query($query, $limit, 0, false, true, $lang_fields);
+	$results = array();
+	foreach ($raw_results as $result) {
+		$id = is_integer($result['id']) ? strval($result['id']) : $result['id'];
+
+		switch ($content_type) {
+			case 'news':
+				if ($get_url) {
+					$url = build_url(array('page' => 'news', 'type' => 'view', 'id' => $id), 'site');
+				}
+
+				break;
+
+			case 'gallery':
+				if ($get_url) {
+					$url = build_url(array('page' => 'galleries', 'type' => 'misc', 'id' => $id), 'site');
+				}
+
+				break;
+
+			case 'image':
+				if ($get_url) {
+					$url = build_url(array('page' => 'galleries', 'type' => 'image', 'id' => $id), 'site');
+				}
+
+				break;
+
+			case 'video':
+				if ($get_url) {
+					$url = build_url(array('page' => 'galleries', 'type' => 'video', 'id' => $id), 'site');
+				}
+
+				break;
+
+			case 'topic':
+				if ($get_url) {
+					$url = build_url(array('page' => 'topicview', 'type' => 'misc', 'id' => $id), 'forum');
+				}
+
+				break;
+		}
+
+		$label = is_integer($result['title']) ? get_translated_text($result['title'], $db) : $result['title'];
+		if ((trim($label) != '') || ($id_search !== null)) {
+			$text = $label . ' -- ' . $content_type . ':' . $id;
+			if (isset($result['date_and_time'])) {
+				$text .= ' -- ' . get_timezoned_date($result['date_and_time']);
+			}
+
+			if ($get_content_field) {
+				$content = is_integer($result['content']) ? get_translated_tempcode($result['content'], $db) : $result['content'];
+			}
+
+			$rep_image = '';
+			if (!empty($result['rep_image'])) {
+				$rep_image = $result['rep_image'];
+			}
+			if (!empty($result['rep_image_2'])) {
+				$rep_image = $result['rep_image_2'];
+			}
+			if (($rep_image != '') && (url_is_local($rep_image))) {
+				$rep_image = get_custom_base_url() . '/' . $rep_image;
+			}
+
+			require_code('images');
+			$image_scan_sources = array($rep_image, $content, get_custom_base_url() . '/uploads/filedump/Generic-Article-Image-Related-Content.jpg');
+			$image = first_image_extractor($image_scan_sources);
+
+			$results[] = array(
+				'id' => $content_type . ':' . $id,
+				'text' => $text,
+				'label' => $label,
+				'content_type' => $content_type,
+				'content_id' => $id,
+				'content' => $content,
+				'add_date' => $result['add_date'],
+				'submitter' => $result['submitter'],
+				'url' => $url,
+				'rep_image' => $rep_image,
+				'image' => $image,
+			);
+		}
+	}
+	return $results;
+}
diff --git a/themes/default/templates_custom/RELATED_CONTENT.tpl b/themes/default/templates_custom/RELATED_CONTENT.tpl
new file mode 100644
index 00000000..24e4baeb
--- /dev/null
+++ b/themes/default/templates_custom/RELATED_CONTENT.tpl
@@ -0,0 +1,34 @@
+{+START,IF_NON_EMPTY,{CONTENT}}
+	<h2>Related content</h2>
+
+	<div class="fancy_news_boxes">
+		{+START,LOOP,CONTENT}
+			<div class="fancy_news_box related_content" style="background-image: url('{$THUMBNAIL;*,{IMAGE},447x298,,,,crop,both}')">
+				<div class="fancy_news_box_inner">
+					<a class="box_link" href="{URL*}"></a>
+					<div class="fancy_news_details">
+						<h2><a href="{URL*}">{+START,IF_EMPTY,{LABEL}}Untitled{+END}{LABEL*}</a></h2>
+
+						<ul>
+							<li>{CONTENT_TYPE_LABEL*}</li>
+
+							<li>{$MAKE_RELATIVE_DATE*,{ADD_DATE}} ago</li>
+
+							{+START,SET,author_details}
+								{+START,IF_NON_EMPTY,{$USERNAME*,{SUBMITTER}}}
+									{!BY_SIMPLE,<a rel="author" href="{$MEMBER_PROFILE_URL*,{SUBMITTER}}">{$USERNAME*,{SUBMITTER}}</a>}
+									{+START,INCLUDE,MEMBER_TOOLTIP}{+END}
+								{+END}
+							{+END}
+							{+START,IF_NON_EMPTY,{$GET,author_details}}
+								<li>
+									{$GET,author_details}
+								</li>
+							{+END}
+						</ul>
+					</div>
+				</div>
+			</div>
+		{+END}
+	</div>
+{+END}
diff --git a/themes/default/templates_custom/RELATED_CONTENT_INPUT.tpl b/themes/default/templates_custom/RELATED_CONTENT_INPUT.tpl
new file mode 100644
index 00000000..699fdcd7
--- /dev/null
+++ b/themes/default/templates_custom/RELATED_CONTENT_INPUT.tpl
@@ -0,0 +1,102 @@
+{$,Parser hint: .innerHTML okay}
+
+{$REQUIRE_CSS,select2}
+{$REQUIRE_JAVASCRIPT,javascript_jquery3}
+{$REQUIRE_JAVASCRIPT,javascript_select2}
+
+<select multiple="multiple" id="related_content" name="related_content[]" class="wide_field">
+	{+START,LOOP,RELATED_CONTENT}
+		<option selected="selected" value="{CONTENT_TYPE*}:{CONTENT_ID*}">{LABEL*}</option>
+	{+END}
+</select>
+
+<script>
+	function escapeHTML(str)
+	{
+		var p = document.createElement("p");
+		p.appendChild(document.createTextNode(str));
+		return p.innerHTML;
+	}
+
+	function getIconFor(id_path, float_dir, image)
+	{
+		var icon = '';
+		/*if (id_path.indexOf('news:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="{$IMG*,bigicons/news}" alt="News article" />';
+		}
+		else if (id_path.indexOf('gallery:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="{$IMG*,bigicons/galleries}" alt="Gallery" />';
+		}
+		else if (id_path.indexOf('image:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="{$IMG*,bigicons/view_this}" alt="Gallery image" />';
+		}
+		else if (id_path.indexOf('video:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="{$IMG*,bigicons/view_this}" alt="Gallery video" />';
+		}
+		else if (id_path.indexOf('topic:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="{$IMG*,bigicons/forums}" alt="Forum topic" />';
+		}*/
+
+		if (id_path.indexOf('news:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="' + image + '" alt="News article" />';
+		}
+		else if (id_path.indexOf('gallery:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="' + image + '" alt="Gallery" />';
+		}
+		else if (id_path.indexOf('image:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="' + image + '" alt="Gallery image" />';
+		}
+		else if (id_path.indexOf('video:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="' + image + '" alt="Gallery video" />';
+		}
+		else if (id_path.indexOf('topic:') == 0) {
+			icon = '<img class="' + float_dir + ' float_separation" width="24" src="' + image + '" alt="Forum topic" />';
+		}
+
+		return icon;
+	}
+
+	var $relatedContent = $("#related_content");
+	$relatedContent.select2({
+		ajax: {
+			url: '{$FIND_SCRIPT;/,related_content_search}' + keep_stub(true),
+			dataType: 'json',
+			processResults: function (data) {
+				return {
+					results: data,
+				};
+			}
+		},
+		templateResult: function(state) {
+			var parts = state.text.split(/ -- /, 3);
+			if (parts.length < 3) {
+				return state.text;
+			}
+
+			var icon = getIconFor(parts[1], 'left', state.image);
+
+			return $(
+				'<div title="' + escapeHTML(parts[1]) + '" class="float_surrounder vertical_alignment"> \
+					' + icon + ' \
+					<strong class="left">' + escapeHTML(parts[0]) + '</strong> \
+					<em class="right">' + escapeHTML(parts[2]) + '<\/em> \
+				</div>'
+			);
+		},
+		templateSelection: function(state) {
+			var parts = state.text.split(/ -- /, 3);
+			if (parts.length < 3) {
+				return state.text;
+			}
+
+			var icon = getIconFor(parts[1], 'right', state.image);
+
+			return $(
+				'<span title="' + escapeHTML(parts[1]) + '" class="vertical_alignment"> \
+					' + icon + ' \
+					<span>' + escapeHTML(parts[0]) + '</span> \
+				</span>'
+			);
+		},
+	});
+</script>
