PageRenderTime 61ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/Release/SSC-KP-20130517/Intranet.root/Intranet.Website/Plugins/GSA/GSA.ascx.cs

#
C# | 1604 lines | 1387 code | 98 blank | 119 comment | 122 complexity | 90bf1439e1715cb88619ae4838224b99 MD5 | raw file
Possible License(s): LGPL-2.0, GPL-2.0, LGPL-2.1
  1. using System;
  2. using System.Collections;
  3. using System.Configuration;
  4. using System.Data;
  5. using System.Linq;
  6. using System.Web;
  7. using System.Web.Security;
  8. using System.Web.UI;
  9. using System.Web.UI.HtmlControls;
  10. using System.Web.UI.WebControls;
  11. using System.Web.UI.WebControls.WebParts;
  12. using System.Xml;
  13. using Intranet.Utils;
  14. using Intranet.WebSite.Domain;
  15. using Intranet.WebSite.ConfigurationSections;
  16. using Intranet.ComponentLibrary;
  17. using Intranet.ComponentLibrary.ComponentLibraryComponents;
  18. using Intranet.WebSite.Plugins.Search;
  19. using Intranet.XmlObjects;
  20. using Intranet.XmlObjects.LanguagePacks;
  21. using System.Collections.Generic;
  22. using System.Text;
  23. using System.Text.RegularExpressions;
  24. using Intranet.Domain.ConfigurationSections;
  25. using System.Net;
  26. using System.Diagnostics;
  27. namespace Intranet.WebSite.Plugins.GSA
  28. {
  29. public partial class GSA : BaseWebSitePlugin
  30. {
  31. private const string GOOGLE_RESULTS_XSLT_NL = @"~/App_Data/DataFormatting/GoogleResultsNL.xslt";
  32. private const string GOOGLE_RESULTS_XSLT_EN = @"~/App_Data/DataFormatting/GoogleResultsENStandard.xslt";
  33. private const string PLUGINNAME = "SearchResults";
  34. private const string PLUGINCONFIGNAME = "SearchResultsConfig";
  35. protected GSALanguagePack currentLanguagePack;
  36. private List<Facet> facets = new List<Facet>();
  37. private List<Facet> facetsGeneral = new List<Facet>();
  38. private List<Facet> facetsIntranet = new List<Facet>();
  39. private List<Facet> facetsKennisplein = new List<Facet>();
  40. #region Init & Load
  41. protected override void OnInit(EventArgs e)
  42. {
  43. this.Name = PLUGINNAME;
  44. if (string.IsNullOrEmpty(this.ConfigurationName))
  45. {
  46. this.ConfigurationName = PLUGINCONFIGNAME;
  47. }
  48. base.OnInit(e);
  49. this.LoadLanguagePack();
  50. }
  51. private void LoadLanguagePack()
  52. {
  53. string languageFile = string.Empty;
  54. foreach (languagepack lp in this.CurrentConfiguration.languagepacks)
  55. {
  56. if (lp.cultureInfo == System.Globalization.CultureInfo.CurrentCulture.Name)
  57. {
  58. languageFile = lp.location;
  59. break;
  60. }
  61. }
  62. this.currentLanguagePack = new GSALanguagePack();
  63. this.currentLanguagePack = ObjectXMLSerializer<GSALanguagePack>.Load(Server.MapPath(languageFile));
  64. }
  65. protected void Page_Load(object sender, EventArgs e)
  66. {
  67. var searchParameters = GetSearchParameters(this.Request);
  68. InitializeAdvancedSearchControls(searchParameters);
  69. if (ContainsGSAQuery(searchParameters))
  70. {
  71. // Set search textbox to be the same as first advanced textbox.
  72. SearchTerm.Value = SearchTermAll.Value;
  73. string resultString = BuildResultString(searchParameters);
  74. SearchedFor.Text = HttpUtility.HtmlEncode(resultString);
  75. this.Page.Title = resultString;
  76. }
  77. else
  78. {
  79. if (searchParameters.ContainsKey("AdvancedSearch") && searchParameters["AdvancedSearch"] == "on")
  80. {
  81. this.Page.Title = currentLanguagePack.TitleAdvanced;
  82. }
  83. else
  84. {
  85. this.Page.Title = currentLanguagePack.TitleNormal;
  86. }
  87. }
  88. if (searchParameters.ContainsKey("AdvancedSearch") && searchParameters["AdvancedSearch"] == "on")
  89. {
  90. //this.SearchPageTitleLiteral.Text = "<h1>Geadvanceerde Zoeken</h1>";
  91. //this.AdvancedSearchLinkLiteral.Text = "<a href=\"javascript:void(0)\" class=\"AdvancedSearchLink\">Geadvanceerd zoeken Aan</a>&nbsp;<a href=\"javascript:void(0)\" class=\"AdvancedSearchLink\">Wissen</a>";
  92. this.SearchPageTitleLiteral.Text = String.Format("<h1>{0}</h1>", currentLanguagePack.TitleAdvanced);
  93. this.AdvancedSearchLinkLiteral.Text = String.Format("<a id=\"AdvancedSearchToggle\" href=\"javascript:void(0)\" class=\"AdvancedSearchLink\">{0}</a>&nbsp;<a id=\"AdvancedSearchClear\" href=\"javascript:void(0)\" class=\"AdvancedSearchLink\">{1}</a>", currentLanguagePack.AdvancedButtonOpen, currentLanguagePack.AdvancedButtonErase);
  94. }
  95. else
  96. {
  97. //this.SearchPageTitleLiteral.Text = "<h1>Zoeken</h1>";
  98. //this.AdvancedSearchLinkLiteral.Text = "<a href=\"javascript:void(0)\" class=\"AdvancedSearchLink\">Geadvanceerd zoeken</a>";
  99. this.SearchPageTitleLiteral.Text = String.Format("<h1>{0}</h1>", currentLanguagePack.TitleNormal);
  100. this.AdvancedSearchLinkLiteral.Text = String.Format("<a id=\"AdvancedSearchToggle\" href=\"javascript:void(0)\" class=\"AdvancedSearchLink\">{0}</a>", currentLanguagePack.AdvancedButtonClosed);
  101. }
  102. // Get results
  103. if (ContainsGSAQuery(searchParameters))
  104. {
  105. // Make sure we have xml output, and add any missing parameters.
  106. searchParameters["output"] = "xml_no_dtd";
  107. if (!searchParameters.ContainsKey("client")) searchParameters["client"] = this.currentLanguagePack.Client_FrontEnd;
  108. if (!searchParameters.ContainsKey("site")) searchParameters["site"] = this.currentLanguagePack.DefaultCollection;
  109. if (!searchParameters.ContainsKey("ie")) searchParameters["ie"] = "utf8";
  110. if (!searchParameters.ContainsKey("oe")) searchParameters["oe"] = "utf8";
  111. if (!searchParameters.ContainsKey("filter")) searchParameters["filter"] = "p"; // Else GSA sees all results from Kennisplein as the same
  112. if (searchParameters.ContainsKey("as_q")) searchParameters["dnavs"] = searchParameters["as_q"];
  113. // Turn advanced panel off
  114. if (searchParameters.ContainsKey("ShowAdvancedPanel")) searchParameters.Remove("ShowAdvancedPanel");
  115. var keys = new List<string>(searchParameters.Keys);
  116. foreach (var key in keys)
  117. {
  118. searchParameters[key] = DecodeUnicodeChars(searchParameters[key], true);
  119. }
  120. // Strip periods (.) from partialfields. Fixes searching for initials in author field, e.g. 'A.B. Fielding' becomes 'AB Fielding'.
  121. if (searchParameters.ContainsKey("partialfields"))
  122. {
  123. searchParameters["partialfields"] = searchParameters["partialfields"].Replace("%2E", String.Empty);
  124. }
  125. if (!searchParameters.ContainsKey("as_q"))
  126. {
  127. searchParameters.Add("as_q", "site:nl");
  128. }
  129. if (searchParameters.ContainsKey("as_q") && searchParameters["as_q"] == string.Empty)
  130. {
  131. searchParameters["as_q"] = "site:nl";
  132. }
  133. if (!searchParameters.ContainsKey("rc"))
  134. {
  135. //Always get actual result count instead of estimated
  136. searchParameters.Add("rc", "1");
  137. }
  138. this.GetResults(this.currentLanguagePack.GSAUrl + "?" + DictionaryToQuerystring(searchParameters));
  139. }
  140. //Set GSA Alerts Script
  141. int positionSearch = this.currentLanguagePack.GSAUrl.IndexOf("search");
  142. string serverAdres = this.currentLanguagePack.GSAUrl.Remove(positionSearch, 6);
  143. AlertJSLiteral.Text = "<script >function getHomeUrl() {return location.href = \"" + serverAdres + "ealerts?shu=\" + escape(document.location.href);}</script>";
  144. this.MyAlertsTitleLiteral.Text = this.currentLanguagePack.GSAAlertsText;
  145. this.GSAHelpLiteral.Text = "<img src=\"/App_Themes/1Logo/images/infoicon_small.jpg\" class=\"GSAInfo\" title='<div class=\"GSAAlertsInfo\">" +
  146. this.currentLanguagePack.GSAAlertsInfo +
  147. "</div>' />";
  148. }
  149. private static bool ContainsGSAQuery(Dictionary<string, string> searchParameters)
  150. {
  151. return searchParameters != null
  152. && !(searchParameters.Count == 2 && searchParameters.ContainsKey("AdvancedSearch") && searchParameters.ContainsKey("ShowAdvancedPanel"))
  153. && !(searchParameters.Count == 1 && searchParameters.ContainsKey("AdvancedSearch"))
  154. && !(searchParameters.Count == 0);
  155. }
  156. private static Dictionary<string, string> GetSearchParameters(HttpRequest currentRequest)
  157. {
  158. //var searchParameters = new Dictionary<string, string>();
  159. var searchParameters = QuerystringToDictionary(currentRequest);
  160. if (searchParameters == null) searchParameters = new Dictionary<string, string>();
  161. // Three possibilities: either 'SearchTerm' or Google-like params ('q', etc) in querystring, or a form post.
  162. if (!String.IsNullOrEmpty(currentRequest.QueryString["SearchTerm"]))
  163. {
  164. //searchParameters["q"] = HttpUtility.UrlEncode(currentRequest.QueryString["SearchTerm"]);
  165. searchParameters["q"] = currentRequest.QueryString["SearchTerm"];
  166. searchParameters.Remove("SearchTerm");
  167. }
  168. //else if (!string.IsNullOrEmpty(currentRequest.QueryString.ToString()))
  169. //{
  170. //var querystring = currentRequest.QueryString.ToString();
  171. // For some reason '+' is used instead of '%20'. Manually decoding them here as they don't get decoded like the rest.
  172. //querystring = querystring.Replace('+', ' ');
  173. //searchParameters = QuerystringToDictionary(querystring);
  174. // searchParameters = QuerystringToDictionary(currentRequest);
  175. //}
  176. else if (!string.IsNullOrEmpty(currentRequest.Form["SearchTerm"]))
  177. {
  178. //searchParameters["q"] = HttpUtility.UrlEncode(currentRequest.Form["SearchTerm"].ToString());
  179. searchParameters["q"] = currentRequest.Form["SearchTerm"].ToString();
  180. }
  181. var keys = new List<string>(searchParameters.Keys);
  182. foreach (var key in keys)
  183. {
  184. searchParameters[key] = DecodeUnicodeChars(searchParameters[key], true);
  185. }
  186. return searchParameters;
  187. }
  188. //private string BuildSearchTerm(Dictionary<string, string> searchParameters)
  189. //{
  190. // var terms = new List<string>();
  191. // // 'All words'
  192. // if (searchParameters.ContainsKey("q") && !String.IsNullOrEmpty(searchParameters["q"])) terms.Add(searchParameters["q"]);
  193. // // 'Any words'
  194. // if (searchParameters.ContainsKey("as_oq") && !String.IsNullOrEmpty(searchParameters["as_oq"]))
  195. // {
  196. // terms.Add(String.Join(" OR ", searchParameters["as_oq"].Split(' ')));
  197. // }
  198. // // 'Exact phrase'
  199. // if (searchParameters.ContainsKey("as_epq") && !String.IsNullOrEmpty(searchParameters["as_epq"]))
  200. // {
  201. // terms.Add("\"" + searchParameters["as_epq"] + "\"");
  202. // }
  203. // // 'Without words'
  204. // if (searchParameters.ContainsKey("as_eq") && !String.IsNullOrEmpty(searchParameters["as_eq"]))
  205. // {
  206. // var without = searchParameters["as_eq"].Split(' ');
  207. // terms.Add(String.Join(" ", without.Select(term => "-" + term).ToArray()));
  208. // }
  209. // //for (var i = 0; i < terms.Count; i++)
  210. // //{
  211. // // terms[i] = HttpUtility.UrlDecode(terms[i]);
  212. // //}
  213. // return String.Join(" ", terms.ToArray());
  214. //}
  215. private void InitializeAdvancedSearchControls(Dictionary<string, string> searchParameters)
  216. {
  217. // Initialize 'Source' dropdown list
  218. if (FilterBySource.Items.Count == 0)
  219. {
  220. //FilterBySource.Items.Add(new ListItem("Alle resultaten", "default_collection"));
  221. //var defaultSource = new ListItem("Alle resultaten", String.Empty);
  222. var defaultSource = new ListItem(currentLanguagePack.FilterBySourceDefaultValue, String.Empty);
  223. FilterBySource.Items.Add(defaultSource);
  224. defaultSource.Selected = true;
  225. // Collections string format example: 'IenM:staging-1ntra|Kennisplein:Kennisplein'
  226. //var collections = new Dictionary<string, string>();
  227. if (!String.IsNullOrEmpty(this.currentLanguagePack.Collections))
  228. {
  229. var pairs = this.currentLanguagePack.Collections.Split('|');
  230. foreach (var pair in pairs)
  231. {
  232. var col = pair.Split(':');
  233. FilterBySource.Items.Add(new ListItem(col[0], col[1]));
  234. }
  235. }
  236. if (FilterBySource.Items.Count <= 2)
  237. {
  238. //Only have 1 collection no need to show the filter by collection
  239. FilterBySourcePanel.Visible = false;
  240. }
  241. }
  242. //if (FilterByLanguage.Items.Count == 0)
  243. //{
  244. // foreach (var kvp in languageCodesToPrettyName)
  245. // {
  246. // FilterByLanguage.Items.Add(new ListItem(kvp.Value, kvp.Key));
  247. // }
  248. //}
  249. if (FilterByFiletype.Items.Count == 0)
  250. {
  251. foreach (var kvp in filetypes)
  252. {
  253. FilterByFiletype.Items.Add(new ListItem(kvp.Key, kvp.Value));
  254. }
  255. }
  256. FillAdvancedSearchInputs(searchParameters);
  257. }
  258. private void FillAdvancedSearchInputs(Dictionary<string, string> searchParameters)
  259. {
  260. // 'All words'
  261. if (searchParameters.ContainsKey("q") && !String.IsNullOrEmpty(searchParameters["q"]))
  262. {
  263. SearchTermAll.Value = searchParameters["q"];
  264. }
  265. // 'Any words'
  266. if (searchParameters.ContainsKey("as_oq") && !String.IsNullOrEmpty(searchParameters["as_oq"]))
  267. {
  268. var orTerms = searchParameters["as_oq"].Split(' ');
  269. for (int i = 0; i < orTerms.Length; i++)
  270. {
  271. if (i == 0) SearchTermAny1.Value = orTerms[i];
  272. if (i == 1) SearchTermAny2.Value = orTerms[i];
  273. if (i == 2) SearchTermAny3.Value = orTerms[i];
  274. }
  275. }
  276. // 'Exact phrase'
  277. if (searchParameters.ContainsKey("as_epq") && !String.IsNullOrEmpty(searchParameters["as_epq"]))
  278. {
  279. SearchTermPhrase.Value = searchParameters["as_epq"];
  280. }
  281. // 'Without words'
  282. if (searchParameters.ContainsKey("as_eq") && !String.IsNullOrEmpty(searchParameters["as_eq"]))
  283. {
  284. SearchTermWithout.Value = searchParameters["as_eq"];
  285. }
  286. // Source
  287. if (searchParameters.ContainsKey("site") && !String.IsNullOrEmpty(searchParameters["site"]))
  288. {
  289. foreach (ListItem sourceItem in FilterBySource.Items)
  290. {
  291. if (String.Equals(searchParameters["site"], sourceItem.Value, StringComparison.OrdinalIgnoreCase))
  292. {
  293. sourceItem.Selected = true;
  294. break;
  295. }
  296. }
  297. }
  298. if (searchParameters.ContainsKey("partialfields") && !String.IsNullOrEmpty(searchParameters["partialfields"]))
  299. {
  300. var partialFields = searchParameters["partialfields"];
  301. // Author
  302. List<string> authorParts = new List<string>();
  303. var authorRegex = new Regex(@"author:([^\.\|\)]+)", RegexOptions.IgnoreCase);
  304. foreach (Match m in authorRegex.Matches(searchParameters["partialfields"]))
  305. {
  306. if (m != null && m.Groups != null && m.Groups.Count >= 2) authorParts.Add(m.Groups[1].Value);
  307. }
  308. var author = String.Join(" ", authorParts.ToArray());
  309. author = author.Replace("%2E", ".");
  310. if (!String.IsNullOrEmpty(author)) FilterByAuthor.Value = author;
  311. // Language
  312. //var languagePattern = new Regex(@"dc%3Alanguage:([a-z]+)", RegexOptions.IgnoreCase);
  313. //var selectedLanguages = new List<string>();
  314. //foreach (Match m in languagePattern.Matches(partialFields))
  315. //{
  316. // if (m != null && m.Groups != null && m.Groups.Count >= 2) selectedLanguages.Add(m.Groups[1].Value);
  317. //}
  318. //foreach (ListItem languageItem in FilterByLanguage.Items)
  319. //{
  320. // languageItem.Selected = selectedLanguages.Contains(languageItem.Value);
  321. //}
  322. }
  323. if (searchParameters.ContainsKey("as_q") && !String.IsNullOrEmpty(searchParameters["as_q"]))
  324. {
  325. var as_qParameters = SplitAs_qParametersString(searchParameters["as_q"]);
  326. // Filetype
  327. foreach (ListItem filetypeItem in FilterByFiletype.Items)
  328. {
  329. if (!String.IsNullOrEmpty(filetypeItem.Value))
  330. {
  331. //filetypeItem.Selected = as_qParameters.Any(p => p.Contains("filetype:" + filetypeItem.Value.Split(',')[0], StringComparison.OrdinalIgnoreCase));
  332. filetypeItem.Selected = as_qParameters.Any(p => p.Contains("ext:" + filetypeItem.Value.Split(';')[0], StringComparison.OrdinalIgnoreCase));
  333. }
  334. }
  335. // Dates
  336. //var dateParam = as_qParameters.Find(p => p.Contains("daterange:", StringComparison.OrdinalIgnoreCase));
  337. var dateParam = as_qParameters.Find(p => p.Contains("inmeta:dc%3Adate:", StringComparison.OrdinalIgnoreCase));
  338. if (!String.IsNullOrEmpty(dateParam))
  339. {
  340. //var datesRegex = new Regex(@"daterange:(\d{4}-\d{1,2}-\d{1,2})?\.\.(\d{4}-\d{1,2}-\d{1,2})?", RegexOptions.IgnoreCase);
  341. var datesRegex = new Regex(@"inmeta:dc%3Adate:(\d{4}-\d{1,2}-\d{1,2})?\.\.(\d{4}-\d{1,2}-\d{1,2})?", RegexOptions.IgnoreCase);
  342. var match = datesRegex.Match(dateParam);
  343. if (match != null && match.Groups != null && match.Groups.Count >= 2)
  344. {
  345. SearchDateFrom.Value = ReverseDateString(match.Groups[1].Value);
  346. SearchDateTo.Value = ReverseDateString(match.Groups[2].Value);
  347. }
  348. }
  349. }
  350. }
  351. private string BuildResultString(Dictionary<string, string> searchParameters)
  352. {
  353. var descriptions = new List<string>();
  354. // 'All words'
  355. if (searchParameters.ContainsKey("q") && !String.IsNullOrEmpty(searchParameters["q"]))
  356. descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionAllWords, searchParameters["q"]));
  357. // 'Any words'
  358. if (searchParameters.ContainsKey("as_oq") && !String.IsNullOrEmpty(searchParameters["as_oq"]))
  359. {
  360. string textValue = string.Empty;
  361. string[] splitValues = searchParameters["as_oq"].Split();
  362. for (int i = 0; i < splitValues.Length; i++)
  363. {
  364. textValue += "'" + splitValues[i] + "'";
  365. if (i < splitValues.Length - 1)
  366. {
  367. textValue += " " + currentLanguagePack.AdvancedControlOR + " ";
  368. }
  369. }
  370. descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionAnyWords, textValue));
  371. }
  372. // 'Exact phrase'
  373. if (searchParameters.ContainsKey("as_epq") && !String.IsNullOrEmpty(searchParameters["as_epq"]))
  374. descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionExactPhrase, searchParameters["as_epq"]));
  375. // 'Without words'
  376. if (searchParameters.ContainsKey("as_eq") && !String.IsNullOrEmpty(searchParameters["as_eq"]))
  377. descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionWithoutWords, searchParameters["as_eq"]));
  378. // Source
  379. if (searchParameters.ContainsKey("site") && !String.IsNullOrEmpty(searchParameters["site"]))
  380. {
  381. var siteListItem = FilterBySource.Items.FindByValue(searchParameters["site"]);
  382. if (siteListItem != null && siteListItem != FilterBySource.Items[0])
  383. descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionSourceFilter, siteListItem.Text));
  384. }
  385. if (searchParameters.ContainsKey("partialfields") && !String.IsNullOrEmpty(searchParameters["partialfields"]))
  386. {
  387. // Author
  388. List<string> authorParts = new List<string>();
  389. var authorRegex = new Regex(@"author:([^\.\|\)]+)", RegexOptions.IgnoreCase);
  390. foreach (Match m in authorRegex.Matches(searchParameters["partialfields"]))
  391. {
  392. if (m != null && m.Groups != null && m.Groups.Count >= 2) authorParts.Add(m.Groups[1].Value);
  393. }
  394. var author = String.Join(" ", authorParts.ToArray());
  395. author = author.Replace("%2E", ".");
  396. if (!String.IsNullOrEmpty(author)) descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionByAuthor, author));
  397. // Language
  398. //var selectedLanguages = new List<string>();
  399. //var languageRegex = new Regex(@"dc%3Alanguage:([a-z]+)", RegexOptions.IgnoreCase);
  400. //foreach (Match matchLang in languageRegex.Matches(searchParameters["partialfields"]))
  401. //{
  402. // if (matchLang != null && matchLang.Groups != null && matchLang.Groups.Count >= 2)
  403. // {
  404. // var language = matchLang.Groups[1].Value;
  405. // if (!String.IsNullOrEmpty(language)) selectedLanguages.Add(LanguageCodesToPrettyText(language));
  406. // }
  407. //}
  408. //if (selectedLanguages.Count > 0)
  409. //{
  410. // var langText = String.Join(" of ", selectedLanguages.ToArray());
  411. // descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionLanguage, langText));
  412. //}
  413. }
  414. // Filetype / extensions
  415. if (searchParameters.ContainsKey("as_q") && !String.IsNullOrEmpty(searchParameters["as_q"]))
  416. {
  417. var selectedFiletypes = new List<string>();
  418. var extensionRegex = new Regex(@"ext:([a-z]+)", RegexOptions.IgnoreCase);
  419. foreach (Match matchExt in extensionRegex.Matches(searchParameters["as_q"]))
  420. {
  421. if (matchExt != null && matchExt.Groups != null && matchExt.Groups.Count >= 2)
  422. {
  423. var ext = matchExt.Groups[1].Value;
  424. if (!String.IsNullOrEmpty(ext))
  425. {
  426. var filetype = filetypes.FirstOrDefault(kvp => kvp.Value.Contains(ext));
  427. if (filetype.Key != null) selectedFiletypes.Add(filetype.Key);
  428. }
  429. }
  430. }
  431. if (selectedFiletypes.Count > 0)
  432. {
  433. selectedFiletypes = selectedFiletypes.Distinct().ToList();
  434. var filetypeText = String.Join(" of ", selectedFiletypes.ToArray());
  435. descriptions.Add(String.Format(currentLanguagePack.ResultDescriptionFiletype, filetypeText));
  436. }
  437. }
  438. // Build the description with comma's and a conjunction ('and') between the last two.
  439. if (descriptions.Count > 0)
  440. {
  441. string result = currentLanguagePack.ResultDescriptionStart + " ";
  442. if (descriptions.Count > 1)
  443. {
  444. result += String.Join(", ", descriptions.GetRange(0, descriptions.Count - 1).ToArray());
  445. result += currentLanguagePack.ResultDescriptionConjunction + descriptions.Last();
  446. }
  447. else
  448. {
  449. result += descriptions[0];
  450. }
  451. result += ".";
  452. return result;
  453. }
  454. else
  455. {
  456. return String.Empty;
  457. }
  458. }
  459. public static List<string> SplitAs_qParametersString(string as_q)
  460. {
  461. if (!String.IsNullOrEmpty(as_q))
  462. {
  463. // The OR and AND are used with the 'filetype' parameter, so gotta keep them intact. Transform them here temporarily to avoid the '+' split below.
  464. as_q = as_q.Replace("+OR+", "_OR_").Replace("+AND+", "_AND_");
  465. var as_qParametersTemp = as_q.Split('+');
  466. var as_qParameters = new List<string>();
  467. foreach (var tempParam in as_qParametersTemp)
  468. {
  469. if (!String.IsNullOrEmpty(tempParam))
  470. {
  471. // Only separate terms that contain ':', else assume it was a second part of the previous term.
  472. // For example, when searching for 'inmeta:RootCategory=R6.6', the '.' in 'R6.6' will get replaced by a space because GSA can't handle periods.
  473. // It will become 'inmeta:RootCategory=R6+6'. Because we split on '+', that would give odd results. That's why we use ':' to check.
  474. if (tempParam.Contains(":"))
  475. {
  476. as_qParameters.Add(tempParam);
  477. }
  478. else
  479. {
  480. if (as_qParameters.Count > 0)
  481. as_qParameters[as_qParameters.Count - 1] += '+' + tempParam;
  482. else
  483. as_qParameters.Add(tempParam);
  484. }
  485. }
  486. }
  487. for (var i = 0; i < as_qParameters.Count; i++)
  488. {
  489. // Return OR and AND to original state
  490. as_qParameters[i] = as_qParameters[i].Replace("_OR_", "+OR+").Replace("_AND_", "+AND+");
  491. // MF: The GSA returns '=' encoded but '~' not. At the same time, it DOES require them both encoded when passing them to GSA. Fuck the GSA. And fuck their whole '+'/' '/'%20' thing too :P.
  492. as_qParameters[i] = as_qParameters[i].Replace("~", "%7E").Replace("+", "%20");
  493. }
  494. return as_qParameters.ToList();
  495. }
  496. return new List<string>();
  497. }
  498. #endregion
  499. #region Search Results & Facets
  500. private void GetResults(string QueryString)
  501. {
  502. XmlDocument xmlGoogleResults = new XmlDocument();
  503. var request = (HttpWebRequest)WebRequest.Create(QueryString);
  504. request.Headers.Add("Accept-Language", "nl");
  505. request.UseDefaultCredentials = true;
  506. string responseFromServer;
  507. using (var response = request.GetResponse())
  508. {
  509. using (var stream = new System.IO.StreamReader(response.GetResponseStream(), Encoding.UTF8))
  510. {
  511. responseFromServer = stream.ReadToEnd();
  512. }
  513. }
  514. xmlGoogleResults.LoadXml(responseFromServer);
  515. RenderFacets(xmlGoogleResults);
  516. //Set results xslt
  517. string xsltFileLocation = string.Empty;
  518. xsltFileLocation = HttpContext.Current.Server.MapPath(GOOGLE_RESULTS_XSLT_NL);
  519. //var searchresults = XMLTransformer.TransformXMLToXHTML(xmlGoogleResults, xsltFileLocation);
  520. var searchresults = XMLTransformer.TransformXMLToXHTML_RespectDisableOutputEscaping(xmlGoogleResults, xsltFileLocation);
  521. //this.SearchResults.Text = HttpUtility.HtmlDecode(searchresults);
  522. this.SearchResults.Text = searchresults;
  523. //Fix Google links to Categories
  524. this.SearchResults.Text = this.SearchResults.Text.Replace("%5C", "\\");
  525. }
  526. private void RenderFacets(XmlDocument xmlSearchResults)
  527. {
  528. this.FacetsHeaderLiteral.Text = String.Format("<div class=\"resultheader\">{0}</div>", currentLanguagePack.FacetsTitle);
  529. // Collections string format example: 'IenM:staging-1ntra|Kennisplein:Kennisplein'
  530. var collections = new Dictionary<string, string>();
  531. if (!String.IsNullOrEmpty(this.currentLanguagePack.Collections))
  532. {
  533. var pairs = this.currentLanguagePack.Collections.Split('|');
  534. foreach (var pair in pairs)
  535. {
  536. var col = pair.Split(':');
  537. collections.Add(col[0], col[1]);
  538. }
  539. }
  540. // 'as_q' parameter, split on '+'
  541. List<string> as_qParameters = new List<string>();
  542. if (xmlSearchResults.SelectSingleNode("/GSP/PARAM[@name='as_q']") != null)
  543. {
  544. as_qParameters = SplitAs_qParametersString(xmlSearchResults.SelectSingleNode("/GSP/PARAM[@name='as_q']").Attributes["original_value"].Value);
  545. }
  546. // Rest of the search url without the 'as_q' parameter
  547. var urlWithoutAs_q = CreateSearchUrl(xmlSearchResults, "as_q");
  548. var searchQuerystring = new SearchQuerystring();
  549. XmlNodeList searchParams = xmlSearchResults.SelectNodes("/GSP/PARAM");
  550. if (searchParams != null)
  551. {
  552. foreach (XmlNode param in searchParams)
  553. {
  554. string name = param.Attributes["name"].Value;
  555. if (name != "start" && name != "swrnum" && !name.Contains("metabased_") && name != "dnavs")
  556. {
  557. string value = param.Attributes["original_value"].Value;
  558. if (name == "as_q")
  559. searchQuerystring.SetParamAs_q(SplitAs_qParametersString(value));
  560. else
  561. searchQuerystring.SetParam(name, value);
  562. }
  563. }
  564. }
  565. // MF: Changed sitecollection facet to use new SearchQuerystring class, for better control over adding and removing params.
  566. // If it works it might be better to convert the other facets too, so they also use the SearchQuerystring class.
  567. //facets.Add(CreateSiteCollectionFacet(xmlSearchResults, collections));
  568. //If theres only one collection there's no point in being able to filter on the same collection
  569. if (collections.Count > 1)
  570. {
  571. facets.Add(CreateSiteCollectionFacet(searchQuerystring, collections));
  572. }
  573. facets.AddRange(CreateMetadataFacets(xmlSearchResults, as_qParameters, urlWithoutAs_q));
  574. facets.Add(CreateDateFacet(as_qParameters, urlWithoutAs_q));
  575. facets.Add(CreateFiletypeFacet(as_qParameters, urlWithoutAs_q));
  576. foreach (var facet in facets)
  577. {
  578. switch (facet.Name)
  579. {
  580. case "PageType":
  581. case "RootCategory":
  582. facetsIntranet.Add(facet);
  583. break;
  584. case "kp:kennistype":
  585. facetsKennisplein.Add(facet);
  586. break;
  587. default:
  588. facetsGeneral.Add(facet);
  589. break;
  590. }
  591. }
  592. var haveSearchResults = (xmlSearchResults.SelectNodes("/GSP/RES").Count > 0);
  593. if (haveSearchResults || facetsGeneral.Any(f => f.Values.Any(v => v.Selected)))
  594. Filters.Text = String.Format("<div class=\"facetBlock\"><h4>{0}</h4>{1}</div>", currentLanguagePack.FacetsGeneralTitle, RenderFacetsHtml(facetsGeneral, haveSearchResults));
  595. else
  596. Filters.Visible = false;
  597. var siteCollectionFacet = facets.Find(f => f.Name == "SiteCollection");
  598. //Now Using lang pack values for Pretty names
  599. //var intranetIenMSelected = (siteCollectionFacet != null && siteCollectionFacet.Values.Any(v => v.PrettyLabel == "Intranet IenM" && v.Selected));
  600. //var kennispleinSelected = (siteCollectionFacet != null && siteCollectionFacet.Values.Any(v => v.PrettyLabel == "Kennisplein" && v.Selected));
  601. var intranetIenMSelected = (siteCollectionFacet != null && siteCollectionFacet.Values.Any(v => v.PrettyLabel == currentLanguagePack.FacetsIntranetTitle && v.Selected));
  602. var kennispleinSelected = (siteCollectionFacet != null && siteCollectionFacet.Values.Any(v => v.PrettyLabel == currentLanguagePack.FacetsKennispleinTitle && v.Selected));
  603. if (facetsIntranet.Count > 0 && intranetIenMSelected && (haveSearchResults || facetsIntranet.Any(f => f.Values.Any(v => v.Selected))))
  604. FiltersIntranet.Text = String.Format("<div class=\"facetBlock\"><h4>{0}</h4>{1}</div>", currentLanguagePack.FacetsIntranetTitle, RenderFacetsHtml(facetsIntranet, haveSearchResults));
  605. else
  606. IenMWrapper.Visible = false;
  607. if (facetsKennisplein.Count > 0 && kennispleinSelected && (haveSearchResults || facetsKennisplein.Any(f => f.Values.Any(v => v.Selected))))
  608. FiltersKennisplein.Text = String.Format("<div class=\"facetBlock\"><h4>{0}</h4>{1}</div>", currentLanguagePack.FacetsKennispleinTitle, RenderFacetsHtml(facetsKennisplein, haveSearchResults));
  609. else
  610. KennispleinWrapper.Visible = false;
  611. if (Filters.Visible == false && FiltersIntranet.Visible == false && FiltersKennisplein.Visible == false)
  612. FacetColumn.Style.Add("visibility", "hidden");
  613. }
  614. private static string CreateSearchUrl(XmlDocument xdoc, params string[] skipParameters)
  615. {
  616. XmlNodeList searchParams = xdoc.SelectNodes("/GSP/PARAM");
  617. StringBuilder link = new StringBuilder();
  618. if (searchParams != null)
  619. {
  620. foreach (XmlNode param in searchParams)
  621. {
  622. string name = param.Attributes["name"].Value;
  623. if (name != "start" && name != "swrnum" && !name.Contains("metabased_") && name != "dnavs" && !skipParameters.Contains(name))
  624. {
  625. string value = param.Attributes["original_value"].Value;
  626. link.Append(string.Format("{0}={1}", name, value));
  627. link.Append("&amp;");
  628. }
  629. }
  630. }
  631. return link.ToString();
  632. }
  633. //private Facet CreateSiteCollectionFacet(XmlDocument xmlSearchResults, Dictionary<string, string> collections)
  634. private Facet CreateSiteCollectionFacet(SearchQuerystring searchQuerystring, Dictionary<string, string> collections)
  635. {
  636. // 'site' parameter
  637. //string siteParameter = null;
  638. //if (xmlSearchResults.SelectSingleNode("/GSP/PARAM[@name='site']") != null)
  639. // siteParameter = xmlSearchResults.SelectSingleNode("/GSP/PARAM[@name='site']").Attributes["value"].Value;
  640. // Rest of the search url without the 'as_q' parameter
  641. //var urlWithoutSiteCollection = CreateSearchUrl(xmlSearchResults, "site");
  642. //var urlWithoutSiteCollection = CreateSearchUrl(xmlSearchResults, "site");
  643. // Create facet
  644. var siteFacet = new Facet("SiteCollection", currentLanguagePack.FacetSourceHeader, null);
  645. siteFacet.Values = new List<FacetValue>();
  646. foreach (var collection in collections)
  647. {
  648. siteFacet.Values.Add(new FacetValue(collection.Key, collection.Value));
  649. }
  650. // Any selected values?
  651. var site = searchQuerystring.GetParamValue("site");
  652. bool facetSelected = false;
  653. foreach (var value in siteFacet.Values)
  654. {
  655. value.Selected = String.Equals(site, value.Value, StringComparison.OrdinalIgnoreCase);
  656. if (value.Selected)
  657. {
  658. facetSelected = true;
  659. break;
  660. }
  661. }
  662. // Set urls for toggling facet values on / off
  663. var localQuerystring = searchQuerystring.Copy();
  664. if (facetSelected)
  665. {
  666. siteFacet.Values.RemoveAll(v => !v.Selected);
  667. localQuerystring.RemoveParam("site");
  668. // Hardcoded linked facets for now... also matched on their PrettyLabel which is not elegant either.
  669. // TODO: add to LanguagePack or Setting or something
  670. // Now using lang pack values for pretty labels
  671. //if (siteFacet.Values[0].PrettyLabel == "Intranet IenM")
  672. if (siteFacet.Values[0].PrettyLabel == currentLanguagePack.FacetsIntranetTitle)
  673. {
  674. localQuerystring.RemoveAs_qParam("inmeta:PageType");
  675. localQuerystring.RemoveAs_qParam("inmeta:RootCategory");
  676. }
  677. //else if (siteFacet.Values[0].PrettyLabel == "Kennisplein")
  678. else if (siteFacet.Values[0].PrettyLabel == currentLanguagePack.FacetsKennispleinTitle)
  679. {
  680. localQuerystring.RemoveAs_qParam("inmeta:kp%253Akennistype");
  681. }
  682. foreach (var value in siteFacet.Values)
  683. {
  684. value.Url = "?" + localQuerystring.ToString();
  685. }
  686. }
  687. else
  688. {
  689. foreach (var value in siteFacet.Values)
  690. {
  691. localQuerystring.SetParam("site", value.Value);
  692. value.Url = String.Format("?" + localQuerystring.ToString());
  693. }
  694. }
  695. return siteFacet;
  696. }
  697. private List<Facet> CreateMetadataFacets(XmlDocument xmlSearchResults, List<string> as_qParameters, string urlWithoutAs_q)
  698. {
  699. var facets = new List<Facet>();
  700. var metadataFacetNodes = xmlSearchResults.SelectNodes("/GSP/RES/PARM/PMT");
  701. foreach (XmlNode metadataFacetNode in metadataFacetNodes)
  702. {
  703. // Create facet (example: 'TargetGroup')
  704. var facet = new Facet()
  705. {
  706. Name = metadataFacetNode.Attributes["NM"].Value,
  707. PrettyLabel = metadataFacetNode.Attributes["DN"].Value,
  708. QueryPrefix = "inmeta",
  709. Values = new List<FacetValue>()
  710. };
  711. if (facet.Name == "dc:language")
  712. {
  713. facet = CreateLanguageFacet(xmlSearchResults, metadataFacetNode, facet);
  714. }
  715. else
  716. {
  717. // Add possible values (examples: 'SSO', 'IVW', 'VenW', ..)
  718. foreach (XmlNode facetValueNode in metadataFacetNode.SelectNodes("PV"))
  719. {
  720. var facetValue = new FacetValue()
  721. {
  722. Value = facetValueNode.Attributes["V"].Value,
  723. PrettyLabel = facetValueNode.Attributes["V"].Value,
  724. Count = Int32.Parse(facetValueNode.Attributes["C"].Value)
  725. };
  726. //if (facet.Name == "dc:language") facetValue.PrettyLabel = LanguageCodesToPrettyText(facetValue.PrettyLabel);
  727. string as_qItem = String.Empty;
  728. // If queryprefix contains column it needs to be double-escaped. Whyyyyyy...
  729. if (facet.Name.Contains(":"))
  730. {
  731. as_qItem = facet.QueryPrefix + ":" + CustomUrlEncode(CustomUrlEncode(facet.Name) + "=" + CustomUrlEncode(facetValue.Value));
  732. }
  733. else
  734. {
  735. // Periods (.) also require some custom businesslogic.
  736. if (facetValue.Value.Contains('.'))
  737. {
  738. as_qItem = facet.QueryPrefix + ":" + CustomUrlEncode(facet.Name + "~" + CustomUrlEncode(facetValue.Value).Replace(".", " "));
  739. }
  740. else
  741. {
  742. as_qItem = facet.QueryPrefix + ":" + CustomUrlEncode(facet.Name + "=" + CustomUrlEncode(facetValue.Value));
  743. }
  744. }
  745. as_qItem = DecodeUnicodeChars(as_qItem, false);
  746. // Currently selected?
  747. facetValue.Selected = as_qParameters.Contains(as_qItem, StringComparer.OrdinalIgnoreCase);
  748. // Search url for toggling this filter on/off
  749. var localAs_qParameters = as_qParameters.ToList();
  750. if (facetValue.Selected)
  751. localAs_qParameters.RemoveAll(p => String.Equals(p, as_qItem, StringComparison.OrdinalIgnoreCase));
  752. else
  753. localAs_qParameters.Add(as_qItem);
  754. facetValue.Url = String.Format("?{0}as_q={1}", urlWithoutAs_q, DecodeUnicodeChars(String.Join(" ", localAs_qParameters.ToArray()), true));
  755. facet.Values.Add(facetValue);
  756. }
  757. }
  758. facets.Add(facet);
  759. }
  760. return facets;
  761. }
  762. private Facet CreateLanguageFacet(XmlDocument xmlSearchResults, XmlNode metadataFacetNode, Facet facet)
  763. {
  764. // Add the possible languagefacet values & their counts. Split values like 'en fr' into 'en' and 'fr'.
  765. foreach (XmlNode facetValueNode in metadataFacetNode.SelectNodes("PV"))
  766. {
  767. // Examples: 'en', 'fr', 'nl en'
  768. var value = facetValueNode.Attributes["V"].Value;
  769. if (value != null) value = value.Trim().ToLower();
  770. var languageCodes = value.Split(' ');
  771. foreach (var languageCode in languageCodes)
  772. {
  773. var count = Int32.Parse(facetValueNode.Attributes["C"].Value);
  774. var existingValue = facet.Values.Find(fv => fv.Value == languageCode);
  775. if (existingValue != null) // If already exists, just add the count.
  776. {
  777. existingValue.Count += count;
  778. }
  779. else // Else add a new language.
  780. {
  781. facet.Values.Add(new FacetValue()
  782. {
  783. Value = languageCode,
  784. PrettyLabel = LanguageCodesToPrettyText(languageCode),
  785. Count = count
  786. });
  787. }
  788. }
  789. }
  790. // Get current partialfields param and create a url without it.
  791. string partialfields = String.Empty;
  792. if (xmlSearchResults.SelectSingleNode("/GSP/PARAM[@name='partialfields']") != null)
  793. partialfields = xmlSearchResults.SelectSingleNode("/GSP/PARAM[@name='partialfields']").Attributes["original_value"].Value;
  794. var urlWithoutPartialFields = CreateSearchUrl(xmlSearchResults, "partialfields");
  795. // Extract the list of currently selected languages based on the partialfields param.
  796. var languageContainerPattern = new Regex(@"\((dc%253Alanguage.+?)\)\.?", RegexOptions.IgnoreCase);
  797. var languageContainerContents = String.Empty;
  798. var languagePattern = new Regex(@"dc%253Alanguage:([a-z]+)", RegexOptions.IgnoreCase);
  799. var selectedLanguages = new List<string>();
  800. var containerMatch = languageContainerPattern.Match(partialfields);
  801. if (containerMatch.Success)
  802. {
  803. languageContainerContents = containerMatch.Groups[1].Value;
  804. foreach (Match languageMatch in languagePattern.Matches(containerMatch.Value))
  805. {
  806. if (languageMatch != null && languageMatch.Groups.Count >= 2)
  807. {
  808. var language = languageMatch.Groups[1].Value;
  809. if (!String.IsNullOrEmpty(language))
  810. selectedLanguages.Add(language.Trim().ToLower());
  811. }
  812. }
  813. }
  814. selectedLanguages = selectedLanguages.Distinct().ToList();
  815. // Set Selected property for each language (facet value)
  816. foreach (var value in facet.Values)
  817. {
  818. value.Selected = selectedLanguages.Contains(value.Value);
  819. }
  820. // If any languages are selected, remove all non-selected languages.
  821. // This is to prevent odd looking (though still technically correct) filters when the search results contain pages that have multiple languages.
  822. if (selectedLanguages.Count > 0)
  823. {
  824. facet.Values.RemoveAll(fv => !fv.Selected);
  825. }
  826. // Go through each languagefacet value again and set the Url property (for toggling this facet value on/off).
  827. foreach (var value in facet.Values)
  828. {
  829. string newPartialFields;
  830. if (value.Selected)
  831. {
  832. var newSelectedLanguages = selectedLanguages.Where(lang => lang != value.Value);
  833. if (newSelectedLanguages.Count() > 0)
  834. {
  835. // Deselect this language, other languages still selected (example: 'en, fr' -> 'en')
  836. var newLangFilters = String.Join("|", newSelectedLanguages.Select(lang => "dc%253Alanguage:" + lang).ToArray());
  837. newPartialFields = partialfields.Replace(languageContainerContents, newLangFilters);
  838. }
  839. else
  840. {
  841. // Deselect this language, no other languages selected (example: 'en' -> '')
  842. newPartialFields = partialfields.Replace(containerMatch.Value, String.Empty);
  843. }
  844. }
  845. else
  846. {
  847. var newSelectedLanguages = selectedLanguages.ToList();
  848. newSelectedLanguages.Add(value.Value);
  849. if (newSelectedLanguages.Count() > 1)
  850. {
  851. // Select this language, other languages also selected (example: 'fr' -> 'fr, en')
  852. var newLangFilters = String.Join("|", newSelectedLanguages.Select(lang => "dc%253Alanguage:" + lang).ToArray());
  853. newPartialFields = partialfields.Replace(languageContainerContents, newLangFilters);
  854. }
  855. else
  856. {
  857. // Select this language, no other languages selected (example: '' -> 'en')
  858. newPartialFields = "(dc%253Alanguage:" + value.Value + ")";
  859. if (!String.IsNullOrEmpty(partialfields)) newPartialFields = partialfields + "." + newPartialFields;
  860. }
  861. }
  862. if (!String.IsNullOrEmpty(newPartialFields))
  863. value.Url = String.Format("?{0}partialfields={1}", urlWithoutPartialFields, DecodeUnicodeChars(newPartialFields, true));
  864. else
  865. value.Url = "?" + urlWithoutPartialFields;
  866. }
  867. return facet;
  868. }
  869. private Facet CreateDateFacet(List<string> as_qParameters, string urlWithoutAs_q)
  870. {
  871. //var dateFacet = new Facet("Date", "Datum", "daterange");
  872. var dateFacet = new Facet("Date", currentLanguagePack.FacetDateHeader, "inmeta:dc%253Adate");
  873. // Predefined facets
  874. dateFacet.Values = new List<FacetValue>();
  875. dateFacet.Values.Add(new FacetValue(currentLanguagePack.FacetDateDay, FormatDateTimeForGoogle(DateTime.Now.AddDays(-1)) + ".."));
  876. dateFacet.Values.Add(new FacetValue(currentLanguagePack.FacetDateWeek, FormatDateTimeForGoogle(DateTime.Now.AddDays(-7)) + ".."));
  877. dateFacet.Values.Add(new FacetValue(currentLanguagePack.FacetDateMonth, FormatDateTimeForGoogle(DateTime.Now.AddMonths(-1)) + ".."));
  878. dateFacet.Values.Add(new FacetValue(currentLanguagePack.FacetDateYear, FormatDateTimeForGoogle(DateTime.Now.AddYears(-1)) + ".."));
  879. List<string> newAs_qParameters = null; // this variable is used multiple times below, didn't want to keep thinking up different variable names... :-P
  880. // Initialize the predefined facets
  881. foreach (var dateValue in dateFacet.Values)
  882. {
  883. // How this filter is represented in the 'as_q' parameter
  884. string newAs_q = dateFacet.QueryPrefix + ":" + dateValue.Value;
  885. // Currently selected?
  886. dateValue.Selected = as_qParameters.Contains(newAs_q, StringComparer.OrdinalIgnoreCase);
  887. // Search url for toggling this filter on/off
  888. newAs_qParameters = as_qParameters.ToList();
  889. if (dateValue.Selected)
  890. newAs_qParameters.RemoveAll(p => String.Equals(p, newAs_q, StringComparison.OrdinalIgnoreCase));
  891. else
  892. newAs_qParameters.Add(newAs_q);
  893. dateValue.Url = String.Format("?{0}as_q={1}", urlWithoutAs_q, String.Join(" ", newAs_qParameters.ToArray()));
  894. }
  895. // Any predefined facets selected? Then we're already done!
  896. var selectedFacetValue = dateFacet.Values.Find(fv => fv.Selected == true);
  897. if (selectedFacetValue != null)
  898. {
  899. // Remove all but the selected item
  900. dateFacet.Values.Clear();
  901. dateFacet.Values.Add(selectedFacetValue);
  902. return dateFacet;
  903. }
  904. // Else we need to add / check for a custom date range facet
  905. else
  906. {
  907. var customRangeValue = new FacetValue("DateCustomRange", null);
  908. // Is a custom range selected?
  909. var selectedCustomRangeFacetValue = as_qParameters.Find(p => p.IndexOf(dateFacet.QueryPrefix + ":") == 0);
  910. if (selectedCustomRangeFacetValue != null)
  911. {
  912. dateFacet.Values.Clear();
  913. dateFacet.Values.Add(customRangeValue);
  914. customRangeValue.Selected = true;
  915. customRangeValue.Value = selectedCustomRangeFacetValue.Substring(selectedCustomRangeFacetValue.IndexOf(":") + 1);
  916. newAs_qParameters = as_qParameters.ToList();
  917. newAs_qParameters.Remove(selectedCustomRangeFacetValue);
  918. customRangeValue.Url = String.Format("?{0}as_q={1}", urlWithoutAs_q, String.Join(" ", newAs_qParameters.ToArray()));
  919. }
  920. else
  921. {
  922. customRangeValue.Selected = false;
  923. customRangeValue.Value = String.Empty;
  924. newAs_qParameters = as_qParameters.ToList();
  925. newAs_qParameters.Add(dateFacet.QueryPrefix + ":{0}..{1}");
  926. customRangeValue.Url = String.Format("?{0}as_q={1}", urlWithoutAs_q, String.Join(" ", newAs_qParameters.ToArray()));
  927. dateFacet.Values.Add(customRangeValue);
  928. }
  929. return dateFacet;
  930. }
  931. }
  932. private Facet CreateFiletypeFacet(List<string> as_qParameters, string urlWithoutAs_q)
  933. {
  934. var filetypeFacet = new Facet("Filetype", currentLanguagePack.FacetFiletypeHeader, "ext");
  935. filetypeFacet.Values = new List<FacetValue>();
  936. foreach (var kvp in filetypes)
  937. {
  938. filetypeFacet.Values.Add(new FacetValue(kvp.Key, kvp.Value));
  939. }
  940. // Get currently selected filetype extensions
  941. var selectedFiletypeExtensions = new List<string>();
  942. var filetypeAs_qParam = as_qParameters.Find(p => p.StartsWith("ext", StringComparison.OrdinalIgnoreCase));
  943. if (filetypeAs_qParam != null)
  944. {
  945. var filetypeMatches = new Regex("ext:([a-z]+)", RegexOptions.IgnoreCase).Matches(filetypeAs_qParam);
  946. foreach (Match match in filetypeMatches)
  947. {
  948. if (match != null && match.Groups != null && match.Groups.Count >= 2 && !String.IsNullOrEmpty(match.Groups[1].Value))
  949. {
  950. selectedFiletypeExtensions.Add(match.Groups[1].Value.ToLower());
  951. }
  952. }
  953. }
  954. // Iterate over all facet values (Word, Excel, ...) and set their Selected property
  955. foreach (var value in filetypeFacet.Values)
  956. {
  957. value.Selected = true;
  958. // Iterate over all extensions in a facet value (Word: doc, docx. ...)
  959. var extensions = value.Value.Split(';');
  960. for (var i = 0; i < extensions.Length; i++)
  961. {
  962. // If any of the extensions in this facet value do not appear in the selected extensions, it isn't selected.
  963. if (!selectedFiletypeExtensions.Any(ext => String.Equals(ext, extensions[i], StringComparison.OrdinalIgnoreCase)))
  964. {
  965. value.Selected = false;
  966. }
  967. }
  968. }
  969. // If we selected a facet value, remove the others
  970. if (filetypeFacet.Values.Any(v => v.Selected))
  971. {
  972. filetypeFacet.Values.RemoveAll(v => !v.Selected);
  973. }
  974. foreach (var value in filetypeFacet.Values)
  975. {
  976. var extensions = value.Value.Split(';');
  977. // Prepare list of extensions to use in facet value toggle url below
  978. var localSelectedFiletypeExtensions = new List<string>(selectedFiletypeExtensions);
  979. if (value.Selected)
  980. localSelectedFiletypeExtensions.RemoveAll(ext => extensions.Contains(ext));
  981. else
  982. localSelectedFiletypeExtensions.AddRange(extensions);
  983. var localAs_qParameters = as_qParameters.ToList();
  984. localAs_qParameters.Remove(filetypeAs_qParam);
  985. var newFiletypeAs_qParam = String.Join("%20OR%20", localSelectedFiletypeExtensions.Select(ext => "ext:" + ext).ToArray());
  986. localAs_qParameters.Add(newFiletypeAs_qParam);
  987. value.Url = String.Format("?{0}as_q={1}", urlWithoutAs_q, String.Join(" ", localAs_qParameters.ToArray()));
  988. }
  989. return filetypeFacet;
  990. }
  991. private string RenderFacetsHtml(List<Facet> facets, bool haveSearchResults)
  992. {
  993. StringBuilder html = new StringBuilder();
  994. //Only show Header if we have facets
  995. if (facets.Count > 0)
  996. {
  997. //html.Append("<div class=\"resultheader\" style=\"background-color: #DEEBE3;\"><div style=\"padding-left: 19px;\"><strong>Verfijn uw resultaten</strong></div></div>");
  998. }
  999. foreach (var facet in facets)
  1000. {
  1001. // If we don't have any search results AND none of the values of this facet are selected, don't show/render it.
  1002. if (!haveSearchResults && !facet.Values.Any(v => v.Selected)) continue;
  1003. html.Append("<div class=\"facet\">");
  1004. html.Append(string.Format("<div class=\"facetheader\">{0}</div>", facet.PrettyLabel));
  1005. html.AppendLine();
  1006. html.AppendLine("<ul class=\"facetlist\">");
  1007. int nodecount = 0;
  1008. foreach (var facetValue in facet.Values.OrderBy(v => !v.Selected).ThenByDescending(v => v.Count))
  1009. {
  1010. if (!(facet.Name == "Date" && facetValue.PrettyLabel == "DateCustomRange"))
  1011. {
  1012. if (facetValue.Selected)
  1013. {
  1014. html.Append(string.Format(
  1015. "<li class=\"facetitemselected\"><a href=\"{0}\" style=\"text-decoration:none;\">{1}<div class=\"facetdelete\"></div></a></li>",
  1016. facetValue.Url, facetValue.PrettyLabel
  1017. ));
  1018. }
  1019. else
  1020. {
  1021. html.Append(string.Format(
  1022. "<li class=\"facetitem\"><a href=\"{0}\" style=\"text-decoration:none;\">{1}</a>&nbsp;{2}</li>",
  1023. facetValue.Url, facetValue.PrettyLabel, facetValue.Count >= 0 ? "(" + facetValue.Count + ")" : String.Empty
  1024. ));
  1025. }
  1026. nodecount++;
  1027. }
  1028. }
  1029. if (facet.Name == "Date")
  1030. {
  1031. var customRangeFacetValue = facet.Values.Find(v => v.PrettyLabel == "DateCustomRange");
  1032. if (customRangeFacetValue != null)
  1033. {
  1034. if (customRangeFacetValue.Selected)
  1035. {
  1036. string dateFrom = String.Empty, dateTo = String.Empty;
  1037. var datePattern = new Regex(@"(\d{4}-\d{1,2}-\d{1,2})?\.\.(\d{4}-\d{1,2}-\d{1,2})?");
  1038. var match = datePattern.Match(customRangeFacetValue.Value ?? String.Empty);
  1039. if (match != null && match.Groups != null && match.Groups.Count >= 3)
  1040. {
  1041. var yearMonthDay = new Regex(@"(\d{4})-(\d{1,2})-(\d{1,2})");
  1042. dateFrom = yearMonthDay.Replace(match.Groups[1].Value, @"$3-$2-$1");
  1043. dateTo = yearMonthDay.Replace(match.Groups[2].Value, @"$3-$2-$1");
  1044. }
  1045. string selectedDateText = String.Empty;
  1046. if (!String.IsNullOrEmpty(dateFrom) && !String.IsNullOrEmpty(dateTo))
  1047. selectedDateText = dateFrom + " tot " + dateTo;
  1048. else if (!String.IsNullOrEmpty(dateFrom))
  1049. selectedDateText += "Vanaf " + dateFrom;
  1050. else if (!String.IsNullOrEmpty(dateTo))
  1051. selectedDateText += "Tot " + dateTo;
  1052. html.AppendLine(String.Format(
  1053. "<li class=\"facetitemselected\"><a href=\"{0}\" style=\"text-decoration:none;\">{1}<div class=\"facetdelete\"></div></a></li>",
  1054. customRangeFacetValue.Url, selectedDateText
  1055. ));
  1056. }
  1057. else
  1058. {
  1059. html.AppendLine(String.Format(@"<li class='datefilter'>
  1060. <input type='hidden' id='DateFacetUrl' value='{0}' />
  1061. <div>{1}</div>
  1062. <div>
  1063. <input id='DateFacetFrom' type='text' class='inputdate' /><span> - </span>
  1064. <input id='DateFacetTo' type='text' class='inputdate' />
  1065. <input id='DateFacetButton' type='button' value='{2}' class='datefilterbutton' />
  1066. </div>
  1067. </li>",
  1068. customRangeFacetValue.Url, currentLanguagePack.FacetDateChooseRange, currentLanguagePack.FacetDateConfirmRange
  1069. ));
  1070. }
  1071. }
  1072. }
  1073. html.AppendLine("</ul>");
  1074. if (nodecount > 8) html.AppendLine("<span class='toggleFacets'>&nbsp;</span>");
  1075. html.AppendLine("</div>");
  1076. }
  1077. return html.ToString();
  1078. }
  1079. private static Dictionary<string, string> filetypes = new Dictionary<string, string>()
  1080. {
  1081. { "Word", "doc;docx" },
  1082. { "Excel", "xls;xlsx" },
  1083. { "Powerpoint", "ppt;pptx" },
  1084. { "PDF", "pdf" }
  1085. };
  1086. // TODO: Languagepack, or better suited for upcoming Settings table?
  1087. // Also, 'xx' -> 'Overig' is quick & dirty way to deal with problem of the 'xx' language code.
  1088. private static Dictionary<string, string> languageCodesToPrettyName = new Dictionary<string, string>() {
  1089. { "en", "Engels" },
  1090. { "nl", "Nederlands" },
  1091. { "de", "Duits" },
  1092. { "fr", "Frans" },
  1093. { "it", "Italiaans" },
  1094. { "es", "Spaans" },
  1095. { "xx", "Overig" }
  1096. };
  1097. private string LanguageCodesToPrettyText(string languageCode)
  1098. {
  1099. // Need dutch names... don't think we can use RegionInfo for that :(. So let's use hardcoded Dictionary...
  1100. if (!String.IsNullOrEmpty(languageCode))
  1101. {
  1102. languageCode = languageCode.Trim();
  1103. var prettyNames = new List<string>();
  1104. foreach (var code in languageCode.Split(' '))
  1105. {
  1106. if (!String.IsNullOrEmpty(code))
  1107. {
  1108. if (languageCodesToPrettyName.ContainsKey(code))
  1109. prettyNames.Add(languageCodesToPrettyName[code]);
  1110. else
  1111. prettyNames.Add(code);
  1112. }
  1113. }
  1114. return String.Join(" ", prettyNames.ToArray());
  1115. }
  1116. else
  1117. {
  1118. //return "Onbekend";
  1119. return currentLanguagePack.FacetLanguageUnknown;
  1120. }
  1121. }
  1122. #endregion
  1123. #region Util
  1124. /// <summary>
  1125. /// Decodes any encoded unicode chars, for example "%u00eb" -> "ë".
  1126. /// Also can decode double encoded unicode chars, for example "%25u00eb".
  1127. /// </summary>
  1128. public static string DecodeUnicodeChars(string str, bool decodeTwice)
  1129. {
  1130. var unicodePatterns = new List<Regex>()
  1131. {
  1132. new Regex("%(25)?u[0-9a-f]{4}", RegexOptions.IgnoreCase),
  1133. new Regex("%(25)?[0-9a-f]{2}%(25)?[0-9a-f]{2}", RegexOptions.IgnoreCase)
  1134. };
  1135. foreach (var pattern in unicodePatterns)
  1136. {
  1137. foreach (Match match in pattern.Matches(str))
  1138. {
  1139. var decodedMatch = HttpUtility.UrlDecode(match.Value.ToLower());
  1140. if (decodeTwice) decodedMatch = HttpUtility.UrlDecode(decodedMatch);
  1141. str = str.Replace(match.Value, decodedMatch);
  1142. }
  1143. }
  1144. return str;
  1145. }
  1146. public static Dictionary<string, string> QuerystringToDictionary(HttpRequest request)
  1147. {
  1148. var dict = new Dictionary<string, string>();
  1149. if (request != null && request.QueryString != null)
  1150. {
  1151. foreach (var key in request.QueryString.AllKeys)
  1152. {
  1153. if (key != null) dict.Add(key, request.QueryString[key]);
  1154. }
  1155. }
  1156. //if (!String.IsNullOrEmpty(querystring))
  1157. //{
  1158. // if (querystring.StartsWith("?")) querystring = querystring.Substring(1);
  1159. // var parameters = querystring.Split('&');
  1160. // foreach (var param in parameters)
  1161. // {
  1162. // var kvp = param.Split('=');
  1163. // dict[kvp[0]] = kvp.Length > 1 ? kvp[1] : String.Empty;
  1164. // }
  1165. //}
  1166. return dict;
  1167. }
  1168. public static string DictionaryToQuerystring(Dictionary<string, string> dict)
  1169. {
  1170. string qs = String.Empty;
  1171. if (dict != null)
  1172. {
  1173. var parameters = dict.Select(kvp => kvp.Key + "=" + CustomUrlEncode(kvp.Value)).ToArray();
  1174. qs = String.Join("&", parameters);
  1175. }
  1176. return qs;
  1177. }
  1178. public static string CustomUrlEncode(string str)
  1179. {
  1180. str = HttpUtility.UrlEncode(str);
  1181. str = str.Replace("+", "%20").Replace("-", "%2D");
  1182. str = UpperCaseEncode(str);
  1183. return str;
  1184. }
  1185. public static string UpperCaseEncode(string value)
  1186. {
  1187. char[] lcencode = value.ToCharArray();
  1188. for (int i = 0; i < lcencode.Length - 2; i++)
  1189. {
  1190. if (lcencode[i] == '%')
  1191. {
  1192. lcencode[i + 1] = char.ToUpper(lcencode[i + 1]);
  1193. lcencode[i + 2] = char.ToUpper(lcencode[i + 2]);
  1194. }
  1195. }
  1196. return new string(lcencode);
  1197. }
  1198. /// <summary>
  1199. /// Reverses a date string from YYYY-MM-DD to DD-MM-YYYY.
  1200. /// </summary>
  1201. public static string ReverseDateString(string date)
  1202. {
  1203. var pattern = new Regex(@"(\d{4})-(\d{1,2})-(\d{1,2})", RegexOptions.IgnoreCase);
  1204. var match = pattern.Match(date);
  1205. if (match != null && match.Groups != null && match.Groups.Count >= 3)
  1206. {
  1207. return match.Groups[3] + "-" + match.Groups[2] + "-" + match.Groups[1];
  1208. }
  1209. return String.Empty;
  1210. }
  1211. private static string FormatDateTimeForGoogle(DateTime dt)
  1212. {
  1213. return dt.Year + "-" + dt.Month + "-" + dt.Day;
  1214. }
  1215. #endregion
  1216. }
  1217. class Facet
  1218. {
  1219. public string PrettyLabel;
  1220. public string Name;
  1221. public string QueryPrefix;
  1222. public List<FacetValue> Values;
  1223. public Facet() { }
  1224. public Facet(string name, string label, string queryPrefix)
  1225. {
  1226. this.Name = name;
  1227. this.PrettyLabel = label;
  1228. this.QueryPrefix = queryPrefix;
  1229. }
  1230. }
  1231. class FacetValue
  1232. {
  1233. public string PrettyLabel;
  1234. public string Value;
  1235. public int Count = -1;
  1236. public bool Selected = false;
  1237. public string Url;
  1238. public FacetValue() { }
  1239. public FacetValue(string label, string value)
  1240. {
  1241. this.PrettyLabel = label;
  1242. this.Value = value;
  1243. }
  1244. }
  1245. class SearchQuerystring
  1246. {
  1247. // Parameters that only have 1 facet linked to them, e.g. the parameter 'site' is only used by the facet 'SiteCollection'
  1248. private List<SinglepartParam> singleParams = new List<SinglepartParam>();
  1249. // The 'as_q' parameter has multiple facets linked to it, like the Date facet and Filetype facet.
  1250. // Example: 'as_q=inmeta:dc%253Adate:2011-10-26..%20ext:pdf'
  1251. private MultipartParam as_qMultiParam;
  1252. public string GetParamValue(string paramName)
  1253. {
  1254. var existingParam = this.singleParams.Find(p => String.Equals(p.paramName, paramName, StringComparison.OrdinalIgnoreCase));
  1255. if (existingParam != null)
  1256. {
  1257. return existingParam.paramValue;
  1258. }
  1259. return null;
  1260. }
  1261. public void SetParam(string paramName, string paramValue)
  1262. {
  1263. var existingParam = this.singleParams.Find(p => String.Equals(p.paramName, paramName, StringComparison.OrdinalIgnoreCase));
  1264. if (existingParam != null)
  1265. {
  1266. existingParam.paramValue = paramValue;
  1267. }
  1268. else
  1269. {
  1270. this.singleParams.Add(new SinglepartParam()
  1271. {
  1272. paramName = paramName,
  1273. paramValue = paramValue
  1274. });
  1275. }
  1276. }
  1277. public void RemoveParam(string paramName)
  1278. {
  1279. this.singleParams.RemoveAll(p => String.Equals(p.paramName, paramName, StringComparison.OrdinalIgnoreCase));
  1280. }
  1281. public void SetParamAs_q(List<string> paramValues)
  1282. {
  1283. this.as_qMultiParam = new MultipartParam()
  1284. {
  1285. paramName = "as_q",
  1286. paramValues = paramValues
  1287. };
  1288. }
  1289. public void RemoveAs_qParam(string paramValue)
  1290. {
  1291. if (this.as_qMultiParam != null)
  1292. {
  1293. this.as_qMultiParam.paramValues.RemoveAll(p => p.StartsWith(paramValue, StringComparison.OrdinalIgnoreCase));
  1294. }
  1295. }
  1296. public override string ToString()
  1297. {
  1298. var querystringParams = new List<string>();
  1299. foreach (var p in singleParams)
  1300. {
  1301. querystringParams.Add(p.paramName + "=" + p.paramValue);
  1302. }
  1303. if (as_qMultiParam != null && as_qMultiParam.paramValues != null)
  1304. {
  1305. var as_qValue = String.Join(" ", as_qMultiParam.paramValues.ToArray());
  1306. if (!String.IsNullOrEmpty(as_qValue)) querystringParams.Add("as_q=" + as_qValue);
  1307. }
  1308. return String.Join("&amp;", querystringParams.ToArray());
  1309. }
  1310. public SearchQuerystring Copy()
  1311. {
  1312. var copy = new SearchQuerystring();
  1313. if (this.singleParams != null)
  1314. {
  1315. foreach (var i in singleParams)
  1316. {
  1317. copy.singleParams.Add(i.Copy());
  1318. }
  1319. }
  1320. if (this.as_qMultiParam != null) copy.as_qMultiParam = this.as_qMultiParam.Copy();
  1321. return copy;
  1322. }
  1323. }
  1324. [DebuggerDisplay("{paramName} = {paramValue}")]
  1325. class SinglepartParam
  1326. {
  1327. public string paramName;
  1328. public string paramValue;
  1329. public SinglepartParam Copy()
  1330. {
  1331. return new SinglepartParam()
  1332. {
  1333. paramName = this.paramName,
  1334. paramValue = this.paramValue
  1335. };
  1336. }
  1337. }
  1338. class MultipartParam
  1339. {
  1340. public string paramName;
  1341. public List<string> paramValues = new List<string>();
  1342. public MultipartParam Copy()
  1343. {
  1344. var copy = new MultipartParam();
  1345. copy.paramName = paramName;
  1346. if (paramValues != null)
  1347. {
  1348. copy.paramValues = new List<string>(paramValues);
  1349. }
  1350. return copy;
  1351. }
  1352. }
  1353. }