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  }