github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/model/search_params.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package model 5 6 import ( 7 "regexp" 8 "strings" 9 "time" 10 ) 11 12 var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) 13 var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) 14 15 type SearchParams struct { 16 Terms string 17 ExcludedTerms string 18 IsHashtag bool 19 InClasses []string 20 ExcludedClasses []string 21 FromUsers []string 22 ExcludedUsers []string 23 AfterDate string 24 ExcludedAfterDate string 25 BeforeDate string 26 ExcludedBeforeDate string 27 OnDate string 28 ExcludedDate string 29 OrTerms bool 30 IncludeDeletedClasses bool 31 TimeZoneOffset int 32 // True if this search doesn't originate from a "current user". 33 SearchWithoutUserId bool 34 } 35 36 // Returns the epoch timestamp of the start of the day specified by SearchParams.AfterDate 37 func (p *SearchParams) GetAfterDateMillis() int64 { 38 date, err := time.Parse("2006-01-02", PadDateStringZeros(p.AfterDate)) 39 if err != nil { 40 date = time.Now() 41 } 42 43 // travel forward 1 day 44 oneDay := time.Hour * 24 45 afterDate := date.Add(oneDay) 46 return GetStartOfDayMillis(afterDate, p.TimeZoneOffset) 47 } 48 49 // Returns the epoch timestamp of the start of the day specified by SearchParams.ExcludedAfterDate 50 func (p *SearchParams) GetExcludedAfterDateMillis() int64 { 51 date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedAfterDate)) 52 if err != nil { 53 date = time.Now() 54 } 55 56 // travel forward 1 day 57 oneDay := time.Hour * 24 58 afterDate := date.Add(oneDay) 59 return GetStartOfDayMillis(afterDate, p.TimeZoneOffset) 60 } 61 62 // Returns the epoch timestamp of the end of the day specified by SearchParams.BeforeDate 63 func (p *SearchParams) GetBeforeDateMillis() int64 { 64 date, err := time.Parse("2006-01-02", PadDateStringZeros(p.BeforeDate)) 65 if err != nil { 66 return 0 67 } 68 69 // travel back 1 day 70 oneDay := time.Hour * -24 71 beforeDate := date.Add(oneDay) 72 return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset) 73 } 74 75 // Returns the epoch timestamp of the end of the day specified by SearchParams.ExcludedBeforeDate 76 func (p *SearchParams) GetExcludedBeforeDateMillis() int64 { 77 date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedBeforeDate)) 78 if err != nil { 79 return 0 80 } 81 82 // travel back 1 day 83 oneDay := time.Hour * -24 84 beforeDate := date.Add(oneDay) 85 return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset) 86 } 87 88 // Returns the epoch timestamps of the start and end of the day specified by SearchParams.OnDate 89 func (p *SearchParams) GetOnDateMillis() (int64, int64) { 90 date, err := time.Parse("2006-01-02", PadDateStringZeros(p.OnDate)) 91 if err != nil { 92 return 0, 0 93 } 94 95 return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset) 96 } 97 98 // Returns the epoch timestamps of the start and end of the day specified by SearchParams.ExcludedDate 99 func (p *SearchParams) GetExcludedDateMillis() (int64, int64) { 100 date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedDate)) 101 if err != nil { 102 return 0, 0 103 } 104 105 return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset) 106 } 107 108 var searchFlags = [...]string{"from", "class", "in", "before", "after", "on"} 109 110 type flag struct { 111 name string 112 value string 113 exclude bool 114 } 115 116 type searchWord struct { 117 value string 118 exclude bool 119 } 120 121 func splitWords(text string) []string { 122 words := []string{} 123 124 foundQuote := false 125 location := 0 126 for i, char := range text { 127 if char == '"' { 128 if foundQuote { 129 // Grab the quoted section 130 word := text[location : i+1] 131 words = append(words, word) 132 foundQuote = false 133 location = i + 1 134 } else { 135 nextStart := i 136 if i > 0 && text[i-1] == '-' { 137 nextStart = i - 1 138 } 139 words = append(words, strings.Fields(text[location:nextStart])...) 140 foundQuote = true 141 location = nextStart 142 } 143 } 144 } 145 146 words = append(words, strings.Fields(text[location:])...) 147 148 return words 149 } 150 151 func parseSearchFlags(input []string) ([]searchWord, []flag) { 152 words := []searchWord{} 153 flags := []flag{} 154 155 skipNextWord := false 156 for i, word := range input { 157 if skipNextWord { 158 skipNextWord = false 159 continue 160 } 161 162 isFlag := false 163 164 if colon := strings.Index(word, ":"); colon != -1 { 165 var flagName string 166 var exclude bool 167 if strings.HasPrefix(word, "-") { 168 flagName = word[1:colon] 169 exclude = true 170 } else { 171 flagName = word[:colon] 172 exclude = false 173 } 174 175 value := word[colon+1:] 176 177 for _, searchFlag := range searchFlags { 178 // check for case insensitive equality 179 if strings.EqualFold(flagName, searchFlag) { 180 if value != "" { 181 flags = append(flags, flag{ 182 searchFlag, 183 value, 184 exclude, 185 }) 186 isFlag = true 187 } else if i < len(input)-1 { 188 flags = append(flags, flag{ 189 searchFlag, 190 input[i+1], 191 exclude, 192 }) 193 skipNextWord = true 194 isFlag = true 195 } 196 197 if isFlag { 198 break 199 } 200 } 201 } 202 } 203 204 if !isFlag { 205 exclude := false 206 if strings.HasPrefix(word, "-") { 207 exclude = true 208 } 209 // trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards) 210 word = searchTermPuncStart.ReplaceAllString(word, "") 211 word = searchTermPuncEnd.ReplaceAllString(word, "") 212 213 // and remove extra pound #s 214 word = hashtagStart.ReplaceAllString(word, "#") 215 216 if len(word) != 0 { 217 words = append(words, searchWord{ 218 word, 219 exclude, 220 }) 221 } 222 } 223 } 224 225 return words, flags 226 } 227 228 func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams { 229 words, flags := parseSearchFlags(splitWords(text)) 230 231 hashtagTermList := []string{} 232 excludedHashtagTermList := []string{} 233 plainTermList := []string{} 234 excludedPlainTermList := []string{} 235 236 for _, word := range words { 237 if validHashtag.MatchString(word.value) { 238 if word.exclude { 239 excludedHashtagTermList = append(excludedHashtagTermList, word.value) 240 } else { 241 hashtagTermList = append(hashtagTermList, word.value) 242 } 243 } else { 244 if word.exclude { 245 excludedPlainTermList = append(excludedPlainTermList, word.value) 246 } else { 247 plainTermList = append(plainTermList, word.value) 248 } 249 } 250 } 251 252 hashtagTerms := strings.Join(hashtagTermList, " ") 253 excludedHashtagTerms := strings.Join(excludedHashtagTermList, " ") 254 plainTerms := strings.Join(plainTermList, " ") 255 excludedPlainTerms := strings.Join(excludedPlainTermList, " ") 256 257 inClasses := []string{} 258 excludedClasses := []string{} 259 fromUsers := []string{} 260 excludedUsers := []string{} 261 afterDate := "" 262 excludedAfterDate := "" 263 beforeDate := "" 264 excludedBeforeDate := "" 265 onDate := "" 266 excludedDate := "" 267 268 for _, flag := range flags { 269 if flag.name == "in" || flag.name == "class" { 270 if flag.exclude { 271 excludedClasses = append(excludedClasses, flag.value) 272 } else { 273 inClasses = append(inClasses, flag.value) 274 } 275 } else if flag.name == "from" { 276 if flag.exclude { 277 excludedUsers = append(excludedUsers, flag.value) 278 } else { 279 fromUsers = append(fromUsers, flag.value) 280 } 281 } else if flag.name == "after" { 282 if flag.exclude { 283 excludedAfterDate = flag.value 284 } else { 285 afterDate = flag.value 286 } 287 } else if flag.name == "before" { 288 if flag.exclude { 289 excludedBeforeDate = flag.value 290 } else { 291 beforeDate = flag.value 292 } 293 } else if flag.name == "on" { 294 if flag.exclude { 295 excludedDate = flag.value 296 } else { 297 onDate = flag.value 298 } 299 } 300 } 301 302 paramsList := []*SearchParams{} 303 304 if len(plainTerms) > 0 || len(excludedPlainTerms) > 0 { 305 paramsList = append(paramsList, &SearchParams{ 306 Terms: plainTerms, 307 ExcludedTerms: excludedPlainTerms, 308 IsHashtag: false, 309 InClasses: inClasses, 310 ExcludedClasses: excludedClasses, 311 FromUsers: fromUsers, 312 ExcludedUsers: excludedUsers, 313 AfterDate: afterDate, 314 ExcludedAfterDate: excludedAfterDate, 315 BeforeDate: beforeDate, 316 ExcludedBeforeDate: excludedBeforeDate, 317 OnDate: onDate, 318 ExcludedDate: excludedDate, 319 TimeZoneOffset: timeZoneOffset, 320 }) 321 } 322 323 if len(hashtagTerms) > 0 || len(excludedHashtagTerms) > 0 { 324 paramsList = append(paramsList, &SearchParams{ 325 Terms: hashtagTerms, 326 ExcludedTerms: excludedHashtagTerms, 327 IsHashtag: true, 328 InClasses: inClasses, 329 ExcludedClasses: excludedClasses, 330 FromUsers: fromUsers, 331 ExcludedUsers: excludedUsers, 332 AfterDate: afterDate, 333 ExcludedAfterDate: excludedAfterDate, 334 BeforeDate: beforeDate, 335 ExcludedBeforeDate: excludedBeforeDate, 336 OnDate: onDate, 337 ExcludedDate: excludedDate, 338 TimeZoneOffset: timeZoneOffset, 339 }) 340 } 341 342 // special case for when no terms are specified but we still have a filter 343 if len(plainTerms) == 0 && len(hashtagTerms) == 0 && 344 len(excludedPlainTerms) == 0 && len(excludedHashtagTerms) == 0 && 345 (len(inClasses) != 0 || len(fromUsers) != 0 || 346 len(excludedClasses) != 0 || len(excludedUsers) != 0 || 347 len(afterDate) != 0 || len(excludedAfterDate) != 0 || 348 len(beforeDate) != 0 || len(excludedBeforeDate) != 0 || 349 len(onDate) != 0 || len(excludedDate) != 0) { 350 paramsList = append(paramsList, &SearchParams{ 351 Terms: "", 352 ExcludedTerms: "", 353 IsHashtag: false, 354 InClasses: inClasses, 355 ExcludedClasses: excludedClasses, 356 FromUsers: fromUsers, 357 ExcludedUsers: excludedUsers, 358 AfterDate: afterDate, 359 ExcludedAfterDate: excludedAfterDate, 360 BeforeDate: beforeDate, 361 ExcludedBeforeDate: excludedBeforeDate, 362 OnDate: onDate, 363 ExcludedDate: excludedDate, 364 TimeZoneOffset: timeZoneOffset, 365 }) 366 } 367 368 return paramsList 369 }