{"id":13339389,"url":"https://github.com/skybrud/Skybrud.Umbraco.Feedback","last_synced_at":"2025-03-11T13:30:41.221Z","repository":{"id":14454033,"uuid":"76565440","full_name":"skybrud/Skybrud.Umbraco.Feedback","owner":"skybrud","description":"Feedback module for Umbraco.","archived":false,"fork":false,"pushed_at":"2023-03-01T14:19:27.000Z","size":1941,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"v4/main","last_synced_at":"2025-03-07T11:07:03.594Z","etag":null,"topics":["csharp","dotnet","feedback","limbo","package","skybrud","umbraco","umbraco-package","umbraco-v7","umbraco-v8"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skybrud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-15T14:13:05.000Z","updated_at":"2021-10-21T01:59:00.000Z","dependencies_parsed_at":"2024-10-30T22:35:27.217Z","dependency_job_id":null,"html_url":"https://github.com/skybrud/Skybrud.Umbraco.Feedback","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skybrud%2FSkybrud.Umbraco.Feedback","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skybrud%2FSkybrud.Umbraco.Feedback/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skybrud%2FSkybrud.Umbraco.Feedback/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skybrud%2FSkybrud.Umbraco.Feedback/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skybrud","download_url":"https://codeload.github.com/skybrud/Skybrud.Umbraco.Feedback/tar.gz/refs/heads/v4/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243042692,"owners_count":20226684,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","feedback","limbo","package","skybrud","umbraco","umbraco-package","umbraco-v7","umbraco-v8"],"created_at":"2024-07-29T19:20:00.208Z","updated_at":"2025-03-11T13:30:40.934Z","avatar_url":"https://github.com/skybrud.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Feedback-modul til Umbraco 7 #\r\n\r\n**Indholdsfortegnelse**\r\n\r\n* [Brug i frontend](#markdown-header-brug-i-frontend)\r\n* [Plugins](#markdown-header-plugins)\r\n    * [GetUsers](#markdown-header-getusers)\r\n    * [OnEntrySubmitting](#markdown-header-onentrysubmitting)\r\n    * [OnEntrySubmitted](#markdown-header-onentrysubmitted)\r\n    * [OnUserAssigned](#markdown-header-onuserassigned)\r\n* [Config](#markdown-header-config)\r\n    * [Statuses](#markdown-header-statuses)\r\n    * [Profiles](#markdown-header-profiles)\r\n        * [Ratings](#markdown-header-ratings)\r\n\r\n## Brug i frontend ##\r\n\r\nHer et eksempel, der tager udgangspunkt i DC intranet's løsning.\r\n\r\nFelterne `name`, `email` og `comment` er som udgangspunkt alle valgfri, men kan konfigureres til, at skulle udfyldes. Feltet `rating` angives til aliaset for den ønskede rating (se mere under [ratings](#markdown-header-ratings)). Standard ratings i modulet er **Positive** (alias: `positive`) og **Negative** (alias: `negative`). \r\n\r\n```javascript\r\n$.ajax({\r\n    url: '/umbraco/Feedback/Frontend/PostFeedback',\r\n    method: 'POST',\r\n    data: {\r\n        siteId: $('body').data('siteid'),\r\n        pageId: $('body').data('pageid'),\r\n        name: 'Filip Bech Bruun-Larsen',\r\n        email: 'fbruun@skybrud.dk',\r\n        rating: 'positive',\r\n        comment: 'Ham Bjerner er faktisk ret flink'\r\n    }\r\n}).done(function(r){\r\n\r\n    console.log( JSON.stringify(r, null, '  ') );\r\n\r\n}).error(function(r){\r\n\r\n    console.log( JSON.stringify(r, null, '  ') );\r\n\r\n});\r\n```\r\n\r\nSåfremt ovenstående AJAX-kald bliver gennemført, vil serveren svare tilbage noget i stil med følgende:\r\n\r\n`{ \"meta\": { \"code\": 200 }, \"data\": 26 }`\r\n\r\nDet vigtigste her er koden, hvor 200 betyder gennemført. Værdien under data er kommentarens ID, men værdien bruges pt. ikke til noget, og data vil måske blive ændret i fremtiden til at returnere noget andet.\r\n\r\nTilsvarende hvis noget går galt, vil serveren svare tilbage noget i stil med:\r\n\r\n`{ \"meta\": { \"code\": 500, \"error\": \"Øv!\" }, \"data\": null }`\r\n\r\nVærdien i error vil være en fejlbeskrivelse, som gerne må vises til brugeren.\r\n\r\n## Plugins\r\n\r\nKonfigurationen af Feedback-modulet sker gennem kode, hvor man i så fald kan lave en klasse, der implementerer interfacet `IFeedbackPlugin` (eller blot nedarver fra den abstrakte klasse `FeedbackPlugin`).\r\n\r\nEt custom plugin kan registreres som følgende:\r\n\r\n```\r\n#!C\r\nnamespace MySite {\r\n    \r\n    public class Startup : ApplicationEventHandler {\r\n\r\n        protected override void ApplicationStarted(UmbracoApplicationBase umbraco, ApplicationContext application) {\r\n\r\n            // Register an instance of the feedback plugin\r\n            FeedbackModule.Instance.AddPlugin(new MySiteFeedbackPlugin());\r\n        \r\n        }\r\n\r\n    }\r\n\r\n}\r\n```\r\n\r\nTeknisk set behøver man ikke tilføje sit plugin over startup, men det giver nok mest mening.\r\n\r\n### GetUsers\r\n\r\nFeedback-modulet holdet styr på en liste over de brugere, der må kunne vælges som ansvarlig på den enkelte feedback-kommentar. Listen over brugere er dog som udgangspunkt tom, og det er derfor nødvendigt, at definere dette via et custom plugin og metoden `GetUsers`.\r\n\r\nTanken er, at en bruger kan komme fra en eller flere datakilder, som så illustreres via interfacet `IFeedbackUser` (eller den abstrakte klasse `FeedbackUser`). I langt de fleste tilfælde er vi blot interesseret i, at hente en liste over de brugere, der er oprettet i Umbraco. Så i et konkret eksempel kunne `GetUsers` metoden implementeres som nedenstående:\r\n\r\n```\r\n#!C\r\npublic override IFeedbackUser[] GetUsers() {\r\n            \r\n    // Attempt to get the dictionary from the items object (the dictionary is kept in the items during the current page cycle)\r\n    var users = HttpContext.Current.Items[\"IntranetFeedbackUsers\"] as Dictionary\u003cint, IFeedbackUser\u003e;\r\n\r\n    // Check whether we already have a dictionary in memory\r\n    if (users == null) {\r\n                \r\n        // Get a reference to the user service\r\n        IUserService service = UmbracoContext.Current.Application.Services.UserService;\r\n\r\n        int total;\r\n\r\n        // Populate the dictionary with users from the database\r\n        users = (\r\n            from IUser user in service.GetAll(0, Int32.MaxValue, out total)\r\n            where !user.Email.EndsWith(\"@skybrud.dk\") \u0026\u0026 !user.Email.EndsWith(\"@umbraco.com\") \u0026\u0026 !user.Email.EndsWith(\"@codemonkeys.dk\")\r\n            select (IFeedbackUser) new FeedbackUser {\r\n                Id = user.Id,\r\n                Email = user.Email,\r\n                Name = user.Name,\r\n                Language = user.Language\r\n            }\r\n        ).ToDictionary(x =\u003e x.Id, x =\u003e x);\r\n\r\n        // Add the dictionary to the items object for later retrieval\r\n        HttpContext.Current.Items[\"IntranetFeedbackUsers\"] = users;\r\n            \r\n    }\r\n\r\n    // Return the users as an array\r\n    return users.Values.ToArray();\r\n        \r\n}\r\n```\r\n\r\nBemærk at brugere med en emailadresse, der ender på *@skybrud.dk*, *@umbraco.com* eller *@codemonkeys.dk* i dette eksempel vil blive ekskluderet fra listen.\r\n\r\n### OnEntrySubmitting\r\n\r\nVia metoden `OnEntrySubmitting` kan vi ændre kommentarer idet de oprettes - altså svarende til inden de bliver skrevet ned i databasen. Dette kunne eksempelvis være at automatisk tilknytte en bruger/redaktør til en kommentar alt efter hvem der er ansvarlig for det site eller den side, hvortil kommentaren gives.\r\n\r\nSom eksempel har vi på Danish Crown's internet følgende implementation af `OnEntrySubmitting`-metoden:\r\n\r\n```\r\n#!C#\r\npublic override bool OnEntrySubmitting(FeedbackModule module, FeedbackEntry entry) {\r\n\r\n    // Specifikt til DC's intranet (her tjekkes om kommentarer oprettes under et intranet site)\r\n    if (entry.Site == null || entry.Site.DocumentTypeAlias != \"SettingsContainer\") return false;\r\n\r\n    // Henter en liste over alle brugere (også fra andre plugins såfremt der er flere)\r\n    Dictionary\u003cint, IFeedbackUser\u003e users = GetUsers().ToDictionary(x =\u003e x.Id, x =\u003e x);\r\n\r\n    // Igen lidt specifikt til DC (her hentes et entry for hvem, der sidst har redigeret den pågældende side)\r\n    ContentReminderLogEntry logEntry = ContentReminderLogEntry.GetFromNodeId(entry.PageId).FirstOrDefault(x =\u003e users.ContainsKey(x.UserId));\r\n\r\n    if (logEntry != null) {\r\n    \r\n        // Såfremt vi finder en entry, assigner vi brugeren til kommentaren\r\n        entry.AssignedTo = users[logEntry.UserId];\r\n    \r\n    } else {\r\n\r\n        // På hvert intranet site har DC mulighed for, at angive en ansvarlig\r\n        string value = entry.Site.GetPropertyValue\u003cstring\u003e(\"feedbackResponsible\");\r\n\r\n        if (!String.IsNullOrWhiteSpace(value) \u0026\u0026 value.StartsWith(\"{\") \u0026\u0026 value.EndsWith(\"}\")) {\r\n            try {\r\n\r\n                // Hent den valgte brugers ID ud fra JSON-objektet\r\n                JObject obj = JObject.Parse(value);\r\n                int responsibleId = (int) obj.GetValue(\"id\");\r\n\r\n                // Assign kommentarer til brugeren såfremt brugeren findes\r\n                if (users.ContainsKey(responsibleId)) {\r\n                    entry.AssignedTo = users[responsibleId];\r\n                }\r\n            \r\n            } catch (Exception) {\r\n\r\n                // Ignorér exception eller gem i en log et sted\r\n\r\n            }\r\n\r\n        }\r\n\r\n    }\r\n\r\n    return true;\r\n\r\n}\r\n```\r\n\r\nDenne metode kan også bruges til at afvise en kommentar, hvis eksempelvis emailadressen ikke er udfyldt.\r\n\r\n### OnEntrySubmitted\r\n\r\nTilsvarende kan man med `OnEntrySubmitted`-metoden fange når en kommentar er blevet gemt i databasen. Denne metode kunne eksempelvis bruges til, at udsende en mail til den rekdaktør, der er blevet assigned jvf. ovenstående metode.\r\n\r\n```\r\n#!C\r\npublic override void OnEntrySubmitted(FeedbackModule module, FeedbackEntry entry) {\r\n\r\n     // Skip now if not assigned\r\n    if (entry.AssignedTo == null) return;\r\n\r\n    // Get the first name of the user\r\n    string firstName = entry.AssignedTo.Name.Split(' ')[0];\r\n\r\n    // Generate the body of the mail to be sent (DescribeEntry is a custom method)\r\n    StringBuilder sb = new StringBuilder();\r\n    sb.AppendLine(\"Hi \" + firstName + \" (\" + entry.AssignedTo.Email + \"),\u003cbr /\u003e\");\r\n    sb.AppendLine(\"\u003cbr /\u003e\");\r\n    sb.AppendLine(\"A new feedback entry has been submitted to which you have been assigned:\u003cbr /\u003e\");\r\n    sb.AppendLine(\"\u003cbr /\u003e\");\r\n    DescribeEntry(sb, entry);\r\n\r\n    // Send the mail to the user (again a custom user)\r\n    SendMail(\"DC Intra - New feedback entry\", entry.AssignedTo, sb.ToString());\r\n\r\n}\r\n```\r\n\r\n### OnUserAssigned\r\n\r\nNår en bruger efter oprettelsen assignes til en kommantar, bliver `OnUserAssigned` kaldt for den pågældende kommentar. Metoden har en henvisning til både den bruger, der tidligere var assigned kommentaren, og den bruger der netop er blevet assigned.\r\n\r\nTypisk kunne man have brug for, at sende en mail til den bruger, der bliver assigned - dette kan i så fald gøres med nedenstående kode:\r\n\r\n```\r\n#!C\r\npublic override void OnUserAssigned(FeedbackModule module, FeedbackEntry entry, IFeedbackUser oldUser, IFeedbackUser newUser) {\r\n\r\n    // Just skip now if no user was assigned\r\n    if (newUser == null) return;\r\n\r\n    // Skip if the new user is the same as the old user\r\n    if (oldUser != null \u0026\u0026 oldUser.Id == newUser.Id) return;\r\n\r\n    // Get the first name of the user\r\n    string firstName = newUser.Name.Split(' ')[0];\r\n\r\n    // Generate the body of the mail to be sent (DescribeEntry is a custom method)\r\n    StringBuilder sb = new StringBuilder();\r\n    sb.AppendLine(\"Hi \" + firstName + \" (\" + newUser.Email + \"),\u003cbr /\u003e\");\r\n    sb.AppendLine(\"\u003cbr /\u003e\");\r\n    sb.AppendLine(\"You have been assigned the following feedback entry:\u003cbr /\u003e\");\r\n    sb.AppendLine(\"\u003cbr /\u003e\");\r\n    DescribeEntry(sb, entry);\r\n\r\n    // Send the mail to the user (again a custom user)\r\n    SendMail(\"DC Intra - You were assigned to a feedback entry\", entry.AssignedTo, sb.ToString());\r\n\r\n}\r\n```\r\n\r\n### OnEntryResultRender\r\n\r\nHar vi brug for at ændre værdierne for de kommentarer, der vises i listen i backoffice, kan dette gøres via `OnEntryResultRender`-metoden. Et eksempel kunne her være virtuelle URL'er på DC's intranet:\r\n\r\n\r\n```\r\n#!C\r\npublic override void OnEntryResultRender(FeedbackModule module, FeedbackEntryResult result) {\r\n\r\n    // If the page wasn't found in the Umbraco cache, we don't bother looking up the virtual URL\r\n    if (result.Page == null) return;\r\n\r\n    // Check for document types using URL rewriting\r\n    switch (result.Page.Content.DocumentTypeAlias) {\r\n        case IntranetConstants.DocumentTypes.IntranetNewsPage:\r\n        case IntranetConstants.DocumentTypes.IntranetBlogger:\r\n        case IntranetConstants.DocumentTypes.IntranetBlogPage:\r\n        case IntranetConstants.DocumentTypes.IntranetActivityPage: {\r\n\r\n            // Lookup the parent site in the Umbraco cache\r\n            IPublishedContent site = UmbracoContext.Current.ContentCache.GetById(result.SiteId);\r\n\r\n            if (site == null) {\r\n                result.Page.Url = null;\r\n            } else {\r\n\r\n                string url = result.Page.Content.UrlWithDomain();\r\n\r\n                int pos1 = url.IndexOf(\"//\");\r\n                int pos2 = pos1 \u003e= 0 ? url.IndexOf('/', pos1 + 2) : url.IndexOf('/');\r\n\r\n                // Calculate the virtual URL\r\n                string domain = (pos2 \u003e 0 ? url.Substring(0, pos2) : \"\");\r\n                result.Page.Url = domain + Utils.VirtualPages.GetVirtualUrl(result.Page.Content, IntranetOverview.GetFromContent(site));\r\n\r\n            }\r\n\r\n            break;\r\n\r\n        }\r\n\r\n    }\r\n\r\n}\r\n```\r\n\r\n## Config\r\n\r\nI forhold til konfigurationen, var det tanken, at man kunne angive denne i `~/config/SkybrudFeedback.config`, men dette er endnu ikke blevet implementeret. Feedback-modulet kører derfor med en fast konfiguration.\r\n\r\n### Statuses\r\n\r\nKonfiguration definerer tre valgmuligheder ift. til en kommentar's status. I standardkonfigurationen er der defineres disse tre:\r\n\r\n* New (ny)\r\n* In progress (inprogress)\r\n* Closed (closed)\r\n\r\nEn ny kommentar vil indledelsesvist blive markeret som **New**, mens en redaktør i backoffice efterfølgende kan ændre til en anden status - eller tilbage til **New** for den sags skyld.\r\n\r\n### Profiles\r\n\r\nKonfigurationen definerer også en eller flere profiler. Feedback-modulet har en standardprofil for hele løsningen, men det er desuden muligt, at definere et profil for et givent site, som så tager over i stedet.\r\n\r\n#### Ratings\r\n\r\nHver enkelt profil definerer en række ratings, som det er muligt for brugeren af hjemmesiden at vælge mellem, når han eller hun skal tilføje en ny kommentar. I den globale standardprofil er der defineret følgende ratings:\r\n\r\n* Positive (positive)\r\n* Negative (negative)\r\n\r\n##### Konfigurationsfil\r\n\r\nPlanen var oprindeligt, at der skulle være en konfigurationsfil i løsning, som blev brugt som udgangspunkt, men at konfiguration (i hukommelsen) så senere (fx ved app start) kunne ændres fra koden. Delen med konfigurationsfilen er endnu ikke blevet implementeret, men det muligt, at ændre konfiguration fra koden.\r\n\r\n##### Kodeeksempler\r\n\r\nIfølge standardprofilen er brugerens emailadresse obligatorisk, mens navn og selve beskeden er valgfri. Hvis du fx vil ændre standardprofilen til, at emailadresse også er valgfri, kan det eksempelvis gøres med følgende stump kode:\r\n\r\n```\r\n#!C\r\nFeedbackConfig.Current.Profiles[0].Fields.Email = FeedbackFieldType.Optional;\r\n```\r\n\r\nAlternativt kan der tilføjes en ny profil for et givent site - dette kan gøres med nedenstående stump kode:\r\n\r\n```\r\n#!C\r\nFeedbackConfig.Current.Profiles.Add(1234, new FeedbackProfile {\r\n    Fields = new FeedbackProfileFields {\r\n        Name = FeedbackFieldType.Optional,\r\n        Email = FeedbackFieldType.Optional,\r\n        Comment = FeedbackFieldType.Optional\r\n    },\r\n    Ratings = FeedbackConfig.Current.Profiles[0].Ratings\r\n});\r\n```\r\n\r\nI eksemplet tilføjes en ny profil for sitet med id'et `1234`, hvor alle tre felter (email, navn og besked) sættes til valgfri. Profilen definerer ligeledes de mulige rating, men her refereres blot de rating, der er defineret for standardprofilen.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskybrud%2FSkybrud.Umbraco.Feedback","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskybrud%2FSkybrud.Umbraco.Feedback","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskybrud%2FSkybrud.Umbraco.Feedback/lists"}