diff --git a/RatingManagerPlugin/Properties/AssemblyInfo.cs b/RatingManagerPlugin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e82b572 --- /dev/null +++ b/RatingManagerPlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Rating Manager Plugin")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fe885520-e570-4524-943c-effc5bae1843")] diff --git a/RatingManagerPlugin/RatingManager.cs b/RatingManagerPlugin/RatingManager.cs new file mode 100644 index 0000000..6915839 --- /dev/null +++ b/RatingManagerPlugin/RatingManager.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ScrewTurn.Wiki.PluginFramework; +using System.Text.RegularExpressions; +using System.Reflection; +using System.IO; + +namespace ScrewTurn.Wiki.Plugins.RatingManagerPlugin { + + /// + /// A plugin for assigning a rating to pages. + /// + public class RatingManager : IFormatterProviderV30 { + + const string defaultDirectoryName = "/__RatingManagerPlugin/"; + const string cssFileName = "RatingManagerPluginCss.css"; + const string jsFileName = "RatingManagerPluginJs.js"; + const string starImageFileName = "RatingManagerPluginStarImage.gif"; + const string ratingFileName = "RatingManagerPluginRatingFile.dat"; + + private IHostV30 _host; + private bool _enableLogging = true; + private static readonly ComponentInformation Info = new ComponentInformation("Rating Manager Plugin", "Threeplicate Srl", "3.0.2.538", "http://www.screwturn.eu", "http://www.screwturn.eu/Version/PluginPack/RatingManager2.txt"); + + private bool foundRatings = false; + + private static readonly Regex VotesRegex = new Regex(@"{rating(\|(.+?))?}", + RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + /// + /// Initializes a new instance of the class. + /// + public RatingManager() { + + } + + /// + /// Specifies whether or not to execute Phase 1. + /// + public bool PerformPhase1 { + get { return false; } + } + + /// + /// Specifies whether or not to execute Phase 2. + /// + public bool PerformPhase2 { + get { return false; } + } + + /// + /// Specifies whether or not to execute Phase 3. + /// + public bool PerformPhase3 { + get { return true; } + } + + /// + /// Gets the execution priority of the provider (0 lowest, 100 highest). + /// + public int ExecutionPriority { + get { return 50; } + } + + /// + /// Performs a Formatting phase. + /// + /// The raw content to Format. + /// The Context information. + /// The Phase. + /// The Formatted content. + public string Format(string raw, ContextInformation context, FormattingPhase phase) { + // {rating} + // _backendpage not found -> ignored + + StringBuilder buffer = new StringBuilder(raw); + try { + if(context.Context == FormattingContext.PageContent && context.Page != null) { + if(context.HttpContext.Request["vote"] != null) { + AddRating(context.Page.FullName, int.Parse(context.HttpContext.Request["vote"])); + System.Web.HttpCookie cookie = new System.Web.HttpCookie("RatingManagerPlugin_" + context.Page.FullName, context.HttpContext.Request["vote"]); + cookie.Expires = DateTime.Now.AddYears(10); + context.HttpContext.Response.Cookies.Add(cookie); + return ""; + } + } + if(context.Page != null) { + ComputeRating(context, buffer, context.Page.FullName); + } + else { + return raw; + } + } + catch(Exception ex) { + LogWarning(string.Format("Exception occurred: {0}", ex.StackTrace)); + } + if(foundRatings) { + buffer.Append(@""); + buffer.Append(@""); + foundRatings = false; + } + return buffer.ToString(); + } + + /// + /// Gets the rating of the plugin from the backendpage and display it to the user. + /// + /// The context. + /// The page content. + /// Full name of the page. + private void ComputeRating(ContextInformation context, StringBuilder buffer, string fullPageName) { + KeyValuePair block = FindAndRemoveFirstOccurrence(buffer); + int numRatings = 0; + while(block.Key != -1) { + foundRatings = true; + numRatings++; + + string result = null; + + if(block.Value.Groups[2].Value != "") { + int average = (int)Math.Round((decimal)GetCurrentAverage(block.Value.Groups[2].Value), 0, MidpointRounding.AwayFromZero); + + result += @""; + + result += @""; + } + else if(context.HttpContext.Request.Cookies.Get("RatingManagerPlugin_" + fullPageName) != null) { + int average = (int)Math.Round((decimal)GetCurrentAverage(fullPageName), 0, MidpointRounding.AwayFromZero); + + result += @""; + + result += @""; + } + else { + int average = (int)Math.Round((decimal)GetCurrentAverage(fullPageName), 0, MidpointRounding.AwayFromZero); + + result += @" + "; + + result += @""; + + } + + result += @""; + + buffer.Insert(block.Key, result); + + block = FindAndRemoveFirstOccurrence(buffer); + } + } + + + private float GetCurrentAverage(string fullPageName) { + float average = 0; + try { + IFilesStorageProviderV30 filesStorageProvider = GetDefaultFilesStorageProvider(); + + MemoryStream stream = new MemoryStream(); + string fileContent = ""; + + if(FileExists(filesStorageProvider, defaultDirectoryName, ratingFileName)) { + filesStorageProvider.RetrieveFile(defaultDirectoryName + ratingFileName, stream, true); + stream.Seek(0, SeekOrigin.Begin); + fileContent = Encoding.UTF8.GetString(stream.ToArray()); + } + + string[] plugins = fileContent.Split(new String[] { "||" }, StringSplitOptions.RemoveEmptyEntries); + + // If the plugin is found return the posizion in the plugins array + // otherwise return -1 + int pluginIndex = SearchPlugin(plugins, fullPageName); + if(pluginIndex != -1) { + string[] pluginDetails = plugins[pluginIndex].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries); + average = (float)int.Parse(pluginDetails[2]) / (float)100; + } + } + catch(Exception ex) { + LogWarning(String.Format("Exception occurred {0}", ex.StackTrace)); + } + return average; + } + + + private void AddRating(string fullPageName, int rate) { + IFilesStorageProviderV30 filesStorageProvider = GetDefaultFilesStorageProvider(); + + MemoryStream stream = new MemoryStream(); + + if(FileExists(filesStorageProvider, defaultDirectoryName, ratingFileName)) { + filesStorageProvider.RetrieveFile(defaultDirectoryName + ratingFileName, stream, true); + stream.Seek(0, SeekOrigin.Begin); + } + string fileContent = Encoding.UTF8.GetString(stream.ToArray()); + + string[] plugins = fileContent.Split(new String[] { "||" }, StringSplitOptions.RemoveEmptyEntries); + + StringBuilder sb = new StringBuilder(); + + // If the plugin is found return the posizion in the plugins array + // otherwise return -1 + int pluginIndex = SearchPlugin(plugins, fullPageName); + if(pluginIndex != -1) { + int numRates = int.Parse(plugins[pluginIndex].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries)[1]); + int average = int.Parse(plugins[pluginIndex].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries)[2]); + int newAverage = ((average * numRates) + (rate * 100)) / (numRates + 1); + numRates++; + plugins[pluginIndex] = fullPageName + "|" + numRates + "|" + newAverage; + foreach(string plugin in plugins) { + sb.Append(plugin + "||"); + } + } + else { + foreach(string plugin in plugins) { + sb.Append(plugin + "||"); + } + sb.Append(fullPageName + "|1|" + (rate * 100)); + } + + stream = new MemoryStream(Encoding.UTF8.GetBytes(sb.ToString())); + + filesStorageProvider.StoreFile(defaultDirectoryName + ratingFileName, stream, true); + + //statisticsPage.Provider.ModifyPage(statisticsPage, statisticsPageContent.Title, statisticsPageContent.User, DateTime.Now, statisticsPageContent.Comment, sb.ToString(), statisticsPageContent.Keywords, statisticsPageContent.Description, SaveMode.Normal); + } + + /// + /// Searches the plugin. + /// + /// The plugins array. + /// The current plugin. + /// + /// The position of the plugin in the array, otherwise -1 + /// + private int SearchPlugin(string[] plugins, string currentPlugin) { + for(int i = 0; i < plugins.Length; i++) { + if(plugins[i].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries)[0] == currentPlugin) + return i; + } + return -1; + } + + /// + /// Finds the and remove first occurrence. + /// + /// The buffer. + /// The index->content data. + private KeyValuePair FindAndRemoveFirstOccurrence(StringBuilder buffer) { + Match match = VotesRegex.Match(buffer.ToString()); + + if(match.Success) { + buffer.Remove(match.Index, match.Length); + return new KeyValuePair(match.Index, match); + } + + return new KeyValuePair(-1, null); + } + + /// + /// Logs the warning. + /// + /// The message. + private void LogWarning(string message) { + if(_enableLogging) { + _host.LogEntry(message, LogEntryType.Warning, null, this); + } + } + + /// + /// Prepares the title of an item for display (always during phase 3). + /// + /// The input title. + /// The context information. + /// The prepared title (no markup allowed). + public string PrepareTitle(string title, ContextInformation context) { + return title; + } + + /// + /// Initializes the Storage Provider. + /// + /// The Host of the Component. + /// The Configuration data, if any. + /// If or are null. + /// If is not valid or is incorrect. + public void Init(IHostV30 host, string config) { + _host = host; + + if(config != null) { + string[] configEntries = config.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); + for(int i = 0; i < configEntries.Length; i++) { + string[] configEntryDetails = configEntries[i].Split(new string[] { "=" }, 2, StringSplitOptions.None); + switch(configEntryDetails[0].ToLowerInvariant()) { + case "logoptions": + if(configEntryDetails[1] == "nolog") { + _enableLogging = false; + } + else { + LogWarning(@"Unknown value in ""logOptions"" configuration string: " + configEntries[i] + "Supported values are: nolog."); + } + break; + default: + LogWarning("Unknown value in configuration string: " + configEntries[i]); + break; + } + } + } + + IFilesStorageProviderV30 filesStorageProvider = GetDefaultFilesStorageProvider(); + + if(!DirectoryExists(filesStorageProvider, defaultDirectoryName)) { + filesStorageProvider.CreateDirectory("/", defaultDirectoryName.Trim('/')); + } + if(!FileExists(filesStorageProvider, defaultDirectoryName, cssFileName)) { + filesStorageProvider.StoreFile(defaultDirectoryName + cssFileName, Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.RatingManagerPlugin.Resources.jquery.rating.css"), true); + } + if(!FileExists(filesStorageProvider, defaultDirectoryName, jsFileName)) { + filesStorageProvider.StoreFile(defaultDirectoryName + jsFileName, Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.RatingManagerPlugin.Resources.jquery.rating.pack.js"), true); + } + if(!FileExists(filesStorageProvider, defaultDirectoryName, starImageFileName)) { + filesStorageProvider.StoreFile(defaultDirectoryName + starImageFileName, Assembly.GetExecutingAssembly().GetManifestResourceStream("ScrewTurn.Wiki.Plugins.RatingManagerPlugin.Resources.star.gif"), true); + } + } + + + private IFilesStorageProviderV30 GetDefaultFilesStorageProvider() { + string defaultFilesStorageProviderName = _host.GetSettingValue(SettingName.DefaultFilesStorageProvider); + return _host.GetFilesStorageProviders(true).First(p => p.GetType().FullName == defaultFilesStorageProviderName); + } + + private bool DirectoryExists(IFilesStorageProviderV30 filesStorageProvider, string directoryName) { + string[] directoryList = filesStorageProvider.ListDirectories("/"); + foreach(string dir in directoryList) { + if(dir == directoryName) return true; + } + return false; + } + + private bool FileExists(IFilesStorageProviderV30 filesStorageProvider, string directory, string fileName) { + string[] filesList = filesStorageProvider.ListFiles(directory); + foreach(string file in filesList) { + if(file == directory + fileName) return true; + } + return false; + } + + /// + /// Method invoked on shutdown. + /// + /// This method might not be invoked in some cases. + public void Shutdown() { + // Nothing to do + } + + /// + /// Gets the Information about the Provider. + /// + public ComponentInformation Information { + get { return Info; } + } + + /// + /// Gets a brief summary of the configuration string format, in HTML. Returns null if no configuration is needed. + /// + public string ConfigHelpHtml { + get { return "Specify logooptions=nolog for disabling warning log messages for exceptions."; } + } + } +} diff --git a/RatingManagerPlugin/RatingManagerPlugin.csproj b/RatingManagerPlugin/RatingManagerPlugin.csproj new file mode 100644 index 0000000..6ffe906 --- /dev/null +++ b/RatingManagerPlugin/RatingManagerPlugin.csproj @@ -0,0 +1,72 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {B65C793F-62C4-4B81-95C4-3E4805E80411} + Library + Properties + ScrewTurn.Wiki.Plugins.RatingManagerPlugin + RatingManagerPlugin + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + none + true + bin\Release\ + TRACE + prompt + 4 + true + + + + + 3.5 + + + + + + AssemblyVersion.cs + + + + + + + + + + + + + + + + {531A83D6-76F9-4014-91C5-295818E2D948} + PluginFramework + + + + + \ No newline at end of file diff --git a/RatingManagerPlugin/Resources/jquery.rating.css b/RatingManagerPlugin/Resources/jquery.rating.css new file mode 100644 index 0000000..9120ba4 --- /dev/null +++ b/RatingManagerPlugin/Resources/jquery.rating.css @@ -0,0 +1,36 @@ +.ui-rating .ui-rating-star, .ui-rating .ui-rating-cancel, .static-rating{ + width:16px; + height:16px; + font-size:2px; + float:left; + text-decoration:none; + vertical-align:middle; + background-image:url('GetFile.aspx?file=/__RatingManagerPlugin/RatingManagerPluginStarImage.gif'); + background-repeat:no-repeat; +} +.ui-rating a { + cursor: pointer; +} +.ui-rating-full { + background-position:left top; +} + +.ui-rating-half { + background-position:left -16px; +} + +.ui-rating-empty { + background-position:left -32px; +} + +.ui-rating-cancel-empty { + background-position:left -64px; +} + +.ui-rating-cancel-full { + background-position:left -80px; +} + +.ui-rating-hover { + background-position:left -48px; +} diff --git a/RatingManagerPlugin/Resources/jquery.rating.pack.js b/RatingManagerPlugin/Resources/jquery.rating.pack.js new file mode 100644 index 0000000..41b4c08 --- /dev/null +++ b/RatingManagerPlugin/Resources/jquery.rating.pack.js @@ -0,0 +1,4 @@ +// Chris Richards 2009 +// rating control for jQuery. version 1.06 +// http://zensoftware.org/ +(function($){$.fn.rating=function(e){var f={showCancel:true,cancelValue:null,startValue:null,disabled:false};$.extend(f,e);var g={hoverOver:function(a){var b=$(a.target);if(b.hasClass("ui-rating-cancel")){b.addClass("ui-rating-cancel-full")}else{b.prevAll().andSelf().not(".ui-rating-cancel").addClass("ui-rating-hover")}},hoverOut:function(a){var b=$(a.target);if(b.hasClass("ui-rating-cancel")){b.addClass("ui-rating-cancel-empty").removeClass("ui-rating-cancel-full")}else{b.prevAll().andSelf().not(".ui-rating-cancel").removeClass("ui-rating-hover")}},click:function(a){var b=$(a.target);var c=f.cancelValue;if(b.hasClass("ui-rating-cancel")){g.empty(b)}else{b.closest(".ui-rating-star").prevAll().andSelf().not(".ui-rating-cancel").attr("className","ui-rating-star ui-rating-full");b.closest(".ui-rating-star").nextAll().not(".ui-rating-cancel").attr("className","ui-rating-star ui-rating-empty");b.siblings(".ui-rating-cancel").attr("className","ui-rating-cancel ui-rating-cancel-empty");c=b.attr("value")}if(!a.data.hasChanged){$(a.data.selectBox).val(c).trigger("change")}},change:function(a){var b=$(this).val();g.setValue(b,a.data.container,a.data.selectBox)},setValue:function(a,b,c){var d={"target":null,"data":{}};d.target=$(".ui-rating-star[value="+a+"]",b);d.data.selectBox=c;d.data.hasChanged=true;g.click(d)},empty:function(a){a.attr("className","ui-rating-cancel ui-rating-cancel-empty").nextAll().attr("className","ui-rating-star ui-rating-empty")}};var h={createContainer:function(a){var b=$("
").attr({title:a.title,className:"ui-rating"}).insertAfter(a);return b},createStar:function(a,b){$("").attr({className:"ui-rating-star ui-rating-empty",title:$(a).text(),value:a.value}).appendTo(b)},createCancel:function(a,b){$("").attr({className:"ui-rating-cancel ui-rating-cancel-empty",title:"Cancel"}).appendTo(b)}};return this.each(function(){if($(this).attr("type")!=="select-one"){return}var a=this;$(a).css("display","none");var b=$(a).attr("id");if(""===b){b="ui-rating-"+$.data(a);$(a).attr("id",b)}var c=h.createContainer(a);if(true!==f.disabled&&$(a).attr("disabled")!==true){$(c).bind("mouseover",g.hoverOver).bind("mouseout",g.hoverOut).bind("click",{"selectBox":a},g.click)}if(f.showCancel){h.createCancel(this,c)}$("option",a).each(function(){h.createStar(this,c)});if(0!==$("#"+b+" option[selected]").size()){g.setValue($(a).val(),c,a)}else{var d=null!==f.startValue?f.startValue:f.cancelValue;g.setValue(d,c,a);$(a).val(d)}$(this).bind("change",{"selectBox":a,"container":c},g.change)})}})(jQuery); diff --git a/RatingManagerPlugin/Resources/star.gif b/RatingManagerPlugin/Resources/star.gif new file mode 100644 index 0000000..0e5b813 Binary files /dev/null and b/RatingManagerPlugin/Resources/star.gif differ diff --git a/ScrewTurnWiki.sln b/ScrewTurnWiki.sln index 35b42d3..29f6c40 100644 --- a/ScrewTurnWiki.sln +++ b/ScrewTurnWiki.sln @@ -65,6 +65,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RssFeedDisplayPlugin", "Rss EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnfuddleTicketsPlugin", "UnfuddleTicketsPlugin\UnfuddleTicketsPlugin.csproj", "{62EC7498-D82C-40FD-B153-5FC2F2FA6D72}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RatingManagerPlugin", "RatingManagerPlugin\RatingManagerPlugin.csproj", "{B65C793F-62C4-4B81-95C4-3E4805E80411}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -155,22 +157,27 @@ Global {62EC7498-D82C-40FD-B153-5FC2F2FA6D72}.Debug|Any CPU.Build.0 = Debug|Any CPU {62EC7498-D82C-40FD-B153-5FC2F2FA6D72}.Release|Any CPU.ActiveCfg = Release|Any CPU {62EC7498-D82C-40FD-B153-5FC2F2FA6D72}.Release|Any CPU.Build.0 = Release|Any CPU + {B65C793F-62C4-4B81-95C4-3E4805E80411}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B65C793F-62C4-4B81-95C4-3E4805E80411}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B65C793F-62C4-4B81-95C4-3E4805E80411}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B65C793F-62C4-4B81-95C4-3E4805E80411}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {88212C14-10A0-4D46-8203-D48534465181} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} - {617D5D30-97F9-48B2-903D-29D4524492E8} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {67590C3A-1A7C-4608-90CA-1C1632D2F643} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {ECB488D9-C8E9-41E0-BE27-27F41F76F8A0} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {F75DFFE1-A8CF-4CC2-B15F-3EC7EAADDCFC} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} + {88212C14-10A0-4D46-8203-D48534465181} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {C7169CA4-9893-4361-96A8-09F87FCF5E8C} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} + {617D5D30-97F9-48B2-903D-29D4524492E8} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {C2F2722A-0B44-4E98-965C-CC1AD1DA511C} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {DEA4E4AA-7452-4598-8277-A7F5D6DE4985} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {B7EE7C86-5031-40EB-B06C-DF5B3564BE17} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {94DDE3D4-0595-405C-9EA6-358B74EC6BC5} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {83363183-7B84-43BF-885F-C728A721140B} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} {62EC7498-D82C-40FD-B153-5FC2F2FA6D72} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} + {B65C793F-62C4-4B81-95C4-3E4805E80411} = {F6E9DB23-D200-4CCE-B42D-7CD1D20FC92D} EndGlobalSection EndGlobal