<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Handling Embedded Files in Content</title><link>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content</link><description /><dc:language>en-US</dc:language><generator>14.0.0.586 14</generator><item><title>Handling Embedded Files in Content</title><link>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content</link><pubDate>Tue, 04 Aug 2020 21:53:26 GMT</pubDate><guid isPermaLink="false">072fc4d6-7fde-486a-aa1f-1dafdf3a302f</guid><dc:creator>Former Member</dc:creator><comments>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content#comments</comments><description>Current Revision posted to Developer Training by Former Member on 08/04/2020 21:53:26&lt;br /&gt;
&lt;p&gt;When creating [[Creating Custom Applications and Content|custom content types]] to the Verint Community platform and utilizing the platform editor within the UI managing that content (using the [[api-documentation:core_v2_editor script api|$core_v2_editor widget API]]), the editor will allow drag-and-drop and menu-based file uploading into the HTML content if that content supports embedded files.&lt;/p&gt;
&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2&gt;&lt;a id="Why_Would_I_Want_to_Enable_Embedded_Files" name="Why_Would_I_Want_to_Enable_Embedded_Files"&gt;&lt;/a&gt;Why Would I Want to Enable Embedded Files?&lt;/h2&gt;
&lt;p&gt;Embedded files are useful when authoring rich formatted content (HTML) to allow inclusion of images, videos, documents, and other media and files to provide more flexibility and interactivity.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id="Implementing_Support_for_Embedded_Files" name="Implementing_Support_for_Embedded_Files"&gt;&lt;/a&gt;Implementing Support for Embedded Files&lt;/h2&gt;
&lt;p&gt;To add support for embedded files within a [[Creating Custom Applications and Content|custom content type]], the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]] plugin type should also be implemented on your content type (which requires a reference to Telligent.Evolution.Core.dll). The &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; interface extends [[api-documentation:IContentType Plugin Type|IContentType]] to add members that enable the content type to interact with the Verint Community platform regarding embedded files.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Basic_Implementation" name="Basic_Implementation"&gt;&lt;/a&gt;Basic Implementation&lt;/h3&gt;
&lt;p&gt;Because an implementation of &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; is dependent on a custom content type which is out-of-scope for this topic, I&amp;#39;ll show examples from private messaging in the core platform.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll implement the &lt;code&gt;IFileEmbeddedContentType&lt;/code&gt;, which requires two members to be implemented. First, &lt;code&gt;CanAddFiles()&lt;/code&gt; identifies whether a user can add files to this content type:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public bool CanAddFiles(int userId)
{
	var user = Users.GetUser(userId);
	if (user == null || user.IsAnonymous)
		return false;

	return true;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;For private messaging, any non-anonymous user can embed files. For other applications, the ability to embed files may be dependent on the application or container/group the content is being created within. To detect the context of the request, the current page&amp;#39;s context can be retrieved by analyzing [[api-documentation:Url In-Process API Service|Url.CurrentContext]]&amp;nbsp;or using another mechanism for detecting the context of the &lt;code&gt;CanAddFiles()&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;CanAddFiles()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, file embedding functionality within the platform content editor will be enabled, otherwise, these functions will not be available.&lt;/p&gt;
&lt;p&gt;The only remaining requirement for an &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; implementation is the &lt;code&gt;SetController()&lt;/code&gt; method:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;private IFileEmbeddableContentTypeController _embeddedFileController;

public void SetController(IFileEmbeddableContentTypeController controller)
{
	_embeddedFileController = controller;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This method provides the plugin with a controller enabling the extraction and reassignment of embedded files within HTML content.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Using_the_Controller_to_Process_Files" name="Using_the_Controller_to_Process_Files"&gt;&lt;/a&gt;Using the Controller to Process Files&lt;/h3&gt;
&lt;p&gt;When files are uploaded through the editor, they&amp;#39;re placed in a temporary file store within the [[Centralized File Storage|Centralized File System]]. Files placed in this temporary location are only accessible to the user who uploaded them and are automatically deleted after 2 hours (by default) of inactivity. Content supporting embedded files should move this temporary content to a final storage location suitably secured for the content type. To implement the movement of files to a final storage location, [[Handling Events|events]] related to the creation or editing of content should be handled and the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|IFileEmbeddableContentTypeController]] provided to the plugin through the &lt;code&gt;SetController()&lt;/code&gt; method should be used.&lt;/p&gt;
&lt;p&gt;For private messaging, we attach handlers to the &lt;code&gt;BeforeCreate&lt;/code&gt; event (since private messages do not support editing, otherwise, we&amp;#39;d also want to handle edit-events). As with all [[Plugins|plugins]], we [[Handling Events|register the event handler]] in the [[api-documentation:IPlugin Plugin Type|Initialize() method of IPlugin]]:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public void Initialize()
{
    var conversationMessageApi = Telligent.Evolution.Extensibility.Apis.Get&amp;lt;Telligent.Evolution.Extensibility.Api.Version1.IConversationMessages&amp;gt;();
	
	conversationMessageApi.Events.BeforeCreate += ConversationMessage_BeforeCreate;
    
    // other event handlers used for this plugin
}

void ConversationMessage_BeforeCreate(ConversationMessageBeforeCreateEventArgs e)
{
    if (_embeddedFileController == null)
		return;

	var hasPermission = CanAddFiles(e.Author.Id.Value);
	var targetFileStore = Telligent.Evolution.Extensibility.Storage.Version1.CentralizedFileStorage.GetFileStore(&amp;quot;conversationfiles&amp;quot;);

	e.Body = _embeddedFileController.SaveFilesInHtml(e.Body, f =&amp;gt; {
		if (!hasPermission)
			_embeddedFileController.InvalidFile(f, &amp;quot;You do not have permission to add files to private messages&amp;quot;);

		string message;
		if (!IsFileValid(e.Author.Id.Value, f.FileName, f.ContentLength, true, out message))
			EmbeddedFileController.InvalidFile(f, message);

		using (var stream = f.OpenReadStream())
		{
			var targetFile = targetFileStore.AddFile(e.ConversationId.Value.ToString(&amp;quot;N&amp;quot;), f.FileName, stream, true);
			if (targetFile != null)
				return targetFile;
			else
				_embeddedFileController.InvalidFile(f, &amp;quot;An error occurred while saving an embedded file.&amp;quot;);
		}

		return null;
	});
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;In the event handler, &lt;code&gt;ConversationMessage_BeforeCreate&lt;/code&gt;, we retrieve the target [[Centralized File Storage|CFS]] file store, &lt;code&gt;conversationfiles&lt;/code&gt;, using the [[api-documentation:CentralizedFileStorage In-Process API Service|CentralizedFileStorage API]]&amp;nbsp;-- this is where valid files will be moved. Then, we use the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|controller&amp;#39;s SaveFilesInHtml() method]] to process each file found within the body (&lt;code&gt;e.Body&lt;/code&gt;) of the private message. If we wanted to support embedding files in other properties of the message (in this case, there are none), we would call &lt;code&gt;SaveFilesInHtml()&lt;/code&gt; for each property.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFilesInHtml()&lt;/code&gt; calls the function defined by the last parameter to the method call for each temporary file in the provided content (in this case, &lt;code&gt;e.Body&lt;/code&gt;). For each file, we&amp;#39;ll verify the user has permission to save files and validate the file type (via the &lt;code&gt;IsFileValid()&lt;/code&gt; method which would be defined in this plugin as well but is out-of-scope for this discussion -- it reviews the file details and returns &lt;code&gt;false&lt;/code&gt; and sets the out &lt;code&gt;message&lt;/code&gt; parameter if a the file is invalid). If there is an issue with any file (one of the validation procedures fails), we call [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|InvalidFile() on the controller]]. This stops processing and reports the provided message back to the user. In production code (this sample is a simplification), we&amp;#39;d want to ensure that this text is [[Translating Plugin Text|properly translated]].&lt;/p&gt;
&lt;p&gt;After validating the file is valid, we&amp;#39;ll move it to the proper target file store by calling [[api-documentation:ICentralizedFileStorageProvider In-Process API Supplementary Type|AddFile() on the target CFS file store]]. This will return the resulting [[api-documentation:ICentralizedFile In-Process API Supplementary Type|ICentralizedFile]] which then must be provided back to the controller.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFileInHtml()&lt;/code&gt; returns the updated HTML with all temporary embedded files appropriately moved.&amp;nbsp;This value should be saved back to the content. For private messages, we set &lt;code&gt;e.Body&lt;/code&gt; to the new value and, because this is in the &lt;code&gt;BeforeCreate&lt;/code&gt; event, the updates will be committed normally (if we processed content in &lt;code&gt;AfterCreate&lt;/code&gt;, for example, the content would have already been committed and we&amp;#39;d need to explicitly save the updated value).&lt;/p&gt;
&lt;h3&gt;&lt;a id="UI_Considerations" name="UI_Considerations"&gt;&lt;/a&gt;UI Considerations&lt;/h3&gt;
&lt;p&gt;To ensure that the content editor is aware of the type of content being edited and to enable the editor to detect if file embedding is supported, it is important that when rendering the editor through [[api-documentation:core_v2_editor script api|$core_v2_editor.Render()]] in a [[Widgets|widget]], that the &lt;code&gt;ContentTypeId&lt;/code&gt; is specified. This identifier&amp;nbsp;should match the &lt;code&gt;ContentTypeId&lt;/code&gt; identified by the [[api-documentation:IContentType Plugin Type|IContentType]] implementation of the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]]. Without this option specified, the editor will not know what type of content is being edited and will never enable file embedding.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;

&lt;div style="font-size: 90%;"&gt;Tags: IFileEmbeddableContentType&lt;/div&gt;
</description></item><item><title>Handling Embedded Files in Content</title><link>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content/revision/4</link><pubDate>Tue, 04 Aug 2020 21:52:58 GMT</pubDate><guid isPermaLink="false">072fc4d6-7fde-486a-aa1f-1dafdf3a302f</guid><dc:creator>Former Member</dc:creator><comments>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content#comments</comments><description>Revision 4 posted to Developer Training by Former Member on 08/04/2020 21:52:58&lt;br /&gt;
&lt;p&gt;When creating [[Creating Custom Applications and Content|custom content types]] to the Verint Community platform and utilizing the platform editor within the UI managing that content (using the [[api-documentation:core_v2_editor script api|$core_v2_editor widget API]]), the editor will allow drag-and-drop and menu-based file uploading into the HTML content if that content supports embedded files.&lt;/p&gt;
&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2&gt;&lt;a id="Why_Would_I_Want_to_Enable_Embedded_Files" name="Why_Would_I_Want_to_Enable_Embedded_Files"&gt;&lt;/a&gt;Why Would I Want to Enable Embedded Files?&lt;/h2&gt;
&lt;p&gt;Embedded files are useful when authoring rich formatted content (HTML) to allow inclusion of images, videos, documents, and other media and files to provide more flexibility and interactivity.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id="Implementing_Support_for_Embedded_Files" name="Implementing_Support_for_Embedded_Files"&gt;&lt;/a&gt;Implementing Support for Embedded Files&lt;/h2&gt;
&lt;p&gt;To add support for embedded files within a [[Creating Custom Applications and Content|custom content type]], the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]] plugin type should also be implemented on your content type (which requires a reference to Telligent.Evolution.Core.dll). The &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; interface extends [[api-documentation:IContentType Plugin Type|IContentType]] to add members that enable the content type to interact with the Verint Community platform regarding embedded files.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Basic_Implementation" name="Basic_Implementation"&gt;&lt;/a&gt;Basic Implementation&lt;/h3&gt;
&lt;p&gt;Because an implementation of &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; is dependent on a custom content type which is out-of-scope for this topic, I&amp;#39;ll show examples from private messaging in the core platform.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll implement the &lt;code&gt;IFileEmbeddedContentType&lt;/code&gt;, which requires two members to be implemented. First, &lt;code&gt;CanAddFiles()&lt;/code&gt; identifies whether a user can add files to this content type:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public bool CanAddFiles(int userId)
{
	var user = Users.GetUser(userId);
	if (user == null || user.IsAnonymous)
		return false;

	return true;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;For private messaging, any non-anonymous user can embed files. For other applications, the ability to embed files may be dependent on the application or container/group the content is being created within. To detect the context of the request, the current page&amp;#39;s context can be retrieved by analyzing [[api-documentation:Url In-Process API Service|Url.CurrentContext]]&amp;nbsp;or using another mechanism for detecting the context of the &lt;code&gt;CanAddFiles()&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;CanAddFiles()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, file embedding functionality within the platform content editor will be enabled, otherwise, these functions will not be available.&lt;/p&gt;
&lt;p&gt;The only remaining requirement for an &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; implementation is the &lt;code&gt;SetController()&lt;/code&gt; method:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;private IFileEmbeddableContentTypeController _embeddedFileController;

public void SetController(IFileEmbeddableContentTypeController controller)
{
	_embeddedFileController = controller;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This method provides the plugin with a controller enabling the extraction and reassignment of embedded files within HTML content.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Using_the_Controller_to_Process_Files" name="Using_the_Controller_to_Process_Files"&gt;&lt;/a&gt;Using the Controller to Process Files&lt;/h3&gt;
&lt;p&gt;When files are uploaded through the editor, they&amp;#39;re placed in a temporary file store within the [[Centralized File Storage|Centralized File System]]. Files placed in this temporary location are only accessible to the user who uploaded them and are automatically deleted after 2 hours (by default) of inactivity. Content supporting embedded files should move this temporary content to a final storage location suitably secured for the content type. To implement the movement of files to a final storage location, [[Handling Events|events]] related to the creation or editing of content should be handled and the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|IFileEmbeddableContentTypeController]] provided to the plugin through the &lt;code&gt;SetController()&lt;/code&gt; method should be used.&lt;/p&gt;
&lt;p&gt;For private messaging, we attach handlers to the &lt;code&gt;BeforeCreate&lt;/code&gt; event (since private messages do not support editing, otherwise, we&amp;#39;d also want to handle edit-events). As with all [[Plugins|plugins]], we [[Handling Events|register the event handler]] in the [[api-documentation:IPlugin Plugin Type|Initialize() method of IPlugin]]:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public void Initialize()
{
    var conversationMessageApi = Telligent.Evolution.Extensibility.Apis.Get&amp;lt;Telligent.Evolution.Extensibility.Api.Version1.IConversationMessages&amp;gt;();
	
	conversationMessageApi.Events.BeforeCreate += ConversationMessage_BeforeCreate;
    
    // other event handlers used for this plugin
}

void ConversationMessage_BeforeCreate(ConversationMessageBeforeCreateEventArgs e)
{
    if (_embeddedFileController == null)
		return;

	var hasPermission = CanAddFiles(e.Author.Id.Value);
	var targetFileStore = Telligent.Evolution.Extensibility.Storage.Version1.CentralizedFileStorage.GetFileStore(&amp;quot;conversationfiles&amp;quot;);

	e.Body = _embeddedFileController.SaveFilesInHtml(e.Body, f =&amp;gt; {
		if (!hasPermission)
			_embeddedFileController.InvalidFile(f, &amp;quot;You do not have permission to add files to private messages&amp;quot;);

		string message;
		if (!IsFileValid(e.Author.Id.Value, f.FileName, f.ContentLength, true, out message))
			EmbeddedFileController.InvalidFile(f, message);

		using (var stream = f.OpenReadStream())
		{
			var targetFile = targetFileStore.AddFile(e.ConversationId.Value.ToString(&amp;quot;N&amp;quot;), f.FileName, stream, true);
			if (targetFile != null)
				return targetFile;
			else
				_embeddedFileController.InvalidFile(f, &amp;quot;An error occurred while saving an embedded file.&amp;quot;);
		}

		return null;
	});
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;In the event handler, &lt;code&gt;ConversationMessage_BeforeCreate&lt;/code&gt;, we retrieve the target [[Centralized File Storage|CFS]] file store, &lt;code&gt;conversationfiles&lt;/code&gt;, using the [[api-documentation:CentralizedFileStorage In-Process API Service|CentralizedFileStorage API]]&amp;nbsp;-- this is where valid files will be moved. Then, we use the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|controller&amp;#39;s SaveFilesInHtml() method]] to process each file found within the body (&lt;code&gt;e.Body&lt;/code&gt;) of the private message. If we wanted to support embedding files in other properties of the message (in this case, there are none), we would call &lt;code&gt;SaveFilesInHtml()&lt;/code&gt; for each property.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFilesInHtml()&lt;/code&gt; calls the function defined by the last parameter to the method call for each temporary file in the provided content (in this case, &lt;code&gt;e.Body&lt;/code&gt;). For each file, we&amp;#39;ll verify the user has permission to save files and validate the file type (via the &lt;code&gt;IsFileValid()&lt;/code&gt; method which would be defined in this plugin as well but is out-of-scope for this discussion -- it reviews the file details and returns &lt;code&gt;false&lt;/code&gt; and sets the out &lt;code&gt;message&lt;/code&gt; parameter if a the file is invalid). If there is an issue with any file (one of the validation procedures fails), we call [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|InvalidFile() on the controller]]. This stops processing and reports the provided message back to the user. In production code (this sample is a simplification), we&amp;#39;d want to ensure that this text is [[Translating Plugin Text|properly translated]].&lt;/p&gt;
&lt;p&gt;After validating the file is valid, we&amp;#39;ll move it to the proper target file store by calling [[api-documentation:ICentralizedFileStorageProvider In-Process API Supplementary Type|AddFile() on the target CFS file store]]. This will return the resulting [[api-documentation:ICentralizedFile In-Process API Supplementary Type|ICentralizedFile]] which then must be provided back to the controller.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFileInHtml()&lt;/code&gt; returns the updated HTML with all temporary embedded files appropriately moved.&amp;nbsp;This value should be saved back to the content. For private messages, we set &lt;code&gt;e.Body&lt;/code&gt; to the new value and, because this is in the &lt;code&gt;BeforeCreate&lt;/code&gt; event, the updates will be committed normally (if we processed content in &lt;code&gt;AfterCreate&lt;/code&gt;, for example, the content would have already been committed and we&amp;#39;d need to explicitly save the updated value).&lt;/p&gt;
&lt;h3&gt;&lt;a id="UI_Considerations" name="UI_Considerations"&gt;&lt;/a&gt;UI Considerations&lt;/h3&gt;
&lt;p&gt;To ensure that the content editor is aware of the type of content being edited and to enable the editor to detect if file embedding is supported, it is important that when rendering the editor through [[api-documentation:core_v2_editor script api|$core_v2_editor.Render()]] in a [[Widgets|widget]], that the &lt;code&gt;ContentTypeId&lt;/code&gt; is specified. This identifier&amp;nbsp;should match the &lt;code&gt;ContentTypeId&lt;/code&gt; identified by the [[api-documentation:IContentType Plugin Type|IContentType]] implementation of the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]]. Without this option specified, the editor will not know what type of content is being edited and will never enable file embedding.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item><item><title>Handling Embedded Files in Content</title><link>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content/revision/3</link><pubDate>Tue, 04 Aug 2020 21:52:28 GMT</pubDate><guid isPermaLink="false">072fc4d6-7fde-486a-aa1f-1dafdf3a302f</guid><dc:creator>Former Member</dc:creator><comments>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content#comments</comments><description>Revision 3 posted to Developer Training by Former Member on 08/04/2020 21:52:28&lt;br /&gt;
&lt;p&gt;When creating [[Creating Custom Applications and Content|custom content types]] to the Verint Community platform and utilizing the platform editor within the UI managing that content (using the [[api-documentation:core_v2_editor script api|$core_v2_editor widget API]]), the editor will allow drag-and-drop and menu-based file uploading into the HTML content if that content supports embedded files.&lt;/p&gt;
&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2&gt;&lt;a id="Why_Would_I_Want_to_Enable_Embedded_Files" name="Why_Would_I_Want_to_Enable_Embedded_Files"&gt;&lt;/a&gt;Why Would I Want to Enable Embedded Files?&lt;/h2&gt;
&lt;p&gt;Embedded files are useful when authoring rich formatted content (HTML) to allow inclusion of images, videos, documents, and other media and files to provide more flexibility and interactivity.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id="Implementing_Support_for_Embedded_Files" name="Implementing_Support_for_Embedded_Files"&gt;&lt;/a&gt;Implementing Support for Embedded Files&lt;/h2&gt;
&lt;p&gt;To add support for embedded files within a [[Creating Custom Applications and Content|custom content type]], the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]] plugin type should also be implemented on your content type (which requires a reference to Telligent.Evolution.Core.dll). The &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; interface extends [[api-documentation:IContentType Plugin Type|IContentType]] to add members that enable the content type to interact with the Verint Community platform regarding embedded files.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Basic_Implementation" name="Basic_Implementation"&gt;&lt;/a&gt;Basic Implementation&lt;/h3&gt;
&lt;p&gt;Because an implementation of &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; is dependent on a custom content type which is out-of-scope for this topic, I&amp;#39;ll show examples from private messaging in the core platform.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll implement the &lt;code&gt;IFileEmbeddedContentType&lt;/code&gt;, which requires two members to be implemented. First, &lt;code&gt;CanAddFiles()&lt;/code&gt; identifies whether a user can add files to this content type:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public bool CanAddFiles(int userId)
{
	var user = Users.GetUser(userId);
	if (user == null || user.IsAnonymous)
		return false;

	return true;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;For private messaging, any non-anonymous user can embed files. For other applications, the ability to embed files may be dependent on the application or container/group the content is being created within. To detect the context of the request, the current page&amp;#39;s context can be retrieved by analyzing [[api-documentation:Url In-Process API Service|Url.CurrentContext]]&amp;nbsp;or using another mechanism for detecting the context of the &lt;code&gt;CanAddFiles()&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;CanAddFiles()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, file embedding functionality within the platform content editor will be enabled, otherwise, these functions will not be available.&lt;/p&gt;
&lt;p&gt;The only remaining requirement for an &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; implementation is the &lt;code&gt;SetController()&lt;/code&gt; method:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;private IFileEmbeddableContentTypeController _embeddedFileController;

public void SetController(IFileEmbeddableContentTypeController controller)
{
	_embeddedFileController = controller;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This method provides the plugin with a controller enabling the extraction and reassignment of embedded files within HTML content.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Using_the_Controller_to_Process_Files" name="Using_the_Controller_to_Process_Files"&gt;&lt;/a&gt;Using the Controller to Process Files&lt;/h3&gt;
&lt;p&gt;When files are uploaded through the editor, they&amp;#39;re placed in a temporary file store within the [[Centralized File Storage|Centralized File System]]. Files placed in this temporary location are only accessible to the user who uploaded them and are automatically deleted after 2 hours (by default) of inactivity. Content supporting embedded files should move this temporary content to a final storage location suitably secured for the content type. To implement the movement of files to a final storage location, [[Handling Events|events]] related to the creation or editing of content should be handled and the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|IFileEmbeddableContentTypeController]] provided to the plugin through the &lt;code&gt;SetController()&lt;/code&gt; method should be used.&lt;/p&gt;
&lt;p&gt;For private messaging, we attach handlers to the &lt;code&gt;BeforeCreate&lt;/code&gt; event (since private messages do not support editing, otherwise, we&amp;#39;d also want to handle edit-events). As with all [[Plugins|plugins]], we [[Handling Events|register the event handler]] in the [[api-documentation:IPlugin Plugin Type|Initialize() method of IPlugin]]:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public void Initialize()
{
    var conversationMessageApi = Telligent.Evolution.Extensibility.Apis.Get&amp;lt;Telligent.Evolution.Extensibility.Api.Version1.IConversationMessages&amp;gt;();
	
	conversationMessageApi.Events.BeforeCreate += ConversationMessage_BeforeCreate;
    
    // other event handlers used for this plugin
}

void ConversationMessage_BeforeCreate(ConversationMessageBeforeCreateEventArgs e)
{
    if (_embeddedFileController == null)
		return;

	var hasPermission = CanAddFiles(e.Author.Id.Value);
	var targetFileStore = Telligent.Evolution.Extensibility.Storage.Version1.CentralizedFileStorage.GetFileStore(&amp;quot;conversationfiles&amp;quot;);

	e.Body = _embeddedFileController.SaveFilesInHtml(e.Body, f =&amp;gt; {
		if (!hasPermission)
			_embeddedFileController.InvalidFile(f, &amp;quot;You do not have permission to add files to private messages&amp;quot;);

		string message;
		if (!IsFileValid(e.Author.Id.Value, f.FileName, f.ContentLength, true, out message))
			EmbeddedFileController.InvalidFile(f, message);

		using (var stream = f.OpenReadStream())
		{
			var targetFile = targetFileStore.AddFile(e.ConversationId.Value.ToString(&amp;quot;N&amp;quot;), f.FileName, stream, true);
			if (targetFile != null)
				return targetFile;
			else
				_embeddedFileController.InvalidFile(f, &amp;quot;An error occurred while saving an embedded file.&amp;quot;);
		}

		return null;
	});
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;In the event handler, &lt;code&gt;ConversationMessage_BeforeCreate&lt;/code&gt;, we retrieve the target [[Centralized File Storage|CFS]] file store, &lt;code&gt;conversationfiles&lt;/code&gt;, using the [[api-documentation:CentralizedFileStorage In-Process API Service|CentralizedFileStorage API]]&amp;nbsp;-- this is where valid files will be moved. Then, we use the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|controller&amp;#39;s SaveFilesInHtml() method]] to process each file found within the body (&lt;code&gt;e.Body&lt;/code&gt;) of the private message. If we wanted to support embedding files in other properties of the message (in this case, there are none), we would call &lt;code&gt;SaveFilesInHtml()&lt;/code&gt; for each property.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFilesInHtml()&lt;/code&gt; calls the function defined by the last parameter to the method call for each temporary file in the provided content (in this case, &lt;code&gt;e.Body&lt;/code&gt;). For each file, we&amp;#39;ll verify the user has permission to save files and validate the file type (via the &lt;code&gt;IsFileValid()&lt;/code&gt; method which would be defined in this plugin as well but is out-of-scope for this discussion -- it reviews the file details and returns &lt;code&gt;false&lt;/code&gt; and sets the out &lt;code&gt;message&lt;/code&gt; parameter if a the file is invalid). If there is an issue with any file (one of the validation procedures fails), we call [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|InvalidFile() on the controller]]. This stops processing and reports the provided message back to the user. In production code (this sample is a simplification), we&amp;#39;d want to ensure that this text is [[Translating Plugin Text|properly translated]].&lt;/p&gt;
&lt;p&gt;After validating the file is valid, we&amp;#39;ll move it to the proper target file store by calling [[api-documentation:ICentralizedFileStorageProvider In-Process API Supplementary Type|AddFile() on the target CFS file store]]. This will return the resulting [[api-documentation:ICentralizedFile In-Process API Supplementary Type|ICentralizedFile]] which then must be provided back to the controller.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFileInHtml()&lt;/code&gt; returns the updated HTML with all temporary embedded files appropriately moved.&amp;nbsp;This value should be saved back to the content. For private messages, we set &lt;code&gt;e.Body&lt;/code&gt; to the new value and, because this is in the &lt;code&gt;BeforeCreate&lt;/code&gt; event, the updates will be committed normally (if we processed content in &lt;code&gt;AfterCreate&lt;/code&gt;, for example, the content would have already been committed and we&amp;#39;d need to explicitly save the updated value).&lt;/p&gt;
&lt;h3&gt;&lt;a id="UI_Considerations" name="UI_Considerations"&gt;&lt;/a&gt;UI Considerations&lt;/h3&gt;
&lt;p&gt;To ensure that the content editor is aware of the type of content being edited and to enable the editor to detect if file embedding is supported, it is important that when rendering the editor through [[api-documentation:core_v2_editor script api#Render|$core_v2_editor.Render()]] in a [[Widgets|widget]], that the &lt;code&gt;ContentTypeId&lt;/code&gt; is specified. This identifier&amp;nbsp;should match the &lt;code&gt;ContentTypeId&lt;/code&gt; identified by the [[api-documentation:IContentType Plugin Type|IContentType]] implementation of the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]]. Without this option specified, the editor will not know what type of content is being edited and will never enable file embedding.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item><item><title>Handling Embedded Files in Content</title><link>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content/revision/2</link><pubDate>Tue, 04 Aug 2020 21:51:56 GMT</pubDate><guid isPermaLink="false">072fc4d6-7fde-486a-aa1f-1dafdf3a302f</guid><dc:creator>Former Member</dc:creator><comments>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content#comments</comments><description>Revision 2 posted to Developer Training by Former Member on 08/04/2020 21:51:56&lt;br /&gt;
&lt;p&gt;When creating [[Creating Custom Applications and Content|custom content types]] to the Verint Community platform and utilizing the platform editor within the UI managing that content (using the [[api-documentation:core v2 editor script api|$core_v2_editor widget API]]), the editor will allow drag-and-drop and menu-based file uploading into the HTML content if that content supports embedded files.&lt;/p&gt;
&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2&gt;&lt;a id="Why_Would_I_Want_to_Enable_Embedded_Files" name="Why_Would_I_Want_to_Enable_Embedded_Files"&gt;&lt;/a&gt;Why Would I Want to Enable Embedded Files?&lt;/h2&gt;
&lt;p&gt;Embedded files are useful when authoring rich formatted content (HTML) to allow inclusion of images, videos, documents, and other media and files to provide more flexibility and interactivity.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id="Implementing_Support_for_Embedded_Files" name="Implementing_Support_for_Embedded_Files"&gt;&lt;/a&gt;Implementing Support for Embedded Files&lt;/h2&gt;
&lt;p&gt;To add support for embedded files within a [[Creating Custom Applications and Content|custom content type]], the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]] plugin type should also be implemented on your content type (which requires a reference to Telligent.Evolution.Core.dll). The &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; interface extends [[api-documentation:IContentType Plugin Type|IContentType]] to add members that enable the content type to interact with the Verint Community platform regarding embedded files.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Basic_Implementation" name="Basic_Implementation"&gt;&lt;/a&gt;Basic Implementation&lt;/h3&gt;
&lt;p&gt;Because an implementation of &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; is dependent on a custom content type which is out-of-scope for this topic, I&amp;#39;ll show examples from private messaging in the core platform.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll implement the &lt;code&gt;IFileEmbeddedContentType&lt;/code&gt;, which requires two members to be implemented. First, &lt;code&gt;CanAddFiles()&lt;/code&gt; identifies whether a user can add files to this content type:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public bool CanAddFiles(int userId)
{
	var user = Users.GetUser(userId);
	if (user == null || user.IsAnonymous)
		return false;

	return true;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;For private messaging, any non-anonymous user can embed files. For other applications, the ability to embed files may be dependent on the application or container/group the content is being created within. To detect the context of the request, the current page&amp;#39;s context can be retrieved by analyzing [[api-documentation:Url In-Process API Service|Url.CurrentContext]]&amp;nbsp;or using another mechanism for detecting the context of the &lt;code&gt;CanAddFiles()&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;CanAddFiles()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, file embedding functionality within the platform content editor will be enabled, otherwise, these functions will not be available.&lt;/p&gt;
&lt;p&gt;The only remaining requirement for an &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; implementation is the &lt;code&gt;SetController()&lt;/code&gt; method:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;private IFileEmbeddableContentTypeController _embeddedFileController;

public void SetController(IFileEmbeddableContentTypeController controller)
{
	_embeddedFileController = controller;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This method provides the plugin with a controller enabling the extraction and reassignment of embedded files within HTML content.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Using_the_Controller_to_Process_Files" name="Using_the_Controller_to_Process_Files"&gt;&lt;/a&gt;Using the Controller to Process Files&lt;/h3&gt;
&lt;p&gt;When files are uploaded through the editor, they&amp;#39;re placed in a temporary file store within the [[Centralized File Storage|Centralized File System]]. Files placed in this temporary location are only accessible to the user who uploaded them and are automatically deleted after 2 hours (by default) of inactivity. Content supporting embedded files should move this temporary content to a final storage location suitably secured for the content type. To implement the movement of files to a final storage location, [[Handling Events|events]] related to the creation or editing of content should be handled and the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|IFileEmbeddableContentTypeController]] provided to the plugin through the &lt;code&gt;SetController()&lt;/code&gt; method should be used.&lt;/p&gt;
&lt;p&gt;For private messaging, we attach handlers to the &lt;code&gt;BeforeCreate&lt;/code&gt; event (since private messages do not support editing, otherwise, we&amp;#39;d also want to handle edit-events). As with all [[Plugins|plugins]], we [[Handling Events|register the event handler]] in the [[api-documentation:IPlugin Plugin Type|Initialize() method of IPlugin]]:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public void Initialize()
{
    var conversationMessageApi = Telligent.Evolution.Extensibility.Apis.Get&amp;lt;Telligent.Evolution.Extensibility.Api.Version1.IConversationMessages&amp;gt;();
	
	conversationMessageApi.Events.BeforeCreate += ConversationMessage_BeforeCreate;
    
    // other event handlers used for this plugin
}

void ConversationMessage_BeforeCreate(ConversationMessageBeforeCreateEventArgs e)
{
    if (_embeddedFileController == null)
		return;

	var hasPermission = CanAddFiles(e.Author.Id.Value);
	var targetFileStore = Telligent.Evolution.Extensibility.Storage.Version1.CentralizedFileStorage.GetFileStore(&amp;quot;conversationfiles&amp;quot;);

	e.Body = _embeddedFileController.SaveFilesInHtml(e.Body, f =&amp;gt; {
		if (!hasPermission)
			_embeddedFileController.InvalidFile(f, &amp;quot;You do not have permission to add files to private messages&amp;quot;);

		string message;
		if (!IsFileValid(e.Author.Id.Value, f.FileName, f.ContentLength, true, out message))
			EmbeddedFileController.InvalidFile(f, message);

		using (var stream = f.OpenReadStream())
		{
			var targetFile = targetFileStore.AddFile(e.ConversationId.Value.ToString(&amp;quot;N&amp;quot;), f.FileName, stream, true);
			if (targetFile != null)
				return targetFile;
			else
				_embeddedFileController.InvalidFile(f, &amp;quot;An error occurred while saving an embedded file.&amp;quot;);
		}

		return null;
	});
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;In the event handler, &lt;code&gt;ConversationMessage_BeforeCreate&lt;/code&gt;, we retrieve the target [[Centralized File Storage|CFS]] file store, &lt;code&gt;conversationfiles&lt;/code&gt;, using the [[api-documentation:CentralizedFileStorage In-Process API Service|CentralizedFileStorage API]]&amp;nbsp;-- this is where valid files will be moved. Then, we use the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|controller&amp;#39;s SaveFilesInHtml() method]] to process each file found within the body (&lt;code&gt;e.Body&lt;/code&gt;) of the private message. If we wanted to support embedding files in other properties of the message (in this case, there are none), we would call &lt;code&gt;SaveFilesInHtml()&lt;/code&gt; for each property.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFilesInHtml()&lt;/code&gt; calls the function defined by the last parameter to the method call for each temporary file in the provided content (in this case, &lt;code&gt;e.Body&lt;/code&gt;). For each file, we&amp;#39;ll verify the user has permission to save files and validate the file type (via the &lt;code&gt;IsFileValid()&lt;/code&gt; method which would be defined in this plugin as well but is out-of-scope for this discussion -- it reviews the file details and returns &lt;code&gt;false&lt;/code&gt; and sets the out &lt;code&gt;message&lt;/code&gt; parameter if a the file is invalid). If there is an issue with any file (one of the validation procedures fails), we call [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|InvalidFile() on the controller]]. This stops processing and reports the provided message back to the user. In production code (this sample is a simplification), we&amp;#39;d want to ensure that this text is [[Translating Plugin Text|properly translated]].&lt;/p&gt;
&lt;p&gt;After validating the file is valid, we&amp;#39;ll move it to the proper target file store by calling [[api-documentation:ICentralizedFileStorageProvider In-Process API Supplementary Type|AddFile() on the target CFS file store]]. This will return the resulting [[api-documentation:ICentralizedFile In-Process API Supplementary Type|ICentralizedFile]] which then must be provided back to the controller.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFileInHtml()&lt;/code&gt; returns the updated HTML with all temporary embedded files appropriately moved.&amp;nbsp;This value should be saved back to the content. For private messages, we set &lt;code&gt;e.Body&lt;/code&gt; to the new value and, because this is in the &lt;code&gt;BeforeCreate&lt;/code&gt; event, the updates will be committed normally (if we processed content in &lt;code&gt;AfterCreate&lt;/code&gt;, for example, the content would have already been committed and we&amp;#39;d need to explicitly save the updated value).&lt;/p&gt;
&lt;h3&gt;&lt;a id="UI_Considerations" name="UI_Considerations"&gt;&lt;/a&gt;UI Considerations&lt;/h3&gt;
&lt;p&gt;To ensure that the content editor is aware of the type of content being edited and to enable the editor to detect if file embedding is supported, it is important that when rendering the editor through [[api-documentation:core v2 editor script api#Render$core_v2_editor.Render()]] in a [[Widgets|widget]], that the &lt;code&gt;ContentTypeId&lt;/code&gt; is specified. This identifier&amp;nbsp;should match the &lt;code&gt;ContentTypeId&lt;/code&gt; identified by the [[api-documentation:IContentType Plugin Type|IContentType]] implementation of the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]]. Without this option specified, the editor will not know what type of content is being edited and will never enable file embedding.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item><item><title>Handling Embedded Files in Content</title><link>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content/revision/1</link><pubDate>Thu, 13 Jun 2019 19:28:51 GMT</pubDate><guid isPermaLink="false">072fc4d6-7fde-486a-aa1f-1dafdf3a302f</guid><dc:creator>Ben Tiedt</dc:creator><comments>https://community.telligent.com/community/11/w/developer-training/65116/handling-embedded-files-in-content#comments</comments><description>Revision 1 posted to Developer Training by Ben Tiedt on 06/13/2019 19:28:51&lt;br /&gt;
&lt;p&gt;When creating [[Creating Custom Applications and Content|custom content types]] to the Telligent Community platform and utilizing the platform editor within the UI managing that content (using the [[api-documentation:core v2 editor widget extension|$core_v2_editor widget API]]), the editor will allow drag-and-drop and menu-based file uploading into the HTML content if that content supports embedded files.&lt;/p&gt;
&lt;p&gt;[toc]&lt;/p&gt;
&lt;h2&gt;&lt;a id="Why_Would_I_Want_to_Enable_Embedded_Files" name="Why_Would_I_Want_to_Enable_Embedded_Files"&gt;&lt;/a&gt;Why Would I Want to Enable Embedded Files?&lt;/h2&gt;
&lt;p&gt;Embedded files are useful when authoring rich formatted content (HTML) to allow inclusion of images, videos, documents, and other media and files to provide more flexibility and interactivity.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;&lt;a id="Implementing_Support_for_Embedded_Files" name="Implementing_Support_for_Embedded_Files"&gt;&lt;/a&gt;Implementing Support for Embedded Files&lt;/h2&gt;
&lt;p&gt;To add support for embedded files within a [[Creating Custom Applications and Content|custom content type]], the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]] plugin type should also be implemented on your content type (which requires a reference to Telligent.Evolution.Core.dll). The &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; interface extends [[api-documentation:IContentType Plugin Type|IContentType]] to add members that enable the content type to interact with the Telligent Community platform regarding embedded files.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Basic_Implementation" name="Basic_Implementation"&gt;&lt;/a&gt;Basic Implementation&lt;/h3&gt;
&lt;p&gt;Because an implementation of &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; is dependent on a custom content type which is out-of-scope for this topic, I&amp;#39;ll show examples from private messaging in the core platform.&lt;/p&gt;
&lt;p&gt;We&amp;#39;ll implement the &lt;code&gt;IFileEmbeddedContentType&lt;/code&gt;, which requires two members to be implemented. First, &lt;code&gt;CanAddFiles()&lt;/code&gt; identifies whether a user can add files to this content type:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public bool CanAddFiles(int userId)
{
	var user = Users.GetUser(userId);
	if (user == null || user.IsAnonymous)
		return false;

	return true;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;For private messaging, any non-anonymous user can embed files. For other applications, the ability to embed files may be dependent on the application or container/group the content is being created within. To detect the context of the request, the current page&amp;#39;s context can be retrieved by analyzing [[api-documentation:Url In-Process API Service|Url.CurrentContext]]&amp;nbsp;or using another mechanism for detecting the context of the &lt;code&gt;CanAddFiles()&lt;/code&gt; request.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;CanAddFiles()&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt;, file embedding functionality within the platform content editor will be enabled, otherwise, these functions will not be available.&lt;/p&gt;
&lt;p&gt;The only remaining requirement for an &lt;code&gt;IFileEmbeddableContentType&lt;/code&gt; implementation is the &lt;code&gt;SetController()&lt;/code&gt; method:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;private IFileEmbeddableContentTypeController _embeddedFileController;

public void SetController(IFileEmbeddableContentTypeController controller)
{
	_embeddedFileController = controller;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;This method provides the plugin with a controller enabling the extraction and reassignment of embedded files within HTML content.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Using_the_Controller_to_Process_Files" name="Using_the_Controller_to_Process_Files"&gt;&lt;/a&gt;Using the Controller to Process Files&lt;/h3&gt;
&lt;p&gt;When files are uploaded through the editor, they&amp;#39;re placed in a temporary file store within the [[Centralized File Storage|Centralized File System]]. Files placed in this temporary location are only accessible to the user who uploaded them and are automatically deleted after 2 hours (by default) of inactivity. Content supporting embedded files should move this temporary content to a final storage location suitably secured for the content type. To implement the movement of files to a final storage location, [[Handling Events|events]] related to the creation or editing of content should be handled and the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|IFileEmbeddableContentTypeController]] provided to the plugin through the &lt;code&gt;SetController()&lt;/code&gt; method should be used.&lt;/p&gt;
&lt;p&gt;For private messaging, we attach handlers to the &lt;code&gt;BeforeCreate&lt;/code&gt; event (since private messages do not support editing, otherwise, we&amp;#39;d also want to handle edit-events). As with all [[Plugins|plugins]], we [[Handling Events|register the event handler]] in the [[api-documentation:IPlugin Plugin Type|Initialize() method of IPlugin]]:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="csharp"&gt;public void Initialize()
{
    var conversationMessageApi = Telligent.Evolution.Extensibility.Apis.Get&amp;lt;Telligent.Evolution.Extensibility.Api.Version1.IConversationMessages&amp;gt;();
	
	conversationMessageApi.Events.BeforeCreate += ConversationMessage_BeforeCreate;
    
    // other event handlers used for this plugin
}

void ConversationMessage_BeforeCreate(ConversationMessageBeforeCreateEventArgs e)
{
    if (_embeddedFileController == null)
		return;

	var hasPermission = CanAddFiles(e.Author.Id.Value);
	var targetFileStore = Telligent.Evolution.Extensibility.Storage.Version1.CentralizedFileStorage.GetFileStore(&amp;quot;conversationfiles&amp;quot;);

	e.Body = _embeddedFileController.SaveFilesInHtml(e.Body, f =&amp;gt; {
		if (!hasPermission)
			_embeddedFileController.InvalidFile(f, &amp;quot;You do not have permission to add files to private messages&amp;quot;);

		string message;
		if (!IsFileValid(e.Author.Id.Value, f.FileName, f.ContentLength, true, out message))
			EmbeddedFileController.InvalidFile(f, message);

		using (var stream = f.OpenReadStream())
		{
			var targetFile = targetFileStore.AddFile(e.ConversationId.Value.ToString(&amp;quot;N&amp;quot;), f.FileName, stream, true);
			if (targetFile != null)
				return targetFile;
			else
				_embeddedFileController.InvalidFile(f, &amp;quot;An error occurred while saving an embedded file.&amp;quot;);
		}

		return null;
	});
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;In the event handler, &lt;code&gt;ConversationMessage_BeforeCreate&lt;/code&gt;, we retrieve the target [[Centralized File Storage|CFS]] file store, &lt;code&gt;conversationfiles&lt;/code&gt;, using the [[api-documentation:CentralizedFileStorage In-Process API Service|CentralizedFileStorage API]]&amp;nbsp;-- this is where valid files will be moved. Then, we use the [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|controller&amp;#39;s SaveFilesInHtml() method]] to process each file found within the body (&lt;code&gt;e.Body&lt;/code&gt;) of the private message. If we wanted to support embedding files in other properties of the message (in this case, there are none), we would call &lt;code&gt;SaveFilesInHtml()&lt;/code&gt; for each property.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFilesInHtml()&lt;/code&gt; calls the function defined by the last parameter to the method call for each temporary file in the provided content (in this case, &lt;code&gt;e.Body&lt;/code&gt;). For each file, we&amp;#39;ll verify the user has permission to save files and validate the file type (via the &lt;code&gt;IsFileValid()&lt;/code&gt; method which would be defined in this plugin as well but is out-of-scope for this discussion -- it reviews the file details and returns &lt;code&gt;false&lt;/code&gt; and sets the out &lt;code&gt;message&lt;/code&gt; parameter if a the file is invalid). If there is an issue with any file (one of the validation procedures fails), we call [[api-documentation:IFileEmbeddableContentTypeController Plugin Supplementary Type|InvalidFile() on the controller]]. This stops processing and reports the provided message back to the user. In production code (this sample is a simplification), we&amp;#39;d want to ensure that this text is [[Translating Plugin Text|properly translated]].&lt;/p&gt;
&lt;p&gt;After validating the file is valid, we&amp;#39;ll move it to the proper target file store by calling [[api-documentation:ICentralizedFileStorageProvider In-Process API Supplementary Type|AddFile() on the target CFS file store]]. This will return the resulting [[api-documentation:ICentralizedFile In-Process API Supplementary Type|ICentralizedFile]] which then must be provided back to the controller.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SaveFileInHtml()&lt;/code&gt; returns the updated HTML with all temporary embedded files appropriately moved.&amp;nbsp;This value should be saved back to the content. For private messages, we set &lt;code&gt;e.Body&lt;/code&gt; to the new value and, because this is in the &lt;code&gt;BeforeCreate&lt;/code&gt; event, the updates will be committed normally (if we processed content in &lt;code&gt;AfterCreate&lt;/code&gt;, for example, the content would have already been committed and we&amp;#39;d need to explicitly save the updated value).&lt;/p&gt;
&lt;h3&gt;&lt;a id="UI_Considerations" name="UI_Considerations"&gt;&lt;/a&gt;UI Considerations&lt;/h3&gt;
&lt;p&gt;To ensure that the content editor is aware of the type of content being edited and to enable the editor to detect if file embedding is supported, it is important that when rendering the editor through [[api-documentation:core v2 editor widget extension|$core_v2_editor.Render()]] in a [[Widgets|widget]], that the &lt;code&gt;ContentTypeId&lt;/code&gt; is specified. This identifier&amp;nbsp;should match the &lt;code&gt;ContentTypeId&lt;/code&gt; identified by the [[api-documentation:IContentType Plugin Type|IContentType]] implementation of the [[api-documentation:IFileEmbeddableContentType Plugin Type|IFileEmbeddableContentType]]. Without this option specified, the editor will not know what type of content is being edited and will never enable file embedding.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;
</description></item></channel></rss>