403Webshell
Server IP : 103.118.17.23  /  Your IP : 216.73.216.169
Web Server : Microsoft-IIS/10.0
System : Windows NT RESELLERPLESK22 10.0 build 20348 (Windows Server 2016) AMD64
User : IWAM_plesk(default) ( 0)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  E:/Inetpub/vhosts/mesa.org.in/httpdocs/grievance/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : E:/Inetpub/vhosts/mesa.org.in/httpdocs/grievance/aspfn.asp
<%


Sub ew_CheckServer(ScriptName)
	On Error Resume Next
	Dim XmlHttp, Result, Box, Message
	Box = "<div class=""box box-danger box-solid"" style=""display: table; width: auto;""><div class=""box-header with-border""><h3 class=""box-title"">{title}</h3></div><div class=""box-body"">{message}</div></div>"
	Err.Clear
	Set XmlHttp = Server.CreateObject("MSXML2.ServerXMLHTTP")
	If Err.Number <> 0 Then
		If IsEmpty(gASPNETMessage) Then
			gASPNETMessage = "MSXML2.ServerXMLHTTP is required but it is not available on this server."
			Message = Replace(Replace(Box, "{title}", "Missing MSXML2.ServerXMLHTTP on this server"), "{message}", gASPNETMessage)
			If ew_NotEmpty(Page) Then
				Page.PageHeader = Message
			Else
				Response.Write Message
			End If
		End If
	Else

		' Test if ASP.NET page set up correctly first
		Result = ew_LoadContentByPost(ew_FullUrl(ScriptName, "aspnet"), "t=1")
		If Result <> "OK" Then
			If IsEmpty(gASPNETMessage) Then
				gASPNETMessage = ""
				Message = Replace(Replace(Box, "{title}", "ASP.NET not properly set up for this site"), "{message}", gASPNETMessage)
				If ew_NotEmpty(Page) Then
					Page.PageHeader = Message
				Else
					Response.Write Message
				End If
			End If
		End If
	End If
	Set XmlHttp = Nothing
	Dim fso, folder, tmpfile, tmpcontent
	Set fso = Server.Createobject("Scripting.FileSystemObject")
	tmpfile = "readme.txt"
	tmpcontent = "Important: This folder is used as the root folder of temporary folders for file upload fields during Add/Edit. It is also used as the root folder of the user files folder of CKEditor." & vbCrLf & vbCrLf & _
		"Make sure that the Web server user have read/write access to this folder." & vbCrLf & vbCrLf & _
		"See ASP Settings -> General Options -> File Upload in the help file for detail."
	folder = ew_UploadPathEx(True, EW_UPLOAD_DEST_PATH)
	If Not fso.FileExists(folder & tmpfile) Then
		If Not ew_SaveFile(folder, tmpfile, tmpcontent) Then
			If IsEmpty(gWritePermissionMessage) Then
				gWritePermissionMessage = "Write Permission is not set up for folder '" & ew_UploadPathEx(True, EW_UPLOAD_DEST_PATH) & "'"
				Message = Replace(Replace(Box, "{title}", "No write permission"), "{message}", gWritePermissionMessag)
				If ew_NotEmpty(Page) Then
					Page.PageHeader = Message
				Else
					Response.Write Message
				End If
			End If
		End If
	End If
	Set fso = Nothing
End Sub

'
' Attributes class ' ASP
' NOTE: Attribute name are case insensitve and stored as lowercase
'
Class cAttributes
	Dim Attributes

	' Class Initialize
	Private Sub Class_Initialize()
		Set Attributes = Dictionary()
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		Set Attributes = Nothing
	End Sub

	Public Function GetItem(Attr)
		Dim att
		att = Trim(Attr)
		GetItem = ""
		If att <> "" Then
			att = LCase(att)
			GetItem = Attributes.Get(att)
		End If
	End Function

	Public Default Property Get Item(Attr)
		Item = GetItem(Attr)
	End Property

	Public Property Let Item(Attr, Value)
		If Exists(Attr) Then
			Call UpdateAttr(Attr, Value, "u")
		Else
			Call AddAttr(Attr, Value)
		End If
	End Property

	Public Sub Clear()
		Call Attributes.Clear ' Clear attributes
	End Sub

	Public Function Exists(Attr)
		Dim att
		att = Trim(Attr)
		Exists = False
		If att <> "" Then
			att = LCase(att)
			Exists = Attributes.ContainsKey(att)
		End If
	End Function

	' Add attributes (as array of array)
	Public Sub AddAttributes(Attrs)
		Dim i
		If IsArray(Attrs) Then
			For i = 0 to UBound(Attrs)
				If IsArray(Attrs(i)) Then
					If UBound(Attrs(i)) >= 1 Then
						Call AddAttribute(Attrs(i)(0), Attrs(i)(1), True) ' Append
					End If
				End If
			Next
		End If
	End Sub

	' Add attribute
	Public Sub AddAttribute(Attr, Value, Append)
		Dim opt
		opt = ew_IIf(Append, "a", "p") ' True = Append, False = Prepend
		If Not UpdateAttr(Attr, Value, opt) Then
			Call AddAttr(Attr, Value)
		End If
	End Sub

	' Update attribute
	Public Sub UpdateAttribute(Attr, Value)
		If Not UpdateAttr(Attr, Value, "u") Then ' Update the attribute
			Call AddAttr(Attr, Value)
		End If
	End Sub

	' Update attribute based on option
	Private Function UpdateAttr(Attr, Value, Opt)
		Dim i, att, val, tmp
		att = Trim(Attr)
		val = Value
		If att <> "" And val <> "" Then
			att = LCase(att)
			If Attributes.ContainsKey(att) Then
				If Opt = "a" Then ' Append
					If ew_SameText(att, "class") Then
						tmp = Attributes.Get(att)
						Call ew_AppendClass(tmp, val)
						Call Attributes.Set(att, tmp)
					Else
						Call Attributes.Set(att, Attributes.Get(att) & " " & val)
					End If
				ElseIf Opt = "p" Then ' Prepend
					If ew_SameText(att, "class") Then
						tmp = Attributes.Get(att)
						Call ew_PrependClass(tmp, val)
						Call Attributes.Set(att, tmp)
					Else
						Call Attributes.Set(att, val & " " & Attributes.Get(att))
					End If
				Else ' Assume update
					Call Attributes.Set(att, val)
				End If
				UpdateAttr = True
				Exit Function
			End If
		End If
		UpdateAttr = False
	End Function

	' Add new attribute
	Private Function AddAttr(Attr, Value)
		Dim att, val
		att = Trim(Attr)
		val = Value
		If att <> "" And val <> "" Then
			att = LCase(att)
			If Not Attributes.ContainsKey(att) Then
				Call Attributes.Add(att, val)
				AddAttr = True
				Exit Function
			End If
		End If
		AddAttr = False
	End Function

	' Attribute to string
	Private Function AttrToString(Attr, Value)
		Dim val, att, wrk
		att = Attr
		val = Value
		wrk = ""
		If att <> "" And (val <> "" Or ew_IsBooleanAttr(att) And Not ew_SameStr(Value, False)) Then ' Allow boolean attributes, e.g. "disabled" and alt
			wrk = wrk & " " & att
			If val <> "" Then wrk = wrk & "=""" & val & """"
		ElseIf att = "alt" And val = "" Then ' Allow alt="" since it is a required attribute
			wrk = wrk & " alt="""""
		End If
		AttrToString = wrk
	End Function

	' Update/Add attribute (alias of UpdateAttribute)
	Public Sub Update(Attr, Value)
		Call UpdateAttribute(Attr, Value)
	End Sub

	' Add new attribute (alias of AddAttr)
	Public Function Add(Attr, Value)
		Add = AddAttr(Attr, Value)
	End Function

	' Append attribute
	Public Sub Append(Attr, Value)
		Call AddAttribute(Attr, Value, True)
	End Sub

	' Prepend attribute
	Public Sub Prepend(Attr, Value)
		Call AddAttribute(Attr, Value, False)
	End Sub

	' Remove attribute
	Public Function Remove(Attr)
		Dim att
		att = Trim(Attr)
		Remove = False
		If att <> "" Then
			att = LCase(att)
			Remove = Attributes.Remove(att)
		End If
	End Function

	' To String
	Public Function ToString(Dict)
		Dim wrk, attr, keys, key, val
		wrk = ""
		keys = Attributes.Keys()
		For Each attr In Keys
			key = Trim(attr)
			val = Trim(Attributes.Get(attr))
			If IsDictionary(Dict) Then
				If Dict.ContainsKey(key) Then
					If ew_SameText(key, "style") Or ew_StartsText(key, "on") Then ' "style" and events
						val = val & ";" & Dict.Get(key)
					Else
						val = val & " " & Dict.Get(key)
					End If
					Dict.Remove(key)
				End If
			End If
			wrk = wrk & AttrToString(key, val)
		Next
		If IsDictionary(Dict) Then
			keys = Dict.Keys()
			For Each attr In Keys
				key = Trim(attr)
				val = Trim(Dict.Get(attr))
				wrk = wrk & AttrToString(key, val)
			Next
		End If
		ToString = wrk
	End Function

	' To array (as array of array)
	Public Function ToArray()
		Dim ar(), i, keys, key
		keys = Attributes.Keys()
		ReDim ar(UBound(keys))
		For i = 0 to UBound(keys)
			key = keys(i)
			ar(i) = Array(key, Attributes.Get(key))
		Next
		ToArray = ar
	End Function
End Class

'
'  Export document class
'
Class cExportDocument
	Dim Table
	Dim Text
	Dim Line
	Dim Header
	Dim Style ' "v"(Vertical) or "h"(Horizontal)
	Dim Horizontal ' Horizontal
	Dim RowCnt
	Dim FldCnt
	Dim ExportCustom
	Dim HasPrev
	Dim HasParent
	Dim Items
	Dim Item

	' Class Initialize
	Private Sub Class_Initialize()
		Text = ""
		Line = ""
		Header = ""
		Style = "h"
		Horizontal = True
		RowCnt = 0
		FldCnt = 0
		ExportCustom = False
		HasPrev = False
		Set Items = Dictionary()
		HasParent = False
	End Sub

	Public Sub ChangeStyle(ToStyle)
		If LCase(ToStyle) = "v" Or LCase(ToStyle) = "h" Then
			Style = LCase(ToStyle)
		End If
		Horizontal = (Table.Export <> "xml" And (Style <> "v" Or Table.Export = "csv"))
	End Sub

	' Table Header
	Public Sub ExportTableHeader()
		Select Case Table.Export
			Case "html", "email", "word", "excel"
				Text = Text & "<table class=""ewExportTable"">"
			Case "csv"

				' No action
			Case "pdf"
				Text = Text & "<table class=""ewTablePdf"">"
			Case "json"
				If Items.Count() > 0 Then
					HasParent = True
				End If
				If HasParent Then
					Call Items.Add(Table.TableName, Dictionary())
				End If
		End Select
	End Sub

	' Field Caption
	Public Sub ExportCaption(fld)
		If Table.Export <> "json" Then
			FldCnt = FldCnt + 1
			Call ExportValueEx(fld, fld.ExportCaption)
		End If
	End Sub

	' Field value
	Public Sub ExportValue(fld)
		If Table.Export <> "json" Then
			Call ExportValueEx(fld, fld.ExportValue(Table.Export))
		End If
	End Sub

	' Field aggregate
	Public Sub ExportAggregate(fld, typ)
		If Table.Export <> "json" Then
			FldCnt = FldCnt + 1
			If Horizontal Then
				Dim val
				val = ""
				If typ = "TOTAL" Or typ = "COUNT" Or typ = "AVERAGE" Then
					val = Language.Phrase(typ) & ": " & fld.ExportValue(Table.Export)
				End If
				Call ExportValueEx(fld, val)
			End If
		End If
	End Sub

	' Export a value (caption, field value, or aggregate)
	Public Sub ExportValueEx(fld, val)
		Select Case Table.Export
			Case "html", "email", "word", "excel", "pdf"
				Text = Text & ("<td" & ew_IIf(EW_EXPORT_CSS_STYLES, fld.CellStyles, "") & ">")
				If Table.Export = "excel" And fld.FldDataType = EW_DATATYPE_STRING And IsNumeric(val) Then
					Text = Text & ("=""" & val & """")
				Else
					Text = Text & val
				End If
				Text = Text & "</td>"
				If Table.Export = "pdf" Then
					Line = Line & Text
				End If
			Case "csv"
				If fld.FldDataType <> EW_DATATYPE_BLOB Then
					If Line <> "" Then Line = Line & ","
					Line = Line & """" & Replace(val & "", """", """""") & """"
				End If
			Case "json"
				If fld.FldDataType = EW_DATATYPE_BOOLEAN Then ' Boolean
					val = ew_ConvertToBool(fld.CurrentValue)
				End If
				If HasParent Or Horizontal Then
					Call Item.Add(fld.FldParm, val)
				Else
					Call Items.Add(fld.FldParm, val)
				End If
		End Select
	End Sub

	' Begin a row
	Public Sub BeginExportRow(cnt)
		RowCnt = RowCnt + 1
		FldCnt = 0
		If Horizontal Then
			Select Case Table.Export
				Case "html", "email", "word", "excel"
					If cnt = -1 Then
						Table.CssClass = "ewExportTableFooter"
					ElseIf cnt = 0 Then
						Table.CssClass = "ewExportTableHeader"
					Else
						Table.CssClass = ew_IIf(cnt Mod 2 = 1, "ewExportTableRow", "ewExportTableAltRow")
					End If
					Text = Text & ("<tr" & ew_IIf(EW_EXPORT_CSS_STYLES, Table.RowStyles, "") & ">")
				Case "pdf"
					If cnt = -1 Then
						Table.CssClass = "ewTablePdfFooter"
					ElseIf cnt = 0 Then
						Table.CssClass = "ewTablePdfHeader"
					Else
						Table.CssClass = ew_IIf(cnt Mod 2 = 1, "ewTableRow", "ewTableAltRow")
					End If
					Line = "<tr" & ew_IIf(EW_EXPORT_CSS_STYLES, Table.RowStyles, "") & ">"
					Text = Text & Line
				Case "csv"
					Line = ""
				Case "json"
					If cnt > 0 Then
						Set Item = Dictionary()
					End If
			End Select
		End If
	End Sub

	' End a row
	Public Sub EndExportRow(cnt)
		If Horizontal Then
			Select Case Table.Export
				Case "html", "email", "word", "excel"
					Text = Text & "</tr>"
				Case "pdf"
					Line = Line & "</tr>"
					Text = Text & "</tr>"
					Header = Line
				Case "csv"
					Line = Line & vbCrLf
					Text = Text & Line
				Case "json"
					If cnt > 0 Then
						If HasParent Then
							Call Items.Get(Table.TableName).Push(Item)
						Else
							Call Items.Push(Item)
						End If
						Set Item = Nothing
					End If
			End Select
		End If
	End Sub

	' Empty row
	Public Sub ExportEmptyRow()
		Select Case Table.Export
			Case "html", "email", "word", "excel", "pdf"
				RowCnt = RowCnt + 1
				Text = Text & "<br>"
		End Select
	End Sub

	' Page break
	Public Sub ExportPageBreak()
		Select Case Table.Export
			Case "pdf"
				If Horizontal Then
					Text = Text & "</table>" ' end current table
					Text = Text & "<p style=""page-break-after:always;"">" ' page break
					Text = Text & "<table class=""ewTablePdf ewTablePdfBorder"">" ' new page header
					Text = Text & Header
				End If
		End Select
	End Sub

	' Export a field
	Public Sub ExportField(fld)
		Dim wrkExportValue, ar, i, fn, parm
		FldCnt = FldCnt + 1
		wrkExportValue = ""
		If fld.FldViewTag = "IMAGE" Then
			If Table.Export = "email" Or Table.Export = "pdf" Then ' Always use image for export to pdf/email
				wrkExportValue = ew_GetFileImgTag(fld, fld.GetTempImage())
			End If
		ElseIf IsArray(fld.HrefValue2) And (Table.Export = "email" Or Table.Export = "pdf") Then ' Export custom view tag
			ar = fld.HrefValue2
			fn = ""
			parm = ""
			For i = 0 to UBound(ar)
				If IsArray(ar(i)) Then
					If UBound(ar(i)) >= 1 Then
						If ar(i)(0) = "exportfn" Then
							fn = ar(i)(1)
						Else
							If parm <> "" Then parm = parm & ", "
							parm = parm & ar(i)(1)
						End If
					End If
				End If
			Next
			If fn <> "" Then
				wrkExportValue = Eval(fn & "(" & parm & ")")
			End If
		End If
		If wrkExportValue = "" And Not IsArray(fld.HrefValue2) Then
			If fld.HrefValue2 <> "" And Table.Export <> "pdf" Then ' Upload field
				If ew_NotEmpty(fld.Upload.DbValue) Then
					wrkExportValue = ew_GetFileATag(fld, fld.HrefValue2)
				End If
			End If
		End If
		If wrkExportValue = "" Then
			wrkExportValue = fld.ExportValue(Table.Export)
		End If
		If Horizontal Then
			Call ExportValueEx(fld, wrkExportValue)
		Else ' Vertical, export as a row
			If Table.Export = "json" Then
				Call ExportValueEx(fld, wrkExportValue)
			End If
			RowCnt = RowCnt + 1
			Dim tdcaption, tdvalue
			tdcaption = "<td"
			Select Case Table.Export
				Case "html", "email", "word", "excel"

					'tdcaption = tdcaption & " class=""ewTableHeader"""
				Case "pdf"
					tdcaption = tdcaption & " class=""" & ew_IIf(FldCnt Mod 2 = 1, "ewTableRow", "ewTableAltRow") & """"
			End Select
			tdcaption = tdcaption & ">"
			Select Case Table.Export
				Case "pdf"
					fld.CellCssClass = ew_IIf(FldCnt Mod 2 = 1, "ewTableRow", "ewTableAltRow")
				Case Else
					fld.CellCssClass = ew_IIf(FldCnt Mod 2 = 1, "ewExportTableRow", "ewExportTableAltRow")
			End Select
			tdvalue = "<td" & ew_IIf(EW_EXPORT_CSS_STYLES, fld.CellStyles, "") & ">"
			Text = Text & ("<tr>" & tdcaption & fld.ExportCaption & "</td>" & tdvalue & wrkExportValue & "</td></tr>")
		End If
	End Sub

	' Table Footer
	Public Sub ExportTableFooter()
		Select Case Table.Export
			Case "html", "email", "word", "excel", "pdf"
				Text = Text & "</table>"
			Case "json"
				Text = Items.ToJson()
		End Select
	End Sub

	' Get meta tag for charset
	Function CharsetMetaTag()
		CharsetMetaTag = "<meta http-equiv=""Content-Type"" content=""text/html" & ew_IIf(EW_CHARSET <> "", "; charset=" & EW_CHARSET, "") & """>" & vbCrLf
	End Function

	Public Sub ExportHeaderAndFooter()
		Dim Header, CssFile
		Select Case Table.Export
			Case "html", "email", "word", "excel", "pdf"
				Header = "<html><head>" & vbCrLf
				Header = Header & CharsetMetaTag()
				If EW_EXPORT_CSS_STYLES Then
					If Table.Export = "pdf" And EW_PDF_STYLESHEET_FILENAME <> "" Then
						CssFile = EW_PDF_STYLESHEET_FILENAME
					Else
						CssFile = EW_PROJECT_STYLESHEET_FILENAME
					End If
					Header = Header & "<style type=""text/css"">" & ew_LoadFile(CssFile) & "</style>" & vbCrLf
				End If
				Header = Header & "</" & "head>" & vbCrLf & "<body>" & vbCrLf
				Text = Header & Text & "</body></html>"
		End Select
	End Sub

	Private Sub Class_Terminate()
		Set Items = Nothing
	End Sub
End Class

' Get file img tag (for export to email/pdf only)
Function ew_GetFileImgTag(fld, fn)
	Dim wrkfiles, i, html
	html = ""
	If fn <> "" Then
		If fld.UploadMultiple Then
			wrkfiles = Split(fn, EW_MULTIPLE_UPLOAD_SEPARATOR)
			For i = 0 to UBound(wrkfiles)
				If wrkfiles(i) <> "" Then
					If html <> "" Then html = html & "<br>"
					html = html & "<img class=""ewImage"" src=""" & wrkfiles(i) & """ alt="""">"
				End If
			Next
		Else
			html = "<img class=""ewImage"" src=""" & fn & """ alt="""">"
		End If
	End If
	ew_GetFileImgTag = html
End Function

' Get file A tag
Function ew_GetFileATag(fld, fn)
	Dim wrkfiles, wrkpath, i, attrs, html
	wrkfiles = ""
	wrkpath = ""
	html = ""
	If fld.FldDataType = EW_DATATYPE_BLOB Then
		If ew_NotEmpty(fld.Upload.DbValue) Then wrkfiles = Array(fn)
	ElseIf fld.UploadMultiple Then
		wrkfiles = Split(fn, EW_MULTIPLE_UPLOAD_SEPARATOR)
		wrkpath = Mid(wrkfiles(0), 1, InStrRev(wrkfiles(0), "/")) ' Get path from first file name
		wrkfiles(0) = Mid(wrkfiles(0), Len(wrkpath)+1)
	Else
		If ew_NotEmpty(fld.Upload.DbValue) Then wrkfiles = Array(fn)
	End If
	If IsArray(wrkfiles) Then
		For i = 0 to UBound(wrkfiles)
			If wrkfiles(i) <> "" Then
				attrs = Array(Array("href", ew_FullUrl(wrkpath & wrkfiles(i), "href")))
				If html <> "" Then html = html & "<br>"
				html = html & ew_HtmlElement("a", attrs, fld.FldCaption, True)
			End If
		Next
	End If
	ew_GetFileATag = html
End Function

' Get file temp image
Function ew_GetFileTempImage(fld, val)
	Dim files, i, cnt, tmpimage, images
	If fld.UploadMultiple Then
		files = Split(val, EW_MULTIPLE_UPLOAD_SEPARATOR)
		cnt = UBound(files)
		images = ""
		For i = 0 to cnt
			If files(i) <> "" Then
				tmpimage = ew_LoadBinaryFile(fld.PhysicalUploadPath & files(i))
				If fld.ImageResize Then Call ew_ResizeBinary(tmpimage, fld.ImageWidth, fld.ImageHeight, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
				If images <> "" Then images = images & EW_MULTIPLE_UPLOAD_SEPARATOR
				images = images & ew_TmpImage(tmpimage)
			End If
		Next
		ew_GetFileTempImage = images
	Else
		ew_GetFileTempImage = ""
		If fld.FldDataType = EW_DATATYPE_BLOB Then
			If Not IsNull(fld.Upload.DbValue) Then
				tmpimage = fld.Upload.DbValue
				If fld.ImageResize Then Call ew_ResizeBinary(tmpimage, fld.ImageWidth, fld.ImageHeight, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
				ew_GetFileTempImage = ew_TmpImage(tmpimage)
			End If
		ElseIf val <> "" Then
			tmpimage = ew_LoadBinaryFile(fld.PhysicalUploadPath & val)
			If fld.ImageResize Then Call ew_ResizeBinary(tmpimage, fld.ImageWidth, fld.ImageHeight, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
			ew_GetFileTempImage = ew_TmpImage(tmpimage)
		End If
	End If
End Function

' Get file upload URL
Function ew_GetFileUploadUrl(fld, val, resize)
	Dim path, fn, key, encrypt
	If Not ew_EmptyStr(val) Then
		encrypt = EW_ENCRYPT_FILE_PATH
		If encrypt Or resize Then
			path = fld.PhysicalUploadPath
		Else
			path = fld.HrefPath
		End If
		key = EW_RANDOM_KEY & Session.SessionID
		If encrypt Then
			fn = EW_FILE_URL & "?t=" & ew_Encrypt(fld.TblName, key) & "&fn=" & ew_Encrypt(path & val, key)
			If resize Then
				fn = fn & "&width=" & fld.ImageWidth & "&height=" & fld.ImageHeight
			End If
		ElseIf resize Then ' Encrypt the physical path
			fn = EW_FILE_URL & "?t=" & ew_UrlEncode(fld.TblName) & "&fn=" & ew_Encrypt(path & val, key) & "&width=" & fld.ImageWidth & "&height=" & fld.ImageHeight
		Else
			If ew_IsRemote(path) Then ' Allow remote file
				fn = path
			Else
				fn = ew_UrlEncodeFilePath(path)
			End If
			fn = fn & ew_UrlEncodeFilePath(val)
		End If
		ew_GetFileUploadUrl = fn
	Else
		ew_GetFileUploadUrl = ""
	End If
End Function

' URL Encode file path
Function ew_UrlEncodeFilePath(path)
	Dim ar, c, scheme
	If path <> "" Then
		ar = Split(path, "/")
		For Each c In ar
			If Not ew_EndsStr(c, ":") Then ' Not protocol, e.g. http:
				c = ew_UrlEncode(c)
			End If
		Next
		ew_UrlEncodeFilePath = Join(ar, "/")
	Else
		ew_UrlEncodeFilePath = ""
	End If
End Function

' Get file view tag
Function ew_GetFileViewTag(fld, val)
	Dim url, name, wrkfile, wrkfiles, bMultiple, href, image, images, fn, bExport
	Dim wrkcnt, wrknames
	If Not ew_EmptyStr(val) Then
		If fld.FldDataType = EW_DATATYPE_BLOB Then
			wrknames = Array(val)
			wrkfiles = Array(val)
		ElseIf fld.UploadMultiple Then
			wrknames = Split(val, EW_MULTIPLE_UPLOAD_SEPARATOR)
			wrkfiles = Split(fld.Upload.DbValue, EW_MULTIPLE_UPLOAD_SEPARATOR)
		Else
			wrknames = Array(val)
			wrkfiles = Array(fld.Upload.DbValue)
		End If
		bMultiple = (Ubound(wrkfiles) > 0)
		href = fld.HrefValue
		images = ""
		bExport = (Table.TableType = "REPORT" And (Table.Export = "excel" Or Table.Export = "word") Or Table.TableType <> "REPORT" And (Table.CustomExport = "pdf" Or Table.CustomExport = "email"))
		wrkcnt = 0
		For Each wrkfile In wrkfiles
			image = ""
			If bExport And fld.FldViewTag = "IMAGE" Then
				fn = ew_GetFileTempImage(fld, wrkfile)
			ElseIf fld.FldDataType = EW_DATATYPE_BLOB Then
				fn = val
			Else
				fn = ew_GetFileUploadUrl(fld, wrkfile, fld.ImageResize)
			End If
			If fld.FldViewTag = "IMAGE" And (fld.IsBlobImage Or ew_IsImageFile(wrkfile)) Then ' Image
				If href = "" And Not fld.UseColorbox Or bExport Then
					If fn <> "" Then
						If ew_IsLazy() Then
							image = "<img class=""ewImage ewLazy img-thumbnail"" alt="""" src=""data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="" data-src=""" & fn & """" & fld.ViewAttributes & ">"
						Else
							image = "<img class=""ewImage img-thumbnail"" alt="""" src=""" & fn & """" & fld.ViewAttributes & ">"
						End If
					End If
				Else
					If fld.UploadMultiple And InStr(href, "%u") > 0 Then
						fld.HrefValue = Replace(href, "%u", ew_GetFileUploadUrl(fld, wrkfile, False))
					End If
					If fn <> "" Then
						If ew_IsLazy() Then
							image = "<a" & fld.LinkAttributes & "><img class=""ewImage ewLazy img-thumbnail"" alt="""" src=""data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="" data-src=""" & fn & """" & fld.ViewAttributes & "></a>"
						Else
							image = "<a" & fld.LinkAttributes & "><img class=""ewImage img-thumbnail"" alt="""" src=""" & fn & """" & fld.ViewAttributes & "></a>"
						End If
					End If
				End If
			Else
				If fld.FldDataType = EW_DATATYPE_BLOB Then
					url = href
					name = ew_IIf(fld.Upload.FileName <> "", fld.Upload.FileName, fld.FldCaption)
				Else
					url = ew_GetFileUploadUrl(fld, wrkfile, False)
					If wrkcnt <= UBound(wrknames) Then
						name = wrknames(wrkcnt)
					Else
						name = wrknames(UBound(wrknames))
					End If
				End If
				If url <> "" Then
					If fld.UploadMultiple And InStr(href, "%u") > 0 Then
						fld.HrefValue = Replace(href, "%u", url)
					End If
					image = "<a" & fld.LinkAttributes & ">" & name & "</a>"
				End If
			End If
			If image <> "" Then
				If bMultiple Then
					images = images & "<li>" & image & "</li>"
				Else
					images = images & image
				End If
			End If
			wrkcnt = wrkcnt + 1
		Next
		If bMultiple And (images <> "") Then
			images = "<ul class=""list-inline"">" & images & "</ul>"
		End If
		ew_GetFileViewTag = images
	Else
		ew_GetFileViewTag = ""
	End If
End Function

' Get image view tag
Function ew_GetImgViewTag(fld, val)
	Dim url, name, href, image, fn
	If Not ew_EmptyStr(val) Then
		href = fld.HrefValue
		image = val
		If val <> "" And InStr(val, "://") = 0 And InStr(val, "\\") = 0 And InStr(val, "javascript:") = 0 Then
			fn = ew_GetImageUrl(fld, val, fld.ImageResize, EW_ENCRYPT_FILE_PATH, True)
		Else
			fn = val
		End If
		If ew_IsImageFile(val) Then ' Image
			If href = "" And Not fld.UseColorbox Then
				If fn <> "" Then
					If ew_IsLazy() Then
						image = "<img class=""ewImage ewLazy img-thumbnail"" alt="""" src=""data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="" data-src=""" & fn & """" & fld.ViewAttributes & ">"
					Else
						image = "<img class=""ewImage img-thumbnail"" alt="""" src=""" & fn & """" & fld.ViewAttributes & ">"
					End If
				End If
			Else
				If fn <> "" Then
					If ew_IsLazy() Then
						image = "<a" & fld.LinkAttributes & "><img class=""ewImage ewLazy img-thumbnail"" alt="""" src=""data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="" data-src=""" & fn & """" & fld.ViewAttributes & "></a>"
					Else
						image = "<a" & fld.LinkAttributes & "><img class=""ewImage img-thumbnail"" alt="""" src=""" & fn & """" & fld.ViewAttributes & "></a>"
					End If
				End If
			End If
		Else
			name = val
			If href <> "" Then
				image = "<a" & fld.LinkAttributes & ">" & name & "</a>"
			Else
				image = name
			End If
		End If
		ew_GetImgViewTag = image
	Else
		ew_GetImgViewTag = ""
	End If
End Function

' Get image URL
Function ew_GetImageUrl(fld, val, resize, encrypt, urlencode)
	Dim fn, key
	If Not ew_EmptyStr(val) Then
		If encrypt Then
			key = EW_RANDOM_KEY & Session.SessionID
			If Not ew_IsPathRooted(val) Then ' Get absolute path ' ASP
				val = ew_ServerMapPath(val)
			End If
			fn = EW_FILE_URL & "?t=" & ew_Encrypt(fld.TblName, key) & "&fn=" & ew_Encrypt(val, key)
			If resize Then
				fn = fn & "&width=" & fld.ImageWidth & "&height=" & fld.ImageHeight
			End If
		ElseIf resize Then
			fn = EW_FILE_URL & "?t=" & ew_UrlEncode(fld.TblName) & "&fn=" & ew_UrlEncodeFilePath(val) & "&width=" & fld.ImageWidth & "&height=" & fld.ImageHeight
		Else
			fn = val
			If urlencode Then
				fn = ew_UrlEncodeFilePath(fn)
			End If
		End If
		ew_GetImageUrl = fn
	Else
		ew_GetImageUrl = ""
	End If
End Function

' Check if image file
Function ew_IsImageFile(fn)
	If Trim(fn & "") = "" Then
		ew_IsImageFile = False
		Exit Function
	End If
	Dim wrkfn
	wrkfn = ew_ImageNameFromUrl(fn)
	Dim Ext, Pos, arExt, FileExt
	arExt = Split(EW_IMAGE_ALLOWED_FILE_EXT & "", ",")
	Ext = ""
	Pos = InStrRev(wrkfn, ".")
	If Pos > 0 Then	Ext = Mid(wrkfn, Pos+1)
	ew_IsImageFile = False
	For Each FileExt In arExt
		If LCase(Trim(FileExt)) = LCase(Ext) Then
			ew_IsImageFile = True
			Exit For
		End If
	Next
End Function

' Get image file name
Function ew_ImageNameFromUrl(fn)
	Dim wrkfn, ar, ar1, i
	wrkfn = fn
	If InStr(wrkfn, "?") > 0 Then
		wrkfn = Mid(wrkfn, InStr(wrkfn, "?")+1) ' Get querystring
		ar = Split(wrkfn, "&")
		For i = 0 to UBound(ar)
			If ar(i) <> "" Then
				ar1 = Split(ar(i), "=")
				If UBound(ar1) >= 1 Then
					If ar1(0) = "fn" Then ' Get fn=...
						wrkfn = ew_UrlDecode(ar1(1))
						Exit For
					End If
				End If
			End If
		Next
	End If
	ew_ImageNameFromUrl = wrkfn
End Function

' Check if lazy loading images
Function ew_IsLazy()
	ew_IsLazy = EW_LAZY_LOAD And (gsExport = "" Or gsExport = "print" And (gsCustomExport = "" Or gsCustomExport = "print"))
End Function

'
'  XML document class
'
Class cXMLDocument
	Dim Encoding
	Dim RootTagName
	Dim SubTblName
	Dim RowTagName
	Dim XmlDoc
	Dim XmlTbl
	Dim XmlSubTbl
	Dim XmlRow
	Dim XmlFld

	' Class Initialize
	Private Sub Class_Initialize()
		Encoding = ""
		RootTagName = "table"
		RowTagName = "row"
		Set XmlDoc = ew_CreateXmlDom()
	End Sub

	Public Sub AddRoot(rootname)
		If rootname <> "" Then
			RootTagName = ew_XmlTagName(rootname)
		End If
		Set XmlTbl = XmlDoc.CreateElement(RootTagName)
		Call XmlDoc.AppendChild(XmlTbl)
	End Sub

	' Add row
	Public Sub AddRow(tablename, rowname)
		If rowname <> "" Then
			RowTagName = ew_XmlTagName(rowname)
		End If
		Set XmlRow = XmlDoc.CreateElement(RowTagName)
		If tablename = "" Then
			If Not IsEmpty(XmlTbl) Then
				Call XmlTbl.AppendChild(XmlRow)
			End If
		Else
			If SubTblName = "" Or SubTblName <> tablename Then
				SubTblName = ew_XmlTagName(tablename)
				Set XmlSubTbl = XmlDoc.CreateElement(SubTblName)
				Call XmlTbl.AppendChild(XmlSubTbl)
			End If
			If Not IsEmpty(XmlSubTbl) Then
				Call XmlSubTbl.AppendChild(XmlRow)
			End If
		End If
	End Sub

	' Add field
	Public Sub AddField(Name, Value)
		Set XmlFld = XmlDoc.CreateElement(ew_XmlTagName(Name))
		Call XmlRow.AppendChild(XmlFld)
		Call XmlFld.AppendChild(XmlDoc.CreateTextNode(Value & ""))
	End Sub

	' XML
	Public Function XML()
		XML = XmlDoc.XML
	End Function

	' Output
	Public Sub Output()
		Dim PI
		If Response.Buffer Then Response.Clear
		Response.ContentType = "text/xml"
		PI = "<?xml version=""1.0"""
		If Encoding <> "" Then PI = PI & " encoding=""" & Encoding & """"
		PI = PI & " ?>"
		Response.Write PI & XmlDoc.XML
	End Sub

	' Output XML for debug
	Public Sub Print()
		If Response.Buffer Then Response.Clear
		Response.ContentType = "text/plain"
		Response.Write ew_HtmlEncode(XmlDoc.XML)
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		Set XmlFld = Nothing
		Set XmlRow = Nothing
		Set XmlTbl = Nothing
		Set XmlDoc = Nothing
	End Sub
End Class

'
'  XML document class (end)
'
'
'  Email class (begin)
'
Class cEmail

	' Class properties
	Dim Sender ' Sender
	Dim Recipient ' Recipient
	Dim Cc ' Cc
	Dim Bcc ' Bcc
	Dim Subject ' Subject
	Dim Format ' Format
	Dim Content ' Content
	Dim Attachments ' Attachments
	Dim EmbeddedImages ' Embedded images
	Dim Charset ' Charset
	Dim SendErrDescription ' Send error description
	Dim SmtpSecure ' Send secure option

	' Class Initialize
	Private Sub Class_Initialize()
		SmtpSecure = EW_SMTP_SECURE_OPTION
		Charset = EW_EMAIL_CHARSET
	End Sub

	' Method to load email from template
	Public Sub Load(fn, langid)
		Dim fso, pos, sWrk, wrkname, wrkext, wrkpath, ar, exist, suffix, wrkfile, wrkid, sContent
		Set fso = Server.CreateObject("Scripting.FileSystemObject")
		langid = ew_IIf(langid = "", gsLanguage, langid)
		pos = InStr(fn, ".")
		If pos > 0 Then
			wrkname = Mid(fn, 1, pos - 1) ' Get file name
			wrkext = Mid(fn, pos + 1) ' Get file extension
			wrkpath = ew_IncludeTrailingDelimiter(ew_IncludeTrailingDelimiter(ew_ScriptFolder(), True) & EW_EMAIL_TEMPLATE_PATH, True) ' Get file path
			ar = ew_IIf(langid <> "", Array("_" & langid, "-" & langid, ""), array(""))
			exist = False
			For Each suffix In ar
				wrkfile = wrkpath & wrkname & suffix & "." & wrkext
				exist = fso.FileExists(Server.MapPath(wrkfile))
				If exist Then Exit For
			Next
			If Not exist Then Exit Sub
			sWrk = ew_LoadFile(wrkfile) ' Load file content
			sWrk = Replace(sWrk, vbCrLf, vbLf) ' Convert to Lf
			sWrk = Replace(sWrk, vbCr, vbLf) ' Convert to Lf
			wrkid = wrkname & "_content"
			If ew_ContainsStr(sWrk, wrkid) Then ' Replace content
				wrkfile = wrkpath & wrkid & "." & wrkext
				If fso.FileExists(Server.MapPath(wrkfile)) Then
					sContent = ew_LoadFile(wrkfile)
					sWrk = Replace(sWrk, "<!--" & wrkid & "-->", sContent)
				End If
			End If
		End If
		Dim sHeader, arrHeader, sName, sValue, j
		If sWrk <> "" Then

			' Locate Header & Mail Content
			i = InStr(sWrk, vbLf&vbLf)
			If i > 0 Then
				sHeader = Mid(sWrk, 1, i)
				Content = Mid(sWrk, i + 2)
				arrHeader = Split(sHeader, vbLf)
				For j = 0 to UBound(arrHeader)
					i = InStr(arrHeader(j), ":")
					If i > 0 Then
						sName = Trim(Mid(arrHeader(j), 1, i-1))
						sValue = Trim(Mid(arrHeader(j), i+1))
						Select Case LCase(sName)
							Case "subject"
								Subject = sValue
							Case "from"
								Sender = sValue
							Case "to"
								Recipient = sValue
							Case "cc"
								Cc = sValue
							Case "bcc"
								Bcc = sValue
							Case "format"
								Format = sValue
						End Select
					End If
				Next
			End If
		End If
		Set fso = Nothing
	End Sub

	' Method to replace sender
	Public Sub ReplaceSender(ASender)
		If ew_ContainsStr(Sender, "<!--$From-->") Then
			Sender = Replace(Sender, "<!--$From-->", ASender)
		Else
			Sender = ASender
		End If
	End Sub

	' Method to replace recipient
	Public Sub ReplaceRecipient(ARecipient)
		If ew_ContainsStr(Recipient, "<!--$To-->") Then
			Recipient = Replace(Recipient, "<!--$To-->", ARecipient)
		Else
			Call AddRecipient(ARecipient)
		End If
	End Sub

	' Method to add recipient
	Public Sub AddRecipient(ARecipient)
		Recipient = ew_Concat(Recipient, ARecipient, ";")
	End Sub

	' Method to add Cc email
	Public Sub AddCc(ACc)
		Cc = ew_Concat(Cc, ACc, ";")
	End Sub

	' Method to add Bcc email
	Public Sub AddBcc(ABcc)
		Bcc = ew_Concat(Bcc, ABcc, ";")
	End Sub

	' Method to replace subject
	Public Sub ReplaceSubject(ASubject)
		If ew_ContainsStr(Subject, "<!--$Subject-->") Then
			Subject = Replace(Subject, "<!--$Subject-->", ASubject)
		Else
			Subject = ASubject
		End If
	End Sub

	' Method to replace content
	Public Sub ReplaceContent(Find, ReplaceWith)
		If ew_Empty(ReplaceWith) Then
			Content = Replace(Content, Find, "")
		Else
			Content = Replace(Content, Find, ReplaceWith)
		End If
	End Sub

	' Method to add embedded image
	Public Sub AddEmbeddedImage(image)
		If image <> "" Then
			If Not IsArray(EmbeddedImages) Then
				ReDim EmbeddedImages(0)
			Else
				ReDim Preserve EmbeddedImages(UBound(EmbeddedImages)+1)
			End If
			EmbeddedImages(UBound(EmbeddedImages)) = image
		End If
	End Sub

	' Method to add attachment
	Public Sub AddAttachment(filename)
		If filename <> "" Then
			If Not IsArray(Attachments) Then
				ReDim Attachments(0)
			Else
				ReDim Preserve Attachments(UBound(Attachments)+1)
			End If
			Attachments(UBound(Attachments)) = filename
		End If
	End Sub

	' Method to send email
	Public Function Send
		SendErrDescription = ""
		Send = ew_SendEmail(Sender, Recipient, Cc, Bcc, Subject, Content, Format, Charset, SmtpSecure, Attachments, EmbeddedImages)
		If Send <> True Then
			SendErrDescription = Send ' Send error description
			Send = False
			Call ew_SetDebugMsg("SendEmail: " & ToJson()) ' Show data for debugging
		End If
	End Function

	' Show object as string
	Public Function AsString()
		Dim dict
		Set dict = Dictionary()
		Call dict.Add("Sender", Sender) ' Sender
		Call dict.Add("Recipient", Recipient) ' Recipient
		Call dict.Add("Cc", Cc) ' Cc
		Call dict.Add("Bcc", Bcc) ' Bcc
		Call dict.Add("Subject", Subject) ' Subject
		Call dict.Add("Format", Format) ' Format
		Call dict.Add("Content", Content) ' Content
		Call dict.Add("Attachments", Attachments) ' Attachments
		Call dict.Add("EmbeddedImages", EmbeddedImages) ' Embedded images
		Call dict.Add("Charset", Charset) ' Charset
		Call dict.Add("SendErrDescription", SendErrDescription) ' Send error description
		Call dict.Add("SmtpSecure", SmtpSecure) ' Send secure option
		AsString = dict.ToJson()
		Set dict = Nothing
	End Function

	' Show object as string (alias of AsString)
	Public Function ToString()
		ToString = AsString()
	End Function

	' Show object as JSON (alias of AsString)
	Public Function ToJson()
		ToJson = AsString()
	End Function
End Class

'
'  Email class (end)
'
'
'  Pager classes and functions (begin)
'
' Create Numeric pager
Function ew_NewNumericPager(FromIndex, PageSize, RecordCount, Range)
	Set ew_NewNumericPager = ew_NewNumericPagerBase(FromIndex, PageSize, RecordCount, Range, EW_AUTO_HIDE_PAGER)
End Function

Function ew_NewNumericPagerBase(FromIndex, PageSize, RecordCount, Range, AutoHidePager)
	Set ew_NewNumericPagerBase = New cNumericPager
	ew_NewNumericPagerBase.FromIndex = CLng(FromIndex)
	ew_NewNumericPagerBase.PageSize = CLng(PageSize)
	ew_NewNumericPagerBase.RecordCount = CLng(RecordCount)
	ew_NewNumericPagerBase.Range = CLng(Range)
	ew_NewNumericPagerBase.AutoHidePager = AutoHidePager
	ew_NewNumericPagerBase.Init
End Function

' Create PrevNext pager
Function ew_NewPrevNextPager(FromIndex, PageSize, RecordCount)
	Set ew_NewPrevNextPager = ew_NewPrevNextPagerBase(FromIndex, PageSize, RecordCount, EW_AUTO_HIDE_PAGER)
End Function

Function ew_NewPrevNextPagerBase(FromIndex, PageSize, RecordCount, AutoHidePager)
	Set ew_NewPrevNextPagerBase = New cPrevNextPager
	ew_NewPrevNextPagerBase.FromIndex = CLng(FromIndex)
	ew_NewPrevNextPagerBase.PageSize = CLng(PageSize)
	ew_NewPrevNextPagerBase.AutoHidePager = ew_IIf(AutoHidePager = "", EW_AUTO_HIDE_PAGER, AutoHidePager)
	ew_NewPrevNextPagerBase.RecordCount = CLng(RecordCount)
	ew_NewPrevNextPagerBase.Init
End Function

' Class for Pager item
Class cPagerItem
	Dim Start, Text, Enabled
End Class

' Class for Numeric pager
Class cNumericPager
	Dim Items()
	Dim Count, FromIndex, ToIndex, RecordCount, PageSize, Range
	Dim FirstButton, PrevButton, NextButton, LastButton, ButtonCount
	Dim Visible, AutoHidePager

	' Class Initialize
	Private Sub Class_Initialize()
		Set FirstButton = New cPagerItem
		Set PrevButton = New cPagerItem
		Set NextButton = New cPagerItem
		Set LastButton = New cPagerItem
		AutoHidePager = True
		Visible = True
	End Sub

	' Method to init pager
	Public Sub Init()
		If FromIndex > RecordCount Then FromIndex = RecordCount
		ToIndex = FromIndex + PageSize - 1
		If ToIndex > RecordCount Then ToIndex = RecordCount
		Count = -1
		ReDim Items(0)
		Call SetupNumericPager
		ReDim Preserve Items(Count)
		If AutoHidePager And FromIndex = 1 And RecordCount <= PageSize Then
			Visible = False
		End If

		' Update button count
		ButtonCount = Count + 1
		If FirstButton.Enabled Then ButtonCount = ButtonCount + 1
		If PrevButton.Enabled Then ButtonCount = ButtonCount + 1
		If NextButton.Enabled Then ButtonCount = ButtonCount + 1
		If LastButton.Enabled Then ButtonCount = ButtonCount + 1
	End Sub

	' Add pager item
	Private Sub AddPagerItem(StartIndex, Text, Enabled)
		Count = Count + 1
		If Count > UBound(Items) Then
			ReDim Preserve Items(UBound(Items)+10)
		End If
		Dim Item
		Set Item = New cPagerItem
		Item.Start = StartIndex
		Item.Text = Text
		Item.Enabled = Enabled
		Set Items(Count) = Item
	End Sub

	' Setup pager items
	Private Sub SetupNumericPager()
		Dim Eof, x, y, dx1, dx2, dy1, dy2, ny, HasPrev, TempIndex
		If RecordCount > PageSize Then
			Eof = (RecordCount < (FromIndex + PageSize))
			HasPrev = (FromIndex > 1)

			' First Button
			TempIndex = 1
			FirstButton.Start = TempIndex
			FirstButton.Enabled = (FromIndex > TempIndex)

			' Prev Button
			TempIndex = FromIndex - PageSize
			If TempIndex < 1 Then TempIndex = 1
			PrevButton.Start = TempIndex
			PrevButton.Enabled = HasPrev

			' Page links
			If HasPrev Or Not Eof Then
				x = 1
				y = 1
				dx1 = ((FromIndex-1)\(PageSize*Range))*PageSize*Range + 1
				dy1 = ((FromIndex-1)\(PageSize*Range))*Range + 1
				If (dx1+PageSize*Range-1) > RecordCount Then
					dx2 = (RecordCount\PageSize)*PageSize + 1
					dy2 = (RecordCount\PageSize) + 1
				Else
					dx2 = dx1 + PageSize*Range - 1
					dy2 = dy1 + Range - 1
				End If
				While x <= RecordCount
					If x >= dx1 And x <= dx2 Then
						Call AddPagerItem(x, y, FromIndex<>x)
						x = x + PageSize
						y = y + 1
					ElseIf x >= (dx1-PageSize*Range) And x <= (dx2+PageSize*Range) Then
						If x+Range*PageSize < RecordCount Then
							Call AddPagerItem(x, y & "-" & (y+Range-1), True)
						Else
							ny = (RecordCount-1)\PageSize + 1
							If ny = y Then
								Call AddPagerItem(x, y, True)
							Else
								Call AddPagerItem(x, y & "-" & ny, True)
							End If
						End If
						x = x + Range*PageSize
						y = y + Range
					Else
						x = x + Range*PageSize
						y = y + Range
					End If
				Wend
			End If

			' Next Button
			NextButton.Start = FromIndex + PageSize
			TempIndex = FromIndex + PageSize
			NextButton.Start = TempIndex
			NextButton.Enabled = Not Eof

			' Last Button
			TempIndex = ((RecordCount-1)\PageSize)*PageSize + 1
			LastButton.Start = TempIndex
			LastButton.Enabled = (FromIndex < TempIndex)
		End If
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		Set FirstButton = Nothing
		Set PrevButton = Nothing
		Set NextButton = Nothing
		Set LastButton = Nothing
		For Each Item In Items
			Set Item = Nothing
		Next
		Erase Items
	End Sub
End Class

' Class for PrevNext pager
Class cPrevNextPager
	Dim FirstButton, PrevButton, NextButton, LastButton
	Dim CurrentPage, PageSize, PageCount, FromIndex, ToIndex, RecordCount
	Dim Visible
	Dim AutoHidePager

	' Class Initialize
	Private Sub Class_Initialize()
		Set FirstButton = New cPagerItem
		Set PrevButton = New cPagerItem
		Set NextButton = New cPagerItem
		Set LastButton = New cPagerItem
		Visible = True
		AutoHidePager = True
	End Sub

	' Method to init pager
	Public Sub Init()
		Dim TempIndex
		If PageSize = 0 Then
			PageSize = RecordCount
		End If
		If PageSize > 0 Then
			CurrentPage = (FromIndex-1)\PageSize + 1
			PageCount = (RecordCount-1)\PageSize + 1
			If FromIndex > RecordCount Then FromIndex = RecordCount
			ToIndex = FromIndex + PageSize - 1
			If ToIndex > RecordCount Then ToIndex = RecordCount

			' First Button
			TempIndex = 1
			FirstButton.Start = TempIndex
			FirstButton.Enabled = (TempIndex <> FromIndex)

			' Prev Button
			TempIndex = FromIndex - PageSize
			If TempIndex < 1 Then TempIndex = 1
			PrevButton.Start = TempIndex
			PrevButton.Enabled = (TempIndex <> FromIndex)

			' Next Button
			TempIndex = FromIndex + PageSize
			If TempIndex > RecordCount Then TempIndex = FromIndex
			NextButton.Start = TempIndex
			NextButton.Enabled = (TempIndex <> FromIndex)

			' Last Button
			TempIndex = ((RecordCount-1)\PageSize)*PageSize + 1
			LastButton.Start = TempIndex
			LastButton.Enabled = (TempIndex <> FromIndex)
		End If
		If AutoHidePager And PageCount = 1 Then
			Visible = False
		End If
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		Set FirstButton = Nothing
		Set PrevButton = Nothing
		Set NextButton = Nothing
		Set LastButton = Nothing
	End Sub
End Class

'
'  Pager classes and functions (end)
'
'
'  Breadcrumb class
'
Class cBreadcrumb
	Dim Links
	Dim SessionLinks
	Dim Visible

	' Class Initialize
	Private Sub Class_Initialize()
		ReDim Links(0)
		Links(0) = Array("home", "HomePage", "default.asp", "ewHome", "", False) ' Home
		Visible = True
	End Sub

	' Check if an item exists
	Public Function Exists(pageid, table, pageurl)
		Dim i, cnt, id, title, url, tbl, cur
		If IsArray(Links) Then
			cnt = UBound(Links) + 1
			For i = 0 to cnt - 1
				If IsArray(Links(i)) Then
					If UBound(Links(i)) >= 4 Then
						id = Links(i)(0)
						title = Links(i)(1)
						url = Links(i)(2)
						tbl = Links(i)(3)
						cur = Links(i)(4)
						If pageid = id And table = tbl And pageurl = url Then
							Exists = True
							Exit Function
						End If
					End If
				End If
			Next
		End If
		Exists = False
	End Function

	' Add breadcrumb
	Public Sub Add(pageid, pagetitle, pageurl, pageurlclass, table, current)
		Dim i, cnt, id, title, url, urlclass, tbl, cur

		' Load session links
		Call LoadSession

		' Get list of master tables
		Dim mastertable, tablevar
		If table <> "" Then
			tablevar = table
			Do While Session(EW_PROJECT_NAME & "_" & tablevar & "_" & EW_TABLE_MASTER_TABLE) & "" <> ""
				tablevar = Session(EW_PROJECT_NAME & "_" & tablevar & "_" & EW_TABLE_MASTER_TABLE)
				If ew_InArray(tablevar, mastertable) Then
					Exit Do
				End If
				If IsArray(mastertable) Then
					ReDim Preserve mastertable(UBound(mastertable)+1)
				Else
					ReDim mastertable(0)
				End If
				mastertable(UBound(mastertable)) = tablevar
			Loop
		End If

		' Add master links first
		If IsArray(SessionLinks) Then
			cnt = UBound(SessionLinks) + 1
			For i = 0 to cnt - 1
				If UBound(SessionLinks(i)) >= 5 Then
					id = SessionLinks(i)(0)
					title = SessionLinks(i)(1)
					url = SessionLinks(i)(2)
					urlclass = SessionLinks(i)(3)
					tbl = SessionLinks(i)(4)
					cur = SessionLinks(i)(5)
					If ew_InArray(tbl, mastertable) And id = "list" Then
						If url = pageurl Then
							Exit For
						End If
						If Not Exists(id, tbl, url) Then
							ReDim Preserve Links(UBound(Links)+1)
							Links(UBound(Links)) = Array(id, title, url, urlclass, tbl, False)
						End If
					End If
				End If
			Next
		End If

		' Add this link
		If Not Exists(pageid, table, pageurl) Then
			ReDim Preserve Links(UBound(Links)+1)
			Links(UBound(Links)) = Array(pageid, pagetitle, pageurl, pageurlclass, table, current)
		End If

		' Save session links
		Call SaveSession
	End Sub

	' Save links to Session
	Private Sub SaveSession()
		Session(EW_SESSION_BREADCRUMB) = Links
	End sub

	' Load links from Session
	Private Sub LoadSession()
		If IsArray(Session(EW_SESSION_BREADCRUMB)) Then
			SessionLinks = Session(EW_SESSION_BREADCRUMB)
		End If
	End Sub

	' Load language phrase
	Private Function LanguagePhrase(title, table, current)
		Dim wrktitle
		wrktitle = ew_IIf(title = table, Language.TablePhrase(title, "TblCaption"), Language.Phrase(title))
		If current Then
			wrktitle = "<span id=""ewPageCaption"">" & wrktitle & "</span>"
		End If
		LanguagePhrase = wrktitle
	End Function

	' Render
	Public Sub Render()
		Dim nav, i, cnt, id, text, title, url, urlclass, tbl, cur
		If Not Visible Or EW_PAGE_TITLE_STYLE = "" Or EW_PAGE_TITLE_STYLE = "None" Then Exit Sub
		nav = "<ul class=""breadcrumb ewBreadcrumbs"">"
		If IsArray(Links) Then
			cnt = UBound(Links) + 1
			If EW_PAGE_TITLE_STYLE = "Caption" Then

				' Already shown in content header, just ignore
				Exit Sub
			Else
				For i = 0 to cnt - 1
					If UBound(Links(i)) >= 5 Then
						id = Links(i)(0)
						title = Links(i)(1)
						url = Links(i)(2)
						urlclass = Links(i)(3)
						tbl = Links(i)(4)
						cur = Links(i)(5)
						If i < cnt - 1 Then
							nav = nav & "<li id=""ewBreadcrumb" & (i + 1) & """>"
						Else
							nav = nav & "<li id=""ewBreadcrumb" & (i + 1) & """ class=""active"">"
							url = "" ' No need to show URL for current page
						End If
						text = LanguagePhrase(title, tbl, cur)
						title = ew_HtmlTitle(text)
						If url <> "" Then
							nav = nav & "<a href=""" & ew_GetUrl(url) & """"
							If title <> "" And title <> text Then
								nav = nav & " title=""" & ew_HtmlEncode(title) & """"
							End If
							If urlclass <> "" Then
								nav = nav & " class=""" & urlclass & """"
							End If
							nav = nav & ">" & text & "</a>"
						Else
							nav = nav & text
						End If
						nav = nav & "</li>"
					End If
				Next
			End If
		End If
		nav = nav & "</ul>"
		Response.Write nav
	End Sub
End Class

'
'  Breadcrumb class (end)
'
'
'  Field class
'
Class cField
	Dim TblName ' Table name
	Dim TblVar ' Table var
	Dim FldName ' Field name
	Dim FldVar ' Field variable name
	Dim FldExpression ' Field expression (used in SQL)
	Dim FldBasicSearchExpression ' Field expression (used in basic search SQL)
	Dim FldIsCustom  ' Custom field
	Dim FldIsVirtual ' Virtual field
	Dim FldVirtualExpression ' Virtual field expression (used in SelectSQL)
	Dim FldForceSelection ' Autosuggest force selection
	Dim FldSelectMultiple ' Select multiple
	Dim FldVirtualSearch ' Search as virtual field
	Dim VirtualValue ' Virtual field value
	Dim TooltipValue ' Field tooltip value
	Dim TooltipWidth ' Field tooltip width
	Dim FldType ' Field type
	Dim FldDataType ' Field data type
	Dim FldBlobType ' For Oracle only
	Dim FldViewTag ' View Tag
	Dim FldHtmlTag ' Html Tag
	Dim FldIsDetailKey ' Detail key
	Dim Visible ' Visible
	Dim Disabled ' Disabled
	Dim ReadOnly ' Read only
	Dim TruncateMemoRemoveHtml ' Remove Html from Memo field
	Dim LookupFn ' Lookup table function(sql) for modifying SQL
	Dim DisplayValueSeparator
	Dim LookupFilters
	Dim OptionCount

	' Field caption
	Dim Caption

	Public Property Let FldCaption(v)
		Caption = v
	End Property

	Public Property Get FldCaption
		If Caption & "" <> "" Then
			FldCaption = Caption
		Else
			FldCaption = Language.FieldPhrase(TblVar, Mid(FldVar,3), "FldCaption")
		End If
	End Property

	' Field parameter name
	Public Property Get FldParm
		FldParm = Mid(FldVar, 3) ' Remove "x_"
	End Property

	Public Property Get FldTitle ' Field title
		FldTitle = Language.FieldPhrase(TblVar, FldParm, "FldTitle")
	End Property

	Public Property Get FldAlt ' Field alt
		FldAlt = Language.FieldPhrase(TblVar, FldParm, "FldAlt")
	End Property
	Dim FldDefaultErrMsg

	Public Property Get FldErrMsg ' Field err msg
		FldErrMsg = Language.FieldPhrase(TblVar, FldParm, "FldErrMsg")
		If FldErrMsg = "" Then FldErrMsg = FldDefaultErrMsg & " - " & FldCaption
	End Property

	' Field option value
	Public Function FldTagValue(i)
		FldTagValue = Language.FieldPhrase(TblVar, FldParm, "FldTagValue" & i)
	End Function

	' Field option caption
	Public Function FldTagCaption(i)
		FldTagCaption = Language.FieldPhrase(TblVar, FldParm, "FldTagCaption" & i)
	End Function

	' Set field visibility
	Public Function SetVisibility(tbl)
		Visible = tbl.GetFieldVisibility(FldParm)
	End Function

	' Field option caption by option value
	Public Function OptionCaption(val)
		Dim i, wrkval, caption
		wrkval = CStr(val&"")
		For i = 0 to OptionCount - 1
			If wrkval = FldTagValue(i + 1) Then
				caption = FldTagCaption(i + 1)
				OptionCaption = ew_IIf(caption <> "", caption, wrkval)
				Exit Function
			End If
		Next
		OptionCaption = wrkval
	End Function

	' Get field user options as array
	Public Function Options(pleaseSelect)
		Dim i, arwrk, value, caption, cnt
		cnt = OptionCount - 1
		If pleaseSelect Then cnt = cnt + 1
		If cnt >= 0 Then
			ReDim arwrk(1,cnt)
			cnt = 0
			If pleaseSelect Then
				arwrk(0,0) = ""
				arwrk(1,0) = Language.Phrase("PleaseSelect")
				cnt = cnt + 1
			End If
			For i = 0 to OptionCount - 1
				value = FldTagValue(i + 1)
				caption = FldTagCaption(i + 1)
				caption = ew_IIf(caption <> "", caption, value)
				arwrk(0,cnt) = value
				arwrk(1,cnt) = caption
				cnt = cnt + 1
			Next
		End If
		Options = arwrk
	End Function

	' Href path ' ASP does not support
	Function HrefPathBase()
		HrefPathBase = ew_UploadPathEx(False, ew_IIf(HrefPath <> "", HrefPath, UploadPath))
	End Function

	' Physical upload path
	Public Property Get PhysicalUploadPath
		PhysicalUploadPath = ew_ServerMapPath(UploadPath)
	End Property

	' Old Physical upload path
	Public Property Get OldPhysicalUploadPath
		OldPhysicalUploadPath = ew_ServerMapPath(OldUploadPath)
	End Property

	' Get select options HTML
	Function SelectOptionListHtml(name, tbl)
		Dim curValue, str, k, cnt, val
		emptywrk = True
		curValue = ew_IIf(tbl.RowType = EW_ROWTYPE_SEARCH, ew_IIf(Mid(name,1,1) = "y",  AdvancedSearch.SearchValue2, AdvancedSearch.SearchValue), CurrentValue)
		str = ""
		If IsArray(EditValue) Then
			arwrk = EditValue
			If FldSelectMultiple Then
				armultiwrk = ew_IIf(curValue&"" = "", Array(), Split(curValue&"",","))
				cnt = UBound(armultiwrk)
				emptywrk = True
				If UBound(EditValue) >= 0 Then
					rowswrk = UBound(arwrk, 2)
					For rowcntwrk = 0 To rowswrk
						selwrk = False
						For ari = 0 To cnt
							If ew_SameStr(arwrk(0,rowcntwrk), armultiwrk(ari)) And Not IsNull(armultiwrk(ari)) Then
								armultiwrk(ari) = Null ' Marked for removal
								selwrk = True
								emptywrk = False
								If selwrk Then
									For k = 0 to UBound(arwrk)
										arwrk(k, rowcntwrk) = ew_RemoveHtml(CStr(arwrk(k, rowcntwrk)&""))
									Next
									str = str & "<option value=""" & ew_HtmlEncode(arwrk(0, rowcntwrk)) & """ selected>" & DisplayValue(arwrk, rowcntwrk) & "</option>"
								End If
							End If
						Next
					Next
				End If
			Else
				If UsePleaseSelect Then
					str = str & "<option value="""">" & PleaseSelectText & "</option>"
				End If
				If UBound(arwrk) >= 0 Then
					rowswrk = UBound(arwrk,2)
					emptywrk = True
					For rowcntwrk = 0 to rowswrk
						If ew_SameStr(curValue, arwrk(0, rowcntwrk)) Then
							emptywrk = False
							For k = 0 to UBound(arwrk)
								arwrk(k, rowcntwrk) = ew_RemoveHtml(CStr(arwrk(k, rowcntwrk)&""))
							Next
							str = str & "<option value=""" & ew_HtmlEncode(arwrk(0, rowcntwrk)) & """ selected>" & DisplayValue(arwrk, rowcntwrk) & "</option>"
							Exit For
						End If
					Next
				End If
			End If
			If FldSelectMultiple Then
				For ari = 0 To cnt
					If Not IsNull(armultiwrk(ari)) Then
						val = Trim(armultiwrk(ari)&"")
						str = str & "<option value=""" & ew_HtmlEncode(val) & """ selected>" & val & "</option>"
					End If
				Next
			Else
				If emptywrk And CStr(curValue&"") <> "" Then
					str = str & "<option value=""" & ew_HtmlEncode(curValue) & """ selected>" & curValue & "</option>"
				End If
			End If
		End If
		If emptywrk Then
			OldValue = ""
		End If
		SelectOptionListHtml = str
	End Function

	' Get radio buttons HTML
	Function RadioButtonListHtml(isDropdown, name, page, tbl)
		Dim curValue, emptywrk, html, str
		emptywrk = True
		curValue = ew_IIf(tbl.RowType = EW_ROWTYPE_SEARCH, ew_IIf(Mid(name,1,1) = "y",  AdvancedSearch.SearchValue2, AdvancedSearch.SearchValue), CurrentValue)
		str = ""
		arwrk = EditValue
		If IsArray(arwrk) Then
			rowswrk = UBound(arwrk)
			For rowcntwrk = 0 To rowswrk - 1
				If ew_SameStr(curValue, arwrk(rowcntwrk,0)) Then
					emptywrk = False
					html = "<input type=""radio"" data-table=""" & TblVar & """ data-field=""" & FldVar & """" &_
						ew_IIf(page > -1, " data-page=""" & page & """", "") &_
						" name=""" & name & """ id=""" & name & "_" & rowcntwrk & """" &_
						" data-value-separator=""" & DisplayValueSeparatorAttribute & """" &_
						" value=""" & ew_HtmlEncode(arwrk(rowcntwrk,0)) & """ checked" & EditAttributes & ">" & DisplayValue(arwrk,rowcntwrk)
					If Not isDropdown Then
						html = "<label class=""radio-inline"">" & html & "</label>"
					End If
					str = str & html
				End If
			Next
			If emptywrk And CStr(curValue&"") <> "" Then
				html = "<input type=""radio"" data-table=""" & TblVar & """ data-field=""" & FldVar & """" &_
					ew_IIf(page > -1, " data-page=""" & page & """", "") &_
					" name=""" & name & """ id=""" & name & "_" &  rowswrk & """" &_
					" data-value-separator=""" & DisplayValueSeparatorAttribute & """" &_
					" value=""" & ew_HtmlEncode(curValue) & """ checked" & EditAttributes & ">" & curValue
				If Not isDropdown Then
					html = "<label class=""radio-inline"">" & html & "</label>"
				End If
				str = str & html
			End If
		End If
		If emptywrk Then OldValue = ""
		RadioButtonListHtml = str
	End Function

	' Get checkboxes HTML
	Function CheckBoxListHtml(isDropdown, name, page, tbl)
		Dim arwrk, cnt, curValue, emptywrk, html, str, val
		emptywrk = True
		curValue = ew_IIf(tbl.RowType = EW_ROWTYPE_SEARCH, ew_IIf(Mid(name,1,1) = "y",  AdvancedSearch.SearchValue2, AdvancedSearch.SearchValue), CurrentValue)
		str = ""
		arwrk = EditValue
		If IsArray(arwrk) Then
			armultiwrk = ew_IIf(CStr(curValue&"") = "", Array(), Split(CStr(curValue&""), ","))
			cnt = UBound(armultiwrk)
			rowswrk = UBound(arwrk)
			emptywrk = True
			For rowcntwrk = 0 To rowswrk - 1
				selwrk = False
				For ari = 0 To cnt
					If ew_SameStr(arwrk(rowcntwrk,0), armultiwrk(ari)) And Not IsNull(armultiwrk(ari)) Then
						armultiwrk(ari) = Null ' Marked for removal
						selwrk = True
						emptywrk = False
						Exit For
					End If
				Next
				If selwrk Then
					html = "<input type=""checkbox"" data-table=""" & TblVar & """ data-field=""" & FldVar & """" &_
					ew_IIf(page > -1, " data-page=""" & page & """", "") &_
					" name=""" & name & """ id=""" & name & "_" & rowcntwrk & """" &_
					" data-value-separator=""" & DisplayValueSeparatorAttribute & """" &_
					" value=""" & ew_HtmlEncode(arwrk(rowcntwrk,0)) & """ checked" & EditAttributes & ">" & DisplayValue(arwrk,rowcntwrk)
					If Not isDropdown Then
						html = "<label class=""checkbox-inline"">" & html & "</label>" ' Note: No spacing within the LABEL tag
					End If
					str = str & html
				End If
			Next
			For ari = 0 To cnt
				If Not IsNull(armultiwrk(ari)) Then
					val = Trim(armultiwrk(ari)&"")
					html = "<input type=""checkbox"" data-table=""" & TblVar & """ data-field=""" & FldVar & """" & _
						ew_IIf(page > -1, " data-page=""" & page & """", "") & _
						" name=""" & name & """ value=""" & ew_HtmlEncode(val) & """ checked" & _
						" data-value-separator=""" & DisplayValueSeparatorAttribute & """" &_
						EditAttributes & ">" & val
					If Not isDropdown Then
						html = "<label class=""checkbox-inline"">" & html & "</label>"
					End If
					str = str & html
				End If
			Next
		End If
		If emptywrk Then OldValue = ""
		CheckBoxListHtml = str
	End Function

	' Get field user options as Json
	Public Function JsonOptions(ps)
		Dim arwrk, value, caption
		Set arwrk = Dictionary()
		If ps Then
			Call arwrk.Add("", Language.Phrase("PleaseSelect"))
		End If
		For i = 0 to OptionCount - 1
			value = FldTagValue(i + 1)
			caption = FldTagCaption(i + 1)
			caption = ew_IIf(caption <> "", caption, value)
			Call arwrk.Add(value, caption)
		Next
		JsonOptions = arwrk.ToJsonArray()
		Set arwrk = Nothing
	End Function

	' Get display field value separator
	' idx (int) display field index (1|2|3)
	Public Function GetDisplayValueSeparator(idx)
		GetDisplayValueSeparator = ", "
		If IsArray(DisplayValueSeparator) Then
			If UBound(DisplayValueSeparator) >= idx - 1 Then
				GetDisplayValueSeparator = DisplayValueSeparator(idx - 1)
			End If
		ElseIf ew_NotEmpty(DisplayValueSeparator) Then
			GetDisplayValueSeparator = DisplayValueSeparator
		End If
	End Function

	' Get display field value separator as attribute value
	Function DisplayValueSeparatorAttribute()
		If IsArray(DisplayValueSeparator) Then
			Dim sAtt, i
			sAtt = ""
			For i = 0 to UBound(DisplayValueSeparator)
				If sAtt <> "" Then sAtt = sAtt & ", "
				sAtt = sAtt & """" & ew_JsEncode2((DisplayValueSeparator(i))) & """"
			Next
			If sAtt <> "" Then sAtt = "[" & sAtt & "]"
			DisplayValueSeparatorAttribute = ew_HtmlEncode(sAtt)
		Else
			DisplayValueSeparatorAttribute = DisplayValueSeparator
		End If
	End Function

	' Get display value (for lookup field)
	Public Function DisplayValue(ar, rownum)
		Dim val, sep, i
		val = ""
		If UBound(ar,2) >= rownum Then
			val = CStr(ar(1, rownum)&"")
			For i = 2 to 4 ' Display field 2 to 4
				sep = GetDisplayValueSeparator(i - 1)
				If Not IsNull(sep) Then ' No separator, skip
					If UBound(ar,1) >= i Then
						If ar(i, rownum)&"" <> "" Then
							val = val & sep & ar(i,rownum)
						End If
					End If
				End If
			Next
		End If
		DisplayValue = val
	End Function

	' Reset attributes for field object
	Public Sub ResetAttrs()
		CssStyle = ""
		CssClass = ""
		CellCssStyle = ""
		CellCssClass = ""
		Call CellAttrs.Clear
		Call EditAttrs.Clear
		Call ViewAttrs.Clear
		Call LinkAttrs.Clear
	End Sub
	Dim FldDateTimeFormat ' Date time format
	Dim CssStyle ' CSS style
	Dim CssClass ' CSS class
	Dim ImageAlt ' Image alt
	Dim ImageWidth ' Image width
	Dim ImageHeight ' Image height
	Dim ImageResize ' Image resize
	Dim IsBlobImage ' Is blob image
	Dim ViewCustomAttributes ' View custom attributes
	Dim CellAttrs ' Cell attributes
	Dim EditAttrs ' Edit attributes
	Dim ViewAttrs ' View attributes

	' View Attributes
	Public Property Get ViewAttributes
		Dim sAtt, Attr, Value, i, attrs, dict
		Dim sStyle, sClass, sAlt
		Set dict = Dictionary()
		attrs = ViewCustomAttributes ' Custom attributes
		If IsArray(attrs) Then ' Custom attributes as array
			For i = 0 to UBound(attrs)
				Attr = attrs(i)(0)
				Value = attrs(i)(1)
				Call dict.Add(LCase(Attr), Value)
			Next
			attrs = ""
		End If
		If FldViewTag = "IMAGE" Then
			sAlt = Trim(ImageAlt&"")
			If sAlt <> "" Then
				If dict.ContainsKey("alt") Then
					Call dict.Set("alt", dict.Get("alt") & " " & sAlt)
				Else
					Call dict.Add("alt", sAlt)
				End If
			End If
		End If
		sStyle = ""
		If FldViewTag = "IMAGE" And CLng(ImageWidth) > 0 And (Not ImageResize Or CLng(ImageHeight) <= 0) Then
			sStyle = sStyle & "width: " & CInt(ImageWidth) & "px; "
		End If
		If FldViewTag = "IMAGE" And CLng(ImageHeight) > 0 And (Not ImageResize Or CLng(ImageWidth) <= 0) Then
			sStyle = sStyle & "height: " & CInt(ImageHeight) & "px; "
		End If
		If Trim(CssStyle&"") <> "" Then
			sStyle = sStyle & Trim(CssStyle&"")
		End If
		If sStyle <> "" Then
			If dict.ContainsKey("style") Then
				Call dict.Set("style", dict.Get("style") & ";" & sStyle)
			Else
				Call dict.Add("style", sStyle)
			End If
		End If
		sClass = Trim(CssClass&"")
		If sClass <> "" Then
			If dict.ContainsKey("class") Then
				Call dict.Set("class", dict.Get("class") & " " & sClass)
			Else
				Call dict.Add("class", sClass)
			End If
		End If
		sAtt = ViewAttrs.ToString(dict)
		Set dict = Nothing
		If attrs <> "" Then ' Custom attributes as string
			sAtt = sAtt & " " & attrs
		End If
		ViewAttributes = sAtt
	End Property
	Dim EditCustomAttributes ' Edit custom attributes

	' Edit attributes
	Public Property Get EditAttributes
		Dim sAtt, Attr, Value, i, attrs, dict
		Set dict = Dictionary()
		attrs = EditCustomAttributes ' Custom attributes
		If IsArray(attrs) Then ' Custom attributes as array
			For i = 0 to UBound(attrs)
				Attr = attrs(i)(0)
				Value = attrs(i)(1)
				Call dict.Add(LCase(Attr), Value)
			Next
			attrs = ""
		End If
		Dim sStyle, sClass
		sStyle = Trim(CssStyle&"")
		If sStyle <> "" Then
			If dict.ContainsKey("style") Then
				Call dict.Set("style", dict.Get("style") & ";" & sStyle)
			Else
				Call dict.Add("style", sStyle)
			End If
		End If
		sClass = Trim(CssClass&"")
		If sClass <> "" Then
			If dict.ContainsKey("class") Then
				Call dict.Set("class", dict.Get("class") & " " & sClass)
			Else
				Call dict.Add("class", sClass)
			End If
		End If
		If Disabled Then
			Call dict.Add("disabled", True)
		End If
		If ReadOnly Then
			If ew_InArray(FldHtmlTag, Array("TEXT", "PASSWORD", "TEXTAREA")) Then ' Elements support readonly
				Call dict.Add("readonly", True)
			Else ' Elements do not support readonly
				Call dict.Add("disabled", True)
				Call dict.Add("data-readonly", "1")
				If dict.ContainsKey("class") Then
					Call dict.Set("class", dict.Get("class") & " " & "disabled")
				Else
					Call dict.Add("class", "disabled")
				End If
			End If
		End If
		sAtt = EditAttrs.ToString(dict)
		Set dict = Nothing
		If attrs <> "" Then ' Custom attributes as string
			sAtt = sAtt & " " & attrs
		End If
		EditAttributes = sAtt
	End Property
	Dim CustomMsg ' Custom message
	Dim CellCssClass ' Cell CSS class
	Dim CellCssStyle ' Cell CSS style
	Dim CellCustomAttributes ' Cell custom attributes
	Dim HeaderCellCssClass ' Header cell (<th>) CSS class
	Dim FooterCellCssClass ' Footer cell (<td> in <tfoot>) CSS

	' Cell Styles (Used in export)
	Public Property Get CellStyles
		Dim sAtt, Value
		Dim sStyle, sClass
		sAtt = ""
		sStyle = CellCssStyle
		If CellAttrs.Exists("style") Then
			Value = CellAttrs("style")
			If Trim(Value) <> "" Then
				sStyle = sStyle & " " & Value
			End If
		End If
		sClass = CellCssClass
		If CellAttrs.Exists("class") Then
			Value = CellAttrs("class")
			If Trim(Value) <> "" Then
				sClass = sClass & " " & Value
			End If
		End If
		If Trim(sStyle) <> "" Then
			sAtt = sAtt & " style=""" & Trim(sStyle) & """"
		End If
		If Trim(sClass) <> "" Then
			sAtt = sAtt & " class=""" & Trim(sClass) & """"
		End If
		CellStyles = sAtt
	End Property

	' Cell Attributes
	Public Property Get CellAttributes
		Dim sAtt, Attr, Value, i, attrs, dict
		Set dict = Dictionary()
		attrs = CellCustomAttributes ' Custom attributes
		If IsArray(attrs) Then ' Custom attributes as array
			For i = 0 to UBound(attrs)
				Attr = attrs(i)(0)
				Value = attrs(i)(1)
				Call dict.Add(LCase(Attr), Value)
			Next
			attrs = ""
		End If
		Dim sStyle, sClass
		sStyle = Trim(CellCssStyle&"")
		If sStyle <> "" Then
			If dict.ContainsKey("style") Then
				Call dict.Set("style", dict.Get("style") & ";" & sStyle)
			Else
				Call dict.Add("style", sStyle)
			End If
		End If
		sClass = Trim(CellCssClass&"")
		If sClass <> "" Then
			If dict.ContainsKey("class") Then
				Call dict.Set("class", dict.Get("class") & " " & sClass)
			Else
				Call dict.Add("class", sClass)
			End If
		End If
		sAtt = CellAttrs.ToString(dict)
		Set dict = Nothing
		If attrs <> "" Then ' Custom attributes as string
			sAtt = sAtt & " " & attrs
		End If
		CellAttributes = sAtt
	End Property
	Dim LinkCustomAttributes ' Link custom attributes
	Dim LinkAttrs ' Link attributes

	' Link attributes
	Public Property Get LinkAttributes
		Dim sAtt, Attr, Value, i, attrs, sHref, dict
		Set dict = Dictionary()
		attrs = LinkCustomAttributes ' Custom attributes
		If IsArray(attrs) Then ' Custom attributes as array
			For i = 0 to UBound(attrs)
				Attr = attrs(i)(0)
				Value = attrs(i)(1)
				Call dict.Add(LCase(Attr), Value)
			Next
			attrs = ""
		End If
		sHref = Trim(HrefValue)
		If sHref <> "" Then
			Call dict.Add("href", sHref)
		End If
		sAtt = LinkAttrs.ToString(dict)
		Set dict = Nothing
		If attrs <> "" Then ' Custom attributes as string
			sAtt = sAtt & " " & attrs
		End If
		LinkAttributes = sAtt
	End Property

	' Header cell CSS class
	Public Function HeaderCellClass()
		Dim sClass
		sClass = "ewTableHeaderCell"
		Call ew_AppendClass(sClass, HeaderCellCssClass)
		HeaderCellClass = sClass
	End Function

	' Footer cell CSS class
	Public Function FooterCellClass()
		Dim sClass
		sClass = "ewTableFooterCell"
		Call ew_AppendClass(sClass, FooterCellCssClass)
		FooterCellClass = sClass
	End Function

	' Add CSS class to all cells
	Sub AddClass(sClass)
		Call ew_AppendClass(CellCssClass, sClass)
		Call ew_AppendClass(HeaderCellCssClass, sClass)
		Call ew_AppendClass(FooterCellCssClass, sClass)
	End Sub

	' Remove CSS class from all cells
	Sub RemoveClass(sClass)
		Call ew_RemoveClass(CellCssClass, sClass)
		Call ew_RemoveClass(HeaderCellCssClass, sClass)
		Call ew_RemoveClass(FooterCellCssClass, sClass)
	End Sub

	' Sort Attributes
	Dim Sortable

	Public Property Get Sort
		Sort = Session(EW_PROJECT_NAME & "_" & TblVar & "_" & EW_TABLE_SORT & "_" & FldVar)
	End Property

	Public Property Let Sort(v)
		If Session(EW_PROJECT_NAME & "_" & TblVar & "_" & EW_TABLE_SORT & "_" & FldVar) <> v Then
			Session(EW_PROJECT_NAME & "_" & TblVar & "_" & EW_TABLE_SORT & "_" & FldVar) = v
		End If
	End Property

	' Reverse sort
	Public Function ReverseSort()
		If Sort = "ASC" Then
			ReverseSort = "DESC"
		Else
			ReverseSort = "ASC"
		End If
	End Function

	' Advanced search
	Private Function UrlParameterName(name)
		Dim parm
		parm = FldParm
		If LCase(name) = LCase("SearchValue") Then
			parm = "x_" & parm
		ElseIf LCase(name) = LCase("SearchOperator") Then
			parm = "z_" & parm
		ElseIf LCase(name) = LCase("SearchCondition") Then
			parm = "v_" & parm
		ElseIf LCase(name) = LCase("SearchValue2") Then
			parm = "y_" & parm
		ElseIf LCase(name) = LCase("SearchOperator2") Then
			parm = "w_" & parm
		End If
		UrlParameterName = parm
	End Function
	Dim MultiUpdate ' Multi update
	Dim OldValue ' Old Value
	Dim ConfirmValue ' Confirm Value
	Dim CurrentValue ' Current value
	Dim ViewValue ' View value
	Dim EditValue ' Edit value
	Dim EditValue2 ' Edit value 2 (search)
	Dim HrefValue ' Href value
	Dim HrefValue2 ' Href value 2 (blob view URL)

	' Value property
	Public Property Get Value
		Value = CurrentValue
	End Property

	' List View value
	Public Property Get ListViewValue
		If FldDataType = EW_DATATYPE_XML Then
			ListViewValue = ViewValue & "&nbsp;"
		ElseIf Trim(ViewValue & "") = "" Then
			ListViewValue = "&nbsp;"
		Else
			Dim Result
			Result = ViewValue & ""

			'Result = ew_RegExReplace("<[^>]+>", Result, "") ' Remove all HTML Tags
			'Result = ew_RegExReplace("</?(b|p|span)[^>]*[^>]*?>", Result, "") ' Remove empty <b>/<p>/<span> tags

			Result = ew_RegExReplace("<[^img][^>]*>", Result, "") ' Remove all except non-empty image tag
			If Trim(Result) = "" Then
				ListViewValue = "&nbsp;"
			Else
				ListViewValue = ViewValue
			End If
		End If
	End Property
	Dim Exportable

	' Export Caption
	Public Property Get ExportCaption
		If EW_EXPORT_FIELD_CAPTION Then
			ExportCaption = FldCaption
		Else
			ExportCaption = FldName
		End If
	End Property
	Dim ExportOriginalValue

	' Export Value
	Public Property Get ExportValue(Export)
		If ExportOriginalValue Then
			ExportValue = CurrentValue
		Else
			ExportValue = ViewValue
		End If
		If Export = "xml" Then
			If IsNull(ExportValue) Then ExportValue = "<Null>"
		End If
	End Property

	' Get temp image
	Public Function GetTempImage()
		Dim wrkdata, wrkwidth, wrkheight, wrkfile, imagefn
		GetTempImage = ""
		If FldDataType = EW_DATATYPE_BLOB Then
			wrkdata = Upload.DbValue
			If ew_NotEmpty(wrkdata) Then
				If ImageResize Then
					wrkwidth = ImageWidth
					wrkheight = ImageHeight
					Call ew_ResizeBinary(wrkdata, wrkwidth, wrkheight, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
				End If
				GetTempImage = ew_TmpImage(wrkdata)
			End If
		Else
			wrkfile = Upload.DbValue
			If ew_Empty(wrkfile) Then
				wrkfile = CurrentValue
			End If
			Set fso = Server.CreateObject("Scripting.FileSystemObject")
			If wrkfile <> "" Then
				If Not UploadMultiple Then
					imagefn = PhysicalUploadPath & wrkfile
					If fso.FileExists(imagefn) Then
						If ImageResize Then
							wrkwidth = ImageWidth
							wrkheight = ImageHeight
							wrkdata = ew_ResizeFileToBinary(imagefn, wrkwidth, wrkheight, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
							GetTempImage = ew_TmpImage(wrkdata)
						Else
							If ew_IsRemote(imagefn) Then
								GetTempImage = ew_TmpImage(ew_LoadBinaryFile(imagefn))
							Else
								GetTempImage = UploadPath & wrkfile
							End If
						End If
					End If
				Else
					Dim tmpfiles, i
					tmpfiles = Split(wrkfile, EW_MULTIPLE_UPLOAD_SEPARATOR)
					For i = 0 to UBound(tmpfiles)
						If tmpfiles(i) <> "" Then
							imagefn = PhysicalUploadPath & tmpfiles(i)
							If fso.FileExists(imagefn) Then
								If ImageResize Then
									wrkwidth = ImageWidth
									wrkheight = ImageHeight
									wrkdata = ew_ResizeFileToBinary(imagefn, wrkwidth, wrkheight, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
									If GetTempImage <> "" Then GetTempImage = GetTempImage & EW_MULTIPLE_UPLOAD_SEPARATOR
									GetTempImage = GetTempImage & ew_TmpImage(wrkdata)
								Else
									If GetTempImage <> "" Then GetTempImage = GetTempImage & EW_MULTIPLE_UPLOAD_SEPARATOR
									If ew_IsRemote(imagefn) Then
										GetTempImage = GetTempImage & ew_TmpImage(ew_LoadBinaryFile(imagefn))
									Else
										GetTempImage = GetTempImage & UploadPath & tmpfiles(i)
									End If
								End If
							End If
						End If
					Next
				End If
			End If
		End If
	End Function

	' Form value
	Private m_FormValue

	Public Property Get FormValue
		FormValue = m_FormValue
	End Property

	Public Property Let FormValue(v)
		If FldDataType = EW_DATATYPE_NUMBER And Not ew_IsNumeric(v) And v&"" <> "" Then ' Check data type
			m_FormValue = Null
		Else
			If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
			m_FormValue = v
		End If
		CurrentValue = m_FormValue
	End Property

	' QueryString value
	Private m_QueryStringValue

	Public Property Get QueryStringValue
		QueryStringValue = m_QueryStringValue
	End Property

	Public Property Let QueryStringValue(v)
		If FldDataType = EW_DATATYPE_NUMBER And Not ew_IsNumeric(v) And v&"" <> "" Then ' Check data type
			m_QueryStringValue = Null
		Else
			If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
			m_QueryStringValue = v
		End If
		CurrentValue = m_QueryStringValue
	End Property

	' Database Value
	Dim m_DbValue

	Public Property Get DbValue
		DbValue = m_DbValue
	End Property

	Public Property Let DbValue(v)
		m_DbValue = v
		CurrentValue = m_DbValue
	End Property

	' Set up database value
	Public Sub SetDbValue(rs, value, def, skip)
		If skip Or Not Visible Or Disabled Then
			On Error Resume Next
			Dim OriginalValue
			OriginalValue = rs(FldName).OriginalValue
			If Err.Number = 0 Then
				If OriginalValue <> rs(FldName) Then
					rs(FldName) = OriginalValue
				End If
			End If
			Exit Sub
		End If
		Select Case FldType
			Case 2, 3, 16, 17, 18, 19, 21 ' Int
				value = ew_StrToInt(value)
				If IsNumeric(value) Then
					m_DbValue = CLng(value)
				Else
					m_DbValue = def
				End If
			Case 20 ' Big Int
				value = ew_StrToInt(value)
				If IsNumeric(value) Then
					m_DbValue = value ' Use original value, CLng may overflow
				Else
					m_DbValue = def
				End If
			Case 5, 6, 14, 131, 139 ' Double
				value = ew_StrToFloat(value)
				If IsNumeric(value) Then
					m_DbValue = CDbl(value)
				Else
					m_DbValue = def
				End If
			Case 4 ' Single
				value = ew_StrToFloat(value)
				If IsNumeric(value) Then
					m_DbValue = CSng(value)
				Else
					m_DbValue = def
				End If
			Case 7, 133, 134, 135, 145, 146 ' Date
				If IsDate(value) Then
					m_DbValue = CDate(value)
				ElseIf ew_IsDate(value) Then
					m_DbValue = value
				Else
					m_DbValue = def
				End If
			Case 201, 203, 129, 130, 200, 202 ' String
				m_DbValue = Trim(value)
				If EW_REMOVE_XSS Then m_DbValue = ew_RemoveXSS(m_DbValue)
				If m_DbValue = "" Then m_DbValue = def
			Case 128, 204, 205 ' Binary
				If IsNull(value) Then
					m_DbValue = def
				Else
					m_DbValue = value
				End If
			Case 72 ' GUID
				If ew_RegExTest("^(\{{1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{1})$", Trim(value)) Then
					m_DbValue = Trim(value)
				Else
					m_DbValue = def
				End If
			Case Else
				m_DbValue = value
		End Select
		OldValue = m_DbValue ' Save old DbValue in OldValue
		rs(FldName) = m_DbValue
	End Sub

	' Session Value
	Public Property Get SessionValue
		SessionValue = Session(EW_PROJECT_NAME & "_" & TblVar & "_" & FldVar & "_SessionValue")
	End Property

	Public Property Let SessionValue(v)
		Session(EW_PROJECT_NAME & "_" & TblVar & "_" & FldVar & "_SessionValue") = v
	End Property

	' Lookup filter query
	Function LookupFilterQuery(isAutoSuggest, pageId, pageObj)
		Dim dict, key, value
		If isAutoSuggest Then
			Call pageObj.SetupAutoSuggestFilters(Me, pageId)
		Else
			Call pageObj.SetupLookupFilters(Me, pageId)
		End If
		If ew_NotEmpty(LookupFilters) Then
			If LookupFilters.Count() > 0 Then
				LookupFilters.Add "lang", gsLanguage
			End If
			Set dict = LookupFilters.ToDictionary()
			For Each key In dict
				If ew_RegExTest("^f\d+$|^s$|^dx\d+$", key) Then ' "f<n>" or "s" or "dx<n>"
					dict(key) = ew_Encrypt(dict(key)) ' Encrypt SQL and filter
				End If
			Next
		End If
		LookupFilterQuery = ew_HttpBuildQuery(dict)
		Set dict = Nothing
	End Function
	Dim Count ' Count
	Dim Total ' Total
	Dim TrueValue
	Dim FalseValue

	' AdvancedSearch Object
	Private m_AdvancedSearch

	Public Property Get AdvancedSearch
		If ew_Empty(m_AdvancedSearch) Then
			Set m_AdvancedSearch = New cAdvancedSearch
		End If
		Set AdvancedSearch = m_AdvancedSearch
	End Property

	' Upload Object
	Private m_Upload

	Public Property Get Upload
		If ew_Empty(m_Upload) Then
			Set m_Upload = New cUpload
			m_Upload.TblVar = TblVar
			m_Upload.FldVar = FldVar
			m_Upload.UploadMultiple = UploadMultiple
			Set m_Upload.Parent = Me
		End If
		Set Upload = m_Upload
	End Property
	Dim UploadPath ' Upload path
	Dim OldUploadPath ' Old upload path (for deleting old image)
	Dim HrefPath ' Href path (for download)
	Dim UploadAllowedFileExt ' Allowed file extensions
	Dim UploadMaxFileSize ' Upload max file size
	Dim UploadMaxFileCount ' Upload max file count
	Dim UploadMultiple ' Multiple Upload
	Dim UseColorbox ' Use Colorbox
	Dim AutoFillOriginalValue
	Dim ReqErrMsg

	' Show object as string
	Public Function AsString()
		Dim AdvancedSearchAsString, UploadAsString
		If ew_NotEmpty(m_AdvancedSearch) Then
			AdvancedSearchAsString = m_AdvancedSearch.AsString
		Else
			AdvancedSearchAsString = "{Null}"
		End If
		If ew_NotEmpty(m_Upload) Then
			UploadAsString = m_Upload.AsString
		Else
			UploadAsString = "{Null}"
		End If
		AsString = "{" & _
			"FldName: " & FldName & ", " & _
			"FldVar: " & FldVar & ", " & _
			"FldExpression: " & FldExpression & ", " & _
			"FldType: " & FldType & ", " & _
			"FldDateTimeFormat: " & FldDateTimeFormat & ", " & _
			"CssStyle: " & CssStyle & ", " & _
			"CssClass: " & CssClass & ", " & _
			"ImageAlt: " & ImageAlt & ", " & _
			"ImageWidth: " & ImageWidth & ", " & _
			"ImageHeight: " & ImageHeight & ", " & _
			"ImageResize: " & ImageResize & ", " & _
			"ViewCustomAttributes: " & ViewCustomAttributes & ", " & _
			"EditCustomAttributes: " & EditCustomAttributes & ", " & _
			"CellCssStyle: " & CellCssStyle & ", " & _
			"CellCssClass: " & CellCssClass & ", " & _
			"Sort: " & Sort & ", " & _
			"MultiUpdate: " & MultiUpdate & ", " & _
			"CurrentValue: " & CurrentValue & ", " & _
			"ViewValue: " & ViewValue & ", " & _
			"EditValue: " & ValueToString(EditValue) & ", " & _
			"EditValue2: " & ValueToString(EditValue2) & ", " & _
			"HrefValue: " & HrefValue & ", " & _
			"HrefValue2: " & HrefValue2 & ", " & _
			"FormValue: " & m_FormValue & ", " & _
			"QueryStringValue: " & m_QueryStringValue & ", " & _
			"DbValue: " & m_DbValue & ", " & _
			"SessionValue: " & SessionValue & ", " & _
			"Count: " & Count & ", " & _
			"Total: " & Total & ", " & _
			"AdvancedSearch: " & AdvancedSearchAsString & ", " & _
			"Upload: " & UploadAsString & _
			"}"
	End Function

	' Value to string
	Private Function ValueToString(value)
		If IsArray(value) Then
			ValueToString = "[Array]"
		Else
			ValueToString = value
		End If
	End Function

	' Place holder
	Private m_PlaceHolder

	Public Property Get PlaceHolder
		If ReadOnly Or EditAttrs.Exists("readonly") Then
			PlaceHolder = ""
		Else
			PlaceHolder = m_PlaceHolder
		End If
	End Property

	Public Property Let PlaceHolder(v)
		m_PlaceHolder = v
	End Property

	Private m_UsePleaseSelect

	Public Property Get UsePleaseSelect
		UsePleaseSelect = m_UsePleaseSelect
	End Property

	Public Property Let UsePleaseSelect(v)
		m_UsePleaseSelect = v
	End Property

	Private m_PleaseSelectText

	Public Property Get PleaseSelectText
		PleaseSelectText = m_PleaseSelectText
	End Property

	Public Property Let PleaseSelectText(v)
		m_PleaseSelectText = v
	End Property

	' Class Initialize
	Private Sub Class_Initialize()
		Count = 0
		Total = 0
		TrueValue = "1"
		FalseValue = "0"
		ImageWidth = 0
		ImageHeight = 0
		ImageResize = False
		UploadPath = EW_UPLOAD_DEST_PATH
		OldUploadPath = EW_UPLOAD_DEST_PATH
		HrefPath = EW_UPLOAD_HREF_PATH
		UploadAllowedFileExt = EW_UPLOAD_ALLOWED_FILE_EXT ' Allowed file extensions
		UploadMaxFileSize = EW_MAX_FILE_SIZE ' Upload max file size
		UploadMaxFileCount = EW_MAX_FILE_COUNT ' Upload max file count
		UploadMultiple = False
		UseColorbox = EW_USE_COLORBOX ' Use Colorbox
		Visible = True
		Disabled = False
		ReadOnly = False
		Sortable = True
		TruncateMemoRemoveHtml = False
		TooltipWidth = 0
		FldIsVirtual = False
		FldIsDetailKey = False
		FldIsCustom = False
		Exportable = True
		ExportOriginalValue = EW_EXPORT_ORIGINAL_VALUE
		DisplayValueSeparator = ", "
		AutoFillOriginalValue = EW_AUTO_FILL_ORIGINAL_VALUE
		ReqErrMsg = Language.Phrase("EnterRequiredField")
		Set CellAttrs = New cAttributes ' Cell attributes
		Set EditAttrs = New cAttributes ' Cell attributes
		Set ViewAttrs = New cAttributes ' Cell attributes
		Set LinkAttrs = New cAttributes ' Cell attributes
		CustomMsg = "" ' Custom message
		CellCssClass = "" ' Cell CSS class
		CellCssStyle = "" ' Cell CSS style
		CellCustomAttributes = "" ' Cell custom attributes
		HeaderCellCssClass = "" ' Header cell (<th>) CSS class
		FooterCellCssClass = "" ' Footer cell (<td> in <tfoot>) CSS class
		UsePleaseSelect = True ' Please select
	End Sub

	' Class terminate
	Private Sub Class_Terminate()
		If ew_NotEmpty(m_AdvancedSearch) Then
			Set m_AdvancedSearch = Nothing
		End If
		If ew_NotEmpty(m_Upload) Then
			Set m_Upload = Nothing
		End If
		Set CellAttrs = Nothing
		Set EditAttrs = Nothing
		Set ViewAttrs = Nothing
		Set LinkAttrs = Nothing
	End Sub
End Class

'
'  Field class (end)
'
'
'  ListOptions class (begin)
'
Class cListOptions
	Dim Items
	Dim CustomItem
	Dim Tag
	Dim TagClassName
	Dim TableVar
	Dim RowCnt
	Dim ScriptType
	Dim ScriptId
	Dim ScriptClassName
	Dim JavaScript
	Dim RowSpan
	Dim UseDropDownButton
	Dim UseButtonGroup
	Dim ButtonClass
	Dim GroupOptionName
	Dim DropDownButtonPhrase
	Dim UseImageAndText

	' Class initialize
	Private Sub Class_Initialize()
		Set Items = Server.CreateObject("Scripting.Dictionary")
		Tag = "td"
		TagClassName = ""
		TableVar = ""
		RowCnt = ""
		ScriptType = "block"
		ScriptId = ""
		ScriptClassName = ""
		JavaScript = ""
		RowSpan = 1
		UseDropDownButton = False
		UseButtonGroup = False
		ButtonClass = ""
		GroupOptionName = "button"
		DropDownButtonPhrase = ""
		UseImageAndText = False
	End Sub

	' Check visible
	Function Visible()
		Dim i
		For i = 0 to Items.Count - 1
			If Items(i).Visible Then
				Visible = True
				Exit Function
			End If
		Next
		Visible = False
	End Function

	' Check group option visible
	Function GroupOptionVisible()
		Dim i, cnt
		cnt = 0
		For i = 0 To Items.Count - 1
			If Items(i).Name <> GroupOptionName And _
				((Items(i).Visible And Items(i).ShowInDropDown And UseDropDownButton) Or _
				(Items(i).Visible And Items(i).ShowInButtonGroup And UseButtonGroup)) Then
				cnt = cnt + 1
				If UseDropDownButton And cnt > 1 Then
					GroupOptionVisible = True
					Exit Function
				ElseIf UseButtonGroup Then
					GroupOptionVisible = True
					Exit Function
				End If
			End If
		Next
		GroupOptionVisible = False
	End Function

	' Add and return a new option
	Public Function Add(Name)
		Set Add = New cListOption
		Add.Name = Name
		Set Add.Parent = Me
		Call Items.Add(Items.Count, Add)
	End Function

	' Load default settings
	Public Sub LoadDefault()
		Dim i
		CustomItem = ""
		For i = 0 to Items.Count - 1
			Items(i).Body = ""
		Next
	End Sub

	' Hide all options
	Public Sub HideAllOptions(Ar)
		Dim i
		For i = 0 to Items.Count - 1
			If IsArray(Ar) Then
				If Not ew_InArray(Items(i).Name, Ar) Then
					Items(i).Visible = False
				End If
			Else
				Items(i).Visible = False
			End If
		Next
	End Sub

	' Show all options
	Public Sub ShowAllOptions()
		Dim i
		For i = 0 to Items.Count - 1
			Items(i).Visible = True
		Next
	End Sub

	' Item count
	Public Function Count()
		Count = Items.Count
	End Function

	' Get item by name
	Public Function GetItem(Name)
		Dim i
		For i = 0 To Items.Count - 1
			If Items(i).Name = Name Then
				Set GetItem = Items(i)
				Exit Function
			End If
		Next
		Set GetItem = Nothing
	End Function

	' Get item by name
	Public Default Property Get Item(Name)
		Set Item = GetItem(Name)
	End Property

	' Get item position
	Public Function ItemPos(Name)
		Dim pos, i
		pos = 0
		For i = 0 To Items.Count - 1
			If Items(i).Name = Name Then
				ItemPos = pos
				Exit Function
			End If
			pos = pos + 1
		Next
		ItemPos = -1
	End Function

	' Move item to position
	Public Sub MoveItem(Name, Pos)
		Dim i, oldpos, bfound
		If Pos < 0 Then ' If negative, count from the end
			Pos = Items.Count + Pos
		End If
		If Pos < 0 Then Pos = 0
		If Pos >= Items.Count Then
			Pos = Items.Count - 1
		End If
		bfound = False
		For i = 0 To Items.Count - 1
			If Items(i).Name = Name Then
				bfound = True
				oldpos = i
				Exit For
			End If
		Next
		If bfound And Pos <> oldpos Then
			Items.Key(oldpos) = Items.Count ' Move out of position first
			If oldpos < Pos Then ' Shuffle backward
				For i = oldpos+1 to Pos
					Items.Key(i) = i-1
				Next
			Else ' Shuffle forward
				For i = oldpos-1 to Pos Step -1
					Items.Key(i) = i+1
				Next
			End If
			Items.Key(Items.Count) = Pos ' Move to position
		End If
	End Sub

	' Render list options
	Public Sub Render(Part, Pos, OptRowCnt, OptScriptType, OptScriptId, OptScriptClassName)
		Dim groupitem, buttonvalue, buttongroups, cnt, i
		If CustomItem = "" Then
			Set groupitem = GetItem(GroupOptionName)
			If ew_NotEmpty(groupitem) Then
				If ShowPos(groupitem.OnLeft, Pos) Then
					If UseDropDownButton Then ' Render dropdown
						buttonvalue = ""
						cnt = 0
						For i = 0 To Items.Count - 1
							If Items(i).Name <> GroupOptionName And Items(i).Visible Then
								If Items(i).ShowInDropDown Then
									buttonvalue = buttonvalue & Items(i).Body
									cnt = cnt + 1
								ElseIf Items(i).Name = "listactions" Then
									Items(i).Body = RenderButtonGroup(Items(i).Body)
								End If
							End If
						Next
						If cnt <= 1 Then
							UseDropDownButton = False ' No need to use drop down button
						Else
							groupitem.Body = RenderDropDownButton(buttonvalue, Pos)
							groupitem.Visible = True
						End If
					End If
					If Not UseDropDownButton And UseButtonGroup Then ' Render button group
						Dim IsVisible
						IsVisible = False
						Set buttongroups = Dictionary()
						For i = 0 To Items.Count - 1
							If Items(i).Name <> GroupOptionName And Items(i).Visible And Items(i).Body <> "" Then
								If Items(i).ShowInButtonGroup Then
									IsVisible = True
									buttonvalue = ew_IIf(UseImageAndText, Items(i).GetImageAndText(Items(i).Body), Items(i).Body)
									If Not buttongroups.ContainsKey(Items(i).ButtonGroupName) Then
										Call buttongroups.Add(Items(i).ButtonGroupName, "")
									End If
									buttonvalue = buttongroups.Get(Items(i).ButtonGroupName) & buttonvalue
									Call buttongroups.Add(Items(i).ButtonGroupName, buttonvalue)
								ElseIf Items(i).Name = "listactions" Then
									Items(i).Body = RenderButtonGroup(Items(i).Body)
								End If
							End If
						Next
						groupitem.Body = ""
						Dim dict, key
						Set dict = buttongroups.ToDictionary()
						For Each key In dict
							groupitem.Body = groupitem.Body & RenderButtonGroup(dict(key))
						Next
						Set dict = Nothing
						If IsVisible Then
							groupitem.Visible = True
						End If
					End If
				End If
			End If
		End If
		If OptScriptId <> "" Then
			Call Write(Part, Pos, OptRowCnt, "block", OptScriptId, OptScriptClassName) ' Original block for ew_ShowTemplates
			Call Write(Part, Pos, OptRowCnt, "blocknotd", OptScriptId, "")
			Call Write(Part, Pos, OptRowCnt, "single", OptScriptId, "")
		Else
			Call Write(Part, Pos, OptRowCnt, OptScriptType, OptScriptId, OptScriptClassName)
		End If
	End Sub

	' Get custom template script tag
	Private Function CustomScriptTag(OptScriptId, OptScriptType, OptScriptClass, OptRowCnt)
		Dim id
		id = "_" & OptScriptId
		If ew_NotEmpty(OptRowCnt) Then id = OptRowCnt & id
		id = "tp" & OptScriptType & id
		CustomScriptTag = "<scr" & "ipt id=""" & id & """" & ew_IIf(ew_NotEmpty(OptScriptClass), " class=""" & OptScriptClass & """", "") & " type=""text/html"">"
	End Function

	' Write list options
	Private Sub Write(Part, Pos, OptRowCnt, OptScriptType, OptScriptId, OptScriptClass)
		RowCnt = OptRowCnt
		ScriptType = OptScriptType
		ScriptId = OptScriptId
		ScriptClassName = OptScriptClass
		JavaScript = ""
		If ScriptId <> "" Then
			Tag = ew_IIf(ScriptType = "block", "td", "span")
			If ScriptType = "block" Then
				If Part = "header" Then
					Response.Write CustomScriptTag(ScriptId, "oh", ScriptClassName, "")
				ElseIf Part = "body" Then
					Response.Write CustomScriptTag(ScriptId, "ob", ScriptClassName, RowCnt)
				ElseIf Part = "footer" Then
					Response.Write CustomScriptTag(ScriptId, "of", ScriptClassName, "")
				End If
			ElseIf ScriptType = "blocknotd" Then
				If Part = "header" Then
					Response.Write CustomScriptTag(ScriptId, "o2h", ScriptClassName, "")
				ElseIf Part = "body" Then
					Response.Write CustomScriptTag(ScriptId, "o2b", ScriptClassName, RowCnt)
				ElseIf Part = "footer" Then
					Response.Write CustomScriptTag(ScriptId, "o2f", ScriptClassName, "")
				End If
				Response.Write "<span>"
			End If
		Else

			'Tag = ew_IIf(Pos <> "" And Pos <> "bottom", "td", "span")
			Tag = ew_IIf(Pos <> "" And Pos <> "bottom", "td", "div")
		End If
		If CustomItem <> "" Then
			Dim cnt, opt
			cnt = 0
			Set opt = Nothing
			For i = 0 to Items.Count - 1
				If ShowItem(Items(i), ScriptId,  Pos) Then cnt = cnt + 1
				If Items(i).Name = CustomItem Then Set opt = Items(i)
			Next
			Dim bUseButtonGroup, bUseImageAndText
			bUseButtonGroup = UseButtonGroup ' Backup options
			bUseImageAndText = UseImageAndText
			UseButtonGroup = True ' Show button group for custom item
			UseImageAndText = True ' Use image and text for custom item
			If ew_NotEmpty(opt) And cnt > 0 Then
				If ScriptId <> "" Or ShowPos(opt.OnLeft, Pos) Then
					Response.Write opt.Render(Part, cnt)
				Else
					Response.Write opt.Render("", cnt)
				End If
			End If
			UseButtonGroup = bUseButtonGroup ' Restore options
			UseImageAndText = bUseImageAndText
		Else
			For i = 0 to Items.Count - 1
				If ShowItem(Items(i), ScriptId, Pos) Then Response.Write Items(i).Render(Part, 1)
			Next
		End If
		If (ScriptType = "block" Or ScriptType = "blocknotd") And ScriptId <> "" Then
			If ScriptType = "blocknotd" Then Response.Write "</span>"
			Response.Write "</scr" & "ipt>"
			If JavaScript <> "" Then Response.Write JavaScript
		End If
	End Sub

	' Show item
	Private Function ShowItem(Item, ScriptId, Pos)
		Dim show
		show = Item.Visible And (ScriptId <> "" Or ShowPos(Item.OnLeft, Pos))
		If show Then
			If UseDropDownButton Then
				show = (Item.Name = GroupOptionName Or Not Item.ShowInDropDown)
			ElseIf UseButtonGroup Then
				show = (Item.Name = GroupOptionName Or Not Item.ShowInButtonGroup)
			End If
		End If
		ShowItem = show
	End Function

	' Show position
	Private Function ShowPos(OnLeft, Pos)
		ShowPos = (OnLeft And Pos = "left") Or (Not OnLeft And Pos = "right") Or (Pos = "") Or (Pos = "bottom")
	End Function

	' Concat options and return concatenated HTML
	' pattern - regular expression pattern for matching the option names, e.g. '/^detail_/'
	Public Function Concat(pattern, separator)
		Dim sWrk, i
		sWrk = ""
		For i = 0 to Items.Count - 1
			If ew_RegExTest(pattern, Items(i).Name) And Items(i).Body <> "" Then
				If sWrk <> "" Then sWrk = sWrk & separator
				sWrk = sWrk & Items(i).Body
			End If
		Next
		Concat = sWrk
	End Function

	' Merge options to the first option and return it
	' pattern - regular expression pattern for matching the option names, e.g. "^detail_"
	Public Function Merge(pattern, separator)
		Dim sWrk, i, first
		sWrk = ""
		For i = 0 to Items.Count - 1
			If ew_RegExTest(pattern, Items(i).Name) Then
				If ew_Empty(first) Then
					Set first = Items(i)
					first.Body = Concat(pattern, separator)
				Else
					Items(i).Visible = False
				End If
			End If
		Next
		Set Merge = first
	End Function

	' Get button group link
	Public Function RenderButtonGroup(body)

		' Get all hidden inputs
		' format: <input type="hidden" ...>

		Dim inputs, inputmatches, i
		inputs = ""
		If ew_RegExMatch("<input\s+([^>]*)>", body, inputmatches) Then
			For i = 0 to inputmatches.Count - 1
				body = Replace(body, inputmatches(i), "", 1, 1)
				If ew_RegExTest("type\s*=\s*[\'""]hidden[\'""]", inputmatches(i).SubMatches(0)) Then
					If IsArray(inputs) Then
						ReDim Preserve inputs(UBound(inputs)+1)
					Else
						ReDim inputs(0)
					End If
					inputs(UBound(inputs)) = inputmatches(i)
				End If
			Next
		End If

		' Get all buttons
		' format: <div class="btn-group">...</div>

		Dim btns, btnmatches
		btns = ""
		If ew_RegExMatch("<div\s+class\s*=\s*[\'""]btn-group[\'""]([^>]*)>([\s\S]*?)<\/div\s*>", body, btnmatches) Then
			For i = 0 to btnmatches.Count - 1
				body = Replace(body, btnmatches(i), "", 1, 1)
				If IsArray(btns) Then
					ReDim Preserve btns(UBound(btns)+1)
				Else
					ReDim btns(0)
				End If
				btns(UBound(btns)) = btnmatches(i)
			Next
		End If
		Dim link, links, matches, submatches, tag, classname, attrs, caption, btngroup
		links = ""

		' Get all links/buttons
		' format: <a ...>...</a> / <button ...>...</button>

		If ew_RegExMatch("<(a|button)([^>]*)>([\s\S]*?)<\/(a|button)\s*>", body, matches) Then
			For i = 0 to matches.Count - 1
				tag = matches(i).SubMatches(0)
				If ew_RegExMatch("\s+class\s*=\s*[\'""]([\s\S]*?)[\'""]", matches(i).SubMatches(1), submatches) Then ' Match class='class'
					classname = submatches(0).SubMatches(0)
					attrs = Replace(matches(i).SubMatches(1), submatches(0), "", 1, 1)
				Else
					classname = ""
					attrs = matches(i).SubMatches(1)
				End If
				caption = matches(i).SubMatches(2)
				If InStr(classname, "btn btn-default") <= 0 Then ' Prepend button classes
					Call ew_PrependClass(classname, "btn btn-default")
				End If
				If ButtonClass <> "" Then
					Call ew_AppendClass(classname, ButtonClass)
				End If
				attrs = " class=""" & classname & """ " & attrs
 				link = "<" & tag & attrs & ">" & caption & "</" & tag & ">"
				links = links & link
			Next
		End If
		If links <> "" Then
			btngroup = "<div class=""btn-group ewButtonGroup"">" & links & "</div>"
		Else
			btngroup = ""
		End If
		If IsArray(btns) Then
			For i = 0 to UBound(btns)
				btngroup = btngroup & btns(i)
			Next
		End If
		If IsArray(inputs) Then
			For i = 0 to UBound(inputs)
				btngroup = btngroup & inputs(i)
			Next
		End If
		RenderButtonGroup = btngroup
	End Function

	' Render drop down button
	Public Function RenderDropDownButton(body, pos)

		' Get all hidden inputs
		' format: <input type="hidden" ...>

		Dim inputs, btnmatches, inputmatches, i, previewlinks
		inputs = ""
		If ew_RegExMatch("<input\s+([^>]*)>", body, inputmatches) Then
			For i = 0 to inputmatches.Count - 1
				body = Replace(body, inputmatches(i), "", 1, 1)
				If ew_RegExTest("type\s*=\s*[\'""]hidden[\'""]", inputmatches(i).SubMatches(0)) Then
					If IsArray(inputs) Then
						ReDim Preserve inputs(UBound(inputs)+1)
					Else
						ReDim inputs(0)
					End If
					inputs(UBound(inputs)) = inputmatches(i)
				End If
			Next
		End If

		' Remove all <div class="hide ewPreview">...</div>
		previewlinks = ""
		If ew_RegExMatch("<div\s+class\s*=\s*[\'""]hide\s+ewPreview[\'""]>([\s\S]*?)(<div([^>]*)>([\s\S]*?)<\/div\s*>)+([\s\S]*?)<\/div\s*>", body, inputmatches) Then
			For i = 0 to inputmatches.Count - 1
				body = Replace(body, inputmatches(i), "", 1, 1)
				previewlinks = previewlinks & inputmatches(i)
			Next
		End If

		' Remove toggle button first <button ... data-toggle="dropdown">...</button>
		If ew_RegExMatch("<button\s+([\s\S]*?)data-toggle\s*=\s*[\'""]dropdown[\'""]\s*>([\s\S]*?)<\/button\s*>", body, btnmatches) Then
			For i = 0 to btnmatches.Count - 1
				body = Replace(body, btnmatches(i), "", 1, 1)
			Next
		End If

		' Get all links/buttons <a ...>...</a> / <button ...>...</button>
		Dim matches, actionmatches, submatches, link, links, submenu, submenulink, submenulinks, action, classname, attrs, caption
		If ew_RegExMatch("<(a|button)([^>]*)>([\s\S]*?)<\/(a|button)\s*>", body, matches) Then
			links = ""
			submenu = False
			submenulink = ""
			submenulinks = ""
			For i = 0 to matches.Count - 1
				tag = matches(i).SubMatches(0)
				If ew_RegExMatch("\s+data-action\s*=\s*[\'""]([\s\S]*?)[\'""]", matches(i).SubMatches(1), actionmatches) Then ' Match data-action='action'
					action = actionmatches(0).SubMatches(0)
				Else
					action = ""
				End If
				If ew_RegExMatch("\s+class\s*=\s*[\'""]([\s\S]*?)[\'""]", matches(i).SubMatches(1), submatches) Then ' Match class='class'
					classname = ew_RegExReplace("btn[\S]*\s+", submatches(0).SubMatches(0), "")
					attrs = Replace(matches(i).SubMatches(1), submatches(0), "", 1, 1)
				Else
					classname = ""
					attrs = matches(i).SubMatches(1)
				End If
				attrs = ew_RegExReplace("\s+title\s*=\s*[\'""]([\s\S]*?)[\'""]", attrs, "") ' Remove title='title'
				If ew_RegExMatch("\s+data-caption\s*=\s*[\'""]([\s\S]*?)[\'""]", attrs, submatches) Then ' Match data-caption='caption'
					caption = submatches(0).SubMatches(0)
				Else
					caption = ""
				End If
				attrs = " class=""" & classname & """ " & attrs
				If LCase(tag) = "button" Then ' Add href for button
					attrs = attrs & " href=""javascript:void(0);"""
				End If
				If UseImageAndText And caption <> "" Then ' Image and text
					If ew_RegExMatch("<img([^>]*)>", matches(i).SubMatches(2), submatches) Then ' <img> tag
						caption = submatches(0) & "&nbsp;&nbsp;" & caption
					ElseIf ew_RegExMatch("<span([^>]*)>([\s\S]*?)<\/span\s*>", matches(i).SubMatches(2), submatches) Then ' <span class='class'></span> tag
						If ew_RegExTest("\s+class\s*=\s*[\'""]([\s\S]*?)[\'""]", submatches(0)) Then ' Match class='class'
							caption = submatches(0) & "&nbsp;&nbsp;" & caption
						End If
					End If
				End If
				If caption = "" Then
					caption = matches(i).SubMatches(2)
				End If
				link = "<a" & attrs & ">" & caption & "</a>"
				If action = "list" Then ' Start new submenu
					If submenu Then ' End previous submenu
						If submenulinks <> "" Then ' Set up submenu
							links = links & "<li class=""dropdown-submenu"">" & submenulink & "<ul class=""dropdown-menu"">" & submenulinks & "</ul></li>"
						Else
							links = links & "<li>" & submenulink & "</li>"
						End If
					End If
					submenu = True
					submenulink = link
					submenulinks = ""
				Else
					If action = "" And submenu Then ' End previous submenu
						If submenulinks <> "" Then ' Set up submenu
							links = links & "<li class=""dropdown-submenu"">" & submenulink & "<ul class=""dropdown-menu"">" & submenulinks & "</ul></li>"
						Else
							links = links & "<li>" & submenulink & "</li>"
						End If
						submenu = False
					End If
					If submenu Then
						submenulinks = submenulinks & "<li>" & link & "</li>"
					Else
						links = links & "<li>" & link & "</li>"
					End If
				End If
			Next
			Dim btnclass, button, btndropdown, btntitle
			If links <> "" Then
				If submenu Then ' End previous submenu
					If submenulinks <> "" Then ' Set up submenu
						links = links & "<li class=""dropdown-submenu"">" & submenulink & "<ul class=""dropdown-menu"">" & submenulinks & "</ul></li>"
					Else
						links = links & "<li>" & submenulink & "</li>"
					End If
				End If
				btnclass = "dropdown-toggle btn btn-default"
				If ButtonClass <> "" Then
					Call ew_AppendClass(btnclass, ButtonClass)
				End If
				btntitle = ew_HtmlTitle(DropDownButtonPhrase)
				btntitle = ew_IIf(DropDownButtonPhrase <> btntitle, " title=""" & btntitle & """", "")
				button = "<button class=""" & btnclass & """" & btntitle & " data-toggle=""dropdown"">" & DropDownButtonPhrase & "<b class=""caret""></b></button><ul class=""dropdown-menu " & ew_IIf(pos = "right", "dropdown-menu-right ", "") & "ewMenu"">" & links & "</ul>"
				If pos = "bottom" Then ' Use dropup
					btndropdown = "<div class=""btn-group dropup ewButtonDropdown"">" & button & "</div>"
				Else
					btndropdown = "<div class=""btn-group ewButtonDropdown"">" & button & "</div>"
				End If
			Else
				btndropdown = ""
			End If
			If IsArray(inputs) Then
				For i = 0 to UBound(inputs)
					btndropdown = btndropdown & inputs(i)
				Next
			End If
			btndropdown = btndropdown & previewlinks
			RenderDropDownButton = btndropdown
		Else
			RenderDropDownButton = ""
		End If
	End Function

	' Class terminate
	Private Sub Class_Terminate()
		Dim i
		For i = 0 To Items.Count - 1
			Set Items(i) = Nothing
		Next
		Set Items = Nothing
	End Sub
End Class

'
'  ListOptions class (end)
'
'
'  ListOption class (begin)
'
Class cListOption
	Dim Name
	Dim OnLeft
	Dim CssStyle
	Dim CssClass
	Dim Visible
	Dim Header
	Dim Body
	Dim Footer
	Dim Parent
	Dim ShowInButtonGroup
	Dim ShowInDropDown
	Dim ButtonGroupName

	' Class initialize
	Private Sub Class_Initialize()
		OnLeft = False
		Visible = True
		ShowInButtonGroup = True
		ShowInDropDown = True
		ButtonGroupName = "_default"
	End Sub

	Private Sub Clear()
		Body = ""
	End Sub

	Public Sub MoveTo(Pos)
		Call Parent.MoveItem(Name, Pos)
	End Sub

	' Render
	Public Function Render(Part, ColSpan)
		Dim value, res, tag, tagname, tagclass, attrs, js
		tagclass = Parent.TagClassName
		If Part = "header" Then
			If tagclass = "" Then tagclass = "ewListOptionHeader"
			value = Header
		ElseIf Part = "body" Then
			If tagclass = "" Then tagclass = "ewListOptionBody"
			If Parent.Tag <> "td" Then
				Call ew_AppendClass(tagclass, "ewListOptionSeparator")
			End If
			value = Body
		ElseIf Part = "footer" Then
			If tagclass = "" Then tagclass = "ewListOptionFooter"
			value = Footer
		Else
			value = Part
		End If
		If value = "" And Parent.Tag = "span" And Parent.ScriptId = "" Then
			Render = ""
			Exit Function
		End If
		res = ew_IIf(value <> "", value, "&nbsp;")
		Call ew_AppendClass(tagclass, CssClass)
		attrs = Array(Array("style", CssStyle), Array("data-name", Name))
		If LCase(Parent.Tag) = "td" And Parent.RowSpan > 1 Then
			attrs = ew_MergeAttrs(attrs, Array(Array("rowspan", Parent.RowSpan)))
		End If
		If LCase(Parent.Tag) = "td" And ColSpan > 1 Then
			attrs = ew_MergeAttrs(attrs, Array(Array("colspan", ColSpan)))
		End If
		tagname = Parent.TableVar & "_" & Name
		If Name <> Parent.GroupOptionName Then
			If Not ew_InArray(Name, Array("checkbox", "rowcnt")) Then
				If Parent.UseImageAndText Then
					res = GetImageAndText(res)
				End If
				If Parent.UseButtonGroup And ShowInButtonGroup Then
					res = Parent.RenderButtonGroup(res)
					If OnLeft And LCase(Parent.Tag) = "td" And ColSpan > 1 Then
						res = "<div style=""text-align: right"">" & res & "</div>"
					End If
				End If
			End If
			If Part = "header" Then
				res = "<span id=""elh_" & tagname & """ class=""" & tagname & """>" & res & "</span>"
			ElseIf Part = "body" Then
				res = "<span id=""el" & Parent.RowCnt & "_" & tagname & """ class=""" & tagname & """>" & res & "</span>"
			ElseIf Part = "footer" Then
				res = "<span id=""elf_" & tagname & """ class=""" & tagname & """>" & res & "</span>"
			End If
		End If
		tag = ew_IIf(Parent.Tag = "td" And Part = "header", "th", Parent.Tag)
		If Parent.UseButtonGroup And ShowInButtonGroup Then
			Call ew_AppendClass(tagclass, "text-nowrap")
		End If
		attrs = ew_MergeAttrs(attrs, Array(Array("class", tagclass)))
		res = ew_HtmlElement(tag, attrs, res, True)
		If Parent.ScriptId <> "" Then
			js = ew_ExtractScript(res, Parent.ScriptClassName & "_js")
			If Parent.ScriptType = "single" Then
				If Part = "header" Then
					res = "<scr" & "ipt id=""tpoh_" & Parent.ScriptId & "_" & Name & """ type=""text/html"">" & res & "</scr" & "ipt>"
				ElseIf Part = "body" Then
					res = "<scr" & "ipt id=""tpob" & Parent.RowCnt & "_" & Parent.ScriptId & "_" & Name & """ type=""text/html"">" & res & "</scr" & "ipt>"
				ElseIf Part = "footer" Then
					res = "<scr" & "ipt id=""tpof_" & Parent.ScriptId & "_" & Name & """ type=""text/html"">" & res & "</scr" & "ipt>"
				End If
			End If
			If js <> "" Then
				If Parent.ScriptType = "single" Then
					res = res & js
				Else
					Parent.JavaScript = Parent.JavaScript & js
				End If
			End If
		End If
		Render = res
	End Function

	' Get image and text link
	Function GetImageAndText(body)
		Dim matches, submatches, i, caption
		If ew_RegExMatch("<a([^>]*)>([\s\S]*?)<\/a\s*>", body, matches) Then
			For i = 0 to matches.Count - 1
				If ew_RegExMatch("\s+data-caption\s*=\s*[\'""]([\s\S]*?)[\'""]", matches(i).SubMatches(0), submatches) Then ' Match data-caption='caption'
					caption = submatches(0).SubMatches(0)
					If ew_RegExTest("<img([^>]*)>", matches(i).SubMatches(1)) Then ' Image and text
						body = Replace(body, matches(i).SubMatches(1), matches(i).SubMatches(1) & "&nbsp;&nbsp;" & caption, 1, 1)
					End If
				End If
			Next
		End If
		GetImageAndText = body
	End Function

	' Convert to string
	Public Function AsString
		AsString = "{" & _
			"Name: " & Name & ", " & _
			"OnLeft: " & OnLeft & ", " & _
			"CssStyle: " & CssStyle & ", " & _
			"CssClass: " & CssClass & ", " & _
			"Visible: " & Visible & ", " & _
			"Header: " & ew_HtmlEncode(Header) & ", " & _
			"Body: " & ew_HtmlEncode(Body) & ", " & _
			"Footer: " & ew_HtmlEncode(Footer) & _
			"}"
	End Function
End Class

'
'  ListOption class (end)
'
'
'  ListActions class (begin)
'
Class cListActions
	Dim m_Items

	Private Sub Class_Initialize()
		m_Items = Array()
	End Sub

	Public Function Exists(Attr)
		Dim i, att
		att = Trim(Attr)
		If att <> "" Then
			For i = 0 to UBound(m_Items)
				If LCase(m_Items(i)(0)) = LCase(att) Then
					Exists = True
					Exit Function
				End If
			Next
		End If
		Exists = False
	End Function

	' Add and return a new option
	Public Sub Add(Name, Action, Allow, Method, SelectType, ConfirmMsg, Icon)
		Dim item
		If Action & "" <> "" Then
			Set item = New cListAction
			item.Name = Name
			item.Caption = Action
			item.Action = Name
			item.Allow = Allow
			item.Method = Method
			item.SelectType = SelectType
			item.ConfirmMsg = ConfirmMsg
			item.Icon = Icon
			If Ubound(m_Items) < 0 Then
				ReDim m_Items(0)
			Else
				ReDim Preserve m_Items(UBound(m_Items)+1)
			End If
			m_Items(UBound(m_Items)) = Array(Name, item)
		End If
	End Sub

	' Get item by name
	Public Function GetItem(Name)
		Dim i, att
		att = Trim(Name)
		If att <> "" Then
			If IsNumeric(att) Then
				att = CLng(att)
				If att >= 0 And att <= UBound(m_Items) Then
					Set GetItem = m_Items(att)(1)
					Exit Function
				End If
			End If
			For i = 0 to UBound(m_Items)
				If LCase(m_Items(i)(0)) = LCase(att) Then
					Set GetItem = m_Items(i)(1)
					Exit Function
				End If
			Next
		End If
		GetItem = Null
	End Function

	' Get item by name
	Public Default Property Get Item(Name)
		Set Item = GetItem(Name)
	End Property

	Public Function Count()
		Count = UBound(m_Items)+1
	End Function
End Class

'
'  ListActions class (end)
'
'
'  ListAction class (begin)
'
Class cListAction
	Dim Name
	Dim Action
	Dim Caption
	Dim Allow
	Dim Method ' Post back (p) / Ajax (a)
	Dim SelectType ' Multiple (m) / Single (s)
	Dim ConfirmMsg
	Dim Icon ' Icon

	' Class initialize
	Private Sub Class_Initialize()
		Action = ""
		Caption = ""
		Allow = True
		Method = EW_ACTION_POSTBACK ' Post back (p) / Ajax (a)
		SelectType = EW_ACTION_MULTIPLE ' Multiple (m) / Single (s)
		ConfirmMsg = ""
		Icon = "glyphicon glyphicon-star ewIcon" ' Icon
	End Sub

	Public Function ToJson(htmlencode)
		Dim json, key, val, j
		Dim ar(1,3)
		Dim arwrk(3)
		ar(0,0) = "msg"
		ar(1,0) = ConfirmMsg
		ar(0,1) = "action"
		ar(1,1) = Action
		ar(0,2) = "method"
		ar(1,2) = Method
		ar(0,3) = "select"
		ar(1,3) = SelectType
		For j = 0 To UBound(ar,2)
			key = """" & ew_JsEncode2(ar(0,j)) & """:"
			val = ar(1,j)
			arwrk(j) = key & ew_VarToJson(val, "", "")
		Next
		json = "{" & Join(arwrk, ",") & "}"
		If htmlencode Then
			json = ew_HtmlEncode(json)
		End If
		ToJson = json
	End Function
End Class

'
'  ListAction class (end)
'
'
'  SubPages class (begin)
'
Class cSubPages
	Dim Justified
	Dim Style
	Dim Parent
	Dim m_Items
	Dim ValidKeys
	Dim m_ActivePageIndex

	' Class initialize
	Private Sub Class_Initialize()
		Justified = False
		Style = "" ' "tabs" or "pills" or "" (panels)
		Parent = "False" ' For Style = "" only, if a selector is provided, then all collapsible elements under the specified parent will be closed when a collapsible item is shown.
		m_Items = Array()
		ValidKeys = Null
		m_ActivePageIndex = ""
	End Sub

	' Get nav style
	Function NavStyle()
		style = " nav-" & Style
		If Justified Then style = style & " nav-justified"
		NavStyle = style
	End Function

	Function Items(idx)
		Dim item
		item = m_Items(idx)
		Set Items = item(1)
	End Function

	' Get page style
	Function TabStyle(k)
		Dim item, sStyle
		Set item = GetItem(k)
		sStyle = ""
		If ActivePageIndex = k Then
			sStyle = "active"
		ElseIf Not IsNull(item) Then
			If Not item.Visible Then
				sStyle = "hidden ewHidden"
			ElseIf item.Disabled And sStyle <> "" Then
				sStyle = "disabled ewDisabled"
			End If
		End If
		TabStyle = ew_IIf(sStyle <> "", " class=""" & sStyle & """", "")
	End Function

	' Get page style
	Function PageStyle(k)
		Dim item

		'k = CStr(k)
		If ActivePageIndex = k Then
			If Style = "" Then
				PageStyle = " in"
				Exit Function
			Else
				PageStyle = " active"
				Exit Function
			End If
		End If
		Set item = GetItem(k)
		If Not IsNull(item) Then
			If Not item.Visible Then
				PageStyle = " hidden ewHidden"
				Exit Function
			ElseIf item.Disabled And Style <> "" Then
				PageStyle = " disabled ewDisabled"
				Exit Function
			End If
		End If
		PageStyle = ""
	End Function

	' Get count
	Function Count()
		Count = UBound(m_Items) + 1
	End Function

	Private Function Exists(Attr)
		Dim i, att
		att = Trim(Attr)
		If att <> "" Then
			For i = 0 to UBound(m_Items)
				If LCase(m_Items(i)(0)) = LCase(att) Then
					Exists = True
					Exit Function
				End If
			Next
		End If
		Exists = False
	End Function

	' Add item by name
	Function Add(Name)
		Dim item
		Set item = New cSubPage
		If Ubound(m_Items) < 0 Then
			ReDim m_Items(0)
		Else
			ReDim Preserve m_Items(UBound(m_Items)+1)
		End If
		m_Items(UBound(m_Items)) = Array(Name, item)
	End Function

	' Get item by key
	Public Function GetItem(k)
		Dim i, att
		att = Trim(k)
		If att <> "" Then
			For i = 0 to UBound(m_Items)
				If LCase(m_Items(i)(0)) = LCase(att) Then
					Set GetItem = m_Items(i)(1)
					Exit Function
				End If
			Next
		End If
		GetItem = Null
	End Function

	' Get item by key
	Public Default Property Get Item(k)
		Set Item = GetItem(k)
	End Property

	' Active page index
	Function ActivePageIndex()
		Dim key, item

		' Return first active page
		For i = 0 to Ubound(m_Items)
			key = m_Items(i)(0)
			Set item = m_Items(i)(1)
			If (Not IsArray(ValidKeys) Or ew_InArray(key, ValidKeys)) And item.Visible And Not item.Disabled And item.Active And CStr(key&"") <> "0" Then ' Not common page
				m_ActivePageIndex = key
				ActivePageIndex = m_ActivePageIndex
				Exit Function
			End If
		Next

		' If not found, return first visible page
		For i = 0 to Ubound(m_Items)
			key = m_Items(i)(0)
			Set item = m_Items(i)(1)
			If (Not IsArray(ValidKeys) Or ew_InArray(key, ValidKeys)) And item.Visible And Not item.Disabled And CStr(key&"") <> "0" Then ' Not common page
				m_ActivePageIndex = key
				ActivePageIndex = m_ActivePageIndex
				Exit Function
			End If
		Next

		' Not found
		ActivePageIndex = Null
	End Function
End Class

'
'  SubPages class (end)
'
'
'  SubPage class (begin)
'
Class cSubPage
	Dim Active
	Dim Visible
	Dim Disabled

	' Class initialize
	Private Sub Class_Initialize()
		Active = False
		Visible = True ' If False, add class "hidden ewHidden" to the li or div.panel
		Disabled = False ' If True, add class "disabled ewDisabled" to the li (for tabs only, panels cannot be disabled)
	End Sub
End Class

'
'  SubPage class (end)
'
' Output SCRIPT tag
Sub ew_AddClientScript(src)
	Call ew_AddClientScriptEx(src, Null)
End Sub

' Output SCRIPT tag
Sub ew_AddClientScriptEx(src, attrs)
	Dim atts
	If EW_RELATIVE_PATH <> "" And Not ew_StartsStr(src, EW_RELATIVE_PATH) Then
		src = EW_RELATIVE_PATH & src
	End If
	atts = ew_MergeAttrs(Array(Array("type", "text/javascript"), Array("src", src)), attrs)
	Response.Write ew_HtmlElement("script", atts, "", True) & vbCrLf
End Sub

' Output LINK tag
Sub ew_AddStylesheet(href)
	Call ew_AddStylesheetEx(href, Null)
End Sub

' Output LINK tag
Sub ew_AddStylesheetEx(href, attrs)
	Dim atts
	If EW_RELATIVE_PATH <> "" And Not ew_StartsStr(href, EW_RELATIVE_PATH) Then
		href = EW_RELATIVE_PATH & href
	End If
	atts = ew_MergeAttrs(Array(Array("rel", "stylesheet"), Array("type", "text/css"), Array("href", href)), attrs)
	Response.Write ew_HtmlElement("link", atts, "", False) & vbCrLf
End Sub

Function ew_MergeAttrs(attrs1, attrs2)
	Dim attrs
	Set attrs = new cAttributes
	If IsArray(attrs1) Then attrs.AddAttributes attrs1
	If IsArray(attrs2) Then attrs.AddAttributes attrs2
	ew_MergeAttrs = attrs.ToArray()
	Set attrs = Nothing
End Function

' XML tag name
Function ew_XmlTagName(name)
	Dim wrkname
	wrkname = Trim(name)

	'If Not ew_RegExTest("\A(?!XML)[a-z][\w0-9-]*", wrkname) Then
	If Not ew_RegExTest("[a-z][\w0-9-]*", wrkname) Then
		wrkname = "_" & wrkname
	End If
	ew_XmlTagName = wrkname
End Function

' Generate random number
Function ew_Random()
	Randomize()
	ew_Random = ew_ZeroPad(CLng(1000000 * Rnd()),6)
End Function

' Load embedded images from content
' format for email: <img src="cid:..." ...>
' format for pdf: <img src="...ew_tmp_nnnnnn.*" ...>
Sub ew_LoadEmbeddedImages(content)
	Dim m, m1, i, j, src, fn, sid, valid
	sid = "s" & Session.SessionID
	If ew_RegExMatch("<img([^>]*)>", content, m) Then
		For i = 0 to m.Count - 1
			If ew_RegExMatch("\s+src\s*=\s*[\'""]([\s\S]*?)[\'""]", m(i).SubMatches(0), m1) Then
				src = m1(0).SubMatches(0)
				If Left(src, 4) = "cid:" Then
					fn = Mid(src, 5)
					valid = True
				Else
					fn = src
					If InStrRev(fn, "/") > 0 Then fn = Mid(fn, InStrRev(fn, "/")+1)
					valid = (Left(fn,Len(sid)) = sid)
				End If
				If valid Then
					If IsArray(gTmpImages) Then
						ReDim Preserve gTmpImages(UBound(gTmpImages)+1)
					Else
						ReDim gTmpImages(0)
					End If
					gTmpImages(UBound(gTmpImages)) = fn
				End If
			End If
		Next
	End If
End Sub

' Save base64 string to file (ASP)
Function ew_SaveBase64StringToFile(base64String, file)
	On Error Resume Next
	ew_SaveBase64StringToFile = False
	Dim doc, nodeB64, oStream
	Set doc = Server.CreateObject("MSXML2.DomDocument")
	Set nodeB64 = Doc.CreateElement("b64")
	nodeB64.DataType = "bin.base64"
	nodeB64.Text = Mid(base64String, InStr(base64String, ",") + 1)
	Set oStream = Server.CreateObject("ADODB.Stream")
	oStream.type = 1 'adTypeBinary
	oStream.Open
	oStream.Write nodeB64.NodeTypedValue
	oStream.SaveToFile file, 2 ' 2=adSaveCreateOverwrite
	oStream.Close
	Set oStream = Nothing
	Set nodeB64 = Nothing
	Set doc = Nothing
	If Err.Number = 0 Then ew_SaveBase64StringToFile = True
End Function

' Save text data to file
Function ew_SaveTextFile(folder, fn, filedata)
	On Error Resume Next
	Dim oStream
	ew_SaveTextFile = False
	If ew_CreateFolder(folder) Then
		Set oStream = Server.CreateObject("ADODB.Stream")
		oStream.Type = 2 ' 2=adTypeText
		oStream.Open
		oStream.Charset = "UTF-8"
		oStream.WriteText = filedata
		oStream.SaveToFile folder & fn, 2 ' 2=adSaveCreateOverwrite
		oStream.Close
		Set oStream = Nothing
		If Err.Number = 0 Then ew_SaveTextFile = True
	End If
End Function

' Load binary file
Function ew_LoadBinaryFile(FilePath)
	On Error Resume Next
	Dim objStream
	Set objStream = Server.CreateObject("ADODB.Stream")
	With objStream
		.Type = 1 ' adTypeBinary
		.Open
		.LoadFromFile FilePath
		ew_LoadBinaryFile = .Read
		.Close
	End With
End Function

' Create temp image file from binary data
Function ew_TmpImage(filedata)
	Dim export, tmpimage
	export = ""
	If Request.QueryString("export").Count > 0 Then
		export = Request.QueryString("export")
	ElseIf ew_IsHttpPost() Then
		If Request.Form("export").Count > 0 Then
			export = Request.Form("export")
		ElseIf Request.Form("exporttype").Count > 0 Then
			export = Request.Form("exporttype")
		End If
	End If
	tmpimage = ew_CreateTmpImage(filedata)
	If tmpimage <> "" Then
		If IsArray(gTmpImages) Then
			ReDim Preserve gTmpImages(UBound(gTmpImages)+1)
		Else
			ReDim gTmpImages(0)
		End If
		gTmpImages(UBound(gTmpImages)) = tmpimage
		ew_TmpImage = ew_TmpImageLnk(tmpimage, export)
	Else
		ew_TmpImage = ""
	End If
End Function

' Create temp image
Function ew_CreateTmpImage(filedata)
	Dim tmpimage, imageext, folder
	imageext = ew_ContentExt(filedata)
	tmpimage = ew_TmpImageFileName(imageext)
	folder = ew_UploadTempPath("", "")
	If ew_SaveFile(folder, tmpimage, filedata) Then
		ew_CreateTmpImage = tmpimage
	Else
		ew_CreateTmpImage = ""
	End If
End Function

' Get temp image file name
Function ew_TmpImageFileName(ext)
	Dim fn, folder, fso
	If Left(ext, 1) <> "." Then ext = "." & ext
	fn = "s" & Session.SessionID & ew_Random() & ext
	folder = ew_UploadPathEx(True, EW_UPLOAD_DEST_PATH)
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	Do While fso.FileExists(folder & fn)
		fn = "s" & Session.SessionID & ew_Random() & ext
	Loop
	Set fso = Nothing
	ew_TmpImageFileName = fn
End Function

' Delete temp images
Sub ew_DeleteTmpImages()
	Dim i, fso, f
	If IsArray(gTmpImages) Then
		Set fso = Server.CreateObject("Scripting.FileSystemObject")
		For i = 0 to UBound(gTmpImages)
			f = ew_UploadTempPath("", "") & gTmpImages(i)
			If fso.FileExists(f) Then
				fso.DeleteFile(f)
			End If
		Next
		Set fso = Nothing
	End If
End Sub

' Get temp image path
Function ew_TmpImageLnk(file, lnktype)
	Dim ar, i, lnk, path
	path = EW_UPLOAD_DEST_PATH
	If file = "" Then
		ew_TmpImageLnk = ""
	ElseIf lnktype = "email" Or lnktype = "cid" Then
		lnk = file
		If lnktype = "email" Then lnk = "cid:" & lnk
		ew_TmpImageLnk = lnk
	ElseIf lnktype = "pdf" Then ' Use full URL (ASP)
		If EW_ROOT_RELATIVE_PATH <> "." Then path = ew_PathCombine(ew_IncludeTrailingDelimiter(EW_ROOT_RELATIVE_PATH, False), ew_IncludeTrailingDelimiter(path, False), False)
		ew_TmpImageLnk = ew_FullUrl(ew_IncludeTrailingDelimiter(path, False) & file, "tmpfile")
	Else ' If EW_UPLOAD_TEMP_PATH, returns physical path, else returns relative path.
		ew_TmpImageLnk = ew_UploadTempPath(EW_UPLOAD_TEMP_PATH <> "" And EW_UPLOAD_TEMP_HREF_PATH <> "", "") & file
	End If
End Function

' Add querystring to URL
Function ew_UrlAddQuery(url, qry)
	If CStr(qry) = "" Then
		ew_UrlAddQuery = url
	Else
		ew_UrlAddQuery = url & ew_IIf(InStr(url, "?") > 0, "&", "?") & qry
	End If
End Function

' Add "hash" parameter to URL
Function ew_UrlAddHash(url, hash)
	ew_UrlAddHash = ew_UrlAddQuery(url, "hash=" & hash)
End Function
%>
<%

'
'  Basic Search class (begin)
'
Class cBasicSearch
	Dim TblVar
	Dim BasicSearchAnyFields
	Dim KeywordDefault
	Dim SearchTypeDefault

	' Keyword
	Private m_Keyword

	Public Property Get Keyword
		Keyword = m_Keyword
	End Property

	Public Property Let Keyword(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_Keyword = v
	End Property

	' SearchType
	Private m_SearchType

	Public Property Get SearchType
		SearchType = m_SearchType
	End Property

	Public Property Let SearchType(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_SearchType = v
	End Property

	Private Property Get Prefix
		Prefix = EW_PROJECT_NAME & "_" & TblVar & "_"
	End Property

	' Session variable name
	Private Function GetSessionName(suffix)
		GetSessionName = Prefix & suffix
	End Function

	' Load default
	Sub LoadDefault()
		Keyword = KeywordDefault
		SearchType = SearchTypeDefault
		If IsEmpty(Session(GetSessionName(EW_TABLE_BASIC_SEARCH_TYPE))) And SearchTypeDefault <> "" Then ' Save default to session
			setType(SearchTypeDefault)
		End If
	End Sub

	' Unset session
	Sub UnsetSession()
		Session.Contents.Remove(GetSessionName(EW_TABLE_BASIC_SEARCH_TYPE))
		Session.Contents.Remove(GetSessionName(EW_TABLE_BASIC_SEARCH))
	End Sub

	' Isset session
	Function IssetSession()
		IssetSession = Not IsEmpty(Session(GetSessionName(EW_TABLE_BASIC_SEARCH)))
	End Function

	' Set keyword
	Sub setKeyword(v)
		Keyword = v
		Session(GetSessionName(EW_TABLE_BASIC_SEARCH)) = v
	End Sub

	' Set type
	Sub setSearchType(v)
		SearchType = v
		Session(GetSessionName(EW_TABLE_BASIC_SEARCH_TYPE)) = v
	End Sub

	' Save
	Sub Save()
		Session(GetSessionName(EW_TABLE_BASIC_SEARCH)) = Keyword
		Session(GetSessionName(EW_TABLE_BASIC_SEARCH_TYPE)) = SearchType
	End Sub

	' Get keyword
	Function getKeyword()
		getKeyword = Session(GetSessionName(EW_TABLE_BASIC_SEARCH))
	End Function

	' Get type
	Function getSearchType()
		getSearchType = Session(GetSessionName(EW_TABLE_BASIC_SEARCH_TYPE))
	End Function

	' Get type name
	Function getSearchTypeName()
		Dim typ, typname
		typ = getSearchType()
		Select Case typ
			Case "=":   typname = Language.Phrase("QuickSearchExact")
			Case "AND": typname = Language.Phrase("QuickSearchAll")
			Case "OR":  typname = Language.Phrase("QuickSearchAny")
			Case Else:  typname = Language.Phrase("QuickSearchAuto")
		End Select
		getSearchTypeName = typname
	End Function

	' Get short type name
	Function getSearchTypeNameShort()
		Dim typ, typname
		typ = getSearchType()
		Select Case typ
			Case "=":   typname = Language.Phrase("QuickSearchExactShort")
			Case "AND": typname = Language.Phrase("QuickSearchAllShort")
			Case "OR":  typname = Language.Phrase("QuickSearchAnyShort")
			Case Else:  typname = Language.Phrase("QuickSearchAutoShort")
		End Select
		If typname <> "" Then typname = typname & "&nbsp;"
		getSearchTypeNameShort = typname
	End Function

	' Get keyword list
	Function KeywordList(sDefault)
		Dim sSearchKeyword, sSearchType, sSearch, ar, p, str, Match, Matches
		sSearchKeyword = ew_IIf(sDefault, KeywordDefault, Keyword)
		sSearchType = ew_IIf(sDefault, SearchTypeDefault, SearchType)
		If sSearchKeyword <> "" Then
			sSearch = Trim(sSearchKeyword)
			Set ar = Dictionary()
			If sSearchType <> "=" Then

				' Match quoted keywords (i.e.: "...")
				'/"([^"]*)"/i'

				If ew_RegExMatch("""([^""]*)""", sSearch, Matches) Then
					For Each Match In Matches
						p = Match.FirstIndex
						str = Mid(sSearch, 1, p)
						sSearch = Mid(sSearch, p + Len(Match) + 1)
						If Len(Trim(str)) > 0 Then
							Call ar.AddArray(Split(Trim(str), " "))
						End If
						ar.Push Match.Submatches(0) ' Save quoted keyword
					Next
				End If

				' Match individual keywords
				If Len(Trim(sSearch)) > 0 Then
					Call ar.AddArray(Split(Trim(sSearch), " "))
				End If
			Else
				ar.Push sSearch
			End If
			KeywordList = ar.ToArray()
			Set ar = Nothing
		End If
	End Function

	' Load
	Sub Load()
		Keyword = getKeyword()
		SearchType = getSearchType()
	End Sub

	' Class initialize
	Private Sub Class_Initialize()
		Keyword = ""
		KeywordDefault = ""
		SearchType = ""
		SearchTypeDefault = ""
		BasicSearchAnyFields = EW_BASIC_SEARCH_ANY_FIELDS
	End Sub
End Class

'
'  Basic Search class (end)
'
'
'  Advanced Search class (begin)
'
Class cAdvancedSearch
	Dim TblVar
	Dim FldVar
	Dim ViewValue ' View value
	Dim ViewValue2 ' View value 2
	Dim SearchValueDefault ' Search value default
	Dim SearchOperatorDefault ' Search operator default
	Dim SearchConditionDefault ' Search condition default
	Dim SearchValue2Default ' Search value 2 default
	Dim SearchOperator2Default ' Search operator 2 default

	' Search value
	Private m_SearchValue

	Public Property Get SearchValue
		SearchValue = m_SearchValue
	End Property

	Public Property Let SearchValue(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_SearchValue = v
	End Property

	' Search operator
	Private m_SearchOperator

	Public Property Get SearchOperator
		SearchOperator = m_SearchOperator
	End Property

	Public Property Let SearchOperator(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_SearchOperator = v
	End Property

	' Search condition
	Private m_SearchCondition

	Public Property Get SearchCondition
		SearchCondition = m_SearchCondition
	End Property

	Public Property Let SearchCondition(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_SearchCondition = v
	End Property

	' Search value 2
	Private m_SearchValue2

	Public Property Get SearchValue2
		SearchValue2 = m_SearchValue2
	End Property

	Public Property Let SearchValue2(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_SearchValue2 = v
	End Property

	' Search operator 2
	Private m_SearchOperator2

	Public Property Get SearchOperator2
		SearchOperator2 = m_SearchOperator2
	End Property

	Public Property Let SearchOperator2(v)
		If EW_REMOVE_XSS Then v = ew_RemoveXSS(v)
		m_SearchOperator2 = v
	End Property

	Private Property Get Prefix
		Prefix = EW_PROJECT_NAME & "_" & TblVar & "_" & EW_TABLE_ADVANCED_SEARCH & "_"
	End Property

	Private Property Get Suffix
		Suffix = "_" & Mid(FldVar, 3)
	End Property

	' Session variable name
	Private Function GetSessionName(infix)
		GetSessionName = Prefix & infix & Suffix
	End Function

	' Unset session
	Sub UnsetSession()
		Session.Contents.Remove(GetSessionName("x"))
		Session.Contents.Remove(GetSessionName("z"))
		Session.Contents.Remove(GetSessionName("v"))
		Session.Contents.Remove(GetSessionName("y"))
		Session.Contents.Remove(GetSessionName("w"))
	End Sub

	' Isset session
	Function IssetSession()
		IssetSession = Not IsEmpty(Session(GetSessionName("x"))) Or _
			Not IsEmpty(Session(GetSessionName("y")))
	End Function

	' Save to session
	Sub Save()
		If Session(GetSessionName("x")) <> SearchValue Then
			Session(GetSessionName("x")) = SearchValue
		End If
		If Session(GetSessionName("y")) <> SearchValue2 Then
			Session(GetSessionName("y")) = SearchValue2
		End If
		If Session(GetSessionName("z")) <> SearchOperator Then
			Session(GetSessionName("z")) = SearchOperator
		End If
		If Session(GetSessionName("v")) <> SearchCondition Then
			Session(GetSessionName("v")) = SearchCondition
		End If
		If Session(GetSessionName("w")) <> SearchOperator2 Then
			Session(GetSessionName("w")) = SearchOperator2
		End If
	End Sub

	' Load from session
	Sub Load()
		SearchValue = Session(GetSessionName("x"))
		SearchOperator = Session(GetSessionName("z"))
		SearchCondition = Session(GetSessionName("v"))
		SearchValue2 = Session(GetSessionName("y"))
		SearchOperator2 = Session(GetSessionName("w"))
	End Sub

	' Get value
	Function GetValue(infix)
		getValue = Session(GetSessionName(infix))
	End Function

	' Load default values
	Sub LoadDefault()
		If SearchValueDefault <> "" Then SearchValue = SearchValueDefault
		If SearchOperatorDefault <> "" Then SearchOperator = SearchOperatorDefault
		If SearchConditionDefault <> "" Then SearchCondition = SearchConditionDefault
		If SearchValue2Default <> "" Then SearchValue2 = SearchValue2Default
		If SearchOperator2Default <> "" Then SearchOperator2 = SearchOperator2Default
	End Sub

	' Class initialize
	Private Sub Class_Initialize()
		SearchValueDefault = ""
		ViewValue = ""
		SearchOperatorDefault = ""
		SearchConditionDefault = ""
		SearchValue2Default = ""
		ViewValue2 = ""
		SearchOperator2Default = ""
	End Sub

	' Show object as string
	Public Function AsString()
		AsString = "{" & _
			"SearchValue: " & SearchValue & ", " & _
			"SearchOperator: " & SearchOperator & ", " & _
			"SearchCondition: " & SearchCondition & ", " & _
			"SearchValue2: " & SearchValue2 & ", " & _
			"SearchOperator2: " & SearchOperator2 & _
			"}"
	End Function

	' Convert to JSON
	Function ToJson()
		If SearchValue <> "" Or SearchValue2 <> "" Or ew_InArray(SearchOperator, Array("IS NULL", "IS NOT NULL")) Or ew_InArray(SearchOperator2, Array("IS NULL", "IS NOT NULL")) Then
			ToJson = """x" & Suffix & """:""" & ew_JsEncode2(SearchValue) & """," & _
				"""z" & Suffix & """:""" & ew_JsEncode2(SearchOperator) & """," & _
				"""v" & Suffix & """:""" & ew_JsEncode2(SearchCondition) & """," & _
				"""y" & Suffix & """:""" & ew_JsEncode2(SearchValue2) & """," & _
				"""w" & Suffix & """:""" & ew_JsEncode2(SearchOperator2) & """"
		Else
			ToJson = ""
		End If
	End Function
End Class

'
'  Advanced Search class (end)
'

%>
<%

'
'  Upload class (begin)
'
Class cUpload
	Dim Index ' Index to handle multiple form elements
	Dim TblVar ' Table variable
	Dim FldVar ' Field variable
	Dim Parent ' Parent field object
	Dim UploadPath ' Upload path
	Dim Message ' Error message
	Dim DbValue ' Value from database
	Dim Value ' Upload value
	Dim FileName ' Upload file name
	Dim FileSize ' Upload file size
	Dim ContentType ' File content type
	Dim ImageWidth ' Image width
	Dim ImageHeight ' Image height
	Dim UploadMultiple ' Multiple upload
	Dim KeepFile ' Keep old file
	Dim Plugins ' Plugins for Resize()

	' Class initialize
	Private Sub Class_Initialize()
		Index = -1
		UploadMultiple = False ' Multiple upload
		KeepFile = False
		Plugins = Array() ' Plugins for Resize()
	End Sub

	' Check the file type of the uploaded file
	Private Function UploadAllowedFileExt(FileName)
		If Trim(FileName & "") = "" Then
			UploadAllowedFileExt = True
			Exit Function
		End If
		Dim Ext, Pos, arExt, FileExt
		arExt = Split(EW_UPLOAD_ALLOWED_FILE_EXT & "", ",")
		Ext = ""
		Pos = InStrRev(FileName, ".")
		If Pos > 0 Then	Ext = Mid(FileName, Pos+1)
		UploadAllowedFileExt = False
		For Each FileExt In arExt
	 		If LCase(Trim(FileExt)) = LCase(Ext) Then
				UploadAllowedFileExt = True
				Exit For
			End If
		Next
	End Function

	' Get upload file
	Public Function UploadFile()

		' Initialize upload value
		Value = Null
		Dim fvar, wrkvar, f, fso
		fvar = ew_IIf(Index < 0, FldVar, Mid(FldVar, 1, 1) & Index & Mid(FldVar, 2))
		wrkvar = "fn_" & fvar
		FileName = Request.Form(wrkvar) ' Get file name
		wrkvar = "fa_" & fvar
		KeepFile = (Request.Form(wrkvar) = "1") ' Check if keep old file
		If Not KeepFile And FileName <> "" And Not UploadMultiple Then
			f = ew_UploadTempPath(fvar, TblVar) & FileName
			Set fso = Server.CreateObject("Scripting.FileSystemObject")
			If fso.FileExists(f) Then
				Value = ew_LoadBinaryFile(f)
				FileSize = LenB(Value)
				ContentType = ew_ContentType(LeftB(Value, 11), f)
				Call ew_GetImageDimension(Value, ImageWidth, ImageHeight)
			End If
		End If
		UploadFile = True ' Normal return
	End Function

	' Resize image
	Public Function Resize(Width, Height, Interpolation)
		Dim wrkWidth, wrkHeight
		If Not IsNull(Value) Then
			wrkWidth = Width
			wrkHeight = Height
			If ew_ResizeBinary(Value, wrkWidth, wrkHeight, Interpolation) Then
				If wrkWidth > 0 And wrkHeight > 0 Then
					ImageWidth = wrkWidth
					ImageHeight = wrkHeight
				End If
				FileSize = LenB(Value)
			End If
		End If
	End Function

	' Get file count
	Function Count()
		If UploadMultiple And ew_NotEmpty(Value) Then
			Count = 1
		ElseIf UploadMultiple And FileName <> "" Then
			ar = Split(FileName, EW_MULTIPLE_UPLOAD_SEPARATOR)
			Count = UBound(ar) + 1
		End If
		Count = 0
	End Function

	' Save uploaded data to file
	Public Function SaveToFile(newFileName, overWrite)
		Dim path
		SaveToFile = False
		path = Parent.PhysicalUploadPath
		If Not IsNull(Value) Then
			If Trim(newFileName & "") = "" Then newFileName = FileName
			If Not overWrite Then
				newFileName = ew_UploadFileNameEx(path, newFileName)
			End If
			FileName = newFileName
			SaveToFile = ew_SaveFile(path, newFileName, Value)
		End If
	End Function

	' Resize and save uploaded data to file
	Public Function ResizeAndSaveToFile(width, height, interpolation, newFileName, overwrite)
		Dim oldValue, oldWidth, oldHeight, oldFileSize
		ResizeAndSaveToFile = False
		If Not IsNull(Value) Then
			oldValue = Value: oldWidth = ImageWidth: oldHeight = ImageHeight: oldFileSize = FileSize ' Save old values
			Call Resize(width, height, interpolation)
			ResizeAndSaveToFile = SaveToFile(newFileName, overwrite)
			Value = oldValue: ImageWidth = oldWidth: ImageHeight = oldHeight: FileSize = oldFileSize ' Restore old values
		End If
	End Function

	' Show object as string
	Public Function AsString()
		Dim dict
		Set dict = Dictionary()
		Call dict.Add("Index", Index)
		Call dict.Add("TblVar", TblVar)
		Call dict.Add("FldVar", FldVar)
		Call dict.Add("UploadPath", UploadPath)
		Call dict.Add("UploadMultiple", UploadMultiple)
		Call dict.Add("KeepFile", KeepFile)
		Call dict.Add("Message", Message)
		Call dict.Add("FileName", FileName)
		Call dict.Add("FileSize", FileSize)
		Call dict.Add("ContentType", ContentType)
		Call dict.Add("ImageWidth", ImageWidth)
		Call dict.Add("ImageHeight", ImageHeight)
		AsString = dict.ToJson()
		Set dict = Nothing
	End Function

	Public Function ToJson()
		ToJson = AsString()
	End Function
End Class

'
'  Upload class (end)
'

%>
<%

'
'  Advanced Security class (begin)
'
Class cAdvancedSecurity
	Dim m_ArUserLevel
	Dim m_ArUserLevelPriv
	Dim m_ArUserLevelID

	' Current user level id / user level
	Dim CurrentUserLevelID
	Dim CurrentUserLevel

	' Current user id / parent user id / user id array
	Dim CurrentUserID
	Dim CurrentParentUserID
	Dim m_ArUserID

	' Class Initialize
	Private Sub Class_Initialize()

		' Init User Level
		If IsLoggedIn() Then
			CurrentUserLevelID = SessionUserLevelID
			If IsNumeric(CurrentUserLevelID) Then
				If CurrentUserLevelID >= -2 Then
					ReDim m_ArUserLevelID(0)
					m_ArUserLevelID(0) = CurrentUserLevelID
				End If
			End If
		Else  ' Anonymous user
			CurrentUserLevelID = -2
			If IsArray(m_ArUserLevelID) Then
				ReDim Preserve m_ArUserLevelID(UBound(m_ArUserLevelID) + 1)
			Else
				ReDim m_ArUserLevelID(0)
			End If
			m_ArUserLevelID(UBound(m_ArUserLevelID)) = CurrentUserLevelID
		End If
		Session(EW_SESSION_USER_LEVEL_LIST) = UserLevelList

		' Init User ID
		CurrentUserID = SessionUserID
		CurrentParentUserID = SessionParentUserID

		' Load user level (for TablePermission_Loading event)
		Call LoadUserLevel
	End Sub

	' Session user id
	Public Property Get SessionUserID
		SessionUserID = Session(EW_SESSION_USER_ID) & ""
	End Property

	Public Property Let SessionUserID(v)
		Session(EW_SESSION_USER_ID) = Trim(v & "")
		CurrentUserID = Trim(v & "")
	End Property

	' Session parent user id
	Public Property Get SessionParentUserID
		SessionParentUserID = Session(EW_SESSION_PARENT_USER_ID) & ""
	End Property

	Public Property Let SessionParentUserID(v)
		Session(EW_SESSION_PARENT_USER_ID) = Trim(v & "")
		CurrentParentUserID = Trim(v & "")
	End Property

	' Current user name
	Public Property Get CurrentUserName
		CurrentUserName = Session(EW_SESSION_USER_NAME) & ""
	End Property

	Public Property Let CurrentUserName(v)
		Session(EW_SESSION_USER_NAME) = v
	End Property

	' Session user level id
	Public Property Get SessionUserLevelID
		SessionUserLevelID = Session(EW_SESSION_USER_LEVEL_ID)
	End Property

	Public Property Let SessionUserLevelID(v)
		Session(EW_SESSION_USER_LEVEL_ID) = v
		CurrentUserLevelID = v
		If IsNumeric(CurrentUserLevelID) Then
			If CurrentUserLevelID >= -2 Then
				ReDim m_ArUserLevelID(0)
				m_ArUserLevelID(0) = CurrentUserLevelID
			End If
		End If
	End Property

	' Session user level value
	Public Property Get SessionUserLevel
		SessionUserLevel = Session(EW_SESSION_USER_LEVEL)
	End Property

	Public Property Let SessionUserLevel(v)
		Session(EW_SESSION_USER_LEVEL) = v
		CurrentUserLevel = v
	End Property

	' Can add
	Public Property Get CanAdd
		CanAdd = ((CurrentUserLevel And EW_ALLOW_ADD) = EW_ALLOW_ADD)
	End Property

	Public Property Let CanAdd(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_ADD)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_ADD))
		End If
	End Property

	' Can delete
	Public Property Get CanDelete
		CanDelete = ((CurrentUserLevel And EW_ALLOW_DELETE) = EW_ALLOW_DELETE)
	End Property

	Public Property Let CanDelete(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_DELETE)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_DELETE))
		End If
	End Property

	' Can edit
	Public Property Get CanEdit
		CanEdit = ((CurrentUserLevel And EW_ALLOW_EDIT) = EW_ALLOW_EDIT)
	End Property

	Public Property Let CanEdit(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_EDIT)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_EDIT))
		End If
	End Property

	' Can view
	Public Property Get CanView
		CanView = ((CurrentUserLevel And EW_ALLOW_VIEW) = EW_ALLOW_VIEW)
	End Property

	Public Property Let CanView(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_VIEW)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_VIEW))
		End If
	End Property

	' Can list
	Public Property Get CanList
		CanList = ((CurrentUserLevel And EW_ALLOW_LIST) = EW_ALLOW_LIST)
	End Property

	Public Property Let CanList(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_LIST)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_LIST))
		End If
	End Property

	' Can report
	Public Property Get CanReport
		CanReport = ((CurrentUserLevel And EW_ALLOW_REPORT) = EW_ALLOW_REPORT)
	End Property

	Public Property Let CanReport(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_REPORT)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_REPORT))
		End If
	End Property

	' Can search
	Public Property Get CanSearch
		CanSearch = ((CurrentUserLevel And EW_ALLOW_SEARCH) = EW_ALLOW_SEARCH)
	End Property

	Public Property Let CanSearch(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_SEARCH)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_SEARCH))
		End If
	End Property

	' Can admin
	Public Property Get CanAdmin
		CanAdmin = ((CurrentUserLevel And EW_ALLOW_ADMIN) = EW_ALLOW_ADMIN)
	End Property

	Public Property Let CanAdmin(b)
		If b Then
			CurrentUserLevel = (CurrentUserLevel Or EW_ALLOW_ADMIN)
		Else
			CurrentUserLevel = (CurrentUserLevel And (Not EW_ALLOW_ADMIN))
		End If
	End Property

	' Last URL
	Public Property Get LastUrl
		LastUrl = Request.Cookies(EW_PROJECT_NAME)("lasturl")
	End Property

	' Save last URL
	Public Sub SaveLastUrl()
		Dim s, q
		s = ew_ScriptName()
		q = Request.ServerVariables("QUERY_STRING")
		If q <> "" Then s = s & "?" & q
		If LastUrl = s Then s = ""
		Response.Cookies(EW_PROJECT_NAME)("lasturl") = s
	End Sub

	' Auto login
	Public Function AutoLogin()
		Dim sUsr, sPwd, sEnc
		AutoLogin = False
		If Not AutoLogin And Request.Cookies(EW_PROJECT_NAME)("autologin") = "autologin" Then
			sUsr = Request.Cookies(EW_PROJECT_NAME)("username")
			sPwd = Request.Cookies(EW_PROJECT_NAME)("password")
			sPwd = ew_Decrypt(sPwd)
			AutoLogin = ValidateUser(sUsr, sPwd, True, False)
		End If
		If Not AutoLogin And EW_ALLOW_LOGIN_BY_URL And Request.QueryString("username").Count > 0 Then
			sUsr = ew_RemoveXSS(Request.QueryString("username"))
			sPwd = ew_RemoveXSS(Request.QueryString("password"))
			sEnc = Request.QueryString("encrypted").Count > 0
			AutoLogin = ValidateUser(sUsr, sPwd, True, sEnc)
		End If
		If Not AutoLogin And EW_ALLOW_LOGIN_BY_SESSION And Session(EW_PROJECT_NAME & "_Username") <> "" Then
			sUsr = Session(EW_PROJECT_NAME & "_Username")
			sPwd = Session(EW_PROJECT_NAME & "_Password")
			sEnc = Session(EW_PROJECT_NAME & "_Encrypted") <> ""
			AutoLogin = ValidateUser(sUsr, sPwd, True, sEnc)
		End If
	End Function

	' Login user
	Public Sub LoginUser(userName, userID, parentUserID, userLevel)
		Session(EW_SESSION_STATUS) = "login"
		If Not IsNull(userName) Then
			CurrentUserName = userName
		End If
		If Not IsNull(userID) Then
			SessionUserID = userID
		End If
		If Not IsNull(parentUserID) Then
			SessionParentUserID = parentUserID
		End If
		If Not IsNull(userLevel) Then
			SessionUserLevelID = CLng(userLevel)
			Call SetupUserLevel
		End If
	End Sub

	' Validate user
	Public Function ValidateUser(usr, pwd, autologin, encrypted)
		Dim RsUser, sFilter, sSql
		Dim customValid, valid
		Dim rs, Cmd, result
		valid = False
		customValid = False

		' Call User Custom Validate event
		If EW_USE_CUSTOM_LOGIN Then
			customValid = User_CustomValidate(usr, pwd)
			If customValid Then

				'Session(EW_SESSION_STATUS) = "login" ' To be setup below
				CurrentUserName = usr ' Load user name
			End If
		End If

		' Check other users
		If Not valid Then
				sFilter = Replace(EW_USER_NAME_FILTER, "%u", ew_AdjustSqlBase(usr, EW_USER_TABLE_DBID))

				' Get SQL from GetSql function from user table
				If IsEmpty(UserTable) Then
					Set UserTable = New cGrievance
					Set UserTableConn = ew_GetConn(UserTable.TableDBID)
				End If
				sSql = UserTable.GetSQL(sFilter, "")
				Set RsUser = UserTableConn.Execute(sSql)
				If Not RsUser.EOF Then
					valid = (customValid Or ew_ComparePassword(RsUser("Password"), pwd, encrypted))
					If valid Then
						Session(EW_SESSION_STATUS) = "login"
						Session(EW_SESSION_SYS_ADMIN) = 0 ' Non System Administrator
						CurrentUserName = RsUser("Email") ' Load user name
						Call UserProfile.Assign(RsUser)
						Call UserProfile.Delete("Password") ' Delete password

						' Call User Validated event
						valid = User_Validated(RsUser)
					End If
				Else  ' User not found in user table
					If customValid Then ' Grant default permissions
						customValid = User_Validated(RsUser)
					End If
				End If
				RsUser.Close
				Set RsUser = Nothing
		End If
		UserProfile.Save
		If customValid Then
			ValidateUser = customValid
			Exit Function
		End If
		If Not valid And Not IsPasswordExpired() Then Session(EW_SESSION_STATUS) = "" ' Clear login status
		ValidateUser = valid
	End Function

	' Load user level from config file
	Public Sub LoadUserLevelFromConfigFile(arUserLevel, arUserLevelPriv, arTable, userpriv)

		' User Level definitions
		arUserLevel = ""
		arUserLevelPriv = ""
		arTable = ""

		' Load user level from config files
		Dim folder, file
		folder = ew_ServerMapPath(EW_CONFIG_FILE_FOLDER)

		' Load user level settings from main config file
		Dim fso, ProjectID, projnode
		Dim userlevel, usergroup, i, group, id, name, priv
		ProjectID = CurrentProjectID
		file = folder & ProjectID & ".xml"
		Set fso = Server.CreateObject("Scripting.FileSystemObject")
		If fso.FileExists(file) Then
			Dim doc
			Set doc = ew_CreateXmlDom()
			If doc.Load(file) Then
				Set projnode = doc.SelectSingleNode("//configuration/project")
				If ew_NotEmpty(projnode) Then
					EW_RELATED_PROJECT_ID = projnode.getAttribute("relatedid")&""
					userlevel = projnode.getAttribute("userlevel")&""
					usergroup = Split(userlevel, ";")
					For i = 0 to UBound(usergroup)
						group = Split(usergroup(i), ",")
						If UBound(group) >= 2 Then
							id = group(0)
							name = group(1)
							priv = group(2)

							' Remove quotes
							If Len(name) >= 2 And Left(name,1) = """" And Right(name,1) = """" Then
								name = Mid(name,2,Len(name)-2)
							End If
							If Not IsArray(arUserLevel) Then
								ReDim arUserLevel(1,0)
							Else
								ReDim Preserve arUserLevel(1,UBound(arUserLevel,2)+1)
							End If
							arUserLevel(0,UBound(arUserLevel,2)) = id
							arUserLevel(1,UBound(arUserLevel,2)) = name
						End If
					Next
				End If
			End If
			Set doc = Nothing

			' Load from main config file
			Call LoadUserLevelFromXml(folder, ProjectID & ".xml", arUserLevelPriv, arTable, userpriv)

			' Load from related config file
			If EW_RELATED_PROJECT_ID <> "" Then
				Call LoadUserLevelFromXml(folder, EW_RELATED_PROJECT_ID & ".xml", arUserLevelPriv, arTable, userpriv)
			End If
		End If

		' Warn user if user level not setup
		If Not IsArray(arUserLevel) Then
			Set fso = Nothing
			Response.Write "Unable to load user level from config file: " & file
			Response.End
		End If

		' Load user priv settings from all config files
		Dim oFolder, oFiles, oFile, sFileName, sFileExt
		If fso.FolderExists(folder) Then
			Set oFolder = fso.GetFolder(folder)
			Set oFiles = oFolder.Files
			For Each oFile In oFiles
				sFileName = oFile.Name
				sFileExt = InStrRev(sFileName, ".")
				If sFileExt < 1 Then sFileExt = "" Else sFileExt = Mid(sFileName, sFileExt+1)
				If LCase(sFileExt) = "xml" Then
					If LCase(sFileName) <> LCase(ProjectID & ".xml") And LCase(sFileName) <> LCase(EW_RELATED_PROJECT_ID & ".xml") Then
						Call LoadUserLevelFromXml(folder, sFileName, arUserLevelPriv, arTable, userpriv)
					End If
				End If
			Next
		End If
		Set fso = Nothing
	End Sub

	' Load user level from xml
	Sub LoadUserLevelFromXml(folder, file, arUserLevelPriv, arTable, userpriv)
		Dim xmlfile, doc
		xmlfile = folder & file
		Set doc = ew_CreateXmlDom()
		If doc.Load(xmlfile) Then

			' Load project id
			Dim projid, projfile, projnode
			projid = ""
			projfile = ""
			Set projnode = doc.SelectSingleNode("//configuration/project")
			If ew_NotEmpty(projnode) Then
				projid = projnode.getAttribute("id")&""
				projfile = projnode.getAttribute("file")&""
				If projid = EW_RELATED_PROJECT_ID Then
					EW_RELATED_LANGUAGE_FOLDER = ew_IncludeTrailingDelimiter(projnode.getAttribute("languagefolder"), True)
				End If
			End If

			' Load user priv
			Dim tablelist, table
			Dim tablevar, tablename, tablecaption, userlevel, priv
			Dim i, usergroup, group, id, name, userlevelpriv
			Set tablelist = doc.SelectNodes("//configuration/project/table")
			For Each table In tablelist
				tablevar = table.getAttribute("id")&""
				tablename = table.getAttribute("name")&""
				tablecaption = table.getAttribute("caption")&""
				userlevel = table.GetAttribute("userlevel")&""
				priv = table.getAttribute("priv")&""
				If Not userpriv Or (userpriv And priv = "1") Then
					usergroup = Split(userlevel, ";")
					For i = 0 to UBound(usergroup)
						group = Split(usergroup(i), ",")
						If UBound(group) >= 2 Then
							id = group(0)
							name = group(1)
							userlevelpriv = group(2)
							If Not IsArray(arUserLevelPriv) Then
								ReDim arUserLevelPriv(2,0)
							Else
								ReDim Preserve arUserLevelPriv(2, UBound(arUserLevelPriv,2)+1)
							End If
							arUserLevelPriv(0,UBound(arUserLevelPriv,2)) = projid & tablename
							arUserLevelPriv(1,UBound(arUserLevelPriv,2)) = id
							arUserLevelPriv(2,UBound(arUserLevelPriv,2)) = userlevelpriv
						End If
					Next
					If Not IsArray(arTable) Then
						ReDim arTable(6,0)
					Else
						ReDim Preserve arTable(6,UBound(arTable,2)+1)
					End If
					arTable(0,UBound(arTable,2)) = tablename
					arTable(1,UBound(arTable,2)) = tablevar
					arTable(2,UBound(arTable,2)) = tablecaption
					arTable(3,UBound(arTable,2)) = priv
					arTable(4,UBound(arTable,2)) = projid
					arTable(5,UBound(arTable,2)) = projfile
				End If
			Next
		End If
		Set doc = Nothing
	End Sub

	' User level security (Anonymous)
	Public Sub SetupUserLevel()

		' Load user level from config file
		Dim arTable
		Call LoadUserLevelFromConfigFile(m_ArUserLevel, m_ArUserLevelPriv, arTable, False)
	End Sub

	' Add user permission
	Private Sub AddUserPermissionBase(UserLevelName, TableName, UserPermission)
		Dim UserLevelID, i

		' Get user level id from user name
		UserLevelID = ""
		If IsArray(m_ArUserLevel) Then
			For i = 0 To UBound(m_ArUserLevel, 2)
				If ew_SameText(UserLevelName, m_ArUserLevel(1, i)) Then
					UserLevelID = m_ArUserLevel(0, i)
					Exit For
				End If
			Next
		End If
		If IsArray(m_ArUserLevelPriv) And UserLevelID <> "" Then
			For i = 0 To UBound(m_ArUserLevelPriv, 2)
				If ew_SameText(m_ArUserLevelPriv(0, i), EW_PROJECT_ID & TableName) And _
				   ew_SameStr(m_ArUserLevelPriv(1, i), UserLevelID) Then
					m_ArUserLevelPriv(2, i) = m_ArUserLevelPriv(2, i) Or UserPermission ' Add permission
					Exit For
				End If
			Next
		End If
	End Sub

	' Add user permission
	Public Sub AddUserPermission(UserLevelName, TableName, UserPermission)
		Dim arUserLevelName, arTableName, sUserLevelName, sTableName
		arUserLevelName = ew_IIf(IsArray(UserLevelName), UserLevelName, Array(UserLevelName))
		arTableName = ew_IIf(IsArray(TableName), TableName, Array(TableName))
		For Each sUserLevelName In arUserLevelName
			For Each UserLevelName In arTableName
				Call AddUserPermissionBase(sUserLevelName, UserLevelName, UserPermission)
			Next
		Next
	End Sub

	' Delete user permission
	Private Sub DeleteUserPermissionBase(UserLevelName, TableName, UserPermission)
		Dim UserLevelID, i

		' Get user level id from user name
		UserLevelID = ""
		If IsArray(m_ArUserLevel) Then
			For i = 0 To UBound(m_ArUserLevel, 2)
				If ew_SameText(UserLevelName, m_ArUserLevel(1, i)) Then
					UserLevelID = m_ArUserLevel(0, i)
					Exit For
				End If
			Next
		End If
		If IsArray(m_ArUserLevelPriv) And UserLevelID <> "" Then
			For i = 0 To UBound(m_ArUserLevelPriv, 2)
				If ew_SameText(m_ArUserLevelPriv(0, i), EW_PROJECT_ID & TableName) And _
				   ew_SameStr(m_ArUserLevelPriv(1, i), UserLevelID) Then
					m_ArUserLevelPriv(2, i) = m_ArUserLevelPriv(2, i) And (127-UserPermission) ' Remove permission
					Exit For
				End If
			Next
		End If
	End Sub

	' Delete user permission
	Public Sub DeleteUserPermission(UserLevelName, TableName, UserPermission)
		Dim arUserLevelName, arTableName, sUserLevelName, sTableName
		arUserLevelName = ew_IIf(IsArray(UserLevelName), UserLevelName, Array(UserLevelName))
		arTableName = ew_IIf(IsArray(TableName), TableName, Array(TableName))
		For Each sUserLevelName In arUserLevelName
			For Each sTableName In arTableName
				Call DeleteUserPermissionBase(sUserLevelName, sTableName, UserPermission)
			Next
		Next
	End Sub

	' Load current user level
	Public Sub LoadCurrentUserLevel(Table)

		' Load again if user level list changed
		If Session(EW_SESSION_USER_LEVEL_LIST_LOADED)&"" <> "" And Session(EW_SESSION_USER_LEVEL_LIST_LOADED)&"" <> Session(EW_SESSION_USER_LEVEL_LIST)&"" Then
			Session(EW_SESSION_AR_USER_LEVEL_PRIV) = ""
		End If
		Call LoadUserLevel
		SessionUserLevel = CurrentUserLevelPriv(Table)
	End Sub

	' Get current user privilege
	Private Function CurrentUserLevelPriv(TableName)
		If IsLoggedIn() Then
			CurrentUserLevelPriv = EW_ALLOW_ALL
		Else

			' Anonymous
			CurrentUserLevelPriv = GetUserLevelPrivEx(TableName, -2)
		End If
	End Function

	' Get User Level ID by User Level name
	Public Function GetUserLevelID(UserLevelName)
		GetUserLevelID = -2 ' Anonymous
		If ew_SameStr(UserLevelName, "Anonymous") Then
			GetUserLevelID = -2
			Exit Function
		ElseIf ew_SameStr(UserLevelName, "Administrator") Then
			GetUserLevelID = -1
			Exit Function
		ElseIf ew_SameStr(UserLevelName, "Default") Then
			GetUserLevelID = 0
			Exit Function
		ElseIf UserLevelName <> "" Then
			If ew_NotEmpty(Language) Then
				If ew_SameStr(UserLevelName, Language.Phrase("UserAnonymous")) Then
					GetUserLevelID = -2
					Exit Function
				ElseIf ew_SameStr(UserLevelName, Language.Phrase("UserAdministrator")) Then
					GetUserLevelID = -1
					Exit Function
				ElseIf ew_SameStr(UserLevelName, Language.Phrase("UserDefault")) Then
					GetUserLevelID = 0
					Exit Function
				End If
			End If
			If IsArray(m_ArUserLevel) Then
				Dim i, levelid, name
				For i = 0 to UBound(m_ArUserLevel, 2)
					levelid = m_ArUserLevel(0, i)
					name =  m_ArUserLevel(1, i)
					If ew_SameStr(name, UserLevelName) Then
						GetUserLevelID = levelid
						Exit Function
					End If
				Next
			End If
		End If
	End Function

	' Add user level (for use with UserLevel_Loading event)
	Sub AddUserLevel(UserLevelName)
		Dim UserLevelID
		If UserLevelName = "" Or IsNull(UserLevelName) Then Exit Sub
		UserLevelID = GetUserLevelID(UserLevelName)
		Call AddUserLevelID(UserLevelID)
	End Sub

	' Add user level by ID
	Sub AddUserLevelID(UserLevelID)
		Dim bFound, i
		If Not IsNumeric(UserLevelID) Then Exit Sub
		If UserLevelID < -1 Then Exit Sub
		bFound = False
		If Not IsArray(m_ArUserLevelID) Then
			ReDim m_ArUserLevelID(0)
		Else
			For i = 0 to UBound(m_ArUserLevelID)
				If m_ArUserLevelID(i) = UserLevelID Then
					bFound = True
					Exit For
				End If
			Next
			If Not bFound Then ReDim Preserve m_ArUserLevelID(UBound(m_ArUserLevelID)+1)
		End If
		If Not bFound Then
			m_ArUserLevelID(UBound(m_ArUserLevelID)) = UserLevelID
			Session(EW_SESSION_USER_LEVEL_LIST) = UserLevelList ' Update session variable (for menu)
		End If
	End Sub

	' Delete user level (for use with UserLevel_Loading event)
	Sub DeleteUserLevel(UserLevelName)
		Dim UserLevelID
		If UserLevelName = "" Or IsNull(UserLevelName) Then Exit Sub
		UserLevelID = GetUserLevelID(UserLevelName)
		Call DeleteUserLevelID(UserLevelID)
	End Sub

	' Delete user level by ID
	Sub DeleteUserLevelID(UserLevelID)
		Dim i, j
		If Not IsNumeric(UserLevelID) Then Exit Sub
		If UserLevelID < -1 Then Exit Sub
		If IsArray(m_ArUserLevelID) Then
			For i = 0 to UBound(m_ArUserLevelID)
				If m_ArUserLevelID(i) = UserLevelID Then
					For j = i+1 to UBound(m_ArUserLevelID)
						m_ArUserLevelID(j-1) = m_ArUserLevelID(j)
					Next
					If UBound(m_ArUserLevelID) = 0 Then
						m_ArUserLevelID = ""
					Else
						ReDim Preserve m_ArUserLevelID(UBound(m_ArUserLevelID)-1)
					End If
					Session(EW_SESSION_USER_LEVEL_LIST) = UserLevelList ' Update session variable (for menu)
					Exit Sub
				End If
			Next
		End If
	End Sub

	' User level list
	Function UserLevelList()
		Dim i
		UserLevelList = ""
		If IsArray(m_ArUserLevelID) Then
			For i = 0 to UBound(m_ArUserLevelID)
				If UserLevelList <> "" Then UserLevelList = UserLevelList & ", "
				UserLevelList = UserLevelList & m_ArUserLevelID(i)
			Next
		End If
	End Function

	' User level name list
	Function UserLevelNameList()
		Dim i
		UserLevelNameList = ""
		If IsArray(m_ArUserLevelID) Then
			For i = 0 to UBound(m_ArUserLevelID)
				If UserLevelNameList <> "" Then UserLevelNameList = UserLevelNameList & ", "
				UserLevelNameList = UserLevelNameList & ew_QuotedValueBase(GetUserLevelName(m_ArUserLevelID(i), True), EW_DATATYPE_STRING, EW_USER_LEVEL_DBID)
			Next
		End If
	End Function

	' Get user privilege based on table name and user level
	Public Function GetUserLevelPrivEx(TableName, UserLevelID)
		On Error Resume Next
		GetUserLevelPrivEx = 0
		If CStr(UserLevelID&"") = "-1" Then ' System Administrator
			GetUserLevelPrivEx = EW_ALLOW_ALL
		ElseIf CLng(UserLevelID) >= 0 OR CLng(UserLevelID) = -2 Then
			If IsArray(m_ArUserLevelPriv) Then
				Dim i
				For i = 0 to UBound(m_ArUserLevelPriv, 2)
					If CStr(m_ArUserLevelPriv(0, i)&"") = CStr(TableName&"") And _
						CStr(m_ArUserLevelPriv(1, i)&"") = CStr(UserLevelID&"") Then
						GetUserLevelPrivEx = m_ArUserLevelPriv(2, i)
						If VarType(GetUserLevelPrivEx) = 14 Then GetUserLevelPrivEx = CLng(GetUserLevelPrivEx)
						If IsNull(GetUserLevelPrivEx) Then GetUserLevelPrivEx = 0
						If Not IsNumeric(GetUserLevelPrivEx) Then GetUserLevelPrivEx = 0
						GetUserLevelPrivEx = CLng(GetUserLevelPrivEx)
						Exit For
					End If
				Next
			End If
		End If
	End Function

	' Get current user level name
	Public Function CurrentUserLevelName()
		CurrentUserLevelName = GetUserLevelName(CurrentUserLevelID, True)
	End Function

	' Get user level name based on user level
	Public Function GetUserLevelName(UserLevelID, Lang)
		Dim UserLevelName
		GetUserLevelName = ""
		If CStr(UserLevelID&"") = "-2" Then
			GetUserLevelName = ew_IIf(Lang, Language.Phrase("UserAnonymous"), "Anonymous")
		ElseIf CStr(UserLevelID&"") = "-1" Then
			GetUserLevelName = ew_IIf(Lang, Language.Phrase("UserAdministrator"), "Administrator")
		ElseIf CStr(UserLevelID&"") = "0" Then
			GetUserLevelName = ew_IIf(Lang, Language.Phrase("UserDefault"), "Default")
		ElseIf UserLevelID > 0 Then
			If IsArray(m_ArUserLevel) Then
				Dim i
				For i = 0 to UBound(m_ArUserLevel, 2)
					If CStr(m_ArUserLevel(0, i)&"") = CStr(UserLevelID&"") Then
						UserLevelName = ""
						If Lang Then
							UserLevelName = Language.Phrase(m_ArUserLevel(1, i))
						End If
						GetUserLevelName = ew_IIf(UserLevelName <> "", UserLevelName, m_ArUserLevel(1, i))
						Exit For
					End If
				Next
			End If
		End If
	End Function

	' Display all the User Level settings (for debug only)
	Public Sub ShowUserLevelInfo()
		Dim i
		If IsArray(m_ArUserLevel) Then
			Response.Write "User Levels:<br>"
			Response.Write "UserLevelId, UserLevelName<br>"
			For i = 0 To UBound(m_ArUserLevel, 2)
				Response.Write "&nbsp;&nbsp;" & m_ArUserLevel(0, i) & ", " & _
					m_ArUserLevel(1, i) & "<br>"
			Next
		Else
			Response.Write "No User Level definitions." & "<br>"
		End If
		If IsArray(m_ArUserLevelPriv) Then
			Response.Write "User Level Privs:<br>"
			Response.Write "TableName, UserLevelId, UserLevelPriv<br>"
			For i = 0 To UBound(m_ArUserLevelPriv, 2)
				Response.Write "&nbsp;&nbsp;" & m_ArUserLevelPriv(0, i) & ", " & _
					m_ArUserLevelPriv(1, i) & ", " & m_ArUserLevelPriv(2, i) & "<br>"
			Next
		Else
			Response.Write "No User Level privilege settings." & "<br>"
		End If
		Response.Write "Current User Level ID = " & CurrentUserLevelID & "<br>"
	End Sub

	' Check privilege for List page (for menu items)
	Public Function AllowList(TableName)
		AllowList = CBool(CurrentUserLevelPriv(TableName) And EW_ALLOW_LIST)
	End Function

	' Check privilege for View page (for Allow-View / Detail-View)
	Public Function AllowView(TableName)
		AllowView = CBool(CurrentUserLevelPriv(TableName) And EW_ALLOW_VIEW)
	End Function

	' Check privilege for Add / Detail-Add
	Public Function AllowAdd(TableName)
		AllowAdd = CBool(CurrentUserLevelPriv(TableName) And EW_ALLOW_ADD)
	End Function

	' Check privilege for Edit page (for Detail-Edit)
	Public Function AllowEdit(TableName)
		AllowEdit = CBool(CurrentUserLevelPriv(TableName) And EW_ALLOW_EDIT)
	End Function

	' Check if user password expired
	Public Function IsPasswordExpired()
		IsPasswordExpired = (Session(EW_SESSION_STATUS) = "passwordexpired")
	End Function

	' Set session password expired
	Public Sub SetSessionPasswordExpired()
		Session(EW_SESSION_STATUS) = "passwordexpired"
	End Sub

	' Set login status
	Public Sub SetLoginStatus(status)
		Session(EW_SESSION_STATUS) = status
	End Sub

	' Check if user password reset
	Public Function IsPasswordReset()
		IsPasswordReset = (Session(EW_SESSION_STATUS) = "passwordreset")
	End Function

	' Check if user is logging in (after changing password)
	Public Function IsLoggingIn()
		IsLoggingIn = (Session(EW_SESSION_STATUS) = "loggingin")
	End Function

	' Check if user is logged in
	Public Function IsLoggedIn()
		IsLoggedIn = (Session(EW_SESSION_STATUS) = "login")
	End Function

	' Check if user is system administrator
	Public Function IsSysAdmin()
		IsSysAdmin = (Session(EW_SESSION_SYS_ADMIN) = 1)
	End Function

	' Is Windows authenticated
	Function IsAuthenticated()
		IsAuthenticated = ew_CurrentWindowsUser() <> ""
	End Function

	' Check if user is administrator
	Function IsAdmin()
		Dim i
		IsAdmin = IsSysAdmin()
	End Function

	' Save user level to session
	Public Sub SaveUserLevel()
		Session(EW_SESSION_AR_USER_LEVEL) = m_ArUserLevel
		Session(EW_SESSION_AR_USER_LEVEL_PRIV) = m_ArUserLevelPriv
		Session(EW_SESSION_USER_LEVEL_LIST) = UserLevelList ' Update session variable (for menu)
	End Sub

	' Load user level from session
	Public Sub LoadUserLevel()
		If Not IsArray(Session(EW_SESSION_AR_USER_LEVEL)) Or Not IsArray(Session(EW_SESSION_AR_USER_LEVEL_PRIV)) Then
			Call SetupUserLevel
			Call SaveUserLevel
		Else
			m_ArUserLevel = Session(EW_SESSION_AR_USER_LEVEL)
			m_ArUserLevelPriv = Session(EW_SESSION_AR_USER_LEVEL_PRIV)
		End If
	End Sub

	' Get user email
	Public Function CurrentUserEmail()
		CurrentUserEmail = CurrentUserInfo("Email")
	End Function

	' Get user info
	Public Function CurrentUserInfo(fieldname)
		If Not EW_DEBUG_ENABLED Then On Error Resume Next
		CurrentUserInfo = Null
		If Not IsSysAdmin() Then
			Dim user
			user = CurrentUserName()
			If user&"" <> "" Then
				CurrentUserInfo = ew_ExecuteScalarByConn("SELECT " & ew_QuotedNameBase(fieldname, EW_USER_TABLE_DBID) & " FROM " & EW_USER_TABLE & " WHERE " & _
					Replace(EW_USER_NAME_FILTER, "%u", ew_AdjustSqlBase(user, EW_USER_TABLE_DBID)), UserTableConn)
			End If
		End If
	End Function

	' UserID Loading event
	Sub UserID_Loading()

		'Response.Write "UserID Loading: " & CurrentUserID & "<br>"
	End Sub

	' UserID Loaded event
	Sub UserID_Loaded()

		'Response.Write "UserID Loaded: " & UserIDList & "<br>"
	End Sub

	' User Level Loaded event
	Sub UserLevel_Loaded()

		'Call AddUserPermission(<UserLevelName>, <TableName>, <UserPermission>)
		'Call DeleteUserPermission(<UserLevelName>, <TableName>, <UserPermission>)

	End Sub

	' Table Permission Loading event
	Sub TablePermission_Loading()

		'Response.Write "Table Permission Loading: " & CurrentUserLevelID & "<br>"
	End Sub

	' Table Permission Loaded event
	Sub TablePermission_Loaded()

		'Response.Write "Table Permission Loaded: " & CurrentUserLevel & "<br>"
	End Sub

	' User Custom Validate event
	Function User_CustomValidate(usr, pwd)

		' Enter your custom code to validate user, return True if valid.
		User_CustomValidate = False
	End Function

	' User Validated event
	Function User_Validated(rs)

		'Session("UserEmail") = rs("Email")
		User_Validated = True
	End Function

	' User PasswordExpired event
	Sub User_PasswordExpired(rs)

	  'Response.Write "User_PasswordExpired"
	End Sub
End Class

'
'  Advanced Security class (end)
'
' User Profile Class
Class cUserProfile

	Public Profile

	Private TimeoutTime

	Private MaxRetryCount, RetryLockoutTime

	Private PasswordExpiryTime

	' Initialze
	Private Sub Class_Initialize()
		Set Profile = Server.CreateObject("Scripting.Dictionary")
		Profile.CompareMode = 1 ' vbTextCompare
		Call InitProfile
		Load
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		Set Profile = Nothing
	End Sub

	' Initialize profile object
	Sub InitProfile()
	End Sub

	' Exists value
	Function Exists(Name)
		Exists = Profile.Exists(Name)
	End Function

	' Has value (alias of Exists)
	Function Has(Name)
		Has = Profile.Exists(Name)
	End Function

	' Get value
	Function GetValue(Name)
		GetValue = Null
		If Profile.Exists(Name) Then
			GetValue = Profile(Name)
		End If
	End Function

	' Get value (alias)
	Function [Get](Name)
		[Get] = GetValue(Name)
	End Function

	' Get value (default property)
	Public Default Property Get Item(Name)
		Item = GetValue(Name)
	End Property

	' Set value
	Sub SetValue(Name, Value)
		If Profile.Exists(Name) Then
			Profile(Name) = Value
		Else
			Call Profile.Add(Name, Value)
		End If
	End Sub

	' Set value (alias)
	Sub [Set](Name, Value)
		Call SetValue(Name, Value)
	End Sub

	' Add value (alias of Set)
	Sub Add(Name, Value)
		Call SetValue(Name, Value)
	End Sub

	' Delete value
	Sub Delete(Name)
		If Profile.Exists(Name) Then
			Profile.Remove(Name)
		End If
	End Sub

	' Remove value (alias of Delete)
	Sub Remove(Name)
		Call Delete(Name)
	End Sub

	' Assign properties from recordset
	Sub Assign(rs)
		If Not EW_DEBUG_ENABLED Then On Error Resume Next
		Dim sType, fld
		If Not rs.EOF Then
			For i = 0 to rs.Fields.Count - 1
				Set fld = rs.Fields(i)
				sType = fld.Type

				' Non blob/memo fields (adBinary/adLongVarChar/adLongVarWChar/adVarBinary/adLongVarBinary)
				If Not ew_InArray(sType, Array(128, 201, 203, 204, 205)) Then
					If Not ew_InArray(sType, Array(200, 202)) Then ' Non adVarChar/adVarWChar fields
						Call Add(fld.Name, fld.Value)
					ElseIf Len(fld.Value) <= EW_DATA_STRING_MAX_LENGTH Then ' adVarChar/adVarWChar < max length allowed
						Call Add(fld.Name, fld.Value)
					End If
				End If
			Next
		End If
	End Sub

	' Check if System Admin
	Private Function IsSystemAdmin(usr)
		IsSystemAdmin = (usr = "" Or usr = EW_ADMIN_USER_NAME Or usr = Language.Phrase("UserAdministrator"))
	End Function

	' Load profile for current user
	Sub Load()
		If ew_NotEmpty(Session(EW_SESSION_USER_PROFILE)) Then
			Call LoadProfile(Session(EW_SESSION_USER_PROFILE))
		End If
	End Sub

	' Save profile to session
	Sub Save()
		Session(EW_SESSION_USER_PROFILE) = ProfileToString(Profile)
	End Sub

	' Load profile from string
	Sub LoadProfile(pstr)
		Dim dict, name, value, wrkstr
		wrkstr = CStr(pstr&"")
		If wrkstr <> "" Then
			Set dict = SplitProfile(wrkstr)
			For Each name In dict
				value = dict(name)
				If Profile.Exists(name) Then
					Profile(name) = value
				Else
					Profile.Add name, value
				End If
			Next
			Set dict = Nothing
		End If
	End Sub

	' Write (var_dump) profile
	Sub WriteProfile()
		Response.Write ew_JsonEncode(Profile)
	End Sub

	' Clear profile
	Sub ClearProfile()
		Profile.RemoveAll
	End Sub

	' Clear profile (alias)
	Sub Clear()
		Call ClearProfile
	End Sub

	' Profile to string
	Function ProfileToString(obj)
		ProfileToString = ew_JsonEncode(obj)
	End Function

	' Split profile
	Private Function SplitProfile(pstr)
		Set SplitProfile = ew_JsonDecode(pstr)
	End Function

	' To JSON (ASP)
	Function ToJson()
		ToJson = ProfileToString(Profile)
	End Function
End Class
%>
<%

'
'  Default Request Form Object Class (begin)
'
Class cFormObj
	Dim Index ' Index to handle multiple form elements
	Dim FormName

	' Class Initialize
	Private Sub Class_Initialize()
		Index = -1
		FormName = ""
	End Sub

	' Get form element name based on index
	Function GetIndexedName(name)
		If Index < 0 Then
			GetIndexedName = name
		Else
			GetIndexedName = Mid(name, 1, 1) & Index & Mid(name, 2)
		End If
	End Function

	' Has value for form element
	Function HasValue(name)
		Dim wrkname, wrkname2
		wrkname = GetIndexedName(name)
		If ew_RegExTest("^(fn_)?(x|o)\d*_", wrkname) And FormName <> "" Then
			wrkname2 = FormName & "$" & wrkname
			If Request.Form(wrkname2).Count > 0 Then
				HasValue = True
				Exit Function
			End If
		End If
		HasValue = (Request.Form(wrkname).Count > 0)
	End Function

	' Get value for form element
	Function GetValue(name)
		Dim wrkname, wrkname2
		wrkname = GetIndexedName(name)
		GetValue = Null
		If ew_RegExTest("^(fn_)?(x|o)\d*_", wrkname) And FormName <> "" Then
			wrkname2 = FormName & "$" & wrkname
			If Request.Form(wrkname2).Count > 0 Then
				GetValue = Request.Form(wrkname2)
			End If
		End If
		If IsNull(GetValue) Then
			If Request.Form(wrkname).Count > 0 Then

				' Special handling for key_m
				If wrkname = "key_m" Then
					If Request.Form(wrkname).Count = 1 Then
						GetValue = Request.Form(wrkname)
					Else
						Dim i, cnt, ar
						cnt = Request.Form(wrkname).Count
						ReDim ar(cnt-1)
						For i = 1 to cnt
							ar(i-1) = Request.Form(wrkname)(i)
						Next
						GetValue = ar
					End If
				Else
					GetValue = Request.Form(wrkname)
				End If
			End If
		End If
	End Function
End Class

'
'  Default Request Form Object Class (end)
'

%>
<%

'
'  Default Upload Object Class (begin)
'
Class cUploadObj
	Dim rawData, separator, lenSeparator, dict
	Dim currentPos, inStrByte, tempValue, mValue, value
	Dim intDict, begPos, endPos
	Dim nameN, isValid, nameValue, midValue
	Dim rawStream
	Dim Index
	Dim hdr, hdrEndPos

	' Class Inialize
	Private Sub Class_Initialize()
		On Error Resume Next
		Err.Clear
		Index = -1
		If Request.TotalBytes > 0 Then
			Set rawStream = Server.CreateObject("ADODB.Stream")
			rawStream.Type = 1 'adTypeBinary
			rawStream.Mode = 3 'adModeReadWrite
			rawStream.Open
			rawStream.Write Request.BinaryRead(Request.TotalBytes)
			If Err.Number <> 0 Then Exit Sub
			rawStream.Position = 0
			rawData = rawStream.Read
			separator = MidB(rawData, 1, InStrB(1, rawData, ChrB(13)) - 1)
			lenSeparator = LenB(separator)
			Set dict = Server.CreateObject("Scripting.Dictionary")
			currentPos = 1
			inStrByte = 1
			tempValue = ""
			While inStrByte > 0
				inStrByte = InStrB(currentPos, rawData, separator)
				mValue = inStrByte - currentPos
				If mValue > 1 Then
					value = MidB(rawData, currentPos, mValue)
					Set intDict = Server.CreateObject("Scripting.Dictionary")
					begPos = 1 + InStrB(1, value, ChrB(34))
					endPos = InStrB(begPos + 1, value, ChrB(34))
					nameN = MidB(value, begPos, endPos - begPos)
					isValid = True
					hdrEndPos = InStrB(1, value, ChrB(13) & ChrB(10) & ChrB(13) & ChrB(10))
					hdr = MidB(value, 1, hdrEndPos - 1)
					If InStrB(1, hdr, StringToByte("Content-Type:")) > 1 Or InStrB(1, hdr, StringToByte("filename=")) > 1 Then
						begPos = 1 + InStrB(endPos + 1, value, ChrB(34))
						endPos = InStrB(begPos + 1, value, ChrB(34))
						If endPos > 0 Then
							intDict.Add "FileName", ConvertToText(rawStream, currentPos + begPos - 2, endPos - begPos, MidB(value, begPos, endPos - begPos))
							begPos = 14 + InStrB(endPos + 1, value, StringToByte("Content-Type:"))
							endPos = InStrB(begPos, value, ChrB(13))
							intDict.Add "ContentType", ConvertToText(rawStream, currentPos + begPos - 2, endPos - begPos, MidB(value, begPos, endPos - begPos))
							begPos = endPos + 4
							endPos = LenB(value)
							nameValue = MidB(value, begPos, ((endPos - begPos) - 1))
						Else
							endPos = begPos + 1
							isValid = False
						End If
					Else
						nameValue = ConvertToText(rawStream, currentPos + endPos + 3, mValue - endPos - 4, MidB(value, endPos + 5))
					End If
					If isValid = True Then
						Dim wrkname
						wrkname = ByteToString(nameN)
						If dict.Exists(wrkname) Then
							Set intDict = dict(wrkname)

							' Special handling for key_m, just append to end
							If wrkname = "key_m" Then
								intDict("Value") = intDict("Value") & nameValue
							Else
								If Right(intDict("Value"), 2) = vbCrLf Then
									intDict("Value") = Left(intDict("Value"), Len(intDict("Value"))-2)
								End If
								intDict("Value") = intDict("Value") & ", " & nameValue
							End If
						Else
							intDict.Add "Value", nameValue
							intDict.Add "Name", nameN
							dict.Add wrkname, intDict
						End If
					End If
				End If
				currentPos = lenSeparator + inStrByte
			Wend
			rawStream.Close
			Set rawStream = Nothing
		End If
	End Sub

	' Get form element name based on index
	Function GetIndexedName(name)
		If Index < 0 Then
			GetIndexedName = name
		Else
			GetIndexedName = Mid(name, 1, 1) & Index & Mid(name, 2)
		End If
	End Function

	' Has value for form element
	Function HasValue(name)
		Dim wrkname
		wrkname = GetIndexedName(name)
		If ew_Empty(dict) Then
			HasValue = False
		Else
			HasValue = dict.Exists(wrkname)
		End If
	End Function

	' Get value for form element
	Function GetValue(name)
		Dim wrkname
		Dim gv
		GetValue = Null ' default return Null
		If ew_NotEmpty(dict) Then
			wrkname = GetIndexedName(name)
			If dict.Exists(wrkname) Then
				gv = CStr(dict(wrkname)("Value"))
				gv = Left(gv, Len(gv)-2)
				GetValue = gv

				' Special handling for key_m
				If wrkname = "key_m" Then
					If InStr(GetValue, vbCrLf) > 0 Then
						GetValue = Split(GetValue, vbCrLf)
					End If
				End If
			End If
		End If
	End Function

	' Get upload file size
	Function GetUploadFileSize(name)
		Dim wrkname
		wrkname = GetIndexedName(name)
		If dict.Exists(wrkname) Then
			GetUploadFileSize = LenB(dict(wrkname)("Value"))
		Else
			GetUploadFileSize = 0
		End If
	End Function

	' Get upload file name
	Function GetUploadFileName(name)
		Dim wrkname, temp, tempPos
		wrkname = GetIndexedName(name)
		If dict.Exists(wrkname) Then
			temp = dict(wrkname)("FileName")
			tempPos = 1 + InStrRev(temp, "\")
			GetUploadFileName = Mid(temp, tempPos)
		Else
			GetUploadFileName = ""
		End If
	End Function

	' Get file content type
	Function GetUploadFileContentType(name)
		Dim wrkname
		wrkname = GetIndexedName(name)
		If dict.Exists(wrkname) Then
			GetUploadFileContentType = dict(wrkname)("ContentType")
		Else
			GetUploadFileContentType = ""
		End If
	End Function

	' Get upload file data
	Function GetUploadFileData(name)
		Dim wrkname
		wrkname = GetIndexedName(name)
		If dict.Exists(wrkname) Then
			GetUploadFileData = dict(wrkname)("Value")
		Else
			GetUploadFileData = Null
		End If
	End Function

	' Get file image width
	Function GetUploadImageWidth(name)
		Dim wrkname
		wrkname = GetIndexedName(name)
		Dim ImageHeight
		Call ew_GetImageDimension(GetUploadFileData(name), GetUploadImageWidth, ImageHeight)
	End Function

	' Get file image height
	Function GetUploadImageHeight(name)
		Dim wrkname
		wrkname = GetIndexedName(name)
		Dim ImageWidth
		Call ew_GetImageDimension(GetUploadFileData(name), ImageWidth, GetUploadImageHeight)
	End Function

	' Convert string to byte
	Function StringToByte(toConv)
		Dim i, tempChar
		For i = 1 to Len(toConv)
			tempChar = Mid(toConv, i, 1)
			StringToByte = StringToByte & ChrB(AscB(tempChar))
		Next
	End Function

	' Convert byte to string
	Private Function ByteToString(ToConv)
		Dim i
		If Not EW_DEBUG_ENABLED Then On Error Resume Next
		For i = 1 to LenB(ToConv)
			ByteToString = ByteToString & Chr(AscB(MidB(ToConv,i,1)))
		Next
	End Function

	' Convert to text
	Function ConvertToText(objStream, iStart, iLength, binData)
		If Not EW_DEBUG_ENABLED Then On Error Resume Next
		If EW_UPLOAD_CHARSET <> "" Then
			Dim tmpStream
			Set tmpStream = Server.CreateObject("ADODB.Stream")
			tmpStream.Type = 1 'adTypeBinary
			tmpStream.Mode = 3 'adModeReadWrite
			tmpStream.Open
			objStream.Position = iStart
			objStream.CopyTo tmpStream, iLength
			tmpStream.Position = 0
			tmpStream.Type = 2 'adTypeText
			tmpStream.Charset = EW_UPLOAD_CHARSET
			ConvertToText = tmpStream.ReadText
			tmpStream.Close
			Set tmpStream = Nothing
		Else
			ConvertToText = ByteToString(binData)
		End If
		ConvertToText = Trim(ConvertToText & "")
	End Function

	' Class terminate
	Private Sub Class_Terminate()

		' Dispose dictionary
		If ew_NotEmpty(intDict) Then
			intDict.RemoveAll
			Set intDict = Nothing
		End If
		If ew_NotEmpty(dict) Then
			dict.RemoveAll
			Set dict = Nothing
		End If
	End Sub
End Class

'
'  Default Upload Object Class (end)
'

%>
<%

'
'  Common functions (begin)
'
Sub ResponseJson()
	Response.CodePage = 65001
	Response.ContentType = "application/json"
	Response.Charset = "utf-8"
End Sub

' Write HTTP header
Sub ew_Header(cache, charset, json)
	Dim export
	export = Request.QueryString("export") & ""
	If (cache) Or (Not cache And ew_IsHttps() And export <> "" And export <> "print") Then ' Allow cache
		Response.AddHeader "Cache-Control", "private, must-revalidate"
		Response.AddHeader "Pragma", "public"
	Else ' No cache
		Response.AddHeader "Cache-Control", "private, no-cache, no-store, must-revalidate"
		Response.AddHeader "Cache-Control", "post-check=0, pre-check=0"
		Response.AddHeader "Pragma", "no-cache"
	End If
	Response.AddHeader "X-UA-Compatible", "IE=edge"
	If json Then
		Call ResponseJson
	Else
		Response.ContentType = "text/html"
		If charset <> "" Then Response.Charset = charset ' Charset
	End If
End Sub

' Get connection object
' Refer to ew_ConnectDb below for array structure
'
Function ew_GetConn(dbid)
	Dim db
	db = ew_GetDb(dbid)
	If IsArray(db) Then

		' Connect to db if necessary
		Call ew_ConnectDb(db)

		' Return connection object
		Dim i, j, id
		For i = 0 to UBound(EW_CONN)
			id = ew_GetDbAttr(EW_CONN(i), "id")
			If id = dbid Then
				For j = 0 to UBound(EW_CONN(i))
					If EW_CONN(i)(j)(0) = "conn" Then
						Set ew_GetConn = EW_CONN(i)(j)(1)
						Exit Function
					End If
				Next
				Exit For
			End If
		Next
	End If
	Set ew_GetConn = Nothing
End Function

' Get database array
' Refer to ew_ConnectDb below for array structure
'
Function ew_GetDb(dbid)
	Dim i
	If ew_EmptyStr(dbid) Or dbid & "" = "0" Then dbid = "DB"
	If ew_CheckInteger(dbid) Then
		dbid = CLng(dbid)
		If dbid >= 0 And dbid <= UBound(EW_CONN) Then
			ew_GetDb = EW_CONN(dbid)
			Exit Function
		End If
	End If
	For i = 0 to UBound(EW_CONN)
		If dbid = ew_GetDbAttr(EW_CONN(i), "id") Then
			ew_GetDb = EW_CONN(i)
			Exit Function
		End If
	Next
	ew_GetDb = Null
End Function

' Get connection type
' Refer to ew_ConnectDb below for array structure
'
Function ew_GetConnectionType(dbid)
	Dim db, dbtype
	dbtype = False
	db = ew_GetDb(dbid)
	If IsArray(db) Then
		dbtype = ew_GetDbAttr(db, "type")
	ElseIf ew_SameText(dbid, "MYSQL") Then
		dbtype = "MYSQL"
	ElseIf ew_SameText(dbid, "POSTGRESQL") Then
		dbtype = "POSTGRESQL"
	ElseIf ew_SameText(dbid, "ORACLE") Then
		dbtype = "ORACLE"
	ElseIf ew_SameText(dbid, "ACCESS") Then
		dbtype = "ACCESS"
	ElseIf ew_SameText(dbid, "MSSQL") Then
		dbtype = "MSSQL"
	End If
	ew_GetConnectionType = dbtype
End Function

' Get cursor location
Function ew_GetCursorLocation(dbid)
	Dim csrloc, dbtype
	If dbid = "DB" Then
		csrloc = EW_CURSOR_LOCATION
	Else
		dbtype = ew_GetConnectionType(dbid)
		If dbtype = "ACCESS" Or dbtype = "MSSQL" Or dbtype = "POSTGRESQL" Then
			csrloc = 2
		Else
			csrloc = 3
		End If
	End If
	ew_GetCursorLocation = csrloc
End Function

' Connect to database
Function ew_Connect(dbid)
	Set ew_Connect = ew_GetConn(dbid)
End Function

' Connect to database
' Access
' * Array(Array("conn", ...), Array("id", ...), Array("type", "ACCESS"), _
' *       Array("provider", ...), Array("relpath", ...), Array("dbname", ...), _
' *       Array("password", ...), Array("qs", ...), Array("qe", ...))
' Other database type
' * Array(Array("conn", ...), Array("id", ...), Array("type", "MSSQL|MYSQL|POSTGRESQL|ORACLE"), _
' *       Array("connectionstring", ...), Array("schema", ...), Array("qs", ...), Array("qe", ...))
'
Sub ew_ConnectDb(db)
	Dim dbid, dbtype, connstr, ar
	Dim provider, relpath, dbname, password, datasource, schema
	Dim cnn
	dbid = ew_GetDbAttr(db, "id")
	dbtype = ew_GetDbAttr(db, "type")

	' Check if connection is open, exit if already opened
	Dim i, j, id
	For i = 0 to UBound(EW_CONN)
		id = ew_GetDbAttr(EW_CONN(i), "id")
		If id = dbid Then
			For j = 0 to UBound(EW_CONN(i))
				If EW_CONN(i)(j)(0) = "conn" Then
					If Not IsNull(EW_CONN(i)(j)(1)) Then Exit Sub
				End If
			Next
			Exit For
		End If
	Next
	If dbtype = "ACCESS" Then
		provider = ew_GetDbAttr(db, "provider")
		relpath = ew_GetDbAttr(db, "relpath")
		dbname = ew_GetDbAttr(db, "dbname")
		password = ew_GetDbAttr(db, "password")
		If relpath = "" Then
			datasource = Server.MapPath(EW_RELATIVE_PATH & dbname)

		' ElseIf Left(relpath, 1) = "." Then ' Relative path starting with "." or ".." (relative to app root)
			' datasource = ew_ServerMapPath(relpath & dbname)

		ElseIf Left(relpath, 2) = "\\" Or InStr(relpath, ":") > 0 Then ' Physical path
			datasource = relpath & dbname
		Else ' Relative to app root
			datasource = ew_ServerMapPath(relpath) & dbname
		End If
		If password <> "" Then
			connstr = provider & ";Data Source=" & datasource & ";Jet OLEDB:Database Password=" & password & ";"
		ElseIf UCase(Right(dbname, 6)) = ".ACCDB" Then ' AccDb
			connstr = provider & ";Data Source=" & datasource & ";Persist Security Info=False;"
		Else
			connstr = provider & ";Data Source=" & datasource & ";"
		End If
	Else
		connstr = ew_GetDbAttr(db, "connectionstring")
	End If

	' Database connecting event
	Call Database_Connecting(connstr)

	' Open connection to the database
	Set cnn = Server.CreateObject("ADODB.Connection")
	cnn.Open connstr
	If dbtype = "ORACLE" Then
		schema = ew_GetDbAttr(db, "schema")
		If schema <> "" then
			cnn.Execute("ALTER SESSION SET CURRENT_SCHEMA = " & schema) ' Set current schema
		End If
		cnn.Execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'yyyy-mm-dd hh24:mi:ss'")
		cnn.Execute("ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT = 'yyyy-mm-dd hh24:mi:ss'")
	ElseIf dbtype = "MSSQL" And EW_DATE_FORMAT_ID > 0 Then

		' Set date format
		cnn.Execute("SET DATEFORMAT ymd")
	End If

	' Database connected event
	Call Database_Connected(cnn)

	' Save connection object to array
	For i = 0 to UBound(EW_CONN)
		id = ew_GetDbAttr(EW_CONN(i), "id")
		If id = dbid Then
			For j = 0 to UBound(EW_CONN(i))
				If EW_CONN(i)(j)(0) = "conn" Then
					Set EW_CONN(i)(j)(1) = cnn
				End If
			Next
			Exit For
		End If
	Next
End Sub

' Close database connections
Sub ew_CloseConn()
	Dim i, j, cnn
	For i = 0 to UBound(EW_CONN)
		For j = 0 to UBound(EW_CONN(i))
			If EW_CONN(i)(j)(0) = "conn" Then
				If ew_NotEmpty(EW_CONN(i)(j)(1)) Then
					Set cnn = EW_CONN(i)(j)(1)
					cnn.Close
					Set cnn = Nothing
					EW_CONN(i)(j)(1) = Null
				End If
			End If
		Next
	Next
End Sub

' Clean up ' ASP
Sub ew_CleanUp
	Set EW_FULL_URL_PROTOCOLS = Nothing
	Set EW_MIME_TYPES = Nothing
	Set EW_CLIENT_VAR = Nothing
	Set LoginStatus = Nothing
End Sub

' Get db array attribute (except conn object)
Function ew_GetDbAttr(db, attr)
	Dim i
	If IsArray(db) Then
		For i = 0 to UBound(db)
			If db(i)(0) = attr Then
				ew_GetDbAttr = db(i)(1)
				Exit Function
			End If
		Next
	Else
		ew_GetDbAttr = Null
	End If
End Function

' Database Connecting event
Sub Database_Connecting(Connstr)

	'Response.Write "Database Connecting"
End Sub

' Database Connected event
Sub Database_Connected(Conn)

	' Example:
	' Conn.Execute("Your SQL")

End Sub

' Check if allow add/delete row
Function ew_AllowAddDeleteRow()
	ew_AllowAddDeleteRow  = True
End Function

' Append like operator
Function ew_Like(pat, dbid)
	Dim dbtype
	dbtype = ew_GetConnectionType(dbid)
	If dbtype = "POSTGRESQL" Then
		ew_Like = ew_IIf(EW_USE_ILIKE_FOR_POSTGRESQL, " ILIKE ", " LIKE ") & pat
	ElseIf dbtype = "MYSQL" Then
		If EW_LIKE_COLLATION_FOR_MYSQL <> "" Then
			ew_Like = " LIKE " & pat & " COLLATE " & EW_LIKE_COLLATION_FOR_MYSQL
		Else
			ew_Like = " LIKE " & pat
		End If
	ElseIf dbtype = "MSSQL" Then
		If EW_LIKE_COLLATION_FOR_MSSQL <> "" Then
			ew_Like = " COLLATE " & EW_LIKE_COLLATION_FOR_MSSQL & " LIKE " & pat
		Else
			ew_Like = " LIKE " & pat
		End If
	Else
		ew_Like = " LIKE " & pat
	End If
End Function

' Return multi-value search SQL
Function ew_GetMultiSearchSql(Fld, FldOpr, FldVal, dbid)
	Dim arVal, i, sVal, sSql, sWrk, dbtype
	If FldOpr = "IS NULL" Or FldOpr = "IS NOT NULL" Then
		ew_GetMultiSearchSql = Fld.FldExpression & " " & FldOpr
	Else
		If FldOpr = "LIKE" Then
			sWrk = ""
		Else
			sWrk = Fld.FldExpression & ew_SearchString(FldOpr, FldVal, EW_DATATYPE_STRING, dbid)
		End If
		arVal = Split(FldVal, ",")
		dbtype = ew_GetConnectionType(dbid)
		For i = 0 to UBound(arVal)
			sVal = Trim(arVal(i))
			If sVal = EW_NULL_VALUE Then
				sSql = Fld.FldExpression & " IS NULL"
			ElseIf sVal = EW_NOT_NULL_VALUE Then
				sSql = Fld.FldExpression & " IS NOT NULL"
			Else
				If FldOpr = "LIKE" Then
					If UBound(arVal) = 0 Or EW_SEARCH_MULTI_VALUE_OPTION = 3 Then
						sSql = Fld.FldExpression & " = '" & ew_AdjustSqlBase(sVal, dbid) & "' OR " & ew_GetMultiSearchSqlPart(Fld, sVal, dbid)
					Else
						sSql = ew_GetMultiSearchSqlPart(Fld, sVal, dbid)
					End If
				Else
					sSql = Fld.FldExpression & ew_SearchString(FldOpr, sVal, EW_DATATYPE_STRING, dbid)
				End If
			End If
			If sWrk <> "" Then
				If EW_SEARCH_MULTI_VALUE_OPTION = 2 Then
					sWrk = sWrk & " AND "
				ElseIf EW_SEARCH_MULTI_VALUE_OPTION = 3 Then
					sWrk = sWrk & " OR "
				End If
			End If
			sWrk = sWrk & "(" & sSql & ")"
		Next
		ew_GetMultiSearchSql = sWrk
	End If
End Function

' Get multi search SQL part
Function ew_GetMultiSearchSqlPart(Fld, FldVal, dbid)
	ew_GetMultiSearchSqlPart = Fld.FldExpression & ew_Like("'" & ew_AdjustSqlBase(FldVal, dbid) & ", %'", dbid) & " OR " & _
		Fld.FldExpression & ew_Like("'%, " & ew_AdjustSqlBase(FldVal, dbid) & ",%'", dbid) & " OR " & _
		Fld.FldExpression & ew_Like("'%, " & ew_AdjustSqlBase(FldVal, dbid) & "'", dbid)
End Function

' Check if float format
Function ew_IsFloatFormat(FldType)
	ew_IsFloatFormat = (FldType = 4 Or FldType = 5 Or FldType = 131 Or FldType = 6)
End Function

' Check if is numeric
Function ew_IsNumeric(Val)
	ew_IsNumeric = IsNumeric(ew_StrToFloat(Val))
End Function

' Convert string to int ' ASP
Function ew_StrToInt(Val)
	Dim v, p
	v = Val
	If IsNull(v) Or v&"" = "" Then
		ew_StrToInt = v
	Else
		v = Trim(v)
		p = InstrRev(v, EW_DECIMAL_POINT)
		If p > 0 Then ' Remove digits after decimal point
			v = Mid(v, 1, p-1)
		End If
		v = Replace(v, EW_THOUSANDS_SEP, "") ' Remove thousands sep
		ew_StrToInt = Trim(v)
	End If
End Function

' Get search SQL
Function ew_GetSearchSql(Fld, FldVal, FldOpr, FldCond, FldVal2, FldOpr2, dbid)
	Dim IsValidValue, curLocale
	curLocale = GetLocale() ' Save current locale
	SetLocale("en-us") ' Set US locale
	ew_GetSearchSql = ""
	Dim sFldExpression, lFldDataType, virtual
	virtual = Fld.FldIsVirtual And Fld.FldVirtualSearch
	sFldExpression = ew_IIf(virtual, Fld.FldVirtualExpression, Fld.FldExpression)
	lFldDataType = Fld.FldDataType
	If virtual Then lFldDataType = EW_DATATYPE_STRING
	If ew_IsFloatFormat(Fld.FldType) Then
		FldVal = ew_StrToFloat(FldVal)
		FldVal2 = ew_StrToFloat(FldVal2)
	End If
	If lFldDataType = EW_DATATYPE_NUMBER Then ' Fix wrong operator
		If FldOpr = "LIKE" Or FldOpr = "STARTS WITH" Or FldOpr = "ENDS WITH" Then
			FldOpr = "="
		ElseIf FldOpr = "NOT LIKE" Then
			FldOpr = "<>"
		End If
		If FldOpr2 = "LIKE" Or FldOpr2 = "STARTS WITH" Or FldOpr = "ENDS WITH" Then
			FldOpr2 = "="
		ElseIf FldOpr2 = "NOT LIKE" Then
			FldOpr2 = "<>"
		End If
	End If
	If FldOpr = "BETWEEN" Then
		IsValidValue = (lFldDataType <> EW_DATATYPE_NUMBER) Or _
			(lFldDataType = EW_DATATYPE_NUMBER And IsNumeric(FldVal) And IsNumeric(FldVal2))
		If FldVal <> "" And FldVal2 <> "" And IsValidValue Then
			ew_GetSearchSql = sFldExpression & " BETWEEN " & ew_QuotedValueBase(FldVal, lFldDataType, dbid) & _
				" AND " & ew_QuotedValueBase(FldVal2, lFldDataType, dbid)
		End If
	Else

		' Handle first value
		If FldVal = EW_NULL_VALUE Or FldOpr = "IS NULL" Then
			ew_GetSearchSql = Fld.FldExpression & " IS NULL"
		ElseIf FldVal = EW_NOT_NULL_VALUE Or FldOpr = "IS NOT NULL" Then
			ew_GetSearchSql = Fld.FldExpression & " IS NOT NULL"
		Else
			IsValidValue = (lFldDataType <> EW_DATATYPE_NUMBER) Or _
				(lFldDataType = EW_DATATYPE_NUMBER And IsNumeric(FldVal))
			If FldVal <> "" And IsValidValue And ew_IsValidOpr(FldOpr, lFldDataType) Then
				ew_GetSearchSql = sFldExpression & ew_SearchString(FldOpr, FldVal, lFldDataType, dbid)
				If Fld.FldDataType = EW_DATATYPE_BOOLEAN And FldVal = Fld.FalseValue And FldOpr = "=" Then
					ew_GetSearchSql = "(" & ew_GetSearchSql & " OR " & sFldExpression & " IS NULL)"
				End If
			End If
		End If

		' Handle second value
		Dim sSql2
		sSql2 = ""
		If FldVal2 = EW_NULL_VALUE Or FldOpr2 = "IS NULL" Then
			sSql2 = Fld.FldExpression & " IS NULL"
		ElseIf FldVal2 = EW_NOT_NULL_VALUE Or FldOpr2 = "IS NOT NULL" Then
			sSql2 = Fld.FldExpression & " IS NOT NULL"
		Else
			IsValidValue = (lFldDataType <> EW_DATATYPE_NUMBER) Or _
				(lFldDataType = EW_DATATYPE_NUMBER And IsNumeric(FldVal2))
			If FldVal2 <> "" And IsValidValue And ew_IsValidOpr(FldOpr2, lFldDataType) Then
				sSql2 = sFldExpression & ew_SearchString(FldOpr2, FldVal2, lFldDataType, dbid)
				If Fld.FldDataType = EW_DATATYPE_BOOLEAN And FldVal2 = Fld.FalseValue And FldOpr2 = "=" Then
					sSql2 = "(" & sSql2 & " OR " & sFldExpression & " IS NULL)"
				End If
			End If
		End If

		' Combine SQL
		If sSql2 <> "" Then
			If ew_GetSearchSql <> "" Then
				ew_GetSearchSql = "(" & ew_GetSearchSql & " " & ew_IIf(FldCond = "OR", "OR", "AND") & " " & sSql2 & ")"
			Else
				ew_GetSearchSql = sSql2
			End If
		End If
	End If
	SetLocale(curLocale) ' Restore current locale
End Function

' Return search string
Function ew_SearchString(FldOpr, FldVal, FldType, dbid)
	Dim sValue
	sValue = CStr(FldVal&"")
	If sValue = EW_NULL_VALUE Or FldOpr = "IS NULL" Then
		ew_SearchString = " IS NULL"
	ElseIf sValue = EW_NOT_NULL_VALUE Or FldOpr = "IS NOT NULL" Then
		ew_SearchString = " IS NOT NULL"
	ElseIf FldOpr = "LIKE" Then
		ew_SearchString = ew_Like(ew_QuotedValueBase("%" & sValue & "%", FldType, dbid), dbid)
	ElseIf FldOpr = "NOT LIKE" Then
		ew_SearchString = " NOT " & ew_Like(ew_QuotedValueBase("%" & sValue & "%", FldType, dbid), dbid)
	ElseIf FldOpr = "STARTS WITH" Then
		ew_SearchString = ew_Like(ew_QuotedValueBase(sValue & "%", FldType, dbid), dbid)
	ElseIf FldOpr = "ENDS WITH" Then
		ew_SearchString = ew_Like(ew_QuotedValueBase("%" & sValue, FldType, dbid), dbid)
	Else
		If FldType = EW_DATATYPE_NUMBER And Not ew_IsNumeric(FldVal) Then ' Invalid field value
 			ew_SearchString = " = -1 AND 1=0"
		Else
			ew_SearchString = " " & FldOpr & " " & ew_QuotedValueBase(sValue, FldType, dbid)
		End If
	End If
End Function

' Check if valid operator
Function ew_IsValidOpr(Opr, FldType)
	ew_IsValidOpr = (Opr = "=" Or Opr = "<" Or Opr = "<=" Or _
		Opr = ">" Or Opr = ">=" Or Opr = "<>")
	If FldType = EW_DATATYPE_STRING Or FldType = EW_DATATYPE_MEMO Then
		ew_IsValidOpr = ew_IsValidOpr Or Opr = "LIKE" Or Opr = "NOT LIKE" Or Opr = "STARTS WITH" Or Opr = "ENDS WITH"
	End If
End Function

' Quoted name for table/field
Function ew_QuotedName(Name)
	ew_QuotedName = ew_QuotedNameBase(Name, "DB")
End Function

' Quoted name for table/field based on dbid
Function ew_QuotedNameBase(Name, dbid)
	Dim db, qs, qe
	db = ew_GetDb(dbid)
	If IsArray(db) Then
		qs = ew_GetDbAttr(db, "qs")
		qe = ew_GetDbAttr(db, "qe")
		Name = Replace(Name, qe, qe & qe)
		ew_QuotedNameBase = qs & Name & qe
	Else  ' Use default quotes
		ew_QuotedNameBase = EW_DB_QUOTE_START & Replace(Name, EW_DB_QUOTE_END, EW_DB_QUOTE_END & EW_DB_QUOTE_END) & EW_DB_QUOTE_END
	End If
End Function

' Double quote value
Function ew_DoubleQuotedValue(Value)
	ew_DoubleQuotedValue = """" & Replace(Value & "", """", """""") & """"
End Function

' Quote field value (default DB)
Function ew_QuotedValue(Value, FldType)
	ew_QuotedValue = ew_QuotedValueBase(Value, FldType, "DB")
End Function

' Quote field value based on dbid
Function ew_QuotedValueBase(Value, FldType, dbid)
	Dim dbtype, val
	dbtype = ew_GetConnectionType(dbid)
	val = Value
	Select Case FldType
	Case EW_DATATYPE_STRING, EW_DATATYPE_MEMO
		If EW_REMOVE_XSS Then
			val = ew_RemoveXSS(val)
		End If
		If dbtype = "MSSQL" Then
			val = "N'" & ew_AdjustSqlBase(val, dbid) & "'"
		Else
			val = "'" & ew_AdjustSqlBase(val, dbid) & "'"
		End If
	Case EW_DATATYPE_GUID
		If dbtype = "ACCESS" Then
			val = "{guid " & ew_AdjustSqlBase(val, dbid) & "}"
		Else
			val = "'" & ew_AdjustSqlBase(val, dbid) & "'"
		End If
	Case EW_DATATYPE_XML
		val = "'" & ew_AdjustSqlBase(val, dbid) & "'"
	Case EW_DATATYPE_DATE, EW_DATATYPE_TIME
		If EW_REMOVE_XSS Then
			val = ew_RemoveXSS(val)
		End If
		If dbtype = "ACCESS" Then
			val = "#" & ew_AdjustSqlBase(val, dbid) & "#"
		ElseIf dbtype = "ORACLE" Then
			val = "TO_DATE('" & ew_AdjustSqlBase(val, dbid) & "', 'YYYY/MM/DD HH24:MI:SS')"
		Else
			val = "'" & ew_AdjustSqlBase(val, dbid) & "'"
		End If
	Case EW_DATATYPE_BOOLEAN
		If dbtype = "MYSQL" Or dbtype = "POSTGRESQL" Then
			val = "'" & ew_AdjustSqlBase(val, dbid) & "'" ' 'Y'|'N' or 'y'|'n' or '1'|'0' or 't'|'f'
		End If
	Case EW_DATATYPE_NUMBER
		If Not ew_IsNumeric(val) Then
			val = "NULL" ' Treat as null
		End If
	End Select
	ew_QuotedValueBase = val
End Function

' Pad zeros before number
Function ew_ZeroPad(m, t)
	ew_ZeroPad = String(t - Len(m), "0") & m
End Function

' Convert different data type value
Function ew_Conv(v, t)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Select Case t

	' If adBigInt/adUnsignedBigInt
	Case 20, 21
		If IsNull(v) Then
			ew_Conv = Null
		Else
			ew_Conv = CLng(v)
		End If

	' If adSmallInt/adInteger/adTinyInt/adUnsignedTinyInt/adUnsignedSmallInt/adUnsignedInt/adBinary
	Case 2, 3, 16, 17, 18, 19, 128
		If IsNull(v) Then
			ew_Conv = Null
		Else
			ew_Conv = CLng(v)
		End If

	' If adSingle
	Case 4
		If IsNull(v) Then
			ew_Conv = Null
		Else
			ew_Conv = CSng(v)
		End If

	' If adDouble/adCurrency/adNumeric/adVarNumeric
	Case 5, 6, 131, 139
		If IsNull(v) Then
			ew_Conv = Null
		Else
			ew_Conv = CDbl(v)
		End If
	Case Else
		ew_Conv = v
	End Select
End Function

' Concat string
Function ew_Concat(str1, str2, sep)
	str1 = Trim(str1)
	str2 = Trim(str2)
	If str1 <> "" And sep <> "" And Right(str1, Len(sep)) <> sep Then
		str1 = str1 & sep
	End If
	ew_Concat = str1 & str2
End Function

' Contains a substring (case-sensitive)
Function ew_ContainsStr(haystack, needle)
	ew_ContainsStr = (InStr(1, haystack, needle, 0) > 0)
End Function

' Contains a substring (case-insensitive)
Function ew_ContainsText(haystack, needle)
	ew_ContainsText = (InStr(1, haystack, needle, 1) > 0)
End Function

' Starts with a substring (case-sensitive)
Function ew_StartsStr(haystack, needle)
	ew_StartsStr = (InStr(1, haystack, needle, 0) = 1)
End Function

' Starts with a substring (case-insensitive)
Function ew_StartsText(haystack, needle)
	ew_StartsText = (InStr(1, haystack, needle, 1) = 1)
End Function

' Ends with a substring (case-sensitive)
Function ew_EndsStr(haystack, needle)
	ew_EndsStr = (InStrRev(haystack, needle, -1, 0) = (Len(haystack) - Len(needle) + 1))
End Function

' Ends with a substring (case-insensitive)
Function ew_EndsText(haystack, needle)
	ew_EndsText = (InStrRev(haystack, needle, -1, 1) = (Len(haystack) - Len(needle) + 1))
End Function

' Same trimmed strings (case-sensitive)
Function ew_SameStr(str1, str2)
	ew_SameStr = (StrComp(Trim(str1), Trim(str2), 0) = 0)
End Function

' Same trimmed strings (case-insensitive)
Function ew_SameText(str1, str2)
	ew_SameText = (StrComp(Trim(str1), Trim(str2), 1) = 0)
End Function

' Write message in text file for debug
Sub ew_Trace(pfx, msg)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Dim fso, ts, sFolder, sFn
	sFolder = EW_AUDIT_TRAIL_PATH
	sFn = pfx & ".txt"
	Set fso = Server.Createobject("Scripting.FileSystemObject")
	Set ts = fso.OpenTextFile(ew_UploadPathEx(True, sFolder) & sFn, 8, True)
	ts.writeline(Date & vbTab & Time & vbTab & msg)
	ts.Close
	Set ts = Nothing
	Set fso = Nothing
End Sub

' Get elapsed time (in seconds)
Function ew_ElapsedTime()
	ew_ElapsedTime = (Timer - StartTimer)
End Function

' Display elapsed time
Sub ew_WriteElapsedTime()
	If EW_DEBUG_ENABLED Then
		Response.Write "<div class=""alert alert-info ewAlert"">Page processing time: " & FormatNumber(ew_ElapsedTime(), 3) & " seconds</div>"
	End If
End Sub

' Compare values with special handling for null values
Function ew_CompareValue(v1, v2)
	If IsNull(v1) And IsNull(v2) Then
		ew_CompareValue = True
	ElseIf IsNull(v1) Or IsNull(v2) Then
		ew_CompareValue = False
	ElseIf VarType(v1) = 14 Or VarType(v2) = 14 Then
		ew_CompareValue = (CDbl(v1) = CDbl(v2))
	Else
		ew_CompareValue = (v1 = v2)
	End If
End Function

' Check if boolean value is True
Function ew_ConvertToBool(value)
	On Error Resume Next
	ew_ConvertToBool = CBool(value)
	If Err.Number <> 0 Then
		Err.Clear
		Dim tmp
		tmp = LCase(value & "")
		ew_ConvertToBool = (tmp = "1" Or tmp = "true" Or tmp = "y" Or tmp = "t")
	End If
End Function

' Add message
Sub ew_AddMessage(msg, msgtoadd)
	If msgtoadd <> "" Then
		If msg <> "" Then
			msg = msg & "<br>"
		End If
		msg = msg & msgtoadd
	End If
End Sub

' Add filter
Sub ew_AddFilter(filter, newfilter)
	If Trim(newfilter) = "" Then Exit Sub
	If Trim(filter) <> "" Then
		filter = "(" & filter & ") AND (" & newfilter & ")"
	Else
		filter = newfilter
	End If
End Sub

' Adjust SQL for special characters
Function ew_AdjustSql(str)
	ew_AdjustSql = ew_AdjustSqlBase(str, "DB")
End Function

' Adjust SQL for special characters based on dbid
Function ew_AdjustSqlBase(str, dbid)
	Dim sWrk, dbtype
	dbtype = ew_GetConnectionType(dbid)
	sWrk = Trim(str & "")
	sWrk = Replace(sWrk, "'", "''") ' Adjust for Single Quote
	If dbtype = "ACCESS" Or dbtype = "MSSQL" THen
		sWrk = Replace(sWrk, "[", "[[]") ' Adjust for Open Square Bracket
	End If
	ew_AdjustSqlBase = sWrk
End Function

' Build select sql based on different SQL part
Function ew_BuildSelectSql(sSelect, sWhere, sGroupBy, sHaving, sOrderBy, sFilter, sSort)
	Dim sSql, sDbWhere, sDbOrderBy
	sDbWhere = sWhere
	Call ew_AddFilter(sDbWhere, sFilter)
	sDbOrderBy = sOrderBy
	If sSort <> "" Then
		sDbOrderBy = sSort
	End If
	sSql = sSelect
	If sDbWhere <> "" Then
		sSql = sSql & " WHERE " & sDbWhere
	End If
	If sGroupBy <> "" Then
		sSql = sSql & " GROUP BY " & sGroupBy
	End If
	If sHaving <> "" Then
		sSql = sSql & " HAVING " & sHaving
	End If
	If sDbOrderBy <> "" Then
		sSql = sSql & " ORDER BY " & sDbOrderBy
	End If
	ew_BuildSelectSql = sSql
End Function

' Executes the query, and returns the row(s) as JSON
Function ew_ExecuteJson(SQL, options)
	ew_ExecuteJson = ew_ExecuteJsonByDbid(SQL, options, "DB")
End Function

' Return JSON by dbid
' options: header(bool), array(bool), firstonly(bool)
Function ew_ExecuteJsonByDbid(SQL, options, dbid)
	Dim header, arr, firstonly, res, opts, i
	Call ew_SetDebugMsg("ExecuteJson(" & dbid & "): " & SQL) ' Show SQL for debugging
	header = False
	arr = False
	firstonly = False
	If IsArray(options) Then
		Set opts = Dictionary(options)
	ElseIf IsDictionary(options) Then
		Set opts = options
	End If
	If IsDictionary(opts) Then
		If opts.ContainsKey("header") Then header = opts.Get("header") ' Set header for JSON
		If opts.ContainsKey("array") Then arr = opts.Get("array")
		If opts.ContainsKey("firstonly") Then firstonly = opts.Get("firstonly")
	End If
	If IsArray(options) Then Set opts = Nothing
	If firstonly Then
		Set res = ew_ExecuteRowByDbid(SQL, dbid)
		If arr And ew_NotEmpty(res) Then res = res.Items()
	Else
		res = ew_ExecuteRowsByDbid(SQL, dbid)
		If arr And IsArray(res) Then
			For i = 0 To UBound(res)
				res(i) = res(i).Items()
			Next
		End If
	End If
	If header Then Call ResponseJson
	If ew_NotEmpty(res) Then
		ew_ExecuteJsonByDbid = ew_JsonEncode(res)
	Else
		ew_ExecuteJsonByDbid = "false"
	End If
End Function

' Return rows as array of dictionary
Function ew_ExecuteRows(SQL)
	ew_ExecuteRows = ew_ExecuteRowsByDbid(SQL, "DB")
End Function

' Return rows as array of dictionary by dbid
Function ew_ExecuteRowsByDbid(SQL, dbid)
	Dim i, rs, fldcnt, dict, res
	Set rs = ew_LoadRecordsetByDbid(SQL, dbid)
	If ew_NotEmpty(rs) Then
		If Not rs.EOF Then
			ew_ExecuteRowsByDbid = ew_RecordsetToArray(rs)
		End If
		rs.Close
		Set rs = Nothing
	End If
End Function

' Return first row as dictionary
Function ew_ExecuteRow(SQL)
	Set ew_ExecuteRow = ew_ExecuteRowByDbid(SQL, "DB")
End Function

' Return first row as dictionary by dbid
Function ew_ExecuteRowByDbid(SQL, dbid)
	On Error Resume Next
	Dim i, rs, fldcnt, dict
	Set dict = Server.CreateObject("Scripting.Dictionary")
	Set rs = ew_LoadRecordsetByDbid(SQL, dbid)
	Set ew_ExecuteRowByDbid = Nothing
	If ew_NotEmpty(rs) Then
		If Not rs.EOF Then
			fldcnt = rs.Fields.Count
			For i = 0 to fldcnt - 1
				dict.Add rs.Fields(i).Name, rs.Fields(i).Value
			Next
			Set ew_ExecuteRowByDbid = dict
		End If
		rs.Close
		Set rs = Nothing
	End If
	Set dict = Nothing
End Function

' Convert recordset to array of Dictionary
Function ew_RecordsetToArray(rs)
	Dim rows, row, i, rowcnt, fldcnt
	If rs.RecordCount > 0 And Not rs.EOF Then
		ReDim rows(rs.RecordCount - 1)

		'rs.MoveFirst
		rowcnt = 0
		Do While Not rs.EOF
			Set row = Server.CreateObject("Scripting.Dictionary")
			fldcnt = rs.Fields.Count
			For i = 0 to fldcnt - 1
				row.Add rs.Fields(i).Name, rs.Fields(i).Value
			Next
			Set rows(rowcnt) = row
			rowcnt = rowcnt + 1
			rs.MoveNext
		Loop

		'rs.MoveFirst
	End If
	ew_RecordsetToArray = rows
End Function

' Convert current record in recordset to JSON
Function ew_RecordToJson(rs)
	Dim row, i, fldcnt, fld
	ew_RecordToJson = "null"
	If ew_Empty(rs) Then Exit Function
	If (rs.State And 1) <> 1 Then Exit Function ' adStateOpen
	If Not rs.EOF Then
		Set row = Dictionary()
		fldcnt = rs.Fields.Count
		For i = 0 to fldcnt - 1
			Set fld = rs.Fields(i)
			If fld.Type = 205 Or fld.Type = 204 Or fld.Type = 124 Then ' Binary
				Call row.Add(fld.Name, "[Binary]")
			Else
				Call row.Add(fld.Name, fld.Value)
			End If
		Next
		ew_RecordToJson = row.ToJson()
		Set row = Nothing
	End If
End Function

' Get record count ' ASP
Function ew_GetRecordCount(SQL, dbid)
	On Error Resume Next
	Err.Clear
	Dim cnn, cnt, pattern, rs, sqlwrk
	Set cnn = ew_GetConn(dbid)
	cnt = -1
	pattern = "\/\*BeginOrderBy\*\/[\s\S]+\/\*EndOrderBy\*\/"
	sqlwrk = ew_RegExReplace(pattern, SQL, "") ' Remove ORDER BY clause (MSSQL)
	pattern = "^SELECT\s+\*\s+FROM"
	If ew_RegExTest(pattern, sqlwrk) Then
		sqlwrk = "SELECT COUNT(*) FROM" & ew_RegExReplace(pattern, sqlwrk, "")
		Set rs = cnn.Execute(sqlwrk)
	Else
		sqlwrk = "SELECT COUNT(*) FROM (" & sqlwrk & ") EW_COUNT_TABLE"
		Set rs = cnn.Execute(sqlwrk)
	End If
	If Err.Number = 0 Then
		If Not rs.EOF And rs.Fields.Count > 0 Then
			cnt = rs.Fields(0)
			Set rs = Nothing
			Set cnn = Nothing
			ew_GetRecordCount = CLng(cnt)
			Exit Function
		End If
	Else
		Err.Clear
	End If

	' Unable to get count, get record count directly
	Set rs = Server.CreateObject("ADODB.Recordset")
	rs.CursorLocation = ew_GetCursorLocation(dbid)
	rs.Open SQL, cnn, 3, 1, 1 ' adOpenStatic, adLockReadOnly, adCmdText
	If Not rs.EOF Then
		ew_GetRecordCount = rs.RecordCount
		rs.Close
		Set rs = Nothing
		Set cnn = Nothing
		Exit Function
	End If

	' Unable to get count, return -1
	ew_GetRecordCount = cnt
End Function

' Get SELECT LIMIT SQL ' ASP
' - MySQL / PostgreSQL, use "SELECT ... LIMIT n OFFSET m"
' - Oracle, use "SELECT * FROM (SELECT ...) WHERE ROWNUM <= n+m"
' - MSSQL2012+, use "SELECT ... OFFSET m ROWS FETCH NEXT n ROWS ONLY"
' - Access / MSSQL, use "SELECT TOP n+m ... FROM ..."
Function ew_GetSelectLimitSql(SQL, limit, offset, hasOrderBy, dbType)
	Dim pattern1, pattern2, sqlwrk
	sqlwrk = SQL
	If dbType = "MYSQL" Or dbType = "POSTGRESQL" Then
		sqlwrk = sqlwrk & " LIMIT " & limit & " OFFSET "  & offset
	ElseIf dbType = "ORACLE" Then
		sqlwrk = "SELECT * FROM (" & sqlwrk & ") WHERE ROWNUM <= " & (offset + limit)
	ElseIf dbType = "MSSQL2012" Then
		If Not hasOrderBy Then sqlwrk = sqlwrk & " ORDER BY @@version"
		sqlwrk = sqlwrk & " OFFSET " & offset & " ROWS FETCH NEXT "  & limit & " ROWS ONLY"
	Else ' Assume MSSQL / Microsoft Access
		pattern1 = "^SELECT\s+\*\s+FROM"
		pattern2 = "^SELECT\s"
		If ew_RegExTest(pattern1, SQL) Then
			sqlwrk = "SELECT TOP " & (offset + limit) & " " & ew_RegExReplace(pattern2, SQL, "")
		Else
			sqlwrk = "SELECT TOP " & (offset + limit) & " * FROM (" & SQL & ") EW_TABLE"
		End If
	End If
	ew_GetSelectLimitSql = sqlwrk
End Function

' Check if MSSQL2012+ ' ASP
Function ew_IsMsSql2012(dbid)
	Dim dbType, version, m
	ew_IsMsSql2012 = False
	dbType = ew_GetConnectionType(dbid)
	If dbType = "MSSQL" Then
		version = ew_ExecuteScalarByDbid("SELECT @@version", dbid)
		If ew_RegExMatch("Microsoft SQL Server (\d+)", version, m) Then
			ew_IsMsSql2012 = CInt(m(0).SubMatches(0)) >= 2012
		End If
	End If
End Function

' Load recordset
Function ew_LoadRecordset(SQL)
	Set ew_LoadRecordset = ew_LoadRecordsetByDbid(SQL, "DB")
End Function

' Load recordset
Function ew_LoadRecordsetByDbid(SQL, dbid)
	Call ew_SetDebugMsg("LoadRecordset(" & dbid & "): " & SQL) ' Show SQL for debugging
	On Error Resume Next
	Err.Clear
	Dim cnn, rs
	Set cnn = ew_GetConn(dbid)
	Set rs = Server.CreateObject("ADODB.Recordset")
	rs.CursorLocation = 3 ' Always use adUseClient to improve performance
	rs.Open SQL, cnn, 0, 1, 1 ' adOpenForwardOnly, adLockReadOnly, adCmdText
	If Err.Number <> 0 Then
		Call ew_SetDebugMsg("LoadRecordset(" & dbid & "): SQL: " & SQL & ". Error: " & Err.Description)
		Set ew_LoadRecordsetByDbid = Nothing
	Else
		Set ew_LoadRecordsetByDbid = rs
	End If
End Function

' Load row
Function ew_LoadRow(SQL)
	Set ew_LoadRow = ew_LoadRowByDbid(SQL, "DB")
End Function

' Load row by connection
Function ew_LoadRowByDbid(SQL, dbid)
	Call ew_SetDebugMsg("LoadRow(" & dbid & "): " & SQL) ' Show SQL for debugging
	On Error Resume Next
	Err.Clear
	Dim cnn, rs
	Set cnn = ew_GetConn(dbid)
	Set rs = Server.CreateObject("ADODB.Recordset")
	rs.Open SQL, cnn
	If Err.Number <> 0 Then
		Call ew_SetDebugMsg("LoadRow(" & dbid & "): SQL: " & SQL & ". Error: " & Err.Description)
		Set ew_LoadRowByDbid = Nothing
	Else
		Set ew_LoadRowByDbid = rs
	End If
End Function

' Note: Object "Conn" is required
' Execute UPDATE, INSERT, or DELETE statements
Function ew_Execute(SQL)
	Set ew_Execute = ew_ExecuteByConn(SQL, Conn)
End Function

' Execute by dbid
Function ew_ExecuteByDbid(SQL, dbid)
	Call ew_SetDebugMsg("Execute(" & dbid & "): " & SQL) ' Show SQL for debugging
	Dim cnn
	Set cnn = ew_GetConn(dbid)
	Set ew_ExecuteByDbid = ew_ExecuteByConn(SQL, cnn)
End Function

' Execute by connection
Function ew_ExecuteByConn(SQL, cnn)
	Set ew_ExecuteByConn = cnn.Execute(SQL)
End Function

' Return SQL scalar value
Function ew_ExecuteScalar(SQL)
	ew_ExecuteScalar = ew_ExecuteScalarByDbid(SQL, "DB")
End Function

' Return scalar value by dbid
Function ew_ExecuteScalarByDbid(SQL, dbid)
	Call ew_SetDebugMsg("ExecuteScalar(" & dbid & "): " & SQL) ' Show SQL for debugging
	Dim cnn
	Set cnn = ew_GetConn(dbid)
	ew_ExecuteScalarByDbid = ew_ExecuteScalarByConn(SQL, cnn)
End Function

' Return scalar value by connection
Function ew_ExecuteScalarByConn(SQL, cnn)
	On Error Resume Next
	Err.Clear

	'ew_ExecuteScalarByConn = Null ' If failed, result is empty
	If Trim(SQL & "") = "" Then
		Call ew_SetDebugMsg("ExecuteScalar: Error: Missing SQL.")
		Exit Function
	End If
	Dim rs
	Set rs = cnn.Execute(SQL)
	If Err.Number <> 0 Then
		Call ew_SetDebugMsg("ExecuteScalar: SQL: " & SQL & ". Error: " & Err.Description)
	ElseIf ew_NotEmpty(rs) Then
		If Not rs.EOF Then ew_ExecuteScalarByConn = rs(0)
		rs.Close
		Set rs = Nothing
	End If
End Function

' Get result in HTML table
' options: 0:fieldcaption(bool|array), 1:horizontal(bool), 2:tablename(string|array), 3:tableclass(string)
Function ew_ExecuteHtml(SQL, options)
	ew_ExecuteHtml = ew_ExecuteHtmlByDbid(SQL, options, "DB")
End Function

' Return html by dbid
Function ew_ExecuteHtmlByDbid(SQL, options, dbid)
	Call ew_SetDebugMsg("ExecuteHtml(" & dbid & "): " & SQL) ' Show SQL for debugging
	On Error Resume Next
	Dim ar, horizontal, html, tblclass, TableClass
	Dim rs, cnt, fldcnt, rowcnt, i, key, val
	TableClass = "table table-bordered ewDbTable" ' Table CSS class name
	If IsArray(options) Then
		ar = options
	Else
		ar = Array()
	End If
	If UBound(ar) >= 1 Then
		horizontal = CBool(ar(1))
	Else
		horizontal = False
	End If
	html = ""
	If UBound(ar) >= 3 Then
		tblclass = ar(3)
	Else
		tblclass = TableClass
	End If
	Set rs = ew_LoadRecordsetByDbid(SQL, dbid)
	If ew_NotEmpty(rs) Then
		cnt = rs.RecordCount
		If cnt > 1 Or horizontal Then ' Horizontal table
			html = "<table class=""" & tblclass & """>"
			html = html & "<thead><tr>"
			fldcnt = rs.Fields.Count
			For i = 0 to fldcnt - 1
				key = rs.Fields(i).Name
				val = rs.Fields(i).Value
				html = html & "<th>" & ew_GetFieldCaption(key, ar) & "</th>"
			Next
			html = html & "</tr></thead>"
			html = html & "<tbody>"
			rowcnt = 0
			Do While Not rs.EOF
				html = html & "<tr>"
				For i = 0 to fldcnt - 1
					key = rs.Fields(i).Name
					val = rs.Fields(i).Value
					html = html & "<td>" & val & "</td>"
				Next
				html = html & "</tr>"
				rs.MoveNext
			Loop
			html = html & "</tbody></table>"
		Else ' Single row, vertical table
			If Not rs.EOF Then
				html = "<table class=""" & tblclass & """><tbody>"
				fldcnt = rs.Fields.Count
				For i = 0 to fldcnt - 1
					key = rs.Fields(i).Name
					val = rs.Fields(i).Value
					html = html & "<tr>"
					html = html & "<td>" & ew_GetFieldCaption(key, ar) & "</td>"
					html = html & "<td>" & val & "</td></tr>"
				Next
				html = html & "</tbody></table>"
			End If
		End If
		rs.Close
		Set rs = Nothing
	End If
	ew_ExecuteHtmlByDbid = html
End Function

' Get field caption
' ar: 0:fieldcaption(bool|array), 1:horizontal(bool), 2:tablename(string|array), 3:tableclass(string)
Function ew_GetFieldCaption(key, ar)
	On Error Resume Next
	Dim caption, tblname, usecaption, arcaptions, i
	caption = ""
	If Not IsArray(ar) Then
		ew_GetFieldCaption = key
		Exit Function
	End If
	If UBound(ar) >= 2 Then
		tblname = ar(2)
	Else
		tblname = ""
	End If
	If UBound(ar) >= 0 Then
		If IsArray(ar(0)) Then
			usecaption = True
			arcaptions = ar(0)
		Else
			usecaption = CBool(ar(0))
			arcaptions = ""
		End If
		If usecaption Then
			If IsArray(arcaptions) Then
				For i = 0 to UBound(arcaptions)
					If IsArray(arcaptions(i)) Then
						If UBound(arcaptions(i)) >= 1 Then
							If arcaptions(i)(0) = key Then
								caption = arcaptions(i)(1)
								Exit For
							End If
						End If
					End If
				Next
			Else
				If IsArray(tblname) Then
					For i = 0 to UBound(tblname)
						caption = Language.FieldPhrase(tblname(i), key, "FldCaption")
						If caption <> "" Then
							Exit For
						End If
					Next
				ElseIf tblname <> "" Then
					caption = Language.FieldPhrase(tblname, key, "FldCaption")
				End If
			End If
		End If
	End If
	If caption <> "" Then
		ew_GetFieldCaption = caption
	Else
		ew_GetFieldCaption = key
	End If
End Function

' Clone recordset
Function ew_CloneRs(RsOld)
	On Error Resume Next
	Err.Clear
	Dim Stream, RsClone

	' Save the recordset to the stream object
	Set Stream = Server.CreateObject("ADODB.Stream")
	RsOld.Save Stream

	' Open the stream object into a new recordset
	Set RsClone = Server.CreateObject("ADODB.Recordset")
	RsClone.Open Stream, , , 2

	' Return the cloned recordset
	Set ew_CloneRs = RsClone

	' Release the reference
	Set RsClone = Nothing
End Function

' Dynamically include a file
Function ew_Include(fn)
	On Error Resume Next
	Dim sIncludeText
	sIncludeText = ew_LoadFile(fn)
	If sIncludeText <> "" Then
		sIncludeText = Replace(sIncludeText, "<" & "%", "")
		sIncludeText = Replace(sIncludeText, "%" & ">", "")
		Execute sIncludeText
		ew_Include = True
	Else
		ew_Include = False
	End If
End Function

' Load a Text File
Function ew_LoadTxt(fn)
	Dim fso, fobj

	' Get text file content
	ew_LoadTxt = ""
	If Trim(fn) <> "" Then
		Set fso = Server.CreateObject("Scripting.FileSystemObject")
		If fso.FileExists(Server.MapPath(fn)) Then
			Set fobj = fso.OpenTextFile(Server.MapPath(fn))
			ew_LoadTxt = fobj.ReadAll ' Read all Content
			fobj.Close
			Set fobj = Nothing
		End If
		Set fso = Nothing
	End If
End Function

' Write Audit Trail (insert/update/delete)
Sub ew_WriteAuditTrail(pfx, curDateTime, script, user, action, table, field, keyvalue, oldvalue, newvalue)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Dim fso, ts, sMsg, sFn, sFolder
	Dim bWriteHeader, sHeader
	Dim bWriteAuditTrail
	Dim userwrk
	userwrk = user
	Dim rsnew, cnn, sql
	If userwrk = "" Then
		userwrk = "-1" ' assume Administrator if no user
	End If
	If EW_AUDIT_TRAIL_TO_DATABASE Then
		Set cnn = ew_GetConn(EW_AUDIT_TRAIL_DBID)
		sql = "SELECT * FROM " & EW_AUDIT_TRAIL_TABLE_NAME & " WHERE 0=1"
		Set rsnew = Server.CreateObject("ADODB.Recordset")
		rsnew.CursorLocation = EW_AUDIT_TRAIL_CURSOR_LOCATION
		Call ew_SetDebugMsg("WriteAuditTrail(" & EW_AUDIT_TRAIL_DBID & "): " & SQL) ' Show SQL for debugging
		rsnew.Open sql, cnn, 1, EW_AUDIT_TRAIL_RECORDSET_LOCKTYPE
		rsnew.AddNew
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_DATETIME) = curDateTime
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_SCRIPT) = script
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_USER) = userwrk
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_ACTION) = action
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_TABLE) = table
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_FIELD) = field
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_KEYVALUE) = keyvalue
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_OLDVALUE) = oldvalue
		rsnew(EW_AUDIT_TRAIL_FIELD_NAME_NEWVALUE) = newvalue
	Else
		Set rsnew = Server.CreateObject("ADODB.Recordset")
		Call rsnew.Fields.Append("datetime", 135, 8)
		Call rsnew.Fields.Append("script", 200, 255)
		Call rsnew.Fields.Append("user", 200, 255)
		Call rsnew.Fields.Append("action", 200, 255)
		Call rsnew.Fields.Append("table", 200, 255)
		Call rsnew.Fields.Append("field", 200, 255)
		Call rsnew.Fields.Append("keyvalue", 201, -1, &H20) ' Allow null (adFldIsNullable)
		Call rsnew.Fields.Append("oldvalue", 201, -1, &H20) ' Allow null (adFldIsNullable)
		Call rsnew.Fields.Append("newvalue", 201, -1, &H20) ' Allow null (adFldIsNullable)
		rsnew.Open
		rsnew.AddNew
		rsnew("datetime") = curDateTime
		rsnew("script") = script
		rsnew("user") = userwrk
		rsnew("action") = action
		rsnew("table") = table
		rsnew("field") = field
		rsnew("keyvalue") = keyvalue
		rsnew("oldvalue") = oldvalue
		rsnew("newvalue") = newvalue
		Call ew_SetDebugMsg("Insert: " & ew_HtmlEncode(ew_RecordToJson(rsnew))) ' Show data for debugging
		rsnew.Update
	End If

	' Call AuditTrail Inserting event
	bWriteAuditTrail = AuditTrail_Inserting(rsnew)
	If Not EW_AUDIT_TRAIL_TO_DATABASE Then
		If bWriteAuditTrail Then

			' Write audit trail to log file
			sHeader = "date/time" & vbTab & _
				"script" & vbTab & _
				"user" & vbTab & _
				"action" & vbTab & _
				"table" & vbTab & _
				"field" & vbTab & _
				"key value" & vbTab & _
				"old value" & vbTab & _
				"new value"
			sMsg = rsnew("datetime") & vbTab & _
				rsnew("script") & vbTab & _
				rsnew("user") & vbTab & _
				rsnew("action") & vbTab & _
				rsnew("table") & vbTab & _
				rsnew("field") & vbTab & _
				rsnew("keyvalue") & vbTab & _
				rsnew("oldvalue") & vbTab & _
				rsnew("newvalue")
			sFolder = EW_AUDIT_TRAIL_PATH
			sFn = pfx & "_" & ew_ZeroPad(Year(Date), 4) & ew_ZeroPad(Month(Date), 2) & ew_ZeroPad(Day(Date), 2) & ".txt"
			Set fso = Server.Createobject("Scripting.FileSystemObject")
			bWriteHeader = Not fso.FileExists(ew_ServerMapPath(sFolder) & sFn)
			Set ts = fso.OpenTextFile(ew_ServerMapPath(sFolder) & sFn, 8, True)
			If bWriteHeader Then
				ts.writeline(sHeader)
			End If
			ts.writeline(sMsg)
			ts.Close
			Set ts = Nothing
			Set fso = Nothing
		End If
		rsnew.Close
		Set rsnew = Nothing
	Else
		If bWriteAuditTrail Then
			Call ew_SetDebugMsg("Insert: " & ew_HtmlEncode(ew_RecordToJson(rsnew))) ' Show data for debugging
			rsnew.Update
		Else
			Call ew_SetDebugMsg("CancelUpdate: " & ew_HtmlEncode(ew_RecordToJson(rsnew))) ' Show data for debugging
			rsnew.CancelUpdate
		End If
		rsnew.Close
		Set rsnew = Nothing
	End If
End Sub

' AuditTrail Inserting event
Function AuditTrail_Inserting(rsnew)
	AuditTrail_Inserting = True
End Function

' Check date format "yyyy-MM-dd HH:mm:ss.fffffff zzz"
Function ew_IsDate(ADate)
	If ADate & "" = "" Then
		ew_IsDate = False
	Else
		ew_IsDate = IsDate(ew_GetDateTimePart(ADate))
	End If
End Function

' Get DateTime part (remove ".fffffff zzz" from format "yyyy-MM-dd HH:mm:ss.fffffff zzz")
Function ew_GetDateTimePart(ADate)
	If IsNull(ADate) Then
		ew_GetDateTimePart = ADate
	ElseIf InStrRev(ADate,".") > 0 And InStr(ADate,":") > 0 Then
		ew_GetDateTimePart = Mid(ADate, 1, InStrRev(ADate, ".") - 1)
		If Not IsDate(ew_GetDateTimePart) Or InStr(ew_GetDateTimePart,":") <= 0 Then ew_GetDateTimePart = ADate
	Else
		ew_GetDateTimePart = ADate
	End If
End Function

Function ew_GetFormatDateTimeID(ANamedFormat)
	Select Case ANamedFormat
		Case 0, 8
			ew_GetFormatDateTimeID = EW_DATE_FORMAT_ID
		Case 1
			Select Case EW_DATE_FORMAT_ID
				Case 5
					ew_GetFormatDateTimeID = 9
				Case 6
					ew_GetFormatDateTimeID = 10
				Case 7
					ew_GetFormatDateTimeID = 11
				Case 12
					ew_GetFormatDateTimeID = 15
				Case 13
					ew_GetFormatDateTimeID = 16
				Case 14
					ew_GetFormatDateTimeID = 17
				Case Else
					ew_GetFormatDateTimeID = EW_DATE_FORMAT_ID
			End Select
		Case 2
			Select Case EW_DATE_FORMAT_ID
				Case 9
					ew_GetFormatDateTimeID = 5
				Case 10
					ew_GetFormatDateTimeID = 6
				Case 11
					ew_GetFormatDateTimeID = 7
				Case 15
					ew_GetFormatDateTimeID = 12
				Case 16
					ew_GetFormatDateTimeID = 13
				Case 17
					ew_GetFormatDateTimeID = 14
				Case Else
					ew_GetFormatDateTimeID = EW_DATE_FORMAT_ID
			End Select
		Case Else
			ew_GetFormatDateTimeID = ANamedFormat
	End Select
End Function

' 0 - Default date format
' 1 - Default date format (with time)
' 2 - Default date format (without time)
' 3 - Long Time (hh:mm:ss AM/PM)
' 4 - Short Time (hh:mm:ss)
' 5 - Short Date (yyyy/mm/dd)
' 6 - Short Date (mm/dd/yyyy)
' 7 - Short Date (dd/mm/yyyy)
' 8 - Short Date (Default) + Short Time (if not 00:00:00)
' 9 - Short Date (yyyy/mm/dd) + Short Time (hh:mm:ss)
' 10 - Short Date (mm/dd/yyyy) + Short Time (hh:mm:ss)
' 11 - Short Date (dd/mm/yyyy) + Short Time (hh:mm:ss)
' 12 - Short Date - 2 digit year (yy/mm/dd)
' 13 - Short Date - 2 digit year (mm/dd/yy)
' 14 - Short Date - 2 digit year (dd/mm/yy)
' 15 - Short Date - 2 digit year (yy/mm/dd) + Short Time (hh:mm:ss)
' 16 - Short Date - 2 digit year (mm/dd/yy) + Short Time (hh:mm:ss)
' 17 - Short Date - 2 digit year (dd/mm/yy) + Short Time (hh:mm:ss)
' Format date time based on format type
Function ew_FormatDateTime(ADate, ANamedFormat)
	Dim sDate
	sDate = ew_GetDateTimePart(ADate)
	If IsDate(sDate) And ANamedFormat = 8 Then
		If Hour(sDate) > 0 Or Minute(sDate) > 0 Or Second(sDate) > 0 Then
			ANamedFormat = 1
		Else
			ANamedFormat = 2
		End If
	End If
	ANamedFormat = ew_GetFormatDateTimeID(ANamedFormat)
	If IsDate(sDate) Then
		If ANamedFormat = 3 Then
			If Hour(sDate) = 0 Then
				If Minute(sDate) = 0 And Second(sDate) = 0 Then
					ew_FormatDateTime = "12 " & Language.Phrase("Midnight")
				Else
					ew_FormatDateTime = "12" & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2) & " " & Language.Phrase("AM")
				End If
			ElseIf Hour(sDate) > 0 And Hour(sDate) < 12 Then
				ew_FormatDateTime = ew_ZeroPad(Hour(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2) & " " & Language.Phrase("AM")
			ElseIf Hour(sDate) = 12 Then
				If Minute(sDate) = 0 And Second(sDate) = 0 Then
					ew_FormatDateTime = "12 " & Language.Phrase("Noon")
				Else
					ew_FormatDateTime = ew_ZeroPad(Hour(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2) & " " & Language.Phrase("PM")
				End If
			ElseIf Hour(sDate) > 12 And Hour(sDate) <= 23 Then
				ew_FormatDateTime = ew_ZeroPad(Hour(sDate)-12, 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2) & " " & Language.Phrase("PM")
			Else
				ew_FormatDateTime = ew_ZeroPad(Hour(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2)
			End If
		ElseIf ANamedFormat = 4 Then
			ew_FormatDateTime = ew_ZeroPad(Hour(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2)
		ElseIf ANamedFormat = 5 Or ANamedFormat = 9 Then
			ew_FormatDateTime = Year(sDate) & EW_DATE_SEPARATOR & Month(sDate) & EW_DATE_SEPARATOR & Day(sDate)
		ElseIf ANamedFormat = 6 Or ANamedFormat = 10 Then
			ew_FormatDateTime = Month(sDate) & EW_DATE_SEPARATOR & Day(sDate) & EW_DATE_SEPARATOR & Year(sDate)
		ElseIf ANamedFormat = 7 Or ANamedFormat = 11 Then
			ew_FormatDateTime = Day(sDate) & EW_DATE_SEPARATOR & Month(sDate) & EW_DATE_SEPARATOR & Year(sDate)
		ElseIf ANamedFormat = 99 Then
			ew_FormatDateTime = ew_ZeroPad(Hour(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2)
		ElseIf ANamedFormat = 12 Or ANamedFormat = 15 Then
			ew_FormatDateTime = Right(Year(sDate),2) & EW_DATE_SEPARATOR & Month(sDate) & EW_DATE_SEPARATOR & Day(sDate)
		ElseIf ANamedFormat = 13 Or ANamedFormat = 16 Then
			ew_FormatDateTime = Month(sDate) & EW_DATE_SEPARATOR & Day(sDate) & EW_DATE_SEPARATOR & Right(Year(sDate),2)
		ElseIf ANamedFormat = 14 Or ANamedFormat = 17 Then
			ew_FormatDateTime = Day(sDate) & EW_DATE_SEPARATOR & Month(sDate) & EW_DATE_SEPARATOR & Right(Year(sDate),2)
		Else
			ew_FormatDateTime = sDate
		End If
		If ANamedFormat >= 9 And ANamedFormat <= 11 Or ANamedFormat >= 15 And ANamedFormat <= 17 Then
				ew_FormatDateTime = ew_FormatDateTime & " " & ew_ZeroPad(Hour(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Minute(sDate), 2) & EW_TIME_SEPARATOR & ew_ZeroPad(Second(sDate), 2)
				If Len(ADate) > Len(sDate) Then ew_FormatDateTime = ew_FormatDateTime & Mid(ADate, Len(sDate)+1)
		End If
	Else
		ew_FormatDateTime = ADate
	End If
End Function

' Unformat date time based on format type
Function ew_UnFormatDateTime(ADate, ANamedFormat)
	ew_UnFormatDateTime = ADate ' Default return date
	Dim arDateTime, arDate, i
	ADate = Trim(ADate & "")
	While Instr(ADate, "  ") > 0
		ADate = Replace(ADate, "  ", " ")
	Wend
	arDateTime = Split(ADate, " ")
	If UBound(arDateTime) < 0 Then
		ew_UnFormatDateTime = ADate
		Exit Function
	End If
	If ANamedFormat = 8 Then
		If UBound(arDateTime) > 0 Then
			ANamedFormat = 1
		Else
			ANamedFormat = 2
		End If
	End If
	ANamedFormat = ew_GetFormatDateTimeID(ANamedFormat)
	arDate = Split(arDateTime(0), EW_DATE_SEPARATOR)
	If UBound(arDate) = 2 Then
		ew_UnFormatDateTime = arDateTime(0)
		If ANamedFormat = 6 Or ANamedFormat = 10 Then ' mmddyyyy
			If ew_CheckUSDate(arDateTime(0)) Then
				ew_UnFormatDateTime = arDate(2) & "/" & arDate(0) & "/" & arDate(1)
			End If
		ElseIf ANamedFormat = 7 Or ANamedFormat = 11 Then ' ddmmyyyy
			If ew_CheckEuroDate(arDateTime(0)) Then
				ew_UnFormatDateTime = arDate(2) & "/" & arDate(1) & "/" & arDate(0)
			End If
		ElseIf ANamedFormat = 5 Or ANamedFormat = 9 Then ' yyyymmdd
			If ew_CheckDate(arDateTime(0)) Then
				ew_UnFormatDateTime = arDate(0) & "/" & arDate(1) & "/" & arDate(2)
			End If
		ElseIf ANamedFormat = 12 Or ANamedFormat = 15 Then ' yymmdd
			If ew_CheckShortDate(arDateTime(0)) Then
				ew_UnFormatDateTime = ew_UnformatYear(arDate(0)) & "/" & arDate(1) & "/" & arDate(2)
			End If
		ElseIf ANamedFormat = 13 Or ANamedFormat = 16 Then ' mmddyy
			If ew_CheckShortUSDate(arDateTime(0)) Then
				ew_UnFormatDateTime = ew_UnformatYear(arDate(2)) & "/" & arDate(0) & "/" & arDate(1)
			End If
		ElseIf ANamedFormat = 14 Or ANamedFormat = 17 Then ' ddmmyy
			If ew_CheckShortEuroDate(arDateTime(0)) Then
				ew_UnFormatDateTime = ew_UnformatYear(arDate(2)) & "/" & arDate(1) & "/" & arDate(0)
			End If
		End If
		If UBound(arDateTime) > 0 Then
			For i = 1 to UBound(arDateTime)
				ew_UnFormatDateTime = ew_UnFormatDateTime & " " & Replace(arDateTime(i), EW_TIME_SEPARATOR, ":")
			Next
		End If
	Else
		If ANamedFormat = 3 Or ANamedFormat = 4 Then
			ADate = Replace(ADate, EW_TIME_SEPARATOR, ":")
		End If
		ew_UnFormatDateTime = ADate
	End If
End Function

' Unformat 2 digit year to 4 digit year
Function ew_UnformatYear(yr)
	ew_UnformatYear = yr
	If Len(yr) = 2 Then
		If IsNumeric(yr) Then
			If CLng(yr) > EW_UNFORMAT_YEAR Then
				ew_UnformatYear = "19" & yr
			Else
				ew_UnformatYear = "20" & yr
			End If
		End If
	End If
End Function

' Format currency
Function ew_FormatCurrency(Expression, NumDigitsAfterDecimal, IncludeLeadingDigit, UseParensForNegativeNumbers, GroupDigits)
	On Error Resume Next
	Dim val, pos
	Dim frac_digits, stramt
	Dim thousandsep, grpdgt, curLocale
	curLocale = GetLocale() ' Save current locale
	SetLocale("en-us") ' Set US locale
	thousandsep = EW_THOUSANDS_SEP
	grpdgt = GroupDigits

	' Check NumDigitsAfterDecimal
	frac_digits = EW_FRAC_DIGITS
	If NumDigitsAfterDecimal = -2 Then ' Use all values after decimal point
		stramt = CStr(Expression)
		If InStrRev(Expression, ".") > 0 Then
			frac_digits = Len(Expression) - InStrRev(Expression, ".")
		Else
			frac_digits = 0
		End If
	ElseIf NumDigitsAfterDecimal > -1 Then
		frac_digits = NumDigitsAfterDecimal
	End If

	' Check UseParensForNegativeNumbers
	Dim n_sign_posn, p_sign_posn
	n_sign_posn = EW_N_SIGN_POSN
	p_sign_posn = EW_P_SIGN_POSN
	If UseParensForNegativeNumbers = -1 Then
		n_sign_posn = 0
		If p_sign_posn = 0 Then
			p_sign_posn = 3
		End If
	ElseIf UseParensForNegativeNumbers = 0 Then
		If n_sign_posn = 0 Then
			n_sign_posn = 3
		End If
	End If

	' Check GroupDigits
	thousandsep = EW_MON_THOUSANDS_SEP
	If grpdgt = -1 Then
	ElseIf grpdgt = 0 Then
		thousandsep = ""
	End If
	val = FormatNumber(Abs(Expression), frac_digits, -1, 0, -1)
	pos = InStrRev(val, ".")
	If pos > 0 Then ' Has decimal place
		val = Replace(Mid(val,1,pos-1), ",", thousandsep) & EW_DECIMAL_POINT & Mid(val,pos+1)
	Else
		val = Replace(val, ",", thousandsep)
	End If

	' Check IncludeLeadingDigit
	If IncludeLeadingDigit = 0 Then
		If Left(val, 2) = "0." Then
			val = Mid(val, 2, Len(val)-1)
		End If
	End If
	Dim sign, key
	If CLng(Expression) < 0 Then
		sign = EW_NEGATIVE_SIGN
		key = EW_N_CS_PRECEDES & EW_N_SEP_BY_SPACE & n_sign_posn
	Else
		sign = EW_POSITIVE_SIGN
		key = EW_P_CS_PRECEDES & EW_P_SEP_BY_SPACE & p_sign_posn
	End If
	Select Case CStr(key)

		' No space between amount and sign
		Case "000"
			ew_FormatCurrency = "(" & val & EW_CURRENCY_SYMBOL & ")"
		Case "001"
			ew_FormatCurrency = sign & val & EW_CURRENCY_SYMBOL
		Case "002"
			ew_FormatCurrency = val & EW_CURRENCY_SYMBOL & sign
		Case "003", "004"
			ew_FormatCurrency = val & sign & EW_CURRENCY_SYMBOL

		' One space between amount and sign
		Case "010"
			ew_FormatCurrency = "(" & val & " " & EW_CURRENCY_SYMBOL & ")"
		Case "011"
			ew_FormatCurrency = sign & val & " " & EW_CURRENCY_SYMBOL
		Case "012"
			ew_FormatCurrency = val & " " & EW_CURRENCY_SYMBOL & sign
		Case "013", "014"
			ew_FormatCurrency = val & " " & sign & EW_CURRENCY_SYMBOL

		' Currency symbol is before amount
		' No space between amount and sign

		Case "100"
			ew_FormatCurrency = "(" & EW_CURRENCY_SYMBOL & val & ")"
		Case "101"
			ew_FormatCurrency = sign & EW_CURRENCY_SYMBOL & val
		Case "102"
			ew_FormatCurrency = EW_CURRENCY_SYMBOL & val & sign
		Case "103"
			ew_FormatCurrency = sign & EW_CURRENCY_SYMBOL & val
		Case "104"
			ew_FormatCurrency = EW_CURRENCY_SYMBOL & sign & val

		' One space between amount and sign
		Case "110"
			ew_FormatCurrency = "(" & EW_CURRENCY_SYMBOL & " " & val & ")"
		Case "111"
			ew_FormatCurrency = sign & EW_CURRENCY_SYMBOL & " " & val
		Case "112"
			ew_FormatCurrency = EW_CURRENCY_SYMBOL & " " & val & sign
		Case "113"
			ew_FormatCurrency = sign & EW_CURRENCY_SYMBOL & " " & val
		Case "114"
			ew_FormatCurrency = EW_CURRENCY_SYMBOL & " " & sign & val
	End Select
	If Err.Number <> 0 Then
		Err.Clear
		ew_FormatCurrency = Expression
	End If
	SetLocale(curLocale) ' Restore current locale
End Function

' Format number
Function ew_FormatNumber(Expression, NumDigitsAfterDecimal, IncludeLeadingDigit, UseParensForNegativeNumbers, GroupDigits)
	On Error Resume Next
	If Not IsNumeric(Expression) Then
		ew_FormatNumber = Expression
		Exit Function
	End If
	Dim thousandsep, grpdgt, curLocale
	curLocale = GetLocale() ' Save current locale
	SetLocale("en-us") ' Set US locale
	thousandsep = EW_THOUSANDS_SEP
	grpdgt = GroupDigits

	' Number in format 999999.999 or 999999,999
	If NumDigitsAfterDecimal = -2 Then ' Use all values after decimal point
		Dim dp

		'dp = Mid(FormatNumber(0.1,1,-1),2,1) ' Get decimal point symbol
		dp = "."
		Expression = Replace(Expression, ",", ".") ' Change 999999,99 to 999999.99
		If InStrRev(Expression, dp) > 0 Then
			NumDigitsAfterDecimal = Len(Expression) - InStrRev(Expression, dp)
		Else
			NumDigitsAfterDecimal = 0
		End If
		thousandsep = ""
		grpdgt = 0
	End If

	' Check UseParensForNegativeNumbers
	Dim n_sign_posn, p_sign_posn
	n_sign_posn = EW_N_SIGN_POSN
	p_sign_posn = EW_P_SIGN_POSN
	If UseParensForNegativeNumbers = -1 Then
		n_sign_posn = 0
		If p_sign_posn = 0 Then
			p_sign_posn = 3
		End If
	ElseIf UseParensForNegativeNumbers = 0 Then
		If n_sign_posn = 0 Then
			n_sign_posn = 3
		End If
	End If

	' Check grpdgt
	If grpdgt = -1 Then
	ElseIf grpdgt = 0 Then
		thousandsep = ""
	End If
	Dim val, pos
	If NumDigitsAfterDecimal = -2 Then thousandsep = ""
	val = FormatNumber(Abs(Expression), NumDigitsAfterDecimal, -1, 0, -1)
	pos = InStrRev(val, ".")
	If pos > 0 Then ' Has decimal place
		val = Replace(Mid(val,1,pos-1), ",", thousandsep) & EW_DECIMAL_POINT & Mid(val,pos+1)
	Else
		val = Replace(val, ",", thousandsep)
	End If

	' Check IncludeLeadingDigit
	If IncludeLeadingDigit = 0 Then
		If Left(val, 2) = "0." Then
			val = Mid(val, 2, Len(val)-1)
		End If
	End If
	Dim sign, key
	If CDbl(Expression) < 0 Then
		sign = EW_NEGATIVE_SIGN
		key = n_sign_posn
	Else
		sign = EW_POSITIVE_SIGN
		key = p_sign_posn
	End If
	Select Case CStr(key)
		Case "0"
			ew_FormatNumber = "(" & val & ")"
		Case "1", "2", "3", "4"
			ew_FormatNumber = sign & val
	End Select
	If Err.Number <> 0 Then
		Err.Clear
		ew_FormatNumber = Expression
	End If
	SetLocale(curLocale) ' Restore current locale
End Function

' Format percent
Function ew_FormatPercent(Expression, NumDigitsAfterDecimal, IncludeLeadingDigit, UseParensForNegativeNumbers, GroupDigits)
	On Error Resume Next
	Dim thousandsep, grpdgt, curLocale
	curLocale = GetLocale() ' Save current locale
	SetLocale("en-us") ' Set US locale
	thousandsep = EW_THOUSANDS_SEP
	grpdgt = GroupDigits

	' Check NumDigitsAfterDecimal
	Dim frac_digits
	frac_digits = EW_FRAC_DIGITS
	If NumDigitsAfterDecimal > -1 Then
		frac_digits = NumDigitsAfterDecimal
	End If

	' Check UseParensForNegativeNumbers
	Dim n_sign_posn, p_sign_posn
	n_sign_posn = EW_N_SIGN_POSN
	p_sign_posn = EW_P_SIGN_POSN
	If UseParensForNegativeNumbers = -1 Then
		n_sign_posn = 0
		If p_sign_posn = 0 Then
			p_sign_posn = 3
		End If
	ElseIf UseParensForNegativeNumbers = 0 Then
		If n_sign_posn = 0 Then
			n_sign_posn = 3
		End If
	End If

	' Check $groupDigits
	If grpdgt = -1 Then
	ElseIf grpdgt = 0 Then
		thousandsep = ""
	End If
	Dim val, pos
	val = FormatNumber(Abs(Expression*100), frac_digits, -1, 0, -1)
	pos = InStrRev(val, ".")
	If pos > 0 Then ' Has decimal place
		val = Replace(Mid(val,1,pos-1), ",", thousandsep) & EW_DECIMAL_POINT & Mid(val,pos+1)
	Else
		val = Replace(val, ",", thousandsep)
	End If

	' Check IncludeLeadingDigit
	If IncludeLeadingDigit = 0 Then
		If Left(val, 2) = "0." Then
			val = Mid(val, 2, Len(val)-1)
		End If
	End If
	Dim sign, key
	If CDbl(Expression) < 0 Then
		sign = EW_NEGATIVE_SIGN
		key = n_sign_posn
	Else
		sign = EW_POSITIVE_SIGN
		key = p_sign_posn
	End If
	Select Case CStr(key)
		Case "0"
			ew_FormatPercent = "(" & val & "%" & ")"
		Case "1", "2", "3", "4"
			ew_FormatPercent = sign & val & "%"
	End Select
	If Err.Number <> 0 Then
		Err.Clear
		ew_FormatPercent = FormatNumber(Expression*100, frac_digits, IncludeLeadingDigit, UseParensForNegativeNumbers, GroupDigits) & "%"
		If Err.Number <> 0 Then
			Err.Clear
			ew_FormatPercent = Expression
		End If
	End If
	SetLocale(curLocale) ' Restore current locale
End Function

' Get title
Function ew_HtmlTitle(Name)
	Dim m
	If ew_RegExMatch("\s+title\s*=\s*[\'""]([\s\S]*?)[\'""]", Name, m) Then
		ew_HtmlTitle = m(0).SubMatches(0)
	ElseIf ew_RegExMatch("\s+data-caption\s*=\s*[\'""]([\s\S]*?)[\'""]", Name, m) Then ' Match data-caption='caption'
		ew_HtmlTitle = m(0).SubMatches(0)
	Else
		ew_HtmlTitle = Name
	End If
End Function

' Get title and image
Function ew_HtmlImageAndText(Name)
	Dim title
	If ew_RegExTest("<span([^>]*)>([\s\S]*?)<\/span\s*>", Name) Or ew_RegExTest("<img([^>]*)>", Name) Then
		title = ew_HtmlTitle(Name)
	Else
		title = Name
	End If
	If title <> Name Then
		ew_HtmlImageAndText = Name & "&nbsp;" & title
	Else
		ew_HtmlImageAndText = Name
	End If
End Function

' Get key value
Function ew_GetKeyValue(Key)
	If IsNull(Key) Then
		ew_GetKeyValue = ""
	ElseIf IsArray(Key) Then
		ew_GetKeyValue = Join(Key, EW_COMPOSITE_KEY_SEPARATOR)
	Else
		ew_GetKeyValue = Key
	End If
End Function

' Convert a value to JSON value
Function ew_VarToJson(val, typ, quote)
	If typ = "" Then typ = TypeName(val)
	typ = LCase(typ)
	If IsNull(val) Then ' Null
		ew_VarToJson = "null"
	ElseIf typ = "number" Then
		ew_VarToJson = val
	ElseIf typ = "boolean" Then ' Boolean
		ew_VarToJson = ew_IIf(val, "true", "false")
	ElseIf typ = "date" Then
		ew_VarToJson = """" & val & """"
	ElseIf typ = "string" Then ' Default, encode as string
		val = Replace(val & "", "\", "\\")
		If quote <> "'" Then quote = """"
		val = Replace(val, quote, "\" & quote)
		val = Replace(val, vbCr, "\r")
		val = Replace(val, vbLf, "\n")
		val = Replace(val, vbTab, "\t")
		ew_VarToJson = quote & val & quote
	ElseIf IsDictionary(val) Then ' Note: Strings are double quoted
		ew_VarToJson = val.ToJson()
	ElseIf val & "" = "" Then ' Empty string
		ew_VarToJson = """"""
	Else
		ew_VarToJson = val
	End If
End Function

' Convert array to JSON
Function ew_ArrayToJsonEx(Ar, Offset)
	Dim arOut, arWrk, isObj, key, val, i, j, dimensions
	Set arOut = Dictionary()
	If IsArray(Ar) Then
		dimensions = ew_GetArrayDim(Ar)
		If dimensions = 1 Then ' One dimensional array
			For i = 0 To UBound(Ar)
				If i >= Offset Then
					If Not IsNull(Ar(i)) Then
						If IsDictionary(Ar(i)) Then ' Dictionary
							arOut.Push Ar(i).ToJson()
						ElseIf IsScriptingDictionary(Ar(i)) Then ' Scripting.Dictionary
							arOut.Push ew_JsonEncode(Ar(i))
						ElseIf IsArray(Ar(i)) Then ' Array
							arOut.Push ew_ArrayToJsonEx(Ar(i), 0)
						Else
							If VarType(Ar(i)) = 14 Then val = CDbl(Ar(i)) ' Convert decimal value
							arOut.Push ew_VarToJson(Ar(i), "", "")
						End If
					End If
				End If
			Next
		ElseIf dimensions = 2 Then ' Two dimensional array
			For j = 0 To UBound(Ar, 2)
				If j >= Offset Then
					Set arWrk = Dictionary()
					For i = 0 To UBound(Ar, 1)
						If TypeName(Ar(i, j)) <> "Byte()" Then ' Not BLOB
							key = Null
							val = Null
							If IsArray(Ar(i, j)) Then
								isObj = True
								If UBound(Ar(i, j)) >= 1 Then
									key = """" & ew_JsEncode2(Ar(i, j)(0)) & """: "
									val = Ar(i, j)(1)
								End If
							Else
								key = ""
								val = Ar(i, j)
							End If
							If Not IsNull(key) And Not IsNull(val) Then
								If VarType(val) = 14 Then val = CDbl(val) ' Convert decimal value
								arWrk.Push key & ew_VarToJson(val, "", "")
							End If
						End If
					Next
					If isObj Then ' Object
						arOut.Push "{" & arWrk.Join(", ") & "}"
					Else ' Array
						arOut.Push "[" & arWrk.Join(", ") & "]"
					End If
					Set arWrk = Nothing
				End If
			Next
		End If
	End If
	ew_ArrayToJsonEx = "[" & arOut.Join(", ") & "]"
	Set arOut = Nothing
End Function

' Convert array to JSON
Function ew_ArrayToJson(Ar)
	If IsArray(Ar) Then
		ew_ArrayToJson = ew_ArrayToJsonEx(Ar, 0)
	ElseIf IsDictionary(Ar) Then
		ew_ArrayToJson = Ar.ToJson()
	ElseIf IsScriptingDictionary(Ar) Then
		ew_ArrayToJson = ew_JsonEncode(Ar)
	End If
End Function

' Get array dimensions
Function ew_GetArrayDim(ByVal arr)
	ew_GetArrayDim = Null
	Dim i
	If IsArray(arr) Then
		For i = 1 To 60
		On Error Resume Next
		UBound arr, i
		If Err.Number <> 0 Then
			ew_GetArrayDim = i-1
			Exit Function
		End If
		Next
		ew_GetArrayDim = i
	End If
End Function

' Get array upper bound
Function ew_GetArrayUBound(ByVal arr, ByVal i)
	ew_GetArrayUBound = Null
	If IsArray(arr) Then
		On Error Resume Next
		Dim b
		b = UBound(arr, i)
		If Err.Number = 0 Then ew_GetArrayUBound = b
	End If
End Function

' Display field value separator
' idx (int) display field index (1|2|3)
' fld (object) field object
Function ew_ValueSeparator(dispidx, fld)
	Dim sep
	If ew_NotEmpty(fld) Then
		sep = fld.DisplayValueSeparator
	Else
		sep = ", "
	End If
	If IsArray(sep) Then
		ew_ValueSeparator = sep(dispidx-1)
	Else
		ew_ValueSeparator = sep
	End If
End Function

' Generate View Option Separator based on current option count (Multi-Select / CheckBox)
' idx (int) zero based value index
Function ew_ViewOptionSeparator(optidx)
	ew_ViewOptionSeparator = ", "
End Function

' Truncate Memo Field based on specified length, string truncated to nearest space or CrLf
Function ew_TruncateMemo(memostr, ln, removeHtml)
	Dim i, j, k
	Dim str
	If removeHtml Then
		str = ew_RemoveHtml(memostr) ' Remove Html
	Else
		str = memostr
	End If
	If Len(str) > 0 And Len(str) > ln Then
		k = 1
		Do While k > 0 And k < Len(str)
			i = InStr(k, str, " ", 1)
			j = InStr(k, str, vbCrLf, 1)
			If i < 0 And j < 0 Then ' Not able to truncate
				ew_TruncateMemo = str
				Exit Function
			Else

				' Get nearest space or CrLf
				If i > 0 And j > 0 Then
					If i < j Then
						k = i
					Else
						k = j
					End If
				ElseIf i > 0 Then
					k = i
				ElseIf j > 0 Then
					k = j
				End If

				' Get truncated text
				If k >= ln Then
					ew_TruncateMemo = Mid(str, 1, k-1) & "..."
					Exit Function
				Else
					k = k + 1
				End If
			End If
		Loop
	Else
		ew_TruncateMemo = str
	End If
End Function

' Remove Html tags from text
Function ew_RemoveHtml(str)
	ew_RemoveHtml = ew_RegExReplace("<[^>]*>", str & "", "")
End Function

' Extract JavaScript from HTML and return converted script
Function ew_ExtractScript(html, cssclass)
	Dim Match, Matches, scripts
	scripts = ""
	If ew_RegExMatch("<script([^>]*)>([\s\S]*?)<\/script\s*>", html, Matches) Then
		For Each Match In Matches
			If ew_RegExTest("(\s+type\s*=\s*[\'""]*(text|application)\/(java|ecma)script[\'""]*)|^((?!\s+type\s*=).)*$", Match.SubMatches(0)) Then ' JavaScript
				html = Replace(html, Match, "", 1, 1) ' Remove the script from HTML
				scripts = scripts & ew_HtmlElement("script", Array(Array("type", "text/html"), Array("class", cssclass)), Match.SubMatches(1)) ' Convert script type and add CSS class, if specified
			End If
		Next
	End If
	ew_ExtractScript = scripts
End Function

' Send email by template
Function ew_SendTemplateEmail(sTemplate, sSender, sRecipient, sCcEmail, sBccEmail, sSubject, arContent)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If sSender <> "" And sRecipient <> "" Then
		Dim Email, i, cnt
		Set Email = New cEmail
		Call Email.Load(sTemplate, gsLanguage)
		Call Email.ReplaceSender(sSender) ' Replace Sender
		Call Email.ReplaceRecipient(sRecipient) ' Replace Recipient
		If sCcEmail <> "" Then Call Email.AddCc(sCcEmail) ' Add Cc
		If sBccEmail <> "" Then Call Email.AddBcc(sBccEmail) ' Add Bcc
		If sSubject <> "" Then Call Email.ReplaceSubject(sSubject) ' Replace subject
		If IsArray(arContent) Then
			cnt = UBound(arContent) - 1
			If cnt Mod 2 = 1 Then cnt = cnt - 1
			For i = 0 to cnt Step 2
				Call Email.ReplaceContent(arContent(i), arContent(i+1))
			Next
		End If
		ew_SendTemplateEmail = Email.Send()
		Set Email = Nothing
	Else
		ew_SendTemplateEmail = False
	End If
End Function

' Send email (supports CDO, w3JMail and ASPEmail)
Function ew_SendEmail(sFrEmail, sToEmail, sCcEmail, sBccEmail, sSubject, sMail, sFormat, sCharset, sSmtpSecure, arAttachments, arImages)
	On Error Resume Next
	Dim i, objMail, EmailComponent, arrEmail, sEmail, dict, objConfig, sSmtpServer, iSmtpServerPort
	ew_SendEmail = False
	Err.Clear
	Set objMail = Server.CreateObject("CDO.Message")
	If ew_NotEmpty(objMail) Then

		' Set up configuration object
		Set objConfig = Server.CreateObject("CDO.Configuration")
		objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = EW_SMTP_SERVER ' cdoSMTPServer
		objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = EW_SMTP_SERVER_PORT ' cdoSMTPServerPort
		objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
		If LCase(sSmtpSecure&"") = "ssl" Then
			objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = True ' Use SSL
		End If
		If EW_SMTP_SERVER_USERNAME <> "" And EW_SMTP_SERVER_PASSWORD <> "" Then
			objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = 1 'cdoBasic (clear text)
			objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/sendusername") = EW_SMTP_SERVER_USERNAME
			objConfig.Fields("http://schemas.microsoft.com/cdo/configuration/sendpassword") = EW_SMTP_SERVER_PASSWORD
		End If
		objConfig.Fields.Update

		' Set up mail object
		objMail.From = sFrEmail
		objMail.To = Replace(sToEmail, ",", ";")
		If sCcEmail <> "" Then
			objMail.Cc = Replace(sCcEmail, ",", ";")
		End If
		If sBccEmail <> "" Then
			objMail.Bcc = Replace(sBccEmail, ",", ";")
		End If
		If sCharset <> "" Then objMail.BodyPart.Charset = sCharset
		If LCase(sFormat) = "html" Then
			objMail.HtmlBody = sMail
			If sCharset <> "" Then objMail.HtmlBodyPart.Charset = sCharset
		Else
			objMail.TextBody = sMail
			If sCharset <> "" Then objMail.TextBodyPart.Charset = sCharset
		End If
		objMail.Subject = sSubject
		If IsArray(arAttachments) Then
			For i = 0 to UBound(arAttachments)
				If Trim(arAttachments(i)) <> "" Then
					Call objMail.AddAttachment(Trim(arAttachments(i)))
				End If
			Next
		End If
		Dim imgfile, cid, objBP
		If IsArray(arImages) Then
			For i = 0 to UBound(arImages)
				imgfile = ew_UploadTempPath("", "") & arImages(i)
				cid = ew_TmpImageLnk(arImages(i), "cid")
				Set objBP = objMail.AddRelatedBodyPart(imgfile, cid, 0) ' cdoRefTypeId = 0
				objBP.Fields.Item("urn:schemas:mailheader:Content-ID") = "<" & cid & ">"
				objBP.Fields.Update
			Next
		End If
		If EW_SMTP_SERVER <> "" And LCase(EW_SMTP_SERVER) <> "localhost" Then
			Set objMail.Configuration = objConfig ' Use Configuration
			objMail.Send
		Else
			objMail.Send ' Send without Configuration
			If Err.Number <> 0 Then
				If Hex(Err.Number) = "80040220" Then ' Requires Configuration
					Set objMail.Configuration = objConfig
					Err.Clear
					objMail.Send
				End If
			End If
		End If
		Set objMail = Nothing
		Set objConfig = Nothing
		ew_SendEmail = (Err.Number = 0)
	End If

	' Failed to send email
	If Not ew_SendEmail Then
		If ew_Empty(objMail) Then
			ew_SendEmail = "Unable to create email component: (" & Err.Number & ") " & Err.Description
		Else
			ew_SendEmail = "(" & Err.Number & ") " & Err.Description
		End If
	End If
End Function

' Clean email content
Function ew_CleanEmailContent(Content)
	Content = ew_RegExReplace("class=""box ewGrid \w+""", Content, "")
	Content = Replace(Content, "class=""table-responsive ewGridMiddlePanel""", "")
	Content = Replace(Content, "table ewTable", "ewExportTable")
	Content = Replace(Content, "</td>", "</td>" & vbCrLf)
	ew_CleanEmailContent = Content
End Function

' Load content at url using XMLHTTP
Function ew_LoadContent(url)

	'On Error Resume Next
	Dim http
	Set http = Server.CreateObject("MSXML2.ServerXMLHTTP")
	http.setTimeouts 5000, 5000, 15000, 15000
	http.open "GET", url, False
	http.send
	ew_LoadContent = http.responseText
	Set http = Nothing
End Function

' Load content at url using XMLHTTP by POST
Function ew_LoadContentByPost(url, data)

	'On Error Resume Next
	Dim http
	Set http = Server.CreateObject("MSXML2.ServerXMLHTTP")
	http.setTimeouts 5000, 5000, 15000, 15000
	http.open "POST", url, False
	http.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
	http.send data
	ew_LoadContentByPost = http.responseText
	Set http = Nothing
End Function

Function ew_FieldDataType(FldType) ' Field data type
	Select Case FldType
		Case 20, 3, 2, 16, 4, 5, 131, 139, 6, 17, 18, 19, 21 ' Numeric
			ew_FieldDataType = EW_DATATYPE_NUMBER
		Case 7, 133, 135, 146 ' Date
			ew_FieldDataType = EW_DATATYPE_DATE
		Case 134, 145 ' Time
			ew_FieldDataType = EW_DATATYPE_TIME
		Case 201, 203 ' Memo
			ew_FieldDataType = EW_DATATYPE_MEMO
		Case 129, 130, 200, 202 ' String
			ew_FieldDataType = EW_DATATYPE_STRING
		Case 11 ' Boolean
			ew_FieldDataType = EW_DATATYPE_BOOLEAN
		Case 72 ' GUID
			ew_FieldDataType = EW_DATATYPE_GUID
		Case 128, 204, 205 ' Binary
			ew_FieldDataType = EW_DATATYPE_BLOB
		Case 141 ' Xml
			ew_FieldDataType = EW_DATATYPE_XML
		Case Else
			ew_FieldDataType = EW_DATATYPE_OTHER
		End Select
End Function

' Upload path
' If PhyPath is true(1), return physical path on the server;
' If PhyPath is false(0), return relative URL
Function ew_UploadPathEx(PhyPath, DestPath)
	Dim Path
	If PhyPath Then
		PhyPath = Not ew_IsRemote(DestPath) ' Not remote
		If PhyPath Then
			DestPath = Replace(DestPath, "/", EW_PATH_DELIMITER)
		End If
		Path = ew_PathCombine(ew_AppRoot(), DestPath, PhyPath)
	Else
		Path = ew_PathCombine(EW_ROOT_RELATIVE_PATH, DestPath, False)
	End If
	ew_UploadPathEx = ew_IncludeTrailingDelimiter(Path, PhyPath)
End Function

' Change the file name of the uploaded file
Function ew_UploadFileNameEx(Folder, FileName)
	Dim OutFileName

	' By default, ewUniqueFilename() is used to get an unique file name.
	' Amend your logic here

	OutFileName = ew_UniqueFilename(Folder, FileName, False)

	' Return computed output file name
	ew_UploadFileNameEx = OutFileName
End Function

' Return path of the uploaded file
' returns global upload folder, for backward compatibility only
Function ew_UploadPath(PhyPath)
	ew_UploadPath = ew_UploadPathEx(PhyPath, EW_UPLOAD_DEST_PATH)
End Function

' Change the file name of the uploaded file
' use global upload folder, for backward compatibility only
Function ew_UploadFileName(FileName)
	ew_UploadFileName = ew_UploadFileNameEx(ew_UploadPath(True), FileName)
End Function

' Generate an unique file name (filename(n).ext)
Function ew_UniqueFilename(Folder, FileName, Indexed)
	If FileName = "" Then FileName = ew_DefaultFileName()
	If FileName = "." Then
		Response.Write "Invalid file name: " & FileName
		Response.End
		Exit Function
	End If
	If Folder = "" Then
		Response.Write "Unspecified folder"
		Response.End
		Exit Function
	End If
	Dim Name, Ext, Pos
	Name = ""
	Ext = ""
	Pos = InStrRev(FileName, ".")
	If Pos = 0 Then
		Name = FileName
		Ext = ""
	Else
		Name = Mid(FileName, 1, Pos - 1)
		Ext = Mid(FileName, Pos + 1)
	End If
	Folder = ew_IncludeTrailingDelimiter(Folder, True)
	Dim fso
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If Not fso.FolderExists(Folder) Then
		If Not ew_CreateFolder(Folder) Then
			Response.Write "Folder does not exist: " & Folder
			Set fso = Nothing
			Exit Function
		End If
	End If
	Dim Suffix, Index, matches
	If Indexed Then
		If ew_RegExMatch("\((\d+)\)$", Name, matches) Then
			Index = matches(0).SubMatches(0)
			Index = Index + 1
		Else
			Index = 1
		End If
		Suffix = "(" & Index & ")"
	Else
		Index = 0
		Suffix = ""
	End If

	' Check to see if filename exists
	Name = ew_RegExReplace("\(\d+\)$", Name, "") ' Remove "(n)" at the end of the file name
	While fso.FileExists(folder & Name & Suffix & "." & Ext)
		Index = Index + 1
		Suffix = "(" & Index & ")"
	Wend
	Set fso = Nothing

	' Return unique file name
	ew_UniqueFilename = Name & Suffix & "." & Ext
End Function

' Create a default file name (yyyymmddhhmmss.bin)
Function ew_DefaultFileName()
	Dim dt
	dt = Now()
	ew_DefaultFileName = ew_ZeroPad(Year(dt), 4) & ew_ZeroPad(Month(dt), 2) &  _
		ew_ZeroPad(Day(dt), 2) & ew_ZeroPad(Hour(dt), 2) & _
		ew_ZeroPad(Minute(dt), 2) & ew_ZeroPad(Second(dt), 2) & ".bin"
End Function

' Application root
Function ew_AppRoot()
	Dim Path
	Path = ""

	'Use root relative path
	Path = ew_PathCombine(Server.MapPath("."), EW_ROOT_RELATIVE_PATH, True)
	If Path = "" Then
		Response.Write "Path of website root unknown."
		Response.End
	End If
	ew_AppRoot = ew_IncludeTrailingDelimiter(Path, True)
End Function

' Get physical path relative to application root, default: False
Function ew_ServerMapPath(Path)
	ew_ServerMapPath = ew_ServerMapPathEx(Path, False)
End Function

' Get physical path relative to application root
Function ew_ServerMapPathEx(Path, IsFile)
	If ew_IsRemote(Path) Then ' ASP
		ew_ServerMapPathEx = Path
		Exit Function
	End If
	Dim filePath, hasExtension, fileName
	filePath = Mid(Path, 1, InStrRev(Path, "/"))
	fileName = Mid(Path, InStrRev(Path, "/")+1)
	hasExtension = ew_ContainsStr(fileName, ".")
	If IsFile Or hasExtension Then ' File
		ew_ServerMapPathEx = ew_UploadPathEx(True, filePath) & fileName
	Else ' Folder
		ew_ServerMapPathEx = ew_UploadPathEx(True, Path)
	End If
End Function

' Write the paths for config/debug only
Sub ew_WritePaths()
	Response.Write "EW_RELATIVE_PATH = " & EW_RELATIVE_PATH & "<br>"
	Response.Write "EW_ROOT_RELATIVE_PATH = " & EW_ROOT_RELATIVE_PATH & "<br>"
	Response.Write "EW_UPLOAD_DEST_PATH = " & EW_UPLOAD_DEST_PATH & "<br>"
	Response.Write "ew_AppRoot() = " & ew_AppRoot() & "<br>"
	Response.Write "Request.ServerVariables(""APPL_PHYSICAL_PATH"") = " & Request.ServerVariables("APPL_PHYSICAL_PATH") & "<br>"
	Response.Write "Request.ServerVariables(""APPL_MD_PATH"") = " & Request.ServerVariables("APPL_MD_PATH") & "<br>"
	Response.Write "Server.MapPath(""."") = " & Server.MapPath(".") & "<br>"
End Sub

' Write info for config/debug only
Function ew_Info()
	Call ew_WritePaths
	Response.Write "CurrentUserName() = " & CurrentUserName() & "<br>"
	Response.Write "CurrentUserID() = " & CurrentUserID() & "<br>"
	Response.Write "CurrentParentUserID() = " & CurrentParentUserID() & "<br>"
	Response.Write "IsLoggedIn() = " & ew_IIf(IsLoggedIn(), "True", "False") & "<br>"
	Response.Write "IsAdmin() = " & ew_IIf(IsAdmin(), "True", "False") & "<br>"
	Response.Write "IsSysAdmin() = " & ew_IIf(IsSysAdmin(), "True", "False") & "<br>"
	If ew_NotEmpty(Security) Then
		Call Security.ShowUserLevelInfo
	End If
End Function

' Get refer URL
Function ew_ReferURL()
	ew_ReferURL = Request.ServerVariables("HTTP_REFERER")
End Function

' Get refer page name
Function ew_ReferPage()
	ew_ReferPage = ew_GetPageName(ew_ReferURL())
End Function

' Get script physical folder
Function ew_ScriptFolder()
	Dim folder, path, p
	folder = ""
	path = Request.ServerVariables("SCRIPT_NAME")
	p = InStrRev(path, EW_PATH_DELIMITER)
	If p > 0 Then
		folder = Mid(path, 1, p - 1)
	End If
	If folder <> "" Then
		ew_ScriptFolder = folder
	Else
		ew_ScriptFolder = "."
	End If
End Function

' Check if folder exists
Function ew_FolderExists(Folder)
	Dim fso
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	ew_FolderExists = fso.FolderExists(Folder)
	Set fso = Nothing
End Function

' Delete file
Sub ew_DeleteFile(FilePath)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Dim fso
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If FilePath <> "" And fso.FileExists(FilePath) Then
		fso.DeleteFile(FilePath)
	End If
	Set fso = Nothing
End Sub

' Rename file
Sub ew_RenameFile(OldFilePath, NewFilePath)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Dim fso
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If OldFilePath <> "" And fso.FileExists(OldFilePath) Then
		fso.MoveFile OldFilePath, NewFilePath
	End If
	Set fso = Nothing
End Sub

' Create folder
Function ew_CreateFolder(Folder)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	ew_CreateFolder = False
	If Folder & "" = "" Then ' Ignore empty folder
		ew_CreateFolder = True
		Exit Function
	End If
	Dim fso
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If Not fso.FolderExists(Folder) Then
		If ew_CreateFolder(fso.GetParentFolderName(Folder)) Then
			fso.CreateFolder(Folder)
			If Err.Number = 0 Then ew_CreateFolder = True
		End If
	Else
		ew_CreateFolder = True
	End If
	Set fso = Nothing
End Function

' Functions for Export
Function ew_ExportHeader(ExpType)
	Select Case ExpType
		Case "html", "email"
			ew_ExportHeader = "<table class=""ewExportTable"">"
			If EW_EXPORT_CSS_STYLES Then
				ew_ExportHeader = "<style>" & ew_LoadFile(EW_PROJECT_STYLESHEET_FILENAME) & "</style>" & ew_ExportHeader
			End If
		Case "word", "excel"
			ew_ExportHeader = "<table>"
		Case "csv"
			ew_ExportHeader = ""
	End Select
End Function

Function ew_ExportFooter(ExpType)
	Select Case ExpType
		Case "html", "email", "word", "excel"
			ew_ExportFooter = "</table>"
		Case "csv"
			ew_ExportFooter = ""
	End Select
End Function

Sub ew_ExportAddValue(str, val, ExpType, Attr)
	Select Case ExpType
		Case "html", "email", "word", "excel"
			str = str & "<td"
			If Attr <> "" Then str = str & " " & Attr
			str = str & ">" & val & "</td>"
		Case "csv"
			If str <> "" Then str = str & ","
			str = str & """" & Replace(val & "", """", """""") & """"
	End Select
End Sub

Function ew_ExportLine(str, ExpType, Attr)
	Select Case ExpType
		Case "html", "email", "word", "excel"
			ew_ExportLine = "<tr"
			If Attr <> "" Then ew_ExportLine = ew_ExportLine & " " & Attr
			ew_ExportLine = ew_ExportLine & ">" & str & "</tr>"
		Case "csv"
			ew_ExportLine = str & vbCrLf
	End Select
End Function

Function ew_ExportField(cap, val, ExpType, Attr)
	Dim sTD
	sTD = "<td"
	If Attr <> "" Then sTD = sTD & " " & Attr
	sTD = sTD & ">"
	ew_ExportField = "<tr>" & sTD & cap & "</td>" & sTD & val & "</td></tr>"
End Function

' Check if field exists in recordset
Function ew_FieldExistInRs(rs, fldname)
	Dim fld
	For Each fld In rs.Fields
		If fld.name = fldname then
			ew_FieldExistInRs = True
			Exit Function
		End If
	Next
	ew_FieldExistInRs = False
End Function

' Calculate field hash
Function ew_GetFldHash(value, fldtype)
	ew_GetFldHash = MD5(ew_GetFldValueAsString(value, fldtype))
End Function

' Get field value as string
Function ew_GetFldValueAsString(value, fldtype)
	If IsNull(value) Then
		ew_GetFldValueAsString = ""
	Else
		If fldtype = 128 Or fldtype = 204 Or fldtype = 205 Then ' Binary
			If EW_BLOB_FIELD_BYTE_COUNT > 0 Then
				ew_GetFldValueAsString = ew_ByteToString(LeftB(value,EW_BLOB_FIELD_BYTE_COUNT))
			Else
				ew_GetFldValueAsString = ew_ByteToString(value)
			End If
		Else

			'ew_GetFldValueAsString = CStr(value)
			ew_GetFldValueAsString = ew_ByteToString(value) ' Avoid binary characters
		End If
	End If
End Function

' Convert byte to string
Function ew_ByteToString(b)
	Dim i
	For i = 1 to LenB(b)

		'ew_ByteToString = ew_ByteToString & Chr(AscB(MidB(b,i,1)))
		ew_ByteToString = ew_ByteToString & CStr(AscB(MidB(b,i,1))) ' Just use the ascii code to avoid Chr conversion error
	Next
End Function

' Write global debug message
Function ew_DebugMsg()
	Dim msg
	msg = gsDebugMsg
	gsDebugMsg = ""
	If gsExport = "" And msg <> "" Then
		ew_DebugMsg = "<div class=""box box-danger box-solid ewDebug""><div class=""box-header with-border""><h3 class=""box-title"">Debug</h3></div><div class=""box-body"">" & msg & "</div></div>"
	End If
End Function

' Set global debug message (2nd argument not used)
Sub ew_SetDebugMsg(v)
	If EW_DEBUG_ENABLED Then
		Call ew_AddMessage(gsDebugMsg, "<p><samp>" & ew_IIf(IsEmpty(StartTimer), "", FormatNumber(ew_ElapsedTime(), 3) & ": ") & v & "</samp></p>")
	End If
End Sub

' Get global debug message
Function ew_GetDebugMsg()
	ew_GetDebugMsg = gsDebugMsg
End Function

Sub ew_SaveDebugMsg()
	If EW_DEBUG_ENABLED Then
		Session("EW_DEBUG_MESSAGE") = gsDebugMsg
	End If
End Sub

' Load global debug message
Sub ew_LoadDebugMsg()
	If EW_DEBUG_ENABLED Then
		gsDebugMsg = Session("EW_DEBUG_MESSAGE")
		Session("EW_DEBUG_MESSAGE") = ""
	End If
End Sub

' Permission denied message
Function ew_DeniedMsg()
	ew_DeniedMsg = Replace(Language.Phrase("NoPermission"), "%s", ew_ScriptName())
End Function

' Get temp upload path
Function ew_UploadTempPath(fldvar, tblvar)
	Dim path, delimiter
	If fldvar <> False Then ' Physical path
		If EW_UPLOAD_TEMP_PATH <> "" And EW_UPLOAD_TEMP_HREF_PATH <> "" Then
			path = ew_IncludeTrailingDelimiter(EW_UPLOAD_TEMP_PATH, True)
		Else
			path = ew_UploadPath(True)
		End If
		If tblvar <> "" Or fldvar <> "" Then
			path = ew_IncludeTrailingDelimiter(path & EW_UPLOAD_TEMP_FOLDER_PREFIX & Session.SessionID, True)
		End If
		If tblvar <> "" Then
			path = ew_IncludeTrailingDelimiter(path & tblvar, True)
		End If
		If fldvar <> "" Then
			path = ew_IncludeTrailingDelimiter(path & fldvar, True)
		End If
		ew_UploadTempPath = path
	Else ' Href path
		If EW_UPLOAD_TEMP_PATH <> "" And EW_UPLOAD_TEMP_HREF_PATH <> "" Then
			 ew_UploadTempPath = ew_IncludeTrailingDelimiter(EW_UPLOAD_TEMP_HREF_PATH, False)
		Else
			 ew_UploadTempPath = ew_UploadPath(False)
		End If
	End If
End Function

' Render upload field to temp path
Sub ew_RenderUploadField(fld, idx)
	Dim fldvar, fso, folder, thumbnailfolder, filename, filepath, f, data, width, height, files, i, srcfile, physical
	fldvar = ew_IIf(idx < 0, fld.FldVar, Mid(fld.FldVar, 1, 1) & idx & Mid(fld.FldVar, 2))
	folder = ew_UploadTempPath(fldvar, fld.TblVar)
	Call ew_CleanUploadTempPaths("") ' Clean all old temp folders
	Call ew_CleanPath(folder, False) ' Clean the upload folder
	Set fso = Server.Createobject("Scripting.FileSystemObject")
	If Not fso.FolderExists(folder) Then
		If Not ew_CreateFolder(folder) Then
			Response.Write "Cannot create folder: " & folder
			Response.End
		End If
	End If
	physical = Not ew_IsRemote(folder)
	thumbnailfolder = ew_PathCombine(folder, EW_UPLOAD_THUMBNAIL_FOLDER, physical)
	If Not fso.FolderExists(thumbnailfolder) Then
		If Not ew_CreateFolder(thumbnailfolder) Then
			Response.Write "Cannot create folder: " & thumbnailfolder
			Response.End
		End If
	End If
	If fld.FldDataType = EW_DATATYPE_BLOB Then ' Blob field
		If ew_NotEmpty(fld.Upload.DbValue) Then

			' Create upload file
			filename = ew_IIf(fld.Upload.FileName <> "", fld.Upload.FileName, fld.FldParm)
			f = ew_IncludeTrailingDelimiter(folder, physical) & filename
			Call ew_CreateUploadFile(f, fld.Upload.DbValue)

			' Create thumbnail file
			f = ew_IncludeTrailingDelimiter(thumbnailfolder, physical) & filename
			data = fld.Upload.DbValue
			width = EW_UPLOAD_THUMBNAIL_WIDTH
			height = EW_UPLOAD_THUMBNAIL_HEIGHT
			Call ew_ResizeBinary(data, width, height, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
			Call ew_CreateUploadFile(f, data)
			fld.Upload.FileName = filename ' Update file name
		End If
	Else ' Upload to folder
		fld.Upload.FileName = fld.Upload.DbValue ' Update file name
		If ew_NotEmpty(fld.Upload.FileName) Then
			filepath = Mid(fld.Upload.FileName, 1, InStrRev(Replace(fld.Upload.FileName, "\", "/"), "/"))
			If filepath <> "" And filepath <> "." Then
				fld.Upload.FileName = Mid(fld.Upload.FileName, Len(filepath)+1)
				filepath = ew_PathCombine(fld.UploadPath, filepath, Not ew_IsRemote(fld.UploadPath))
			Else
				filepath = fld.UploadPath
			End If

			' Create upload file
			filename = fld.Upload.FileName
			If fld.UploadMultiple Then
				files = Split(filename, EW_MULTIPLE_UPLOAD_SEPARATOR)
			Else
				ReDim files(0)
				files(0) = filename
			End If
			For i = 0 to UBound(files)
				filename = files(i)
				If filename <> "" Then
					srcfile = ew_ServerMapPath(filepath) & filename
					f = ew_IncludeTrailingDelimiter(folder, physical) & filename
					If fso.FileExists(srcfile) Then
						data = ew_LoadBinaryFile(srcfile)
						Call ew_CreateUploadFile(f, data)
					Else
						Call ew_CreateImageFromText(Language.Phrase("FileNotFound"), f)
						data = ew_LoadBinaryFile(f)
					End If

					' Create thumbnail file
					f = ew_IncludeTrailingDelimiter(thumbnailfolder, physical) & filename
					width = EW_UPLOAD_THUMBNAIL_WIDTH
					height = EW_UPLOAD_THUMBNAIL_HEIGHT
					Call ew_ResizeBinary(data, width, height, EW_THUMBNAIL_DEFAULT_INTERPOLATION)
					Call ew_CreateUploadFile(f, data)
				End If
			Next
		End If
	End If
	Set fso = Nothing
End Sub

' Write uploaded file (ASP)
Function ew_CreateUploadFile(f, data)
	Dim idx
	If InStrRev(f, ".") <= 0 Then
		Dim ext
		ext = ew_ContentExt(LeftB(data, 11))
		If ext <> "" Then
			f = f & ext
		End If
	End If
	idx = InStrRev(f, ew_IIf(ew_IsRemote(f), "/", "\"))
	ew_CreateUploadFile = ew_SaveFile(Left(f, idx), Mid(f, idx + 1), data)
End Function

' Create image from text (ASP)
Sub ew_CreateImageFromText(data, file)

	' Use hard-coded image
	' Dim fso, wrkfile
	' wrkfile = Server.MapPath(EW_IMAGE_FOLDER & "filenotfound-" & CurrentLanguageID() & ".png") ' filenotfound-<lang>.png
	' Set fso = Server.CreateObject("Scripting.FileSystemObject")
	' If Not fso.FileExists(wrkfile) Then ' Fallback
	' 	wrkfile = Server.MapPath(EW_IMAGE_FOLDER & "filenotfound.png") ' filenotfound.png
	' End If
	' If fso.FileExists(wrkfile) Then
	' 	fso.CopyFile wrkfile, file, True
	' End If
	' Set fso = Nothing
	' Use base64 string

	ew_SaveBase64StringToFile data, file ' data must be base64 string
End Sub

' Clean temp upload folders
Sub ew_CleanUploadTempPaths(sessionid)
	Dim folder, fso, oRootFolder, oFolders, oSubFolder, oFiles, oFile
	Dim subfolder, tempfolder, lastmdtime
	On Error Resume Next
	If EW_UPLOAD_TEMP_PATH <> "" Then
		folder = ew_IncludeTrailingDelimiter(EW_UPLOAD_TEMP_PATH, True)
	Else
		folder = ew_UploadPath(True)
	End If
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If fso.FolderExists(folder) Then

		' Get root folder
		Set oRootFolder = fso.GetFolder(folder)

		' Process list of subfolders
		Set oFolders = oRootFolder.SubFolders
		For Each oSubFolder In oFolders
			subfolder = oSubFolder.Name
			tempfolder = ew_PathCombine(folder, subfolder, True)
			If EW_UPLOAD_TEMP_FOLDER_PREFIX & sessionid = subfolder Then ' Clean session folder
				Call ew_CleanPath(tempfolder, True)
			ElseIf ew_StartsStr(subfolder, EW_UPLOAD_TEMP_FOLDER_PREFIX) Then
				If EW_UPLOAD_TEMP_FOLDER_PREFIX & Session.SessionID <> subfolder Then
					If ew_IsEmptyPath(tempfolder) Then ' Empty folder
						Call ew_CleanPath(tempfolder, True)
					Else ' Old folder
						lastmdtime = oSubFolder.DateLastModified
						If CLng(DateDiff("n", lastmdtime, Now)) > EW_UPLOAD_TEMP_FOLDER_TIME_LIMIT Then
							Call ew_CleanPath(tempfolder, True)
						End If
					End If
				End If
			End If
		Next
	End If
	Set fso = Nothing
End Sub

' Clean temp upload folder
Sub ew_CleanUploadTempPath(fld, idx)
	Dim fldvar, folder
	On Error Resume Next
	fldvar = ew_IIf(idx < 0, fld.FldVar, Mid(fld.FldVar, 1, 1) & idx & Mid(fld.FldVar, 2))
	folder = ew_UploadTempPath(fldvar, fld.TblVar)
	Call ew_CleanPath(folder, True) ' Clean the upload folder

	' Remove table temp folder if empty
	folder = ew_UploadTempPath("", fld.TblVar)
	If ew_IsEmptyPath(folder) Then
		Call ew_CleanPath(folder, True)
	End If

	' Remove complete temp folder if empty
	folder = ew_UploadTempPath("", "")
	If ew_IsEmptyPath(folder) Then
		Call ew_CleanPath(folder, True)
	End If
End Sub

' Clean folder
Sub ew_CleanPath(folder, delete)
	Dim fso, oRootFolder, oFolders, oSubFolder, oFiles, oFile, tempfolder
	On Error Resume Next
	folder = ew_IncludeTrailingDelimiter(folder, True)
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If fso.FolderExists(folder) Then
		Set oRootFolder = fso.GetFolder(folder)
		Set oFiles = oRootFolder.Files
		For Each oFile In oFiles
			oFile.Delete
		Next

		' Clear sub folders
		Set oFolders = oRootFolder.SubFolders
		For Each oSubFolder In oFolders
			tempfolder = ew_PathCombine(folder, oSubFolder.Name, True)
			Call ew_CleanPath(tempfolder, delete)
		Next
		If delete Then
			oRootFolder.Delete
		End If
	End If
	Set fso = Nothing
End Sub

' Check if empty folder
Function ew_IsEmptyPath(folder)
	Dim IsEmptyPath
	Dim fso, oRootFolder, oFolders, oSubFolder, oFiles, tempfolder
	On Error Resume Next
	IsEmptyPath = True
	folder = ew_IncludeTrailingDelimiter(folder, True)
	Set fso = Server.Createobject("Scripting.FileSystemObject")
	If fso.FolderExists(folder) Then
		Set oRootFolder = fso.GetFolder(folder)
		Set oFiles = oRootFolder.Files
		If oFiles.Count > 0 Then
			ew_IsEmptyPath = False ' No need to check further
			Set fso = Nothing
			Exit Function
		End If
		Set oFolders = oRootFolder.SubFolders
		For Each oSubFolder In oFolders
			tempfolder = ew_PathCombine(folder, oSubFolder.Name, True)
			IsEmptyPath = ew_IsEmptyPath(tempfolder)
			If Not IsEmptyPath Then
				ew_IsEmptyPath = False ' No need to check further
				Set fso = Nothing
				Exit Function
			End If
		Next
	Else
		IsEmptyPath = False
	End If
	ew_IsEmptyPath = IsEmptyPath
End Function

' Get file count
Function ew_FolderFileCount(folder)
	Dim fso, oFolder
	On Error Resume Next
	ew_FolderFileCount = 0
	folder = ew_IncludeTrailingDelimiter(folder, True)
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If fso.FolderExists(folder) Then
		Set oFolder = fso.GetFolder(folder)
		ew_FolderFileCount = oFolder.Files.Count
	End If
	Set fso = Nothing
End Function

' Encrypt password ' ASP
Function ew_EncryptPassword(input)
	If EW_PASSWORD_HASH Then
		ew_EncryptPassword = ew_BlowfishEncrypt(input, EW_PROJECT_ID)
	Else
		ew_EncryptPassword = MD5(input)
	End If
End Function

' Compare password ' ASP
Function ew_ComparePassword(pwd, input, encrypted)
	If encrypted Then
		ew_ComparePassword = (pwd = input)
	ElseIf EW_CASE_SENSITIVE_PASSWORD Then
		If EW_ENCRYPTED_PASSWORD Then
			ew_ComparePassword = (pwd = ew_EncryptPassword(input))
		Else
			ew_ComparePassword = (pwd = input)
		End If
	Else
		If EW_ENCRYPTED_PASSWORD Then
			ew_ComparePassword = (pwd = ew_EncryptPassword(LCase(input)))
		Else
			ew_ComparePassword = (LCase(pwd) = LCase(input))
		End If
	End If
End Function

' Check empty string
Function ew_EmptyStr(value)
	Dim str
	str = value & ""
	str = Replace(str, "&nbsp;", "")
	ew_EmptyStr = (Trim(str) = "")
End Function

' Check empty value
Function ew_Empty(value)
	If IsNull(value) Then
		ew_Empty = True
		Exit Function
	ElseIf IsObject(value) Then ' Note: Check IsObject before IsEmpty since IsObject(value) can still be IsEmpty(value).
		ew_Empty = value Is Nothing
		Exit Function
	ElseIf IsEmpty(value) Then
		ew_Empty = True
		Exit Function
	ElseIf IsArray(value) Then
		Dim dimension
		dimension = ew_GetArrayDim(value)
		If dimension = 0 Then ' Dim value()
			ew_Empty = True
		ElseIf dimension = 1 And UBound(value) = -1 Then ' value = Array()
			ew_Empty = True
		Else
			ew_Empty = False
		End If
		Exit Function
	ElseIf TypeName(value) <> "Unknown" Then
		ew_Empty = (value & "" = "")
		Exit Function
	End If
	ew_Empty = False
End Function

' Check not empty value
Function ew_NotEmpty(value)
	ew_NotEmpty = Not ew_Empty(value)
End Function

' Get profile object
Function Profile()
	If ew_NotEmpty(UserProfile) Then
		Set Profile = UserProfile
	Else
		Set Profile = New cUserProfile
	End If
End Function

' Get hex values
Function ew_BinToHex(vStream)
	Dim reVal, i
	reVal = 0
	For i = 1 To LenB(vStream)
		reVal = reVal * 256 + AscB(MidB(vStream, i, 1))
	Next
	ew_BinToHex = Hex(reVal)
End Function

' Check if file exists
Function ew_FileExists(Folder, File)
	Dim fso
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	ew_FileExists = fso.FileExists(ew_IncludeTrailingDelimiter(Folder, True) & File)
	Set fso = Nothing
End Function

' Load file content (both ASCII and UTF-8)
Function ew_LoadFile(FileName)
	On Error Resume Next
	Dim fso, FilePath
	ew_LoadFile = ""
	Set fso = Server.CreateObject("Scripting.FileSystemObject")
	If Trim(FileName) <> "" Then
		If fso.FileExists(FileName) Then
			FilePath = FileName
		Else
			FilePath = Server.MapPath(FileName)
		End If
		If fso.FileExists(FilePath) Then
			If ew_GetFileCharset(FilePath) = "UTF-8" Then
				ew_LoadFile = ew_LoadUTF8File(FilePath)
			Else
				Dim iFile, iData
				Set iFile = fso.GetFile(FilePath)
				Set iData = iFile.OpenAsTextStream
				ew_LoadFile = iData.ReadAll
				iData.Close
				Set iData = Nothing
				Set iFile = Nothing
			End If
		End If
	End If
	Set fso = Nothing
End Function

' Open UTF8 file
Function ew_LoadUTF8File(FilePath)
	On Error Resume Next
	Dim objStream
	Set objStream = Server.CreateObject("ADODB.Stream")
	With objStream
		.Type = 2
		.Mode = 3
		.Open
		.CharSet = "UTF-8"
		.LoadFromFile FilePath
		ew_LoadUTF8File = .ReadText
		.Close
	End With
End Function

' Get file charset (UTF-8 and UNICODE)
Function ew_GetFileCharset(FilePath)
	On Error Resume Next
	Dim objStream, LoadBytes
	Set objStream = Server.CreateObject("ADODB.Stream")
	With objStream
		.Type = 1
		.Mode = 3
		.Open
		.LoadFromFile FilePath
		LoadBytes = .Read(3) ' Get first 3 bytes as BOM
		.Close
	End With
	Set objStream = Nothing
	Dim FileCharset, strFileHead

	' Get hex values
	strFileHead = ew_BinToHex(LoadBytes)

	' UTF-8
	If strFileHead = "EFBBBF" Then
		ew_GetFileCharset = "UTF-8" ' UTF-8
	Else
		ew_GetFileCharset = "" ' Non UTF-8
	End If
End Function

' Decode json string
Function ew_JsonDecode(JsonStr)
	Dim str
	str = JsonStr & ""
	ew_JsonDecode = Null
	If str <> "" Then
		Dim json, idx
		Set json = New cJson
		idx = json.SkipWhitespace(str, 1)
		If Mid(str, idx, 1) = "{" Then
			Set ew_JsonDecode = json.Decode(str)
		Else
			ew_JsonDecode = json.Decode(str)
		End If
		Set json = Nothing
	End If
End Function

' Encode object as json string
Function ew_JsonEncode(obj)
	ew_JsonEncode = "null"
	If Not IsNull(obj) Then
		Dim json
		Set json = New cJson
		ew_JsonEncode = json.Encode(obj)
		Set json = Nothing
	End If
End Function

' JSON class
' adapted from: http://demon.tw/my-work/vbs-json.html
Class cJson

	Private Whitespace, NumberRegex, StringChunk

	Private b, f, r, n, t

	Private Sub Class_Initialize()
		Whitespace = " " & vbTab & vbCr & vbLf
		b = ChrW(8)
		f = vbFormFeed
		r = vbCr
		n = vbLf
		t = vbTab
		Set NumberRegex = New RegExp
		NumberRegex.Pattern = "(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?"
		NumberRegex.Global = False
		NumberRegex.MultiLine = True
		NumberRegex.IgnoreCase = True
		Set StringChunk = New RegExp
		StringChunk.Pattern = "([\s\S]*?)([""\\\x00-\x1f])"
		StringChunk.Global = False
		StringChunk.MultiLine = True
		StringChunk.IgnoreCase = True
	End Sub

	'Return a JSON string representation of a VBScript data structure
	'Supports the following objects and types
	'+-------------------+---------------+
	'| VBScript          | JSON          |
	'+===================+===============+
	'| Dictionary        | object        |
	'+-------------------+---------------+
	'| Array             | array         |
	'+-------------------+---------------+
	'| String            | string        |
	'+-------------------+---------------+
	'| Number            | number        |
	'+-------------------+---------------+
	'| True              | true          |
	'+-------------------+---------------+
	'| False             | false         |
	'+-------------------+---------------+
	'| Null              | null          |
	'+-------------------+---------------+
	Public Function Encode(ByRef obj)
		Dim buf, i, c, g

		' Handle JScript Dictionary '***
		If IsDictionary(obj) Then
			Encode = obj.ToJson()
			Exit Function
		End If
		Set buf = Server.CreateObject("Scripting.Dictionary")
		Select Case VarType(obj)
			Case vbNull
				buf.Add buf.Count, "null"
			Case vbBoolean
				If obj Then
					buf.Add buf.Count, "true"
				Else
					buf.Add buf.Count, "false"
				End If
			Case vbInteger, vbLong, vbSingle, vbDouble
				buf.Add buf.Count, obj
			Case vbString
				buf.Add buf.Count, """"
				For i = 1 To Len(obj)
					c = Mid(obj, i, 1)
					Select Case c
						Case """" buf.Add buf.Count, "\"""
						Case "\"  buf.Add buf.Count, "\\"
						Case "/"  buf.Add buf.Count, "/"
						Case b    buf.Add buf.Count, "\b"
						Case f    buf.Add buf.Count, "\f"
						Case r    buf.Add buf.Count, "\r"
						Case n    buf.Add buf.Count, "\n"
						Case t    buf.Add buf.Count, "\t"
						Case Else
							If AscW(c) >= 0 And AscW(c) <= 31 Then
								c = Right("0" & Hex(AscW(c)), 2)
								buf.Add buf.Count, "\u00" & c
							Else
								buf.Add buf.Count, c
							End If
					End Select
				Next
				buf.Add buf.Count, """"
			Case vbArray + vbVariant
				g = True
				buf.Add buf.Count, "["
				For Each i In obj
					If TypeName(i) <> "Byte()" Then  '*** Not BLOB
						If g Then g = False Else buf.Add buf.Count, ","
						buf.Add buf.Count, Encode(i)
					End If
				Next
				buf.Add buf.Count, "]"
			Case vbArray + vbByte  '*** BLOB
				buf.Add buf.Count, "[Binary]"
			Case vbObject
				If TypeName(obj) = "Dictionary" Then
					g = True
					buf.Add buf.Count, "{"
					For Each i In obj
						If g Then g = False Else buf.Add buf.Count, ","
						buf.Add buf.Count, """" & i & """" & ":" & Encode(obj(i))
					Next
					buf.Add buf.Count, "}"
				Else

					'Err.Raise 8732,,"None dictionary object" '***
					buf.Add buf.Count, "false" '***
				End If
			Case Else
				buf.Add buf.Count, """" & CStr(obj) & """"
		End Select
		Encode = Join(buf.Items, "")
	End Function

	'Return the VBScript representation of ``str(``
	'Performs the following translations in decoding
	'+---------------+-------------------+
	'| JSON          | VBScript          |
	'+===============+===================+
	'| object        | Dictionary        |
	'+---------------+-------------------+
	'| array         | Array             |
	'+---------------+-------------------+
	'| string        | String            |
	'+---------------+-------------------+
	'| number        | Double            |
	'+---------------+-------------------+
	'| true          | True              |
	'+---------------+-------------------+
	'| false         | False             |
	'+---------------+-------------------+
	'| null          | Null              |
	'+---------------+-------------------+
	Public Function Decode(ByRef str)
		Dim idx
		idx = SkipWhitespace(str, 1)
		If Mid(str, idx, 1) = "{" Then
			Set Decode = ScanOnce(str, 1)
		Else
			Decode = ScanOnce(str, 1)
		End If
	End Function

	Private Function ScanOnce(ByRef str, ByRef idx)
		Dim c, ms
		idx = SkipWhitespace(str, idx)
		c = Mid(str, idx, 1)
		If c = "{" Then
			idx = idx + 1
			Set ScanOnce = ParseObject(str, idx)
			Exit Function
		ElseIf c = "[" Then
			idx = idx + 1
			ScanOnce = ParseArray(str, idx)
			Exit Function
		ElseIf c = """" Then
			idx = idx + 1
			ScanOnce = ParseString(str, idx)
			Exit Function
		ElseIf c = "n" And StrComp("null", Mid(str, idx, 4)) = 0 Then
			idx = idx + 4
			ScanOnce = Null
			Exit Function
		ElseIf c = "t" And StrComp("true", Mid(str, idx, 4)) = 0 Then
			idx = idx + 4
			ScanOnce = True
			Exit Function
		ElseIf c = "f" And StrComp("false", Mid(str, idx, 5)) = 0 Then
			idx = idx + 5
			ScanOnce = False
			Exit Function
		End If
		Set ms = NumberRegex.Execute(Mid(str, idx))
		If ms.Count = 1 Then
			idx = idx + ms(0).Length
			ScanOnce = CDbl(ms(0))
			Exit Function
		End If

		'Err.Raise 8732,,"No JSON object could be ScanOnced"
		'Err.Raise 8732,,""

	End Function

	Private Function ParseObject(ByRef str, ByRef idx)
		Dim c, key, value
		Set ParseObject = Server.CreateObject("Scripting.Dictionary")
		idx = SkipWhitespace(str, idx)
		c = Mid(str, idx, 1)
		If c = "}" Then
			idx = idx + 1 '***
			Exit Function
		ElseIf c <> """" Then
			Err.Raise 8732,,"Expecting property name"
		End If
		idx = idx + 1
		Do
			key = ParseString(str, idx)
			idx = SkipWhitespace(str, idx)
			If Mid(str, idx, 1) <> ":" Then
				Err.Raise 8732,,"Expecting : delimiter"
			End If
			idx = SkipWhitespace(str, idx + 1)
			If Mid(str, idx, 1) = "{" Then
				Set value = ScanOnce(str, idx)
			Else
				value = ScanOnce(str, idx)
			End If
			ParseObject.Add key, value
			idx = SkipWhitespace(str, idx)
			c = Mid(str, idx, 1)
			If c = "}" Then
				Exit Do
			ElseIf c <> "," Then
				Err.Raise 8732,,"Expecting , delimiter"
			End If
			idx = SkipWhitespace(str, idx + 1)
			c = Mid(str, idx, 1)
			If c <> """" Then
				Err.Raise 8732,,"Expecting property name"
			End If
			idx = idx + 1
		Loop
		idx = idx + 1
	End Function

	Private Function ParseArray(ByRef str, ByRef idx)
		Dim c, values, value
		Set values = Server.CreateObject("Scripting.Dictionary")
		idx = SkipWhitespace(str, idx)
		c = Mid(str, idx, 1)
		If c = "]" Then
			idx = idx + 1 '***
			ParseArray = values.Items
			Exit Function
		End If
		Do
			idx = SkipWhitespace(str, idx)
			If Mid(str, idx, 1) = "{" Then
				Set value = ScanOnce(str, idx)
			Else
				value = ScanOnce(str, idx)
			End If
			values.Add values.Count, value
			idx = SkipWhitespace(str, idx)
			c = Mid(str, idx, 1)
			If c = "]" Then
				Exit Do
			ElseIf c <> "," Then
				Err.Raise 8732,,"Expecting , delimiter"
			End If
			idx = idx + 1
		Loop
		idx = idx + 1
		ParseArray = values.Items
	End Function

	Private Function ParseString(ByRef str, ByRef idx)
		Dim chunks, content, terminator, ms, esc, char
		Set chunks = Server.CreateObject("Scripting.Dictionary")
		Do
			Set ms = StringChunk.Execute(Mid(str, idx))
			If ms.Count = 0 Then
				Err.Raise 8732,,"Unterminated string starting"
			End If
			content = ms(0).Submatches(0)
			terminator = ms(0).Submatches(1)
			If Len(content) > 0 Then
				chunks.Add chunks.Count, content
			End If
			idx = idx + ms(0).Length
			If terminator = """" Then
				Exit Do
			ElseIf terminator <> "\" Then
				Err.Raise 8732,,"Invalid control character"
			End If
			esc = Mid(str, idx, 1)
			If esc <> "u" Then
				Select Case esc
					Case """" char = """"
					Case "\"  char = "\"
					Case "/"  char = "/"
					Case "b"  char = b
					Case "f"  char = f
					Case "n"  char = n
					Case "r"  char = r
					Case "t"  char = t
					Case Else Err.Raise 8732,,"Invalid escape"
				End Select
				idx = idx + 1
			Else
				char = ChrW("&H" & Mid(str, idx + 1, 4))
				idx = idx + 5
			End If
			chunks.Add chunks.Count, char
		Loop
		ParseString = Join(chunks.Items, "")
	End Function

	Public Function SkipWhitespace(ByRef str, ByVal idx) '***
		Do While idx <= Len(str) And _
			InStr(Whitespace, Mid(str, idx, 1)) > 0
			idx = idx + 1
		Loop
		SkipWhitespace = idx
	End Function
End Class

' Menu class
Class cMenu

	Public Id

	Public IsRoot

	Public FollowLink ' For sidebar menu

	Public Accordion ' For sidebar menu

	Public UseSubmenuForRootHeader

	Public Items

	' Init
	Private Sub Class_Initialize()
		IsRoot = False
		FollowLink = True
		Accordion = True
		UseSubmenuForRootHeader = EW_USE_SUBMENU_FOR_ROOT_HEADER
		Set Items = Dictionary()
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		Set Items = Nothing
	End Sub

	' Get menu item count
	Function Count()
		Count = Items.Count()
	End Function

	' Move item to position
	Sub MoveItem(Text, Pos)
		Dim i, oldpos, item
		If Pos < 0 Then
			Pos = 0
		ElseIf Pos >= Items.Count() Then
			Pos = Items.Count() - 1
		End If
		oldpos = -1
		For i = 0 To Items.Count() -1
			Set item = Items.Get(i)
			If item.Text = Text Then
				oldpos = i
				Exit For
			End If
		Next
		If oldpos <> Pos Then
			Items.ChangeKey oldpos, Items.Count() ' Move out of position first
			If oldpos < Pos Then ' Shuffle backward
				For i = oldpos+1 to Pos
					Items.ChangeKey i, i-1
				Next
			Else ' Shuffle forward
				For i = oldpos-1 to Pos Step -1
					Items.ChangeKey i, i+1
				Next
			End If
			Items.ChangeKey Items.Count(), Pos ' Move to position
		End If
	End Sub

	' Create a menu item
	Function NewMenuItem(id, name, text, url, parentid, allowed, isHeader, isCustomUrl, icon, label)
		Set NewMenuItem = New cMenuItem
		NewMenuItem.Id = id
		NewMenuItem.Name = name
		NewMenuItem.Text = text
		NewMenuItem.Url = url
		NewMenuItem.ParentId = parentid
		NewMenuItem.Allowed = allowed
		NewMenuItem.IsHeader = isHeader
		NewMenuItem.IsCustomUrl = isCustomUrl
		NewMenuItem.Icon = icon
		NewMenuItem.Label = label
	End Function

	Sub AddMenuItem(id, name, text, url, parentid, allowed, isHeader, isCustomUrl, icon, label)
		Dim item, oParentMenu
		Set item = NewMenuItem(id, name, text, url, parentid, allowed, isHeader, isCustomUrl, icon, label)
		If Not MenuItem_Adding(item) Then
			Exit Sub
		End If
		If item.ParentId < 0 Then
			Call AddItem(item)
		Else
			Set oParentMenu = FindItem(item.ParentId)
			If ew_NotEmpty(oParentMenu) Then
				Call oParentMenu.AddItem(item)
			End If
		End If
	End Sub

	' Add item to internal dictionary
	Sub AddItem(item)
		Call Items.Add(Items.Count(), item)
	End Sub

	' Clear all menu items
	Sub Clear()
		Items.Clear()
	End Sub

	' Find item
	Function FindItem(id)
		Dim i, item, subm
		Set FindItem = Nothing
		For i = 0 To Items.Count() -1
			Set item = Items.Get(i)
			If item.Id = id Then
				Set FindItem = item
				Exit Function
			ElseIf Not IsEmpty(item.SubMenu) Then
				Set subm = item.SubMenu
				Set FindItem = subm.FindItem(id)
				If ew_NotEmpty(FindItem) Then Exit Function
			End If
		Next
	End Function

	' Find item by menu text
	Function FindItemByText(txt)
		Dim i, item, subm
		Set FindItemByText = Nothing
		For i = 0 To Items.Count() -1
			Set item = Items.Get(i)
			If item.Text = txt Then
				Set FindItemByText = item
				Exit Function
			ElseIf Not IsEmpty(item.SubMenu) Then
				Set subm = item.SubMenu
				Set FindItemByText = subm.FindItemByText(txt)
				If ew_NotEmpty(FindItemByText) Then Exit Function
			End If
		Next
	End Function

	' Check if a menu item should be shown
	Function RenderItem(item)
		Dim i, subitem, subm
		If Not IsEmpty(item.SubMenu) Then
			Set subm = item.SubMenu
			For i = 0 To subm.Items.Count() - 1
				Set subitem = subm.Items.Get(i)
				If subm.RenderItem(subitem) Then
					RenderItem = True
					Exit Function
				End If
			Next
		End If
		RenderItem = (item.Allowed And item.Url <> "")
	End Function

	' Check if a menu item should be opened
	Function IsItemOpened(item)
		Dim i, subitem, subm
		If Not IsEmpty(item.SubMenu) Then
			Set subm = item.SubMenu
			For i = 0 To subm.Items.Count() - 1
				Set subitem = subm.Items.Get(i)
				If subm.IsItemOpened(subitem) Then
					IsItemOpened = True
					Exit Function
				End If
			Next
		End If
		IsItemOpened = item.Active
	End Function

	' Check if this menu should be rendered
	Function RenderMenu()
		Dim i, item
		For i = 0 To Items.Count() - 1
			Set item = Items.Get(i)
			If RenderItem(item) Then
				RenderMenu = True
				Exit Function
			End If
		Next
		RenderMenu = False
	End Function

	' Check if this menu should be opened
	Function IsOpened()
		Dim i, item
		For i = 0 To Items.Count() - 1
			Set item = Items.Get(i)
			If IsItemOpened(item) Then
				IsOpened = True
				Exit Function
			End If
		Next
		IsOpened = False
	End Function

	' Render the menu as array of object
	Function Render()
		Dim i, j, item, subm, subitem, url
		If IsRoot Then Call Menu_Rendering(Me)
		If Not RenderMenu() Then
			Render = "null"
			Exit Function
		End If
		Dim menu
		Set menu = Dictionary()
		url = Mid(ew_CurrentUrl(), InStrRev(ew_CurrentUrl(), "/") + 1)
		For i = 0 To Items.Count() - 1
			Set item = Items.Get(i)
			If (RenderItem(item)) Then
				If item.IsHeader And (Not IsRoot Or Not UseSubmenuForRootHeader) Then ' Group title (Header)
					Call menu.Push(item.Render(False))
					If Not IsEmpty(item.SubMenu) Then
						Set subm = item.SubMenu
						For j = 0 to subm.Items.Count() - 1
							Set subitem = subm.Items.Get(j)
							If RenderItem(subitem) Then
								If Not subitem.IsCustomUrl And ew_CurrentPage() = ew_GetPageName(subitem.Url) Or subitem.IsCustomUrl And url = subitem.Url Then
									subitem.Active = True
									subitem.Url = "#"
								End If
								Call menu.Push(subitem.Render(True))
							End If
						Next
					End If
				Else
					If Not item.IsCustomUrl And ew_CurrentPage() = ew_GetPageName(item.Url) Or item.IsCustomUrl And url = item.Url Then
						item.Active = True
						item.Url = "#"
					End If
					Call menu.Push(item.Render(True))
				End If
			End If
		Next
		If IsRoot Then
			Call Menu_Rendered(Me)
		End If
		If menu.Count() > 0 Then
			Render = "[" & menu.Join(",") & "]"
		Else
			Render = "null"
		End If
		Set menu = Nothing
	End Function

	' Returns the menu as JSON
	Function ToJson()
		ToJson = "{""items"":" & Render() & ", ""followLink"":" & ew_VarToJson(FollowLink, "boolean", "") & ", ""accordion"":" & ew_VarToJson(Accordion, "boolean" ,"") & "}"
	End Function

	' Returns the menu as script tag
	Function ToScript()
		ToScript = "<script type=""text/javascript"">ewVar.menu = " & RootMenu.ToJson() & ";</script>"
	End Function
End Class

' Menu item class
Class cMenuItem

	Public Id

	Public Name

	Public Text

	Public Url

	Public ParentId

	Public SubMenu ' Data type = cMenu

	Public Allowed

	Public Target

	Public IsHeader

	Public IsCustomUrl

	Public Href

	Public Active

	Public Icon

	Public Attrs

	Public Label ' HTML to be placed in pull-right-container (for vertical menu only)

	Private Sub Class_Initialize()
		Url = ""
		ParentId = -1
		Allowed = True
		IsHeader = False
		IsCustomUrl = False
		Active = False
	End Sub

	Private Sub Class_Terminate()
		Set SubMenu = Nothing
	End Sub

	Sub AddItem(item) ' Add submenu item
		If IsEmpty(SubMenu) Then
			Set SubMenu = New cMenu
			SubMenu.Id = Id
		End If
		Call SubMenu.AddItem(item)
	End Sub

	' Render
	Function Render(deep)
		Dim sUrl, sClass, cls, dict, ar, wrk
		sUrl = ew_GetUrl(Url)
		If ew_IsMobile() And Not IsCustomUrl Then
			sUrl = Replace(sUrl, "#", ew_IIf(ew_ContainsStr(sUrl, "?"), "&", "?") & "hash=")
		End If
		If Trim(sUrl) = "" Then
			sUrl = "#"
		End If
		Href = sUrl
		Attrs = Trim(Attrs)
		If Attrs <> "" Then
			Attrs = " " & Attrs
		End If
		sClass = Trim(Icon)
		If sClass <> "" Then
			ar = Split(sClass, " ")
			Set dict = Dictionary(ar)
			For Each cls In ar
				If (ew_StartsStr(cls, "fa-") And Not dict.ContainsValue("fa")) Then
					dict.Push "fa"
				ElseIf (ew_StartsStr(cls, "glyphicon-") And Not dict.ContainsValue("glyphicon")) Then
					dict.Push "glyphicon"
				End If
			Next
			Icon = dict.Join(" ")
			Set dict = Nothing
		End If
		wrk = "{"
		wrk = wrk & "id:" & ew_VarToJson(Id, "number", "") & ","
		wrk = wrk & "name:" & ew_VarToJson(Name, "string", """") & ","
		wrk = wrk & "text:" & ew_VarToJson(Text, "string", """") & ","
		wrk = wrk & "parentId:" & ew_VarToJson(ParentId, "number", "") & ","
		wrk = wrk & "target:" & ew_VarToJson(Target, "string", """") & ","
		wrk = wrk & "isHeader:" & ew_VarToJson(IsHeader, "boolean", "") & ","
		wrk = wrk & "href:" & ew_VarToJson(Href, "string", """") & ","
		wrk = wrk & "active:" & ew_VarToJson(Active, "boolean", "") & ","
		wrk = wrk & "icon:" & ew_VarToJson(Icon, "string", """") & ","
		wrk = wrk & "attrs:" & ew_VarToJson(Attrs, "string", """") & ","
		wrk = wrk & "label:" & ew_VarToJson(Label, "string", """") & ","
		If deep And ew_NotEmpty(SubMenu) Then
			wrk = wrk & "items:" & SubMenu.Render() & ","
			wrk = wrk & "open:" & ew_VarToJson(SubMenu.IsOpened, "boolean", "")
		Else
			wrk = wrk & "items:null,"
			wrk = wrk & "open:false"
		End If
		wrk = wrk & "}"
		Render = wrk
	End Function

	Function AsString()
		Dim dict
		Set dict = Dictionary()
		Call dict.Add("Id", Id) ' Id
		Call dict.Add("Name", Name) ' Name
		Call dict.Add("Text", Text) ' Text
		Call dict.Add("Url", Url) ' Url
		Call dict.Add("ParentId", ParentId) ' ParentId
		Call dict.Add("Allowed", Allowed) ' Allowed
		Call dict.Add("Target", Target) ' Target
		Call dict.Add("IsHeader", IsHeader) ' IsHeader
		Call dict.Add("IsCustomUrl", IsCustomUrl) ' IsCustomUrl
		Call dict.Add("Href", Href) ' Href
		Call dict.Add("Active", Active) ' Active
		Call dict.Add("Icon", Icon) ' Icon
		Call dict.Add("Attrs", Attrs) ' Attrs
		Call dict.Add("Label", Label) ' Send secure option
		AsString = dict.ToJson()
		Set dict = Nothing	
	End Function
End Class

' Menu Rendering event
Function MenuItem_Adding(Item)

	'Response.Write Item.AsString
	' Return False if menu item not allowed

	MenuItem_Adding = True
End Function

' Menu Rendering event
Sub Menu_Rendering(Menu)

	' Change menu items here
End Sub

' Menu Rendered event
Sub Menu_Rendered(Menu)

	' Clean up here
End Sub

'
'  Language class (begin)
'
Class cLanguage
	Dim LanguageId
	Dim objDOM
	Dim objDict
	Dim LanguageFolder
	Dim Template ' JsRender template
	Dim Method
	Dim Target
	Dim LanguageType
	Dim Key

	' Class initialize
	Private Sub Class_Initialize()
		LanguageFolder = EW_RELATIVE_PATH & EW_LANGUAGE_FOLDER
		Method = "prependTo" ' JsRender template method
		Target = ".navbar-custom-menu .nav" ' JsRender template target
		LanguageType = "LI" ' LI/DROPDOWN (for used with top Navbar) or SELECT/RADIO (NOT for used with top Navbar)
	End Sub

	' Load phrases
	Public Sub LoadPhrases()

		' Set up file list
		Call LoadFileList()

		' Set up language id
		If IsEmpty(LanguageId) Then
			If Request.QueryString("language") <> "" Then
				LanguageId = Request.QueryString("language")
				Session(EW_SESSION_LANGUAGE_ID) = LanguageId
			ElseIf Session(EW_SESSION_LANGUAGE_ID) <> "" Then
				LanguageId = Session(EW_SESSION_LANGUAGE_ID)
			Else
				LanguageId = EW_LANGUAGE_DEFAULT_ID
			End If
		End If
		gsLanguage = LanguageId
		If EW_USE_DOM_XML Then
			Set objDOM = ew_CreateXmlDom()
			objDOM.async = False
		Else
			Set objDict = Server.CreateObject("Scripting.Dictionary")
		End If

		' Load current language
		Load(LanguageId)

		' Call Language Load event
		Call Language_Load
	End Sub

	' Terminate
	Private Sub Class_Terminate()
		If EW_USE_DOM_XML Then
			Set objDOM = Nothing
		Else
			Set objDict = Nothing
		End If
	End Sub

	' Load language file list
	Private Sub LoadFileList()
		If IsArray(EW_LANGUAGE_FILE) Then
			For i = 0 to UBound(EW_LANGUAGE_FILE)
				EW_LANGUAGE_FILE(i)(1) = LoadFileDesc(Server.MapPath(LanguageFolder & EW_LANGUAGE_FILE(i)(2)))
			Next
		End If
	End Sub

	' Load language file description
	Private Function LoadFileDesc(File)
		LoadFileDesc = ""
		Set objDOM = ew_CreateXmlDom()
		objDOM.async = False
		objDOM.Load(File)
		If objDOM.ParseError.ErrorCode = 0 Then
			LoadFileDesc = GetNodeAtt(objDOM.documentElement, "desc")
		End If
	End Function

	' Load language file
	Private Sub Load(id)
		Dim sFileName, result
		sFileName = GetFileName(id)
		If sFileName = "" Then
			sFileName = GetFileName(EW_LANGUAGE_DEFAULT_ID)
		End If
		If sFileName = "" Then Exit Sub
		If EW_USE_DOM_XML Then
			objDOM.Load(sFileName)
			If objDOM.ParseError.ErrorCode = 0 Then
				objDOM.setProperty "SelectionLanguage", "XPath"
			End If
		Else
			Call XmlToCollection(sFileName)
		End If

		' Set up locale settings from locale file
		Set result = ew_LocaleConv()
		If result.Exists("decimal_point") Then EW_DECIMAL_POINT = result("decimal_point")
		If result.Exists("thousands_sep") Then EW_THOUSANDS_SEP = result("thousands_sep")
		If result.Exists("mon_decimal_point") Then EW_MON_DECIMAL_POINT = result("mon_decimal_point")
		If result.Exists("mon_thousands_sep") Then EW_MON_THOUSANDS_SEP = result("mon_thousands_sep")
		If result.Exists("currency_symbol") Then EW_CURRENCY_SYMBOL = result("currency_symbol")
		If result.Exists("positive_sign") Then EW_POSITIVE_SIGN = result("positive_sign") ' Note: $positive_sign can be empty.
		If result.Exists("negative_sign") Then EW_NEGATIVE_SIGN = result("negative_sign")
		If result.Exists("frac_digits") Then EW_FRAC_DIGITS = result("frac_digits")
		If result.Exists("p_cs_precedes") Then EW_P_CS_PRECEDES = result("p_cs_precedes")
		If result.Exists("p_sep_by_space") Then EW_P_SEP_BY_SPACE = result("p_sep_by_space")
		If result.Exists("n_cs_precedes") Then EW_N_CS_PRECEDES = result("n_cs_precedes")
		If result.Exists("n_sep_by_space") Then EW_N_SEP_BY_SPACE = result("n_sep_by_space")
		If result.Exists("p_sign_posn") Then EW_P_SIGN_POSN = result("p_sign_posn")
		If result.Exists("n_sign_posn") Then EW_N_SIGN_POSN = result("n_sign_posn")
		If result.Exists("date_sep") Then EW_DATE_SEPARATOR = result("date_sep")
		If result.Exists("time_sep") Then EW_TIME_SEPARATOR = result("time_sep")
		If result.Exists("date_format") Then
			EW_DATE_FORMAT = ew_DateFormat(result("date_format"))
			EW_DATE_FORMAT_ID = ew_DateFormatId(result("date_format"))
		End If
	End Sub

	Private Sub IterateNodes(Node)
		If Node.baseName = vbNullString Then Exit Sub
		Dim Index, Id, Client, ImageUrl, ImageWidth, ImageHeight, ImageClass
		If Node.nodeType = 1 And Node.baseName <> "ew-language" Then ' NODE_ELEMENT
			Id = ""
			If Node.attributes.length > 0 Then
				Id = Node.getAttribute("id")
			End If
			If Node.hasChildNodes Then
				Key = Key & Node.baseName & "/"
				If Id <> "" Then Key = Key & Id & "/"
			End If
			If Id <> "" And Not Node.hasChildNodes Then ' phrase
				Id = Node.baseName & "/" & Id
				Client = Node.getAttribute("client") & ""
				ImageUrl = Node.getAttribute("imageurl") & ""
				ImageWidth = Node.getAttribute("imagewidth") & ""
				ImageHeight = Node.getAttribute("imageheight") & ""
				ImageClass = Node.getAttribute("class") & ""
				If Id <> "" Then
					objDict(Key & Id & "/attr/value") = Node.getAttribute("value") & ""
					If Client <> "" Then objDict(Key & Id & "/attr/client") = Client
					If ImageUrl <> "" Then objDict(Key & Id & "/attr/imageurl") = ImageUrl
					If ImageWidth <> "" Then objDict(Key & Id & "/attr/imagewidth") = ImageWidth
					If ImageHeight <> "" Then objDict(Key & Id & "/attr/imageheight") = ImageHeight
					If ImageClass <> "" Then objDict(Key & Id & "/attr/class") = ImageClass
				End If
			End If
		End If
		If Node.hasChildNodes Then
			For Index = 0 To Node.childNodes.length - 1
				Call IterateNodes(Node.childNodes(Index))
			Next
			Index = InStrRev(Key, "/" & Node.baseName & "/")
			If Index > 0 Then Key = Left(Key, Index)
		End If
	End Sub

	' Convert XML to Collection
	Private Sub XmlToCollection(File)
		Dim I, xmlr
		Key = "/"
		Set xmlr = ew_CreateXmlDom()
		xmlr.async = False
		xmlr.Load(File)
		For I = 0 To xmlr.childNodes.length - 1
			Call IterateNodes(xmlr.childNodes(I))
		Next
		Set xmlr = Nothing
	End Sub

	' Get language file name
	Private Function GetFileName(Id)
		GetFileName = ""
		If IsArray(EW_LANGUAGE_FILE) Then
			For i = 0 to UBound(EW_LANGUAGE_FILE)
				If EW_LANGUAGE_FILE(i)(0) = Id Then
					GetFileName = Server.MapPath(LanguageFolder & EW_LANGUAGE_FILE(i)(2))
					Exit For
				End If
			Next
		End If
	End Function

	' Get node attribute
	Private Function GetNodeAtt(Node, Att)
		If ew_NotEmpty(Node) Then
			GetNodeAtt = Node.getAttribute(Att)
		Else
			GetNodeAtt = ""
		End If
	End Function

	' Set node attribute
	Private Function SetNodeAtt(Node, Att, Value)
		If ew_NotEmpty(Node) Then
			Node.setAttribute Att, Value
		End If
	End Function

	' Get dictionary attribute
	Private Function GetDictAtt(Att)
		If objDict.Exists(Att) Then
			GetDictAtt = objDict(Att)
		Else
			GetDictAtt = ""
		End If
	End Function

	' Get phrase
	Public Function Phrase(Id)
		Phrase = PhraseBase(Id, False)
	End Function

	Public Function PhraseBase(Id, UseText)
		Dim Text, ImageUrl, ImageWidth, ImageHeight, ImageClass, Style
		If EW_USE_DOM_XML Then
			ImageUrl = GetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), "imageurl")
			ImageWidth = GetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), "imagewidth")
			ImageHeight = GetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), "imageheight")
			ImageClass = GetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), "class")
			Text = GetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), "value")
		Else
			ImageUrl = GetDictAtt("/global/phrase/" & LCase(Id) & "/attr/imageurl")
			ImageWidth = GetDictAtt("/global/phrase/" & LCase(Id) & "/attr/imagewidth")
			ImageHeight = GetDictAtt("/global/phrase/" & LCase(Id) & "/attr/imageheight")
			ImageClass = GetDictAtt("/global/phrase/" & LCase(Id) & "/attr/class")
			Text = GetDictAtt("/global/phrase/" & LCase(Id) & "/attr/value")
		End If
		If Not UseText And ImageClass <> "" Then
			PhraseBase = "<span data-phrase=""" & Id & """ class=""" & ImageClass & """ data-caption=""" & ew_HtmlEncode(Text) & """></span>"
		ElseIf Not UseText And ImageUrl <> "" Then
			Style = ew_IIf(ImageWidth <> "", " width: " & ImageWidth & "px;", "")
			Style = Style & ew_IIf(ImageHeight <> "", " height: " & ImageHeight & "px;", "")
			PhraseBase = "<img data-phrase=""" & Id & """ src=""" & ew_HtmlEncode(ImageUrl) & """ style=""" & Style & """ alt=""" & ew_HtmlEncode(Text) & """ title=""" & ew_HtmlEncode(Text) & """>"
		Else
			PhraseBase = Text
		End If
	End Function

	' Set phrase
	Public Sub SetPhrase(Id, Value)
		Call SetPhraseAttr(Id, "value", Value)
	End Sub

	' Get project phrase
	Public Function ProjectPhrase(Id)
		If EW_USE_DOM_XML Then
			ProjectPhrase = GetNodeAtt(objDOM.SelectSingleNode("//project/phrase[@id='" & LCase(Id) & "']"), "value")
		Else
			ProjectPhrase = GetDictAtt("/project/phrase/" & LCase(Id) & "/attr/value")
		End If
	End Function

	' Set project phrase
	Public Sub SetProjectPhrase(Id, Value)
		If EW_USE_DOM_XML Then
			Call SetNodeAtt(objDOM.SelectSingleNode("//project/phrase[@id='" & LCase(Id) & "']"), "value", Value)
		Else
			objDict("/project/phrase/" & LCase(Id) & "/attr/value") = Value
		End If
	End Sub

	' Get menu phrase
	Public Function MenuPhrase(MenuId, Id)
		If EW_USE_DOM_XML Then
			MenuPhrase = GetNodeAtt(objDOM.SelectSingleNode("//project/menu[@id='" & MenuId & "']/phrase[@id='" & LCase(Id) & "']"), "value")
		Else
			MenuPhrase = GetDictAtt("/project/menu/" & MenuId & "/phrase/" & LCase(Id) & "/attr/value")
		End If
	End Function

	' Set menu phrase
	Public Sub SetMenuPhrase(MenuId, Id, Value)
		If EW_USE_DOM_XML Then
			Call SetNodeAtt(objDOM.SelectSingleNode("//project/menu[@id='" & MenuId & "']/phrase[@id='" & LCase(Id) & "']"), "value", Value)
		Else
			objDict("/project/menu/" & MenuId & "/phrase/" & LCase(Id) & "/attr/value") = Value
		End If
	End Sub

	' Get table phrase
	Public Function TablePhrase(TblVar, Id)
		If EW_USE_DOM_XML Then
			TablePhrase = GetNodeAtt(objDOM.SelectSingleNode("//project/table[@id='" & LCase(TblVar) & "']/phrase[@id='" & LCase(Id) & "']"), "value")
		Else
			TablePhrase = GetDictAtt("/project/table/" & LCase(TblVar) & "/phrase/" & LCase(Id) & "/attr/value")
		End If
	End Function

	' Set table phrase
	Public Sub SetTablePhrase(TblVar, Id, Value)
		If EW_USE_DOM_XML Then
			Call SetNodeAtt(objDOM.SelectSingleNode("//project/table[@id='" & LCase(TblVar) & "']/phrase[@id='" & LCase(Id) & "']"), "value", Value)
		Else
			objDict("/project/table/" & LCase(TblVar) & "/phrase/" & LCase(Id) & "/attr/value") = Value
		End If
	End Sub

	' Get field phrase
	Public Function FieldPhrase(TblVar, FldVar, Id)
		If EW_USE_DOM_XML Then
			FieldPhrase = GetNodeAtt(objDOM.SelectSingleNode("//project/table[@id='" & LCase(TblVar) & "']/field[@id='" & LCase(FldVar) & "']/phrase[@id='" & LCase(Id) & "']"), "value")
		Else
			FieldPhrase = GetDictAtt("/project/table/" & LCase(TblVar) & "/field/" & LCase(FldVar) & "/phrase/" & LCase(Id) & "/attr/value")
		End If
	End Function

	' Set field phrase
	Public Sub SetFieldPhrase(TblVar, FldVar, Id, Value)
		If EW_USE_DOM_XML Then
			Call SetNodeAtt(objDOM.SelectSingleNode("//project/table[@id='" & LCase(TblVar) & "']/field[@id='" & LCase(FldVar) & "']/phrase[@id='" & LCase(Id) & "']"), "value", Value)
		Else
			objDict("/project/table/" & LCase(TblVar) & "/field/" & LCase(FldVar) & "/phrase/" & LCase(Id) & "/attr/value") = Value
		End If
	End Sub

	' Get phrase attribute
	Function PhraseAttr(Id, Name)
		If EW_USE_DOM_XML Then
			PhraseAttr = GetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), LCase(Name))
		Else
			PhraseAttr = GetDictAtt("/global/phrase/" & LCase(Id) & "/attr/" & LCase(Name))
		End If
	End Function

	' Set phrase attribute
	Public Sub SetPhraseAttr(Id, Name, Value)
		If EW_USE_DOM_XML Then
			Call SetNodeAtt(objDOM.SelectSingleNode("//global/phrase[@id='" & LCase(Id) & "']"), LCase(Name), Value)
		Else
			objDict("/global/phrase/" & LCase(Id) & "/attr/" & LCase(Name)) = Value
		End If
	End Sub

	' Get phrase class
	Public Function PhraseClass(Id)
		PhraseClass = PhraseAttr(Id, "class")
	End Function

	' Set phrase attribute
	Public Sub SetPhraseClass(Id, Value)
		Call SetPhraseAttr(Id, "class", Value)
	End Sub

	' Output XML as JSON
	Public Function XmlToJson(XPath)
		Dim Node, NodeList, Id, Value, Str
		Set NodeList = objDOM.selectNodes(XPath)
		Str = "{"
		For Each Node In NodeList
			Id = GetNodeAtt(Node, "id")
			Value = GetNodeAtt(Node, "value")
			Str = Str & """" & ew_JsEncode2(Id) & """:""" & ew_JsEncode2(Value) & ""","
		Next
		If Right(Str, 1) = "," Then Str = Left(Str, Len(Str)-1)
		Str = Str & "}"
		XmlToJson = Str
	End Function

	' Output collection as JSON
	Public Function CollectionToJson(Prefix, Client)
		Dim Name, Id, Str, Pos, Keys, I
		Dim Suffix, IsClient
		Suffix = "/attr/value"
		Str = "{"
		Keys = objDict.Keys
		For I = 0 To Ubound(Keys)
			Name = Keys(I)
			If Left(Name, Len(Prefix)) = Prefix And Right(Name, Len(Suffix)) = Suffix Then
				Pos = InStrRev(Name, Suffix)
				Id = Mid(Name, Len(Prefix) + 1, Pos - Len(Prefix) - 1)
				IsClient = (GetDictAtt(Prefix & Id & "/attr/client") = "1")
				If Not Client Or Client And IsClient Then
					Str = Str & """" & ew_JsEncode2(Id) & """:""" & ew_JsEncode2(GetDictAtt(Name)) & ""","
				End If
			End If
		Next
		If Right(Str, 1) = "," Then Str = Left(Str, Len(Str)-1)
		Str = Str & "}"
		CollectionToJson = Str
	End Function

	' Output all phrases as JSON
	Public Function AllToJson()
		If EW_USE_DOM_XML Then
			AllToJson ="var ewLanguage = new ew_Language(" & XmlToJson("//global/phrase") & ");"
		Else
			AllToJson = "var ewLanguage = new ew_Language(" & CollectionToJson("/global/phrase/", False) & ");"
		End If
	End Function

	' Output client phrases as JSON
	Public Function ToJson()
		Dim dict, langs
		Set dict = Dictionary()
		Set langs = GetLanguages()
		If langs.Count() > 1 Then
			Call dict.Add("languages", langs)
		Else
			Call dict.Add("languages", Array())
		End If
		Call ew_SetClientVar("languages", dict)
		Set langs = Nothing
		Set dict = Nothing
		If EW_USE_DOM_XML Then
			ToJson = "var ewLanguage = new ew_Language(" & XmlToJson("//global/phrase[@client='1']") & ");"
		Else
			ToJson = "var ewLanguage = new ew_Language(" & CollectionToJson("/global/phrase/", True) & ");"
		End If
	End Function

	' Output language selection form
	Public Function GetLanguages()
		Dim ar, dict, langid
		Set ar = Dictionary()
		If IsArray(EW_LANGUAGE_FILE) Then
			For i = 0 to UBound(EW_LANGUAGE_FILE)
				Set dict = Dictionary()
				langid = EW_LANGUAGE_FILE(i)(0)
				Call dict.Add("id", langid)
				Call dict.Add("desc", ew_IIf(Phrase(langid) <> "", Phrase(langid), EW_LANGUAGE_FILE(i)(1)))
				Call dict.Add("selected", langid = gsLanguage)
				Call ar.Push(dict)
				Set dict = Nothing
			Next
		End If
		Set GetLanguages = ar
		Set ar = Nothing
	End Function

	' Get template
	Public Function GetTemplate()
		GetTemplate = Template&""
		If IsEmpty(Template) Then
			If ew_SameText(LanguageType, "LI") Then ' LI template (for used with top Navbar)
				GetTemplate = "{{for languages}}<li{{if selected}} class=""active""{{/if}}><a href=""#"" class=""ewTooltip"" title=""{{>desc}}"" onclick=""ew_SetLanguage(this);"" data-language=""{{:id}}"">{{:id}}</a></li>{{/for}}"
			ElseIf ew_SameText(LanguageType, "DROPDOWN") Then ' DROPDOWN template (for used with top Navbar)
				GetTemplate = "<li class=""dropdown""><a href=""#"" class=""dropdown-toggle"" data-toggle=""dropdown"" role=""button"" aria-haspopup=""true"" aria-expanded=""false""><span class=""glyphicon glyphicon-globe ewIcon"" aria-hidden=""true""></span>&nbsp;<span class=""caret""></span></a><ul class=""dropdown-menu"">{{for languages}}<li{{if selected}} class=""active""{{/if}}><a href=""#"" onclick=""ew_SetLanguage(this);"" data-language=""{{:id}}"">{{>desc}}</a></li>{{/for}}</ul></li>"
			ElseIf ew_SameText(LanguageType, "SELECT") Then ' SELECT template (NOT for used with top Navbar)
				GetTemplate = "<div class=""ewLanguageOption""><select class=""form-control"" id=""ewLanguage"" name=""ewLanguage"" onchange=""ew_SetLanguage(this);"">{{for languages}}<option value=""{{:id}}""{{if selected}} selected{{/if}}>{{:desc}}</option>{{/for}}</select></div>"
			ElseIf ew_SameText(LanguageType, "RADIO") Then ' RADIO template (NOT for used with top Navbar)
				GetTemplate = "<div class=""ewLanguageOption""><div class=""btn-group"" data-toggle=""buttons"">{{for languages}}<label class=""btn btn-default ewTooltip"" data-container=""body"" data-placement=""bottom"" title=""{{>desc}}""><input type=""radio"" name=""ewLanguage"" autocomplete=""off"" onchange=""ew_SetLanguage(this);{{if selected}} checked{{/if}}"" value=""{{:id}}"">{{:id}}</label>{{/for}}</div></div>"
			End If
		End If
	End Function

	' Language Load event
	Sub Language_Load()

		' Example:
		'Call SetPhrase("MyID", "MyValue") ' Refer to language file for the actual phrase id
		'Call SetPhraseClass("MyID", "glyphicon glyphicon-xxx ewIcon") ' Refer to http://getbootstrap.com/components/#glyphicons for icon name

	End Sub
End Class

'
'  Language class (end)
'
' Format sequence number
Function ew_FormatSeqNo(seq)
	ew_FormatSeqNo =  Replace(Language.Phrase("SequenceNumber"), "%s", seq)
End Function

' Encode value for single-quoted JavaScript string
Function ew_JsEncode(val)
	val = Replace(val & "", "\", "\\")
	val = Replace(val, "'", "\'")

'	val = Replace(val, vbCrLf, "\r\n")
'	val = Replace(val, vbCr, "\r")
'	val = Replace(val, vbLf, "\n")

	val = Replace(val, vbCrLf, "<br>")
	val = Replace(val, vbCr, "<br>")
	val = Replace(val, vbLf, "<br>")
	ew_JsEncode = val
End Function

' Encode value for double-quoted Javascript string
Function ew_JsEncode2(val)
	val = Replace(val & "", "\", "\\")
	val = Replace(val, """", "\""")

'	val = Replace(val, vbCrLf, "\r\n")
'	val = Replace(val, vbCr, "\r")
'	val = Replace(val, vbLf, "\n")

	val = Replace(val, vbCrLf, "<br>")
	val = Replace(val, vbCr, "<br>")
	val = Replace(val, vbLf, "<br>")
	ew_JsEncode2 = val
End Function

' Encode value to single-quoted Javascript string for HTML attributes
Function ew_JsEncode3(val)
	val = Replace(val & "", "\", "\\")
	val = Replace(val, "'", "\'")
	val = Replace(val, """", "&quot;")
	ew_JsEncode3 = val
End Function

' Get current script name
Function ew_ScriptName()
	ew_ScriptName = Request.ServerVariables("SCRIPT_NAME")
End Function

' Check if HTTP POST
Function ew_IsPost()
	ew_IsPost = ew_SameText(Request.ServerVariables("REQUEST_METHOD"), "POST")
End Function

' Check if HTTP POST (Deprecated, for backward compatibility only, to be removed)
Function ew_IsHttpPost()
	ew_IsHttpPost = ew_IsPost()
End Function

' Cast date/time field for LIKE
Function ew_CastDateFieldForLike(fld, namedformat, dbid)
	Dim dbtype, isDateTime, shortYear, dateformat
	ew_CastDateFieldForLike = fld
	dbtype = ew_GetConnectionType(dbid)
	isDateTime = False ' Date/Time
	If namedformat = 0 Or namedformat = 1 Or namedformat = 2 Or namedformat = 8 Then
		isDateTime = namedformat = 1 Or namedformat = 8
		namedformat = EW_DATE_FORMAT_ID
	End If
	shortYear = namedformat >= 12 And namedformat <= 17
	isDateTime = isDateTime Or ew_InArray(namedformat, Array(9, 10, 11, 15, 16, 17))
	dateFormat = ""
	Select Case namedformat
		Case 3
			If dbtype = "MYSQL" Then
				dateFormat = "%h" & EW_TIME_SEPARATOR & "%i" & EW_TIME_SEPARATOR & "%s %p"
			ElseIf dbtype = "ACCESS" Then
				dateFormat = "hh" & EW_TIME_SEPARATOR & "nn" & EW_TIME_SEPARATOR & "ss AM/PM"
			ElseIf dbtype = "MSSQL" Then
				dateFormat = "REPLACE(LTRIM(RIGHT(CONVERT(VARCHAR(19), %s, 0), 7)), ':', '" & EW_TIME_SEPARATOR & "')" ' Use hh:miAM (or PM) only or SQL too lengthy
			ElseIf dbtype = "ORACLE" Then
				dateFormat = "HH" & EW_TIME_SEPARATOR & "MI" & EW_TIME_SEPARATOR & "SS AM"
			End If
		Case 4
			If dbtype = "MYSQL" Then
				dateFormat = "%H" & EW_TIME_SEPARATOR & "%i" & EW_TIME_SEPARATOR & "%s"
			ElseIf dbtype = "ACCESS" Then
				dateFormat = "hh" & EW_TIME_SEPARATOR & "nn" & EW_TIME_SEPARATOR & "ss"
			ElseIf dbtype = "MSSQL" Then
				dateFormat = "REPLACE(CONVERT(VARCHAR(8), %s, 108), ':', '" & EW_TIME_SEPARATOR & "')"
			ElseIf dbtype = "ORACLE" Then
				dateFormat = "HH24" & EW_TIME_SEPARATOR & "MI" & EW_TIME_SEPARATOR & "SS"
			End If
		Case 5, 9, 12, 15
			If dbtype = "MYSQL" Then
				dateFormat = ew_IIf(shortYear, "%y", "%Y") & EW_DATE_SEPARATOR & "%m" & EW_DATE_SEPARATOR & "%d"
				If isDateTime Then
					dateFormat = dateFormat & " %H" & EW_TIME_SEPARATOR & "%i" & EW_TIME_SEPARATOR & "%s"
				End If
			ElseIf dbtype = "ACCESS" Then
				dateFormat = ew_IIf(shortYear, "yy", "yyyy") & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "dd"
				If isDateTime Then
					dateFormat = dateFormat & " hh" & EW_TIME_SEPARATOR & "nn" & EW_TIME_SEPARATOR & "ss"
				End If
			ElseIf dbtype = "MSSQL" Then
				dateFormat = "REPLACE(" & ew_IIf(shortYear, "CONVERT(VARCHAR(8), %s, 2)", "CONVERT(VARCHAR(10), %s, 102)") & ", '.', '" & EW_DATE_SEPARATOR & "')"
				If isDateTime Then
					dateFormat = "(" & dateFormat & " + ' ' + REPLACE(CONVERT(VARCHAR(8), %s, 108), ':', '" & EW_TIME_SEPARATOR & "'))"
				End If
			ElseIf dbtype = "ORACLE" Then
				dateFormat = ew_IIf(shortYear, "YY", "YYYY") & EW_DATE_SEPARATOR & "MM" & EW_DATE_SEPARATOR & "DD"
				If isDateTime Then
					dateFormat = dateFormat & " HH24" & EW_TIME_SEPARATOR & "MI" & EW_TIME_SEPARATOR & "SS"
				End If
			End If
		Case 6, 10, 13, 16
			If dbtype = "MYSQL" Then
				dateFormat = "%m" & EW_DATE_SEPARATOR & "%d" & EW_DATE_SEPARATOR & ew_IIf(shortYear, "%y", "%Y")
				If isDateTime Then
					dateFormat = dateFormat & " %H" & EW_TIME_SEPARATOR & "%i" & EW_TIME_SEPARATOR & "%s"
				End If
			ElseIf dbtype = "ACCESS" Then
				dateFormat = "mm" & EW_DATE_SEPARATOR & "dd" & EW_DATE_SEPARATOR & ew_IIf(shortYear, "yy", "yyyy")
				If isDateTime Then
					dateFormat = dateFormat & " hh" & EW_TIME_SEPARATOR & "nn" & EW_TIME_SEPARATOR & "ss"
				End If
			ElseIf dbtype = "MSSQL" Then
				dateFormat = "REPLACE(" & ew_IIf(shortYear, "CONVERT(VARCHAR(8), %s, 1)", "CONVERT(VARCHAR(10), %s, 101)") & ", '/', '" & EW_DATE_SEPARATOR & "')"
				If isDateTime Then
					dateFormat = "(" & dateFormat & " + ' ' + REPLACE(CONVERT(VARCHAR(8), %s, 108), ':', '" & EW_TIME_SEPARATOR & "'))"
				End If
			ElseIf dbtype = "ORACLE" Then
				dateFormat = "MM" & EW_DATE_SEPARATOR & "DD" & EW_DATE_SEPARATOR & ew_IIf(shortYear, "YY", "YYYY")
				If isDateTime Then
					dateFormat = dateFormat & " HH24" & EW_TIME_SEPARATOR & "MI" & EW_TIME_SEPARATOR & "SS"
				End If
			End If
		Case 7, 11, 14, 17
			If dbtype = "MYSQL" Then
				dateFormat = "%d" & EW_DATE_SEPARATOR & "%m" & EW_DATE_SEPARATOR & ew_IIf(shortYear, "%y", "%Y")
				If isDateTime Then
					dateFormat = dateFormat & " %H" & EW_TIME_SEPARATOR & "%i" & EW_TIME_SEPARATOR & "%s"
				End If
			ElseIf dbtype = "ACCESS" Then
				dateFormat = "dd" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & ew_IIf(shortYear, "yy", "yyyy")
				If isDateTime Then
					dateFormat = dateFormat & " hh" & EW_TIME_SEPARATOR & "nn" & EW_TIME_SEPARATOR & "ss"
				End If
			ElseIf dbtype = "MSSQL" Then
				dateFormat = "REPLACE(" & ew_IIf(shortYear, "CONVERT(VARCHAR(8), %s, 3)", "CONVERT(VARCHAR(10), %s, 103)") & ", '/', '" & EW_DATE_SEPARATOR & "')"
				If isDateTime Then
					dateFormat = "(" & dateFormat & " + ' ' + REPLACE(CONVERT(VARCHAR(8), %s, 108), ':', '" & EW_TIME_SEPARATOR & "'))"
				End If
			ElseIf dbtype = "ORACLE" Then
				dateFormat = "DD" & EW_DATE_SEPARATOR & "MM" & EW_DATE_SEPARATOR & ew_IIf(shortYear, "YY", "YYYY")
				If isDateTime Then
					dateFormat = dateFormat & " HH24" & EW_TIME_SEPARATOR & "MI" & EW_TIME_SEPARATOR & "SS"
				End If
			End If
	End Select
	If dateFormat <> "" Then
		If dbtype = "MYSQL" Then
			ew_CastDateFieldForLike = "DATE_FORMAT(" & fld & ", '" & dateFormat & "')"
		ElseIf dbtype = "ACCESS" Then
			ew_CastDateFieldForLike = "FORMAT(" & fld & ", '" & dateFormat & "')"
		ElseIf dbtype = "MSSQL" Then
			ew_CastDateFieldForLike = Replace(dateFormat, "%s", fld)
		ElseIf dbtype = "ORACLE" Then
			ew_CastDateFieldForLike = "TO_CHAR(" & fld & ", '" & dateFormat & "')"
		End If
	End If
End Function

' Get current page name
Function ew_CurrentPage()
	ew_CurrentPage = ew_GetPageName(ew_ScriptName())
End Function

' Get page name
Function ew_GetPageName(url)
	If url <> "" Then
		ew_GetPageName = url
		If InStr(ew_GetPageName, "?") > 0 Then
			ew_GetPageName = Mid(ew_GetPageName, 1, InStr(ew_GetPageName, "?") - 1) ' Remove querystring first
		End If
		ew_GetPageName = Mid(ew_GetPageName, InStrRev(ew_GetPageName, "/") + 1) ' Remove path
	Else
		ew_GetPageName = ""
	End If
End Function

' Get domain URL
Function ew_DomainUrl()
	Dim sUrl, bSSL, sPort, defPort
	sUrl = "http"
	bSSL = ew_IsHttps()
	sPort = Request.ServerVariables("SERVER_PORT")
	If bSSL Then defPort = "443" Else defPort = "80"
	If sPort = defPort Then sPort = "" Else sPort = ":" & sPort
	If bSSL Then sUrl = sUrl & "s"
	sUrl = sUrl & "://"
	sUrl = sUrl & Request.ServerVariables("SERVER_NAME") & sPort
	ew_DomainUrl = sUrl
End Function

' Get css file
Function ew_CssFile(f)
	If EW_CSS_FLIP Then
		ew_CssFile = ew_RegExReplace("(.css)$", f, "-rtl.css")
	Else
		ew_CssFile = f
	End If
End Function

' IIf function
Function ew_IIf(cond, v1, v2)
	On Error Resume Next
	If cond & "" = "" Then
		ew_IIf = v2
	ElseIf CBool(cond) Then
		ew_IIf = v1
	Else
		ew_IIf = v2
	End If
End Function

' Check if HTTPS
Function ew_IsHttps()
	ew_IsHttps = (Request.ServerVariables("HTTPS") <> "" And Request.ServerVariables("HTTPS") <> "off")
End Function

' Get current URL
Function ew_CurrentUrl()
	Dim s, q
	s = ew_ScriptName()
	q = Request.ServerVariables("QUERY_STRING")
	If q <> "" Then s = s & "?" & q
	ew_CurrentUrl = s
End Function

' Convert to full URL
Function ew_FullUrl(url, sType)
	Dim sUrl, protocol
	If ew_IsRemote(url) Then
		ew_FullUrl = url
		Exit Function
	End If
	sUrl = ew_DomainUrl() & ew_ScriptName()
	If Not ew_EmptyStr(url) Then
		sUrl = Mid(sUrl, 1, InStrRev(sUrl, "/")) & url
	End If
	protocol = EW_FULL_URL_PROTOCOLS.Get(sType)
	If Not ew_EmptyStr(protocol) Then
		sUrl = ew_RegExReplace("^\w+(?!:\/\/)", sUrl, protocol)
	End If
	ew_FullUrl = sUrl
End Function

' Get relative URL
Function ew_GetUrl(url)
	Dim path
	If Not ew_EmptyStr(url) And Not ew_StartsStr(url, "/") And Not ew_ContainsStr(url, "://") And Not ew_ContainsStr(url, "\\") And Not ew_ContainsStr(url, "javascript:") Then
		path = ""
		If InStrRev(url, "/") > 0 Then
			path = Mid(url, 1, InStrRev(url, "/"))
			url = Mid(url, InStrRev(url, "/") + 1)
		End If
		path = ew_PathCombine(EW_RELATIVE_PATH, path, False)
		If path <> "" Then path = ew_IncludeTrailingDelimiter(path, False)
		ew_GetUrl = path & url
	Else
		ew_GetUrl = url
	End If
End Function

' Get match collection by regular expression
Function ew_RegExMatch(expr, src, m)
	Dim re
	Set re = New RegExp
	re.IgnoreCase = True
	re.Global = True
	re.Pattern = expr
	Set m = re.Execute(src)
	ew_RegExMatch = (m.Count > 0)
	Set re = Nothing
End Function

' Create XML DOM object
Function ew_CreateXmlDom()
	On Error Resume Next
	Dim ProgId
	ProgId = Array("MSXML2.DOMDocument", "Microsoft.XMLDOM") ' Add other ProgID here
	Dim i
	For i = 0 To UBound(ProgId)
		Set ew_CreateXmlDom = Server.CreateObject(ProgId(i))
		If Err.Number = 0 Then Exit For
	Next
End Function

' Check if responsive layout
Function ew_IsResponsiveLayout()
	ew_IsResponsiveLayout = EW_USE_RESPONSIVE_LAYOUT
End Function

' Get internal default date format (e.g. "yyyy/mm/dd"") from date format (int)
' 5 - Ymd (default)
' 6 - mdY
' 7 - dmY
' 9 - YmdHis
' 10 - mdYHis
' 11 - dmYHis
' 12 - ymd
' 13 - mdy
' 14 - dmy
' 15 - ymdHis
' 16 - mdyHis
' 17 - dmyHis
Function ew_DateFormat(dateFormat)
	ew_DateFormat = "yyyy" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "dd"
	If IsNumeric(dateFormat) Then
		dateFormat = CInt(dateFormat)
		Select Case dateFormat
			Case 5
			Case 9
				ew_DateFormat = "yyyy" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "dd"
			Case 6
			Case 10
				ew_DateFormat = "mm" & EW_DATE_SEPARATOR & "dd" & EW_DATE_SEPARATOR & "yyyy"
			Case 7
			Case 11
				ew_DateFormat = "dd" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "yyyy"
			Case 12
			Case 15
				ew_DateFormat = "yy" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "dd"
			Case 13
			Case 16
				ew_DateFormat = "mm" & EW_DATE_SEPARATOR & "dd" & EW_DATE_SEPARATOR & "yy"
			Case 14
			Case 17
				ew_DateFormat = "dd" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "yy"
		End Select
	ElseIf CStr(dateFormat&"") <> "" Then
		Select Case Mid(dateFormat, 1, 3)
			Case "Ymd"
				ew_DateFormat = "yyyy" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "dd"
			Case "mdY"
				ew_DateFormat = "mm" & EW_DATE_SEPARATOR & "dd" & EW_DATE_SEPARATOR & "yyyy"
			Case "dmY"
				ew_DateFormat = "dd" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "yyyy"
			Case "ymd"
				ew_DateFormat = "yy" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "dd"
			Case "mdy"
				ew_DateFormat = "mm" & EW_DATE_SEPARATOR & "dd" & EW_DATE_SEPARATOR & "yy"
			Case "dmy"
				ew_DateFormat = "dd" & EW_DATE_SEPARATOR & "mm" & EW_DATE_SEPARATOR & "yy"
		End Select
	End If
End Function

' Validate locale file date format
Function ew_DateFormatId(dateFormat)
	ew_DateFormatId = 5
	If IsNumeric(dateFormat) Then
		dateFormat = CInt(dateFormat)
		ew_DateFormatId = ew_IIf(ew_InArray(dateFormat, Array(5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17)),dateFormat, 5)
	ElseIf dateFormat & "" <> "" Then
		Select Case dateFormat
			Case "Ymd"
				ew_DateFormatId = 5
			Case "mdY"
				ew_DateFormatId = 6
			Case "dmY"
				ew_DateFormatId = 7
			Case "YmdHis"
				ew_DateFormatId = 9
			Case "mdYHis"
				ew_DateFormatId = 10
			Case "dmYHis"
				ew_DateFormatId = 11
			Case "ymd"
				ew_DateFormatId = 12
			Case "mdy"
				ew_DateFormatId = 13
			Case "dmy"
				ew_DateFormatId = 14
			Case "ymdHis"
				ew_DateFormatId = 15
			Case "mdyHis"
				ew_DateFormatId = 16
			Case "dmyHis"
				ew_DateFormatId = 17
		End Select
	End If
End Function

' Get path relative to a base path
Function ew_PathCombine(ByVal BasePath, ByVal RelPath, ByVal PhyPath)
	Dim Path, Path2, p1, p2, Delimiter
	If ew_IsRemote(RelPath) Then ' Allow remote file
		ew_PathCombine = RelPath
		Exit Function
	End If
	PhyPath = Not ew_IsRemote(BasePath) And PhyPath
	Delimiter = ew_IIf(PhyPath, "\", "/")
	If BasePath <> Delimiter Then ' If BasePath = root, do not remove delimiter
		BasePath = ew_RemoveTrailingDelimiter(BasePath, PhyPath)
	End If
	If PhyPath Then
		RelPath = Replace(RelPath, "/", EW_PATH_DELIMITER)
		RelPath = Replace(RelPath, "\", EW_PATH_DELIMITER)
	Else
		RelPath = Replace(RelPath, "\", "/")
	End If
	RelPath = ew_IncludeTrailingDelimiter(RelPath, PhyPath)
	p1 = InStr(RelPath, Delimiter)
	Path2 = ""
	While p1 > 0
		Path = Left(RelPath, p1)
		If Path = Delimiter Or Path = "." & Delimiter Then

			' Skip
		ElseIf Path = ".." & Delimiter Then
			p2 = InStrRev(BasePath, Delimiter)
			If p2 = 1 Then ' BasePath = "/xxx", cannot move up
				BasePath = Delimiter
			ElseIf p2 > 0 And Right(BasePath, 2) <> ".." Then
				BasePath = Left(BasePath, p2-1)
			ElseIf BasePath <> "" And BasePath <> "." And BasePath <> ".." Then
				BasePath = ""
			Else
				Path2 = Path2 & ".." & Delimiter
			End If
		Else
			Path2 = Path2 & Path
		End If
		RelPath = Mid(RelPath, p1+1)
		p1 = InStr(RelPath, Delimiter)
	Wend
	If BasePath <> "" And BasePath <> "." Then
		ew_PathCombine = ew_IncludeTrailingDelimiter(BasePath, PhyPath) & Path2 & RelPath
	Else
		ew_PathCombine = Path2 & RelPath
	End If
End Function

' Remove the last delimiter for a path
Function ew_RemoveTrailingDelimiter(ByVal Path, ByVal PhyPath)
	Dim Physical, Delimiter
	Physical = PhyPath And Not ew_IsRemote(Path)
	Delimiter = ew_IIf(Physical, "\", "/")
	While Right(Path, 1) = Delimiter
		Path = Left(Path, Len(Path) - 1)
	Wend
	ew_RemoveTrailingDelimiter = Path
End Function

' Include the last delimiter for a path
Function ew_IncludeTrailingDelimiter(ByVal Path, ByVal PhyPath)
	Dim Physical
	Physical = PhyPath And Not ew_IsRemote(Path)
	ew_IncludeTrailingDelimiter = ew_RemoveTrailingDelimiter(Path, Physical) & ew_IIf(Physical, "\", "/")
End Function

' Build HTML element
Function ew_HtmlElement(tagname, attrs, innerhtml, endtag)
	Dim html, i, name, attr
	html = "<" & tagname
	If IsArray(attrs) Then
		For i = 0 to UBound(attrs)
			If IsArray(attrs(i)) Then
				If UBound(attrs(i)) >= 1 Then
					name = Trim(attrs(i)(0))
					attr = Trim(attrs(i)(1))
					If name <> "" And (attr <> "" Or ew_IsBooleanAttr(name) And Not ew_SameStr(attr, False)) Then ' Allow boolean attributes, e.g. "disabled"
						html = html & " " & name
						If attr <> "" Then
							html = html & "=""" & ew_HtmlEncode(attr) & """"
						End If
					ElseIf name = "alt" And attr = "" Then ' Allow alt="" since it is a required attribute
						html = html & " alt="""""
					End If
				End If
			End If
		Next
	End If
	html = html & ">"
	If innerhtml <> "" Then
		html = html & innerhtml
	End If
	If endtag Then
		html = html & "</" & tagname & ">"
	End If
	ew_HtmlElement = html
End Function

' Encode HTML
Function ew_HtmlEncode(Expression)

	' Note: Do not use Server.HTMLEncode since it will convert accented characters to &#nnn;
	Dim wrkstr
	wrkstr = Replace(Expression & "", "&", "&amp;") ' Replace &
	wrkstr = Replace(wrkstr, "<", "&lt;") ' Replace <
	wrkstr = Replace(wrkstr, ">", "&gt;") ' Replace >
	wrkstr = Replace(wrkstr, """", "&quot;") ' Replace "
	ew_HtmlEncode = wrkstr
End Function

' Prepend CSS class name
Sub ew_PrependClass(attr, classname)
	Dim ar1, ar2, dict
	classname = Trim(classname&"")
	If classname <> "" Then
		attr = Trim(attr&"")
		If attr <> "" Then ar2 = Split(attr, " ")
		ar1 = Array(classname)
		Set dict = Dictionary(ar1)
		dict.AddArray ar2
		dict.Unique()
		attr = Join(dict.ToArray(), " ")
		Set dict = Nothing
	End If
End Sub

' Append CSS class name
Sub ew_AppendClass(attr, classname)
	Dim ar1, ar2, dict
	classname = Trim(classname&"")
	If classname <> "" Then
		attr = Trim(attr&"")
		If attr <> "" Then ar1 = Split(attr, " ")
		ar2 = Array(classname)
		Set dict = Dictionary(ar1)
		dict.AddArray ar2
		dict.Unique()
		attr = Join(dict.ToArray(), " ")
		Set dict = Nothing
	End If
End Sub

' Remove CSS class name
Sub ew_RemoveClass(attr, classname)
	Dim ar, dict
	classname = Trim(classname&"")
	If classname <> "" Then
		attr = Trim(attr&"")
		If attr <> "" Then ar = Split(attr, " ")
		Set dict = Dictionary(ar)
		dict.RemoveValue classname
		dict.Unique()
		attr = Join(dict.ToArray(), " ")
		Set dict = Nothing
	End If
End Sub

' Get numeric formatting information
Function ew_LocaleConv()
	Dim langid, localefile
	langid = gsLanguage
	localefile = LCase(langid) & ".json"
	If Not ew_FileExists(Server.MapPath(EW_RELATIVE_PATH & EW_LOCALE_FOLDER), localefile) Then	' Locale file not found, fall back to English ("en") locale
		localefile = "en.json"
	End If
	Set ew_LocaleConv = ew_JsonDecode(ew_LoadUTF8File(Server.MapPath(EW_RELATIVE_PATH & EW_LOCALE_FOLDER & localefile)))
End Function

' Build query from dictionary
Function ew_HttpBuildQuery(dict)
	Dim wrkstr, key, value
	wrkstr = ""
	If ew_NotEmpty(dict) Then
		For Each key In dict
			value = dict(key)
			If Not wrkstr = "" Then wrkstr = wrkstr & "&"
			wrkstr = wrkstr & key & "=" & Server.URLEncode(value)
		Next
	End If
	ew_HttpBuildQuery = wrkstr
End Function

' Get session timeout time (seconds)
Function ew_SessionTimeoutTime()
	Dim mlt
	If EW_SESSION_TIMEOUT > 0 Then ' User specified timeout time
		mlt = EW_SESSION_TIMEOUT * 60
	Else ' Get session timeout time
		mlt = Session.Timeout * 60
	End If
	If mlt < 60 Then
		mlt = 1200 ' ASP default (1200s = 20min)
	End If
	ew_SessionTimeoutTime = mlt - 30 ' Add some safety margin
End Function

' Is Boolean attribute
Function ew_IsBooleanAttr(attr)
	ew_IsBooleanAttr = ew_InArray(LCase(attr), EW_BOOLEAN_HTML_ATTRIBUTES)
End Function

' Check if an element is in array
Function ew_InArray(el, ar)
	If IsArray(ar) Then
		Dim i
		For i = 0 to UBound(ar)
			If ew_SameStr(el & "", ar(i) & "") Then
				ew_InArray = True
				Exit Function
			End If
		Next
		ew_InArray = False
	Else
		ew_InArray = False
	End If
End Function

' Is auto login (login with option "Auto login until I logout explicitly")
Function IsAutoLogin()
	IsAutoLogin = (Session(EW_SESSION_USER_LOGIN_TYPE) = "a")
End Function

' Check if menu item is allowed for current user level
Function AllowListMenu(TableName)
	On Error Resume Next
	Dim i, priv, thispriv, userlevellist, arUserLevelPriv
	If IsLoggedIn() Then
		userlevellist = CurrentUserLevelList ' Get user level id list
		If userlevellist & "" = "" Then ' Not defined, just get user level
			userlevellist = CurrentUserLevel
		End If
	Else
		userlevellist = -2 ' Get anonymous user level ID
	End If
	If IsListItem(userlevellist, "-1") Then
		AllowListMenu = True
	Else
		AllowListMenu = False
		priv = 0
		arUserLevelPriv = Session(EW_SESSION_AR_USER_LEVEL_PRIV)
		If IsArray(arUserLevelPriv) Then
			For i = 0 to UBound(arUserLevelPriv, 2)
				If CStr(arUserLevelPriv(0, i)) = CStr(TableName) Then
					If IsListItem(userlevellist, arUserLevelPriv(1, i)) Then
						thispriv = arUserLevelPriv(2, i)
						If VarType(thispriv) = 14 Then thispriv = CLng(thispriv)
						If IsNull(thispriv) Then thispriv = 0
						If Not IsNumeric(thispriv) Then thispriv = 0
						thispriv = CLng(thispriv)
						priv = priv Or thispriv
					End If
				End If
			Next
		End If
		AllowListMenu = CBool(priv And 8)
	End If
End Function

' Is list item
Function IsListItem(list, item)
	Dim ar, i
	If list = "" Then
		IsListItem = False
	Else
		ar = Split(list, ",")
		For i = 0 to UBound(ar)
			If CStr(item&"") = CStr(Trim(ar(i)&"")) Then
				IsListItem = True
				Exit Function
			End If
		Next
		IsListItem = False
	End If
End Function

'
'  Common functions (end)
'

%>
<%

' Functions for backward compatibility
' Get current user name
Function CurrentUserName()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		CurrentUserName = Security.CurrentUserName
	Else
		CurrentUserName = Session(EW_SESSION_USER_NAME) & ""
	End If
End Function

' Get current user ID
Function CurrentUserID()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		CurrentUserID = Security.CurrentUserID
	Else
		CurrentUserID = Session(EW_SESSION_USER_ID) & ""
	End If
End Function

' Get current parent user ID
Function CurrentParentUserID()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		CurrentParentUserID = Security.CurrentParentUserID
	Else
		CurrentParentUserID = Session(EW_SESSION_PARENT_USER_ID) & ""
	End If
End Function

' Get current user level
Function CurrentUserLevel()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		CurrentUserLevel = Security.CurrentUserLevelID
	Else
		CurrentUserLevel = Session(EW_SESSION_USER_LEVEL_ID)
	End If
End Function

' Get current user level list
Function CurrentUserLevelList()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		CurrentUserLevelList = Security.UserLevelList
	Else
		CurrentUserLevelList = Session(EW_SESSION_USER_LEVEL_LIST) & ""
	End If
End Function

' Get Current user info
Function CurrentUserInfo(fldname)
	If ew_NotEmpty(UserProfile) Then
		If UserProfile.Has(fldname) Then
			CurrentUserInfo = UserProfile(fldname)
			Exit Function
		End If
	End If
	If ew_NotEmpty(Security) Then
		CurrentUserInfo = Security.CurrentUserInfo(fldname)
		Exit Function
	ElseIf EW_USER_TABLE <> "" And Not IsSysAdmin() Then
		Dim user
		user = CurrentUserName()
		If user <> "" Then
			CurrentUserInfo = ew_ExecuteScalarByDbid("SELECT " & ew_QuotedNameBase(fldname, EW_USER_TABLE_DBID) & " FROM " & EW_USER_TABLE & " WHERE " & Replace(EW_USER_NAME_FILTER, "%u", ew_AdjustSqlBase(user, EW_USER_TABLE_DBID)), EW_USER_TABLE_DBID)
			Exit Function
		End If
	End If
	CurrentUserInfo = Null
End Function

' Get current language ID
Function CurrentLanguageID()
	CurrentLanguageID = gsLanguage
End Function

' Get current project ID
Function CurrentProjectID()
	If ew_NotEmpty(Page) Then
		CurrentProjectID = Page.ProjectID
	Else
		CurrentProjectID = EW_PROJECT_ID
	End If
End Function

' Get current export file name
Function CurrentExportFile()
	CurrentExportFile = gsExportFile
End Function

' Get current page ID
Function CurrentPageID()
	If ew_NotEmpty(Page) Then
		CurrentPageID = Page.PageID
		Exit Function
	ElseIf Not IsEmpty(EW_PAGE_ID) Then
		CurrentPageID = EW_PAGE_ID
		Exit Function
	End If
	CurrentPageID = ""
End Function

' Allow list
Function AllowList(TableName)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		AllowList = Security.AllowList(TableName)
	Else
		AllowList = True
	End If
End Function

' Allow add
Function AllowAdd(TableName)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		AllowAdd = Security.AllowAdd(TableName)
	Else
		AllowAdd = True
	End If
End Function

' Is Password Expired
Function IsPasswordExpired()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsPasswordExpired = Security.IsPasswordExpired
	Else
		IsPasswordExpired = (Session(EW_SESSION_STATUS) = "passwordexpired")
	End If
End Function

' Set session password expired
Public Sub SetSessionPasswordExpired()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		Call Security.SetSessionPasswordExpired
	Else
		Session(EW_SESSION_STATUS) = "passwordexpired"
	End If
End Sub

' Is password reset
Function IsPasswordReset()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsPasswordReset = Security.IsPasswordReset
	Else
		IsPasswordReset = (Session(EW_SESSION_STATUS) = "passwordreset")
	End If
End Function

' Is Logging In
Function IsLoggingIn()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsLoggingIn = Security.IsLoggingIn
	Else
		IsLoggingIn = (Session(EW_SESSION_STATUS) = "loggingin")
	End If
End Function

' Is Logged In
Function IsLoggedIn()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsLoggedIn = Security.IsLoggedIn
	Else
		IsLoggedIn = (Session(EW_SESSION_STATUS) = "login")
	End If
End Function

' Is Authenticated
Function IsAuthenticated()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsAuthenticated = Security.IsAuthenticated()
	Else
		IsAuthenticated = ew_CurrentWindowsUser() <> ""
	End If
End Function

' Is Admin
Function IsAdmin()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsAdmin = Security.IsAdmin
	Else
		IsAdmin = (Session(EW_SESSION_SYS_ADMIN) = 1)
	End If
End Function

' Is System Admin
Function IsSysAdmin()
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	If ew_NotEmpty(Security) Then
		IsSysAdmin = Security.IsSysAdmin
	Else
		IsSysAdmin = (Session(EW_SESSION_SYS_ADMIN) = 1)
	End If
End Function

' Is export
Function IsExport(format)
	If ew_NotEmpty(format) Then
		IsExport = ew_SameText(gsExport, format)
	Else
		IsExport = gsExport <> ""
	End If
End Function

' Is dictonary
Function IsDictionary(obj)
	IsDictionary = False
	If VarType(obj) = vbString Then
		If TypeName(obj) = "JScriptTypeInfo" Then
			If obj.ToString() = "Dictionary" Then
				IsDictionary = True
			End If
		End If
	End If
End Function

' Is dictonary
Function IsScriptingDictionary(obj)
	IsScriptingDictionary = False
	If VarType(obj) = vbObject Then
		If TypeName(obj) = "Dictionary" Then
			IsScriptingDictionary = True
		End If
	End If
End Function

' Get current page object
Function CurrentPage()
	If ew_NotEmpty(Page) Then
		Set CurrentPage = Page
	Else
		Set CurrentPage = Nothing
	End If
End Function

' Get current table object
Function CurrentTable()
	If ew_NotEmpty(Table) Then
		Set CurrentTable = Table
	Else
		Set CurrentTable = Nothing
	End If
End Function

' Get current master table object
Function CurrentMasterTable()
	Dim tbl
	Set tbl = CurrentTable()
	If ew_NotEmpty(tbl) Then
		Set CurrentMasterTable = tbl.MasterTable
	Else
		Set CurrentMasterTable = Nothing
	End If
End Function

' Get current detail table object
Function CurrentDetailTable()
	If ew_NotEmpty(Grid) Then
		Set CurrentDetailTable = Grid
	Else
		Set CurrentDetailTable = Nothing
	End If
End Function

' Get current page heading
Function CurrentPageHeading()
	Dim heading
	CurrentPageheading = ""
	If EW_PAGE_TITLE_STYLE <> "Title" And ew_NotEmpty(Page) Then
		heading = Page.PageHeading
		If heading <> "" Then
			CurrentPageHeading = heading
			Exit Function
		End If
	End If
	CurrentPageHeading = Language.ProjectPhrase("BodyTitle")
End Function

' Get current page subheading
Function CurrentPageSubheading()
	Dim heading
	heading = ""
	If EW_PAGE_TITLE_STYLE <> "Title" And ew_NotEmpty(Page) Then
		heading = Page.PageSubheading
	End If
	CurrentPageSubheading = heading
End Function

' Set up login status
Sub SetupLoginStatus()
	LoginStatus.Add "isLoggedIn", IsLoggedIn()
	LoginStatus.Add "currentUserName", CurrentUserName()
	LoginStatus.Add "logoutUrl", ew_GetUrl("logout.asp")
	LoginStatus.Add "logoutText", Language.Phrase("Logout")
	LoginStatus.Add "loginUrl", ew_GetUrl("login.asp")
	LoginStatus.Add "loginText", Language.Phrase("Login")
	LoginStatus.Add "canLogin", Not IsLoggedIn() And Not ew_EndsStr(Request.ServerVariables("URL"), LoginStatus.Get("loginUrl"))
	LoginStatus.Add "canLogout", IsLoggedIn()
End Sub
%>
<%

' Get server variable by name
Function ew_GetServerVariable(Name)
	ew_GetServerVariable = Request.ServerVariables(Name)
End Function

' Get user IP
Function ew_CurrentUserIP()
	ew_CurrentUserIP = ew_GetServerVariable("REMOTE_HOST")
End Function

' Get current host name, e.g. "www.mycompany.com"
Function ew_CurrentHost()
	ew_CurrentHost = ew_GetServerVariable("HTTP_HOST")
End Function

' Get current Windows user (for Windows Authentication)
Function ew_CurrentWindowsUser()
	ew_CurrentWindowsUser = ew_GetServerVariable("LOGON_USER") ' REMOTE_USER or LOGON_USER or AUTH_USER
End Function

' Get current date in default date format
Function ew_CurrentDate()
	ew_CurrentDate = Date
	Select Case EW_DATE_FORMAT_ID
		Case 5, 9, 12, 15
			ew_CurrentDate = ew_FormatDateTime(ew_CurrentDate, 5)
		Case 6, 10, 13, 16
			ew_CurrentDate = ew_FormatDateTime(ew_CurrentDate, 6)
		Case 7, 11, 14, 17
			ew_CurrentDate = ew_FormatDateTime(ew_CurrentDate, 7)
	End Select
	If EW_DATE_SEPARATOR <> "/" Then ew_CurrentDate = Replace(ew_CurrentDate, EW_DATE_SEPARATOR, "/")
End Function

' Get current time in hh:mm:ss format
Function ew_CurrentTime()
	Dim DT
	DT = Now()
	ew_CurrentTime = ew_ZeroPad(Hour(DT), 2) & ":" & _
		ew_ZeroPad(Minute(DT), 2) & ":" & ew_ZeroPad(Second(DT), 2)
End Function

' Get current date in default date format with
' Current time in hh:mm:ss format
Function ew_CurrentDateTime()
	ew_CurrentDateTime = ew_CurrentDate() & " " & ew_CurrentTime()
End Function

' Get current date in standard format (yyyy/mm/dd)
Function ew_StdCurrentDate()
	ew_StdCurrentDate = ew_StdDate(Date)
End Function

' Get date in standard format (yyyy/mm/dd)
Function ew_StdDate(dt)
	ew_StdDate = ew_ZeroPad(Year(dt), 4) & "/" & ew_ZeroPad(Month(dt), 2) & "/" & ew_ZeroPad(Day(dt), 2)
End Function

' Get current date and time in standard format (yyyy/mm/dd hh:mm:ss)
Function ew_StdCurrentDateTime()
	ew_StdCurrentDateTime = ew_StdDateTime(Now)
End Function

' Get date/time in standard format (yyyy/mm/dd hh:mm:ss)
Function ew_StdDateTime(dt)
	ew_StdDateTime = ew_ZeroPad(Year(dt), 4) & "/" & ew_ZeroPad(Month(dt), 2) & "/" & ew_ZeroPad(Day(dt), 2) & " " & _
		ew_ZeroPad(Hour(dt), 2) & ":" & ew_ZeroPad(Minute(dt), 2) & ":" & ew_ZeroPad(Second(dt), 2)
End Function

' Merge array
Function ew_ArrayMerge(ar1, ar2)
	Dim ar, dict
	Set dict = Dictionary(ar1, ar2)
	ew_ArrayMerge = dict.ToArray()
	Set dict = Nothing
End Function

' Remove duplicate values from an array
Function ew_ArrayUnique(ar)
	Dim dict
	Set dict = Dictionary(ar)
	dict.Unique()
	ew_ArrayUnique = dict.ToArray()
	Set dict = Nothing
End Function

' Get array difference
Function ew_ArrayDiff(ar1, ar2)
	Dim i, ar, dict, intersect
	Set dict = Dictionary(ar1)
	For i = 0 to UBound(ar2)
		If dict.ContainsValue(ar2(i)) Then dict.RemoveValue ar2(i)
	Next
	ew_ArrayDiff = dict.ToArray()
	Set dict = Nothing
End Function

' Get array intersection
Function ew_ArrayIntersect(ar1, ar2)
	Dim i, ar, dict, dict1
	Set dict = Dictionary()
	Set dict1 = Dictionary(ar1)
	For i = 0 to UBound(ar2)
		If dict1.ContainsValue(ar2(i)) Then dict.Push ar2(i)
	Next
	ew_ArrayIntersect = dict.ToArray()
	Set dict = Nothing
	Set dict1 = Nothing
End Function

' Remove XSS
Function ew_RemoveXSS(val)
	Dim search, ra, i, j, Found, val_before, pattern, replacement, ar

	' Handle null value
	If IsNull(val) Then
		ew_RemoveXSS = val
		Exit Function
	End If

	' Handle array
	If IsArray(val) Then
		ReDim ar(UBound(val))
		For i = 0 to UBound(val)
			ar(i) = ew_RemoveXSS(val(i))
		Next
		ew_RemoveXSS = ar
		Exit Function
	End If

	' Remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
	' This prevents some character re-spacing such as <java\0script>
	' Note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs

	pattern = "([\x00-\x08][\x0b-\x0c][\x0e-\x20])"
	val = ew_RegExReplace(pattern, val & "", "")

	' Straight replacements, the user should never need these since they're normal characters
	' This prevents like <IMG SRC=&#X40&#X61&#X76&#X61&#X73&#X63&#X72&#X69&#X70&#X74&#X3A&#X61&#X6C&#X65&#X72&#X74&#X28&#X27&#X58&#X53&#X53&#X27&#X29>

	search = "abcdefghijklmnopqrstuvwxyz"
	search = search & "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	search = search & "1234567890!@#$%^&*()"
	search = search & "~`"";:?+/={}[]-_|'\"
	For i = 1 To Len(search)

		' ;? matches the ;, which is optional
		' 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
		' &#x0040 @ search for the hex values

		pattern = "(&#[x|X]0{0,8}" & Hex(Asc(Mid(search, i, 1))) & ";?)" ' With a ;
		val = ew_RegExReplace(pattern, val, Mid(search, i, 1))

		' &#00064 @ 0{0,7} matches '0' zero to seven times
		pattern = "(&#0{0,8}" & Asc(Mid(search, i, 1)) & ";?)" ' With a ;
		val = ew_RegExReplace(pattern, val, Mid(search, i, 1))
	Next

	' Now the only remaining whitespace attacks are \t, \n, and \r
	ra = EW_XSS_ARRAY
	Found = True ' Keep replacing as long as the previous round replaced something
	Do While Found
		val_before = val
		For i = 0 To UBound(ra)
			pattern = ""
			For j = 1 To Len(ra(i))
				If j > 1 Then
					pattern = pattern & "("
					pattern = pattern & "(&#[x|X]0{0,8}([9][a][b]);?)?"
					pattern = pattern & "|(&#0{0,8}([9][10][13]);?)?"
					pattern = pattern & ")?"
				End If
				pattern = pattern & Mid(ra(i), j, 1)
			Next
			replacement = Mid(ra(i), 1, 2) & "<x>" & Mid(ra(i), 3) ' Add in <> to nerf the tag
			val = ew_RegExReplace(pattern, val, replacement) ' Filter out the hex tags
			If val_before = val Then

				' No replacements were made, so exit the loop
				Found = False
			End If
		Next
	Loop
	ew_RemoveXSS = val
End Function

' Check token
Function ew_CheckToken(t, timeout)
	On Error Resume Next
	If timeout <= 0 Then
		timeout = ew_SessionTimeoutTime()
	End If
	ew_CheckToken = (DateDiff("n", ew_Decrypt(t), ew_StdCurrentDateTime()) < timeout)
End Function

' Create token
Function ew_CreateToken()
	On Error Resume Next
	ew_CreateToken = ew_Encrypt(ew_StdCurrentDateTime())
End Function

' Set client variable
Sub ew_SetClientVar(name, value)
	Dim key
	key = name & ""
	If key <> "" Then
		Call EW_CLIENT_VAR.Add(key, value)
	End If
End Sub

' Is remote path
Function ew_IsRemote(Path)
	ew_IsRemote = ew_RegExTest(EW_REMOTE_FILE_PATTERN, Path)
End Function

' Is rooted path (C:\xxx or \\xxx) ' ASP
Function ew_IsPathRooted(Path)
	ew_IsPathRooted = ew_RegExTest("^([a-zA-Z]:\\|\\\\)\w+", Path)
End Function

' Copy file
Function ew_CopyFile(src, dest)
	On Error Resume Next
	Dim fso
	Set fso = Server.Createobject("Scripting.FileSystemObject")
	If fso.FileExists(src) Then
		fso.CopyFile src, dest, True
		ew_CopyFile = (Err.Number = 0)
	Else
		ew_CopyFile = False
	End If
	Set fso = Nothing
End Function

' Get content file extension
Function ew_ContentExt(data)
	Dim ct, i, key
	ct = ew_ContentType(LeftB(data, 11), "")
	If ct <> "" Then
		key = EW_MIME_TYPES.GetKey(ct)
		If Not IsNull(key) Then
			ew_ContentExt = "." & key
		Else
			ew_ContentExt = "" ' Unknown extension
		End If
	Else
		ew_ContentExt = "" ' Unknown extension
	End If
End Function

' Get image content type
Function ew_ContentType(data, fn)
	Dim sGifHeader1, sGifHeader2, sJpgHeader1, sJpgHeader2, sPngHeader, sBmpHeader, sPdfHeader
	sGifHeader1 = ChrB(71) & ChrB(73) & ChrB(70) & ChrB(56) & ChrB(55) & ChrB(97) ' \x47\x49\x46\x38\x37\x61
	sGifHeader2 = ChrB(71) & ChrB(73) & ChrB(70) & ChrB(56) & ChrB(57) & ChrB(97) ' \x47\x49\x46\x38\x39\x61

	'sJpgHeader1 = ChrB(255) & ChrB(216) & ChrB(255) & ChrB(224) ' \xFF\xD8\xFF\xE0
	'sJpgHeader2 = ChrB(74) & ChrB(70) & ChrB(73) & ChrB(70) & ChrB(0) ' \x4A\x46\x49\x46\x00

	sJpgHeader1 = ChrB(255) & ChrB(216) ' \xFF\xD8
	sPngHeader = ChrB(137) & ChrB(80) & ChrB(78) & ChrB(71) & ChrB(13) & ChrB(10) & ChrB(26) & ChrB(10) ' \x89\x50\x4E\x47\x0D\x0A\x1A\x0A
	sBmpHeader = ChrB(66) & ChrB(77) ' \x42\x4D
	sPdfHeader = ChrB(37) & ChrB(80) & ChrB(68) & ChrB(70) ' \x25\x50\x44\x46
	If MidB(data,1,6) = sGifHeader1 Or MidB(data,1,6) = sGifHeader2 Then ' Check if gif
		ew_ContentType = "image/gif"

	'ElseIf MidB(data,1,4) = sJpgHeader1 Or MidB(data,7,5) = sJpgHeader2 Then ' Check if jpg
	ElseIf MidB(data,1,2) = sJpgHeader1 Then ' Check if jpg
		ew_ContentType = "image/jpeg"
	ElseIf MidB(data,1,8) = sPngHeader Then ' Check if png
		ew_ContentType = "image/png"
	ElseIf MidB(data,1,2) = sBmpHeader Then ' Check if bmp
		ew_ContentType = "image/bmp"
	ElseIf MidB(data,1,4) = sPdfHeader Then ' Check if pdf
		ew_ContentType = "application/pdf"
	ElseIf fn <> "" Then

		' Use file extension to get mime type
		Dim extension
		extension = LCase(Mid(fn, InStrRev(fn, ".") + 1))
		If EW_MIME_TYPES.ContainsKey(extension) Then
			ew_ContentType = EW_MIME_TYPES.Get(extension)
			Exit Function
		End If
		ew_ContentType = "image"
	Else
		ew_ContentType = "image"
	End If
End Function

' Get image dimension
Sub ew_GetImageDimension(img, wd, ht)
	Dim sPNGHeader, sGIFHeader, sBMPHeader, sJPGHeader, sHeader, sImgType
	sImgType = "(unknown)"

	' Image headers, do not changed
	sPNGHeader = ChrB(137) & ChrB(80) & ChrB(78)
	sGIFHeader = ChrB(71) & ChrB(73) & ChrB(70)
	sBMPHeader = ChrB(66) & ChrB(77)
	sJPGHeader = ChrB(255) & ChrB(216) & ChrB(255)
	sHeader = MidB(img, 1, 3)

	' Handle GIF
	If sHeader = sGIFHeader Then
		sImgType = "GIF"
		wd = ew_ConvertLength(MidB(img, 7, 2))
		ht = ew_ConvertLength(MidB(img, 9, 2))

	' Handle BMP
	ElseIf LeftB(sHeader, 2) = sBMPHeader Then
		sImgType = "BMP"
		wd = ew_ConvertLength(MidB(img, 19, 2))
		ht = ew_ConvertLength(MidB(img, 23, 2))

	' Handle PNG
	ElseIf sHeader = sPNGHeader Then
		sImgType = "PNG"
		wd = ew_ConvertLength2(MidB(img, 19, 2))
		ht = ew_ConvertLength2(MidB(img, 23, 2))

	' Handle JPG
	Else
		Dim size, markersize, pos, bEndLoop
		size = LenB(img)
		pos = InStrB(img, sJPGHeader)
		If pos <= 0 Then
			wd = -1
			ht = -1
			Exit Sub
		End If
		sImgType = "JPG"
		pos = pos + 2
		bEndLoop = False
		Do While Not bEndLoop and pos < size
			Do While AscB(MidB(img, pos, 1)) = 255 and pos < size
				pos = pos + 1
			Loop
			If AscB(MidB(img, pos, 1)) < 192 or AscB(MidB(img, pos, 1)) > 195 Then
				markersize = ew_ConvertLength2(MidB(img, pos+1, 2))
				pos = pos + markersize + 1
			Else
				bEndLoop = True
			End If
		Loop
		If Not bEndLoop Then
			wd = -1
			ht = -1
		Else
			wd = ew_ConvertLength2(MidB(img, pos+6, 2))
			ht = ew_ConvertLength2(MidB(img, pos+4, 2))
		End If
	End If
End Sub

' Convert length
Function ew_ConvertLength(b)
	ew_ConvertLength = CLng(AscB(LeftB(b, 1)) + (AscB(RightB(b, 1)) * 256))
End Function

' Convert length 2
Function ew_ConvertLength2(b)
	ew_ConvertLength2 = CLng(AscB(RightB(b, 1)) + (AscB(LeftB(b, 1)) * 256))
End Function
%>
<%

'
'  Get upload object (begin)
'
Function ew_GetUploadObj()
		Set ew_GetUploadObj = New cUploadObj
End Function

'
'  Get upload object (end)
'

%>
<%

' Save binary to file
Function ew_SaveFile(folder, fn, filedata)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Dim oStream
	ew_SaveFile = False
	If IsNull(filedata) Then Exit Function
	If Not ew_SaveFileByComponent(folder, fn, filedata) Then
		If ew_CreateFolder(folder) Then
			Set oStream = Server.CreateObject("ADODB.Stream")
			oStream.Type = 1 ' 1=adTypeBinary
			oStream.Open
			oStream.Write ew_ConvertToBinary(filedata)
			oStream.SaveToFile ew_IncludeTrailingDelimiter(folder, True) & fn, 2 ' 2=adSaveCreateOverwrite
			oStream.Close
			Set oStream = Nothing
			If Err.Number = 0 Then ew_SaveFile = True
		End If
	End If
End Function

' Convert raw to binary
Function ew_ConvertToBinary(rawdata)
	If Not EW_DEBUG_ENABLED Then On Error Resume Next
	Dim oRs
	Set oRs = Server.CreateObject("ADODB.Recordset")

	' Create field in an empty RecordSet
	Call oRs.Fields.Append("Blob", 205, LenB(rawdata)) ' Add field with type adLongVarBinary
	Call oRs.Open
	Call oRs.AddNew

	'Call oRs.Fields("Blob").AppendChunk(rawdata & ChrB(0))
	Call oRs.Fields("Blob").AppendChunk(rawdata)
	Call oRs.Update

	' Save Blob Data
	ew_ConvertToBinary = oRs.Fields("Blob").GetChunk(LenB(rawdata))

	' Close RecordSet
	oRs.Close
	Set oRs = Nothing
End Function

' Get Resize Object
Sub ew_GetResizeObj()
	If ew_Empty(gResizeObj) Then
		Set gResizeObj = New cAspJpegResize
		If Not gResizeObj.CanResize Then
			Set gResizeObj = Nothing
			Set gResizeObj = New cAspNetResize
		End If
	End If
End Sub

' Resize binary to thumbnail
Function ew_ResizeBinary(filedata, width, height, interpolation)
	Call ew_GetResizeObj
	ew_ResizeBinary = gResizeObj.ResizeBinary(filedata, width, height, interpolation)
End Function

' Resize file to thumbnail file
Function ew_ResizeFile(fn, tn, width, height, interpolation)
	Call ew_GetResizeObj
	ew_ResizeFile = gResizeObj.ResizeFile(fn, tn, width, height, interpolation)
End Function

' Resize file to binary
Function ew_ResizeFileToBinary(fn, width, height, interpolation)
	Call ew_GetResizeObj
	ew_ResizeFileToBinary = gResizeObj.ResizeFileToBinary(fn, width, height, interpolation)
End Function

' Save file by component
Function ew_SaveFileByComponent(folder, fn, filedata)
	Call ew_GetResizeObj
	ew_SaveFileByComponent = gResizeObj.SaveFileByComponent(folder, fn, filedata)
End Function

'
'  AspJpeg Resize class
'
' AspJpeg wrapper class
Class cAspJpegResize
	Dim ResizeObj
	Dim ResizeObjExists
	Dim KeepAspectRatio
	Dim ResizeOption

	' Class Initialize
	Private Sub Class_Initialize()
		On Error Resume Next
		Set ResizeObj = Server.CreateObject("Persits.Jpeg")
		ResizeObjExists = (Err.Number = 0)
		Err.Clear
		KeepAspectRatio = EW_KEEP_ASPECT_RATIO
		ResizeOption = EW_RESIZE_OPTION
	End Sub

	Private Sub Class_Terminate()
		If CanResize Then
			Set ResizeObj = Nothing
		End If
	End Sub

	' Can resize
	Function CanResize()
		CanResize = ResizeObjExists
	End Function

	' Resize binary
	Public Function ResizeBinary(filedata, width, height, interpolation)
		On Error Resume Next
		ResizeBinary = False
		If CanResize Then
			ResizeObj.OpenBinary filedata
			If Err.Number <> 0 Then
				Err.Clear
				Exit Function
			End If
			If GetResizeDimension(ResizeObj.OriginalWidth, ResizeObj.OriginalHeight, width, height) Then
				ResizeObj.Width = width
				ResizeObj.Height = height
				filedata = ResizeObj.Binary
			End If
			If Err.Number = 0 Then
				ResizeBinary = True ' Return successful
			Else

				'Response.Write Err.Description ' Uncomment to show error
				Err.Clear ' Clear error
			End If
		End If
	End Function

	' Resize file
	Public Function ResizeFile(fn, tn, width, height, interpolation)
		On Error Resume Next
		ResizeFile = False
		If CanResize Then
			ResizeObj.Open fn
			If Err.Number <> 0 Then
				Err.Clear
				Exit Function
			End If
			If GetResizeDimension(ResizeObj.OriginalWidth, ResizeObj.OriginalHeight, width, height) Then
				ResizeObj.Interpolation = interpolation
				ResizeObj.Width = width
				ResizeObj.Height = height
			End If
			ResizeObj.Save tn
			If Err.Number = 0 Then
				ResizeFile = True ' Return successful
			Else

				'Response.Write Err.Description ' Uncomment to show error
				Err.Clear ' Clear error
			End If
		End If
	End Function

	' Resize file to binary
	Public Function ResizeFileToBinary(fn, width, height, interpolation)
		On Error Resume Next
		ResizeFileToBinary = Null
		If CanResize Then
			ResizeObj.Open fn
			If Err.Number <> 0 Then
				Err.Clear
				Exit Function
			End If
			If GetResizeDimension(ResizeObj.OriginalWidth, ResizeObj.OriginalHeight, width, height) Then
				ResizeObj.Interpolation = interpolation
				ResizeObj.Width = width
				ResizeObj.Height = height
			End If
			ResizeFileToBinary = ResizeObj.Binary
		End If
		If Err.Number <> 0 Then ' Error

			'Response.Write Err.Description ' Uncomment to show error
			Err.Clear ' Clear error
		End If
	End Function

	' Set up resize width/height
	Function GetResizeDimension(ImageWidth, ImageHeight, ResizeWidth, ResizeHeight)
		On Error Resume Next
		Dim wrkImageWidth, wrkImageHeight, wrkResizeWidth, wrkResizeHeight
		GetResizeDimension = False
		wrkImageWidth = CLng(ImageWidth)
		wrkImageHeight = CLng(ImageHeight)
		wrkResizeWidth = CLng(ResizeWidth)
		wrkResizeHeight = CLng(ResizeHeight)
		If wrkImageWidth = "" Or IsNull(wrkImageWidth) Or Not IsNumeric(wrkImageWidth) Then wrkImageWidth = 0
		If wrkImageHeight = "" Or IsNull(wrkImageHeight) Or Not IsNumeric(wrkImageHeight) Then wrkImageHeight = 0
		If wrkResizeWidth = "" Or IsNull(wrkResizeWidth) Or Not IsNumeric(wrkResizeWidth) Then wrkResizeWidth = 0
		If wrkResizeHeight = "" Or IsNull(wrkResizeHeight) Or Not IsNumeric(wrkResizeHeight) Then wrkResizeHeight = 0

		' Keep Aspect Ratio
		If KeepAspectRatio Then
			If ResizeOption = "Width" And wrkResizeWidth > 0 And wrkResizeWidth < wrkImageWidth Then ' fix width
				wrkResizeHeight = 0
			ElseIf ResizeOption = "Height" And wrkResizeHeight > 0 And wrkResizeHeight < wrkImageHeight Then ' fix height
				wrkResizeWidth = 0
			ElseIf ResizeOption = "Smaller" And wrkResizeWidth > 0 And wrkResizeHeight > 0 Then ' always smaller
				If CDbl(wrkImageWidth/wrkResizeWidth) > CDbl(wrkImageHeight/wrkResizeHeight) Then
					wrkResizeHeight = wrkImageHeight * wrkResizeWidth / wrkImageWidth
				Else
					wrkResizeWidth = wrkImageWidth * wrkResizeHeight / wrkImageHeight
				End If
			ElseIf ResizeOption = "Larger" And wrkResizeWidth > 0 And wrkResizeHeight > 0 Then ' always larger
				If CDbl(wrkImageWidth/wrkResizeWidth) < CDbl(wrkImageHeight/wrkResizeHeight) Then
					wrkResizeHeight = wrkImageHeight * wrkResizeWidth / wrkImageWidth
				Else
					wrkResizeWidth = wrkImageWidth * wrkResizeHeight / wrkImageHeight
				End If
			End If
		End If
		If wrkResizeWidth <= 0 Then ' maintain aspect ratio, resize by height
			If wrkImageHeight > wrkResizeHeight Then
				ResizeHeight = wrkResizeHeight
				ResizeWidth = wrkImageWidth * wrkResizeHeight / wrkImageHeight
				GetResizeDimension = True
			End If
		ElseIf wrkResizeHeight <= 0 Then ' maintain aspect ratio, resize by width
			If wrkImageWidth > wrkResizeWidth Then
				ResizeWidth = wrkResizeWidth
				ResizeHeight = wrkImageHeight * wrkResizeWidth / wrkImageWidth
				GetResizeDimension = True
			End If
		Else
			If wrkImageWidth >= wrkResizeWidth And wrkImageHeight >= wrkResizeHeight Then
				ResizeWidth = wrkResizeWidth
				ResizeHeight = wrkResizeHeight
				GetResizeDimension = True
			End If
		End If
		If Err.Number <> 0 Then
			Err.Clear
		End If
	End Function

	' Save file by component
	Public Function SaveFileByComponent(folder, fn, filedata)
		On Error Resume Next
		SaveFileByComponent = False
		If CanResize Then
			If ew_CreateFolder(folder) Then
				ResizeObj.OpenBinary filedata
				If Err.Number <> 0 Then
					Err.Clear
					Exit Function
				End If
				ResizeObj.Save folder & fn
				If Err.Number <> 0 Then
					Err.Clear
					Exit Function
				End If
				SaveFileByComponent = True
			End If
		End If
	End Function
End Class

'
'  ASPJpeg Resize class (End)
'

%>
<%

'
'  ASP.NET Resize class (fallback)
'
Class cAspNetResize

	' Resize binary to thumbnail
	Public Function ResizeBinary(filedata, width, height, interpolation)
		ResizeBinary = True ' Use original binary
	End Function

	' Resize file to thumbnail file
	Public Function ResizeFile(fn, tn, width, height, interpolation)
		On Error Resume Next
		Dim fso

		' Just copy across
		Set fso = Server.Createobject("Scripting.FileSystemObject")
		If fso.FileExists(fn) Then
			fso.CopyFile fn, tn, True
		End If
		Set fso = Nothing
		If Err.Number = 0 Then ' Return successful
			ResizeFile = True
		Else ' Error

			'Response.Write Err.Description ' Uncomment to show error
			Err.Clear ' Clear error
			ResizeFile = False
		End If
	End Function

	' Resize file to binary
	Public Function ResizeFileToBinary(fn, width, height, interpolation)
		On Error Resume Next
		Dim oStream, fso
		ResizeFileToBinary = Null

		' Return file content in binary
		Set fso = Server.Createobject("Scripting.FileSystemObject")
		If fso.FileExists(fn) Then
			Set oStream = Server.CreateObject("ADODB.Stream")
			oStream.Type = 1 ' 1=adTypeBinary
			oStream.Open
			oStream.LoadFromFile fn
			ResizeFileToBinary = oStream.Read
			oStream.Close
			Set oStream = Nothing
		End If
		Set fso = Nothing
		If Err.Number <> 0 Then ' Return successful

			'Response.Write Err.Description ' Uncomment to show error
			Err.Clear ' Clear error
		End If
	End Function

	' Save file by component
	Public Function SaveFileByComponent(folder, fn, filedata)
		SaveFileByComponent = False ' Use ew_SaveFile
	End Function
End Class

'
' ASP.NET Resize class (End)
'----------------------------

%>
<script language="JavaScript" runat="server">
var window = {}; // For mobile-detect.js
</script>
<script language="JavaScript" src="js/mobile-detect.min.js" runat="server"></script>
<script language="JavaScript" src="js/ewvalidator.js" runat="server"></script>
<script language="JavaScript" src="js/md5.js" runat="server"></script>
<script language="JavaScript" src="js/tea-block.js" runat="server"></script>
<script language="JavaScript" runat="server">

function ew_IsMobile() {
	var md = new window.MobileDetect(String(Request.ServerVariables("HTTP_USER_AGENT")));
	return md.mobile() ? true : false;
}

function ew_RegExTest(expr, str, flags) {
	var re = new RegExp(expr, flags || "ig");
	return re.test(String(str));
}

function ew_RegExReplace(expr, subject, replacement, flags) {
	var re = new RegExp(expr, flags || "ig");
	return (subject === null) ? null : String(subject).replace(re, replacement);
}

function ew_RegExEscape(expr) {
	return expr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

function ew_UrlEncode(str) {
	return encodeURIComponent(str);
}

function ew_UrlDecode(str) {
	return decodeURIComponent(str);
}
var isScriptingDictionary = function(a) { return a && typeof a.Keys === "unknown"; }; // Assume Scripting.Dictionary
var isVBArray = function(a) {
	if (typeof a === "unknown") {
		try {
			var tmp = new VBArray(a);
			return true;
		} catch(err) {
			return false;
		}
	}
	return false;
};
// JSON
var JSON = {
	parse: function(sJSON) { return eval('(' + sJSON + ')'); },
	stringify: (function() {
		var toString = Object.prototype.toString;
		var isArray = function(a) { return toString.call(a) === '[object Array]'; };
		var escMap = {'"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t'};
		var escFunc = function(m) { return escMap[m] || '\\u' + (m.charCodeAt(0) + 0x10000).toString(16).substr(1); };
		var escRE = /[\\"\u0000-\u001F\u2028\u2029]/g;
		return function stringify(value) {
			if (value == null) {
				return 'null';
			} else if (typeof value === 'number') {
				return isFinite(value) ? value.toString() : 'null';
			} else if (typeof value === 'boolean') {
				return value.toString();
			} else if (typeof value === 'object') {
				if (typeof value.toJSON === 'function') {
					return stringify(value.toJSON());
				} else if (isArray(value)) {
					var res = '[';
					for (var i = 0; i < value.length; i++)
						res += (i ? ', ' : '') + stringify(value[i]);
					return res + ']';
				} else if (toString.call(value) === '[object Object]') {
					if (isScriptingDictionary(value)) { // Assume Scripting.Dictionary //***
						var tmp = [], keys = (new VBArray(value.Keys())).toArray();
						for (var i = 0; i < keys.length; i++)
							tmp.push(stringify(keys[i]) + ': ' + stringify(value(keys[i])));
						return '{' + tmp.join(', ') + '}';
					} else if (value instanceof Dictionary && value.IsArray()) { // Array as Dictionary //***
						var val = value.ToJSArray();
						var res = '[';
						for (var i = 0; i < val.length; i++)
							res += (i ? ', ' : '') + stringify(val[i]);
						return res + ']';
					} else { // Object
						var tmp = [];
						for (var k in value) {
							if (value.hasOwnProperty(k))
								tmp.push(stringify(k) + ': ' + stringify(value[k]));
						}
						return '{' + tmp.join(', ') + '}';
					}
				}
			} else if (typeof value === 'unknown') {
				if (isVBArray(value)) {
					var val = (new VBArray(value)).toArray(), res = '[';
					for (var i = 0; i < val.length; i++)
						res += (i ? ', ' : '') + stringify(val[i]);
					return res + ']';
				}
			}
			return '"' + String(value).replace(escRE, escFunc) + '"';
		};
	})()
};
// Dictionary
// Note: key is case-sensitive
var Dictionary = (function() {
	var isArray = function(a) { return Object.prototype.toString.call(a) === '[object Array]'; };
	// Constructor, e.g. Dictionary(VBArray, JSArray, Scripting.Dictionary, JSON, ...)

	function Dictionary() {
		if (!(this instanceof Dictionary)) {
			var d = new Dictionary();
			for (var i = 0; i < arguments.length; i++) {
				var data = arguments[i];
				if (isVBArray(data) || isArray(data)) // Array
					d.AddArray(data);
				else if (isScriptingDictionary(data)) // Scripting.Dictionary
					d.AddDictionary(data);
				else if (typeof data === "string") // Assume JSON
					d.AddJson(data);
			}
			return d;
		}
	}
	// Get dictionary count
	Dictionary.prototype.Count = function() {
		var count = 0;
		for (var key in this) {
			if (this.hasOwnProperty(key))
				count++;
		}
		return count;
	};
	// Get dictionary item by key
	Dictionary.prototype.Get = function(key) {
		return this.hasOwnProperty(key) ? this[key] : null;
	}
	// Get dictionary key by value
	Dictionary.prototype.GetKey = function(value) {
		for (var key in this) {
			if (this.hasOwnProperty(key) && this[key] === value)
				return key;
		}
		return null;
	}
	// Change key in dictionary
	Dictionary.prototype.ChangeKey = function(oldKey, newKey) {
		if (oldKey !== newKey && this.hasOwnProperty(oldKey) && !this.hasOwnProperty(newKey)) {
			this[newKey] = this[oldKey];
			delete this[oldKey];
			return true;
		}
		return false;
	}
	// Is array
	Dictionary.prototype.IsArray = function() {
		for (var key in this) {
			if (this.hasOwnProperty(key) && !key.match(/^\d+$/))
				return false;
		}
		return true;
	}
	// Add a value and returns the new length
	Dictionary.prototype.Push = function(value) {
		this.Add(this.Count(), value);
		return this.Count();
	}
	// Return values as delimited values
	Dictionary.prototype.Join = function(separator) {
		var values = [];
		for (var key in this) {
			if (this.hasOwnProperty(key))
				values.push(this[key]);
		}
		return values.join(separator);
	}
	// Add dictionary item by key
	Dictionary.prototype.Add = function(key, value) {
		this[key] = value;
	}
	// Set dictionary item by key
	Dictionary.prototype.Set = function(key, value) {
		this[key] = value;
	}
	// Add dictionary items by json, e.g. [["k1","v1"],["k2","v2"]] or {"k1":"v1","k2":"v2"}
	Dictionary.prototype.AddJson = function(json) {
		try {
			var o = JSON.parse(json);
			if (isArray(o)) {
				for (var i = 0, ln = o.length; i < ln; i++) {
					var kvp = o[i];
					this.Add(kvp[0], kvp[1]);
				}
			} else if (typeof o === 'object') {
				for (var k in o)
					if (o.hasOwnProperty(k))
						this.Add(k, o[k]);
			}
		} catch(err) {}
	}
	// Add dictionary items by array or VBArray, e.g. Array(Array("k1", "v1"), Array("k2","v2")) or Array("v1", "v2")
	Dictionary.prototype.AddArray = function(a) {
		if (isVBArray(a)) { // VBArray
			var vbar = new VBArray(a);
			var ar = vbar.toArray();
			for (var i = 0, ln = ar.length; i < ln; i++) {
				if (isVBArray(ar[i])) { // VBArray
					var kvp = ar[i].toArray();
					this.Add(kvp[0], kvp[1]);
				} else {
					this.Push(ar[i]);
				}
			}
		} else if (isArray(a)) {
			for (var i = 0, ln = a.length; i < ln; i++) {
				if (isArray(a[i])) { // Assume ["key, "value"]
					var kvp = a[i];
					this.Add(kvp[0], kvp[1]);
				} else {
					this.Push(a[i]);
				}
			}
		}
	}
	// Add dictionary items by Scripting.Dictionaryy
	Dictionary.prototype.AddDictionary = function(dict) {
		if (isScriptingDictionary(dict)) {
			var keys = new VBArray(dict.Keys()).toArray();
			for (var i = 0, ln = keys.length; i < ln; i++) {
				var key = keys[i];
				this.Add(key, dict(key));
			}
		}
	}
	// Clear dictionary
	Dictionary.prototype.Clear = function() {
		var result = true;
		for (var key in this) {
			if (this.hasOwnProperty(key))
				result = result && delete this[key];
		}
		return result;
	}
	// Check if dictionary contains key
	Dictionary.prototype.ContainsKey = function(key) {
		return this.hasOwnProperty(key);
	}
	// Check if dictionary contains value
	Dictionary.prototype.ContainsValue = function(value) {
		for (var key in this) {
			if (this.hasOwnProperty(key) && this[key] === value)
				return true;
		}
		return false;
	}
	// Remove item in dictionary by key
	Dictionary.prototype.Remove = function(key) {
		if (this.hasOwnProperty(key))
			return delete this[key];
		return false;
	}
	// Remove item(s) in dictionary by value
	Dictionary.prototype.RemoveValue = function(value) {
		var result = true;
		for (var key in this) {
			if (this.hasOwnProperty(key) && this[key] === value)
				result = result && delete this[key];
		}
		return result;
	}
	// Remove duplicate values in dictionary
	Dictionary.prototype.Unique = function() {
		var ar = Dictionary();
		for (var key in this) {
			if (this.hasOwnProperty(key)) {
				if (!ar.ContainsValue(this[key]))
					ar.Push(this[key]);
				else
					delete this[key];
			}
		}
	}
	// To string
	// Note: toString, not ToString
	Dictionary.prototype.toString = function() {
		return "Dictionary";
	}
	// To JSON
	Dictionary.prototype.ToJson = function() {
		if (this.IsArray())
			return JSON.stringify(this.ToJSArray());
		else
			return JSON.stringify(this);
	}
	// To array
	Dictionary.prototype.ToJSArray = function() {
		var ar = [], isArray = this.IsArray();
		for (var key in this) {
			if (this.hasOwnProperty(key)) {
				if (isArray)
					ar.push(this[key]);
				else
					ar.push([key, this[key]]);
			}
		}
		return ar;
	};
	// To JSON array
	Dictionary.prototype.ToJsonArray = function() {
		return JSON.stringify(this.ToJSArray());
	}
	// Return VBScript Dictionary
	Dictionary.prototype.ToDictionary = function() {
		var dict = new ActiveXObject("Scripting.Dictionary");
		for (var key in this) {
			if (this.hasOwnProperty(key))
				dict.Add(key, this[key]);
		}
		return dict;
	}
	// Return values as one-dimensional VBArray
	Dictionary.prototype.ToArray = function() {
		return this.ToDictionary().Items();
	}
	// Return keys as one-dimensional VBArray
	Dictionary.prototype.Keys = function() {
		return this.ToDictionary().Keys();
	}
	// Return values as one-dimensional VBArray (same as ToArray)
	Dictionary.prototype.Values = function() {
		return this.ToDictionary().Items();
	}
	return Dictionary;
}());

function ew_Encrypt(plaintext, key) {
	return Tea.encrypt(plaintext, key || EW_RANDOM_KEY).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}

function ew_Decrypt(ciphertext, key) {
	ciphertext = String(ciphertext).replace(/-/g, "+").replace(/_/g, "/");
	return Tea.decrypt(ciphertext, key || EW_RANDOM_KEY);
}
</script>

Youez - 2016 - github.com/yon3zu
LinuXploit