| @@ -0,0 +1,78 @@ | |||||
| # RouteKit Classic ASP - MVC Starter | |||||
| A clean starting point for building Classic ASP applications with the RouteKit MVC framework. | |||||
| ## Quick Setup | |||||
| 1. Copy this folder to your IIS server | |||||
| 2. Point your IIS site root to the `public/` folder | |||||
| 3. Update `public/web.config`: | |||||
| - Set `ConnectionString` to your database path | |||||
| - Set `ErrorLogPath` if you want file logging | |||||
| 4. Ensure the IIS URL Rewrite module is installed | |||||
| 5. Browse to `http://localhost/` — you should see the welcome page | |||||
| ## Project Structure | |||||
| ``` | |||||
| MVC-Starter/ | |||||
| public/ # IIS ROOT - point your IIS site here | |||||
| Default.asp # Front controller (entry point) | |||||
| web.config # IIS config, routes, connection strings | |||||
| core/ # Framework core (do not modify) | |||||
| autoload_core.asp # Loads all core libraries | |||||
| router.wsc # Route matching engine | |||||
| mvc.asp # MVC dispatcher | |||||
| lib.*.asp # Core libraries | |||||
| app/ | |||||
| controllers/ # Your controllers go here | |||||
| views/ # Your views go here | |||||
| shared/ # Shared layout (header, footer) | |||||
| models/ # POBOs go here | |||||
| repositories/ # Repository classes go here | |||||
| db/ | |||||
| migrations/ # Database migrations | |||||
| webdata.accdb # Access database | |||||
| scripts/ # Code generators | |||||
| generateController.vbs | |||||
| generateMigration.vbs | |||||
| GenerateRepo.vbs | |||||
| runMigrations.vbs | |||||
| ``` | |||||
| ## Adding a New Feature | |||||
| ### 1. Generate a migration | |||||
| ```bash | |||||
| cscript //nologo scripts\generateMigration.vbs create_my_table | |||||
| ``` | |||||
| ### 2. Generate POBO and Repository | |||||
| ```bash | |||||
| cscript //nologo scripts\GenerateRepo.vbs /table:my_table /pk:id | |||||
| ``` | |||||
| Move generated files to `app/models/` and `app/repositories/`. | |||||
| ### 3. Generate a controller | |||||
| ```bash | |||||
| cscript //nologo scripts\generateController.vbs MyController "Index;Show(id);Create;Store" | |||||
| ``` | |||||
| Move generated file to `app/controllers/`. | |||||
| ### 4. Wire it up | |||||
| - Register in `core/lib.ControllerRegistry.asp` | |||||
| - Include in `app/controllers/autoload_controllers.asp` | |||||
| - Add routes in `public/Default.asp` | |||||
| - Create views in `app/views/MyController/` | |||||
| ## Included Controllers | |||||
| - **HomeController** - Welcome page at `/` | |||||
| - **ErrorController** - 404 handler at `/404` | |||||
| ## Requirements | |||||
| - Windows Server with IIS | |||||
| - Classic ASP enabled | |||||
| - IIS URL Rewrite module | |||||
| - Microsoft Access Database Engine (for .accdb support) | |||||
| @@ -0,0 +1,37 @@ | |||||
| <% | |||||
| Class ErrorController_Class | |||||
| Private m_useLayout | |||||
| Private Sub Class_Initialize() | |||||
| m_useLayout = True | |||||
| End Sub | |||||
| Public Property Get useLayout | |||||
| useLayout = m_useLayout | |||||
| End Property | |||||
| Public Property Let useLayout(v) | |||||
| m_useLayout = v | |||||
| End Property | |||||
| Public Sub Index() | |||||
| Response.Write "Index called" | |||||
| End Sub | |||||
| Public Sub NotFound() | |||||
| Response.Status = "404 Not Found" | |||||
| %> | |||||
| <!--#include file="../Views/Error/NotFound.asp" --> | |||||
| <% | |||||
| End Sub | |||||
| End Class | |||||
| Dim ErrorController_Class__Singleton | |||||
| Function ErrorController() | |||||
| If IsEmpty(ErrorController_Class__Singleton) Then | |||||
| Set ErrorController_Class__Singleton = New ErrorController_Class | |||||
| End If | |||||
| Set ErrorController = ErrorController_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,42 @@ | |||||
| <% | |||||
| Class HomeController_Class | |||||
| Private m_useLayout | |||||
| Private m_title | |||||
| Private Sub Class_Initialize() | |||||
| m_useLayout = True | |||||
| m_title = "Home" | |||||
| End Sub | |||||
| Public Property Get useLayout | |||||
| useLayout = m_useLayout | |||||
| End Property | |||||
| Public Property Let useLayout(v) | |||||
| m_useLayout = v | |||||
| End Property | |||||
| Public Property Get Title | |||||
| Title = m_title | |||||
| End Property | |||||
| Public Property Let Title(v) | |||||
| m_title = v | |||||
| End Property | |||||
| Public Sub index() | |||||
| %> | |||||
| <!--#include file="../views/Home/index.asp" --> | |||||
| <% | |||||
| End Sub | |||||
| End Class | |||||
| Dim HomeController_Class__Singleton | |||||
| Function HomeController() | |||||
| If IsEmpty(HomeController_Class__Singleton) Then | |||||
| Set HomeController_Class__Singleton = New HomeController_Class | |||||
| End If | |||||
| Set HomeController = HomeController_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,2 @@ | |||||
| <!--#include file="HomeController.asp" --> | |||||
| <!--#include file="ErrorController.asp" --> | |||||
| @@ -0,0 +1,55 @@ | |||||
| <div class="main-content"> | |||||
| <div class="d-flex align-items-center justify-content-center min-vh-100 bg-light"> | |||||
| <div class="card shadow-sm border-0 text-center p-4 p-md-5 error-card"> | |||||
| <div class="mb-3"> | |||||
| <i class="bi bi-exclamation-triangle-fill display-3 text-warning"></i> | |||||
| </div> | |||||
| <h1 class="h3 mb-3">404 - Page Not Found</h1> | |||||
| <p class="text-muted mb-3"> | |||||
| The page you’re looking for could not be found. It may have been moved, deleted, | |||||
| or the address might be incorrect. | |||||
| </p> | |||||
| <% | |||||
| Dim redirectSeconds : redirectSeconds = GetAppSetting("Error404RedirectSeconds") | |||||
| If redirectSeconds = "nothing" Or Not IsNumeric(redirectSeconds) Then redirectSeconds = 5 | |||||
| %> | |||||
| <p class="text-muted mb-3"> | |||||
| You'll be redirected to the homepage in | |||||
| <span id="countdown"><%= redirectSeconds %></span> seconds. | |||||
| </p> | |||||
| <a href="/" class="btn btn-primary mt-2"> | |||||
| Go to Homepage Now | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <style> | |||||
| .error-card { | |||||
| max-width: 540px; | |||||
| border-radius: 1rem; | |||||
| } | |||||
| .error-card .bi { | |||||
| line-height: 1; | |||||
| } | |||||
| </style> | |||||
| <script> | |||||
| (function () { | |||||
| let seconds = <%= redirectSeconds %>; | |||||
| const countdown = document.getElementById('countdown'); | |||||
| const timer = setInterval(function () { | |||||
| seconds--; | |||||
| if (countdown) { | |||||
| countdown.textContent = seconds; | |||||
| } | |||||
| if (seconds <= 0) { | |||||
| clearInterval(timer); | |||||
| window.location.href = '/'; | |||||
| } | |||||
| }, 1000); | |||||
| })(); | |||||
| </script> | |||||
| @@ -0,0 +1,85 @@ | |||||
| <div class="row mb-4"> | |||||
| <div class="col-lg-8"> | |||||
| <div class="card shadow-sm mb-3"> | |||||
| <div class="card-body"> | |||||
| <h1 class="h3 mb-3">Welcome to RouteKit Classic ASP</h1> | |||||
| <p class="text-muted"> | |||||
| Your lightweight, opinionated MVC-style framework for Classic ASP. | |||||
| </p> | |||||
| <p> | |||||
| This <code>Home.Index</code> view is using the shared | |||||
| <code>Header.asp</code> and <code>Footer.asp</code> layout files. | |||||
| </p> | |||||
| <p class="mb-0"> | |||||
| Start by wiring up your controllers, repositories, and views — this page is just a | |||||
| friendly placeholder so you know everything is hooked up correctly. | |||||
| </p> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-lg-4"> | |||||
| <div class="card border-0 bg-light mb-3"> | |||||
| <div class="card-body"> | |||||
| <h2 class="h5 mb-3">Quick info</h2> | |||||
| <ul class="list-unstyled mb-0 small"> | |||||
| <li class="mb-1"> | |||||
| <strong>View:</strong> | |||||
| <code>app/Views/Home.Index.asp</code> | |||||
| </li> | |||||
| <li class="mb-1"> | |||||
| <strong>Layout:</strong> | |||||
| <code>Shared/Header.asp</code> & <code>Shared/Footer.asp</code> | |||||
| </li> | |||||
| <li class="mb-1"> | |||||
| <strong>Default route:</strong> | |||||
| typically <code>/Home/Index</code> or <code>/</code> via the dispatcher. | |||||
| </li> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row gy-3"> | |||||
| <div class="col-md-4"> | |||||
| <div class="card h-100 shadow-sm"> | |||||
| <div class="card-body"> | |||||
| <h2 class="h5">Next step: Controllers</h2> | |||||
| <p class="small text-muted"> | |||||
| Use your <code>generateController.vbs</code> script to scaffold new controllers. | |||||
| </p> | |||||
| <pre class="small mb-0"><code>cscript //nologo Scripts\generateController.vbs ^ | |||||
| Home "Index"</code></pre> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-4"> | |||||
| <div class="card h-100 shadow-sm"> | |||||
| <div class="card-body"> | |||||
| <h2 class="h5">POBO & Repository</h2> | |||||
| <p class="small text-muted"> | |||||
| Generate strongly-typed POBOs and repositories from your Access/SQL schema. | |||||
| </p> | |||||
| <pre class="small mb-0"><code>cscript //nologo Scripts\GenerateRepo.vbs ^ | |||||
| /table:Users /pk:UserId</code></pre> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-md-4"> | |||||
| <div class="card h-100 shadow-sm"> | |||||
| <div class="card-body"> | |||||
| <h2 class="h5">Where to put stuff</h2> | |||||
| <ul class="small mb-0"> | |||||
| <li><code>/core/</code> – framework libs (DAL, routing, helpers)</li> | |||||
| <li><code>/app/Views/</code> – pages like this one</li> | |||||
| <li><code>/public/</code> – IIS root (Default.asp, web.config)</li> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @@ -0,0 +1,8 @@ | |||||
| </div> | |||||
| </main> | |||||
| <!-- Bootstrap bundle (with Popper) --> | |||||
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,85 @@ | |||||
| <!doctype html> | |||||
| <% | |||||
| Response.Charset = "utf-8" | |||||
| Response.CodePage = 65001 | |||||
| ' Safe title resolution | |||||
| Dim pageTitle | |||||
| If IsObject(CurrentController) Then | |||||
| On Error Resume Next | |||||
| pageTitle = CurrentController.Title | |||||
| If Err.Number <> 0 Then | |||||
| pageTitle = "RouteKit Classic ASP" | |||||
| Err.Clear | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End If | |||||
| If Len(pageTitle) = 0 Then pageTitle = "RouteKit Classic ASP" | |||||
| %> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="utf-8" /> | |||||
| <title><%= pageTitle %></title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |||||
| <!-- Bootstrap CSS --> | |||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" /> | |||||
| <!-- Bootstrap Icons (optional) --> | |||||
| <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" /> | |||||
| <!-- App CSS --> | |||||
| <link href="/css/site.css" rel="stylesheet" /> | |||||
| <style> | |||||
| body { | |||||
| background-color: #f5f5f5; | |||||
| } | |||||
| .rk-navbar-brand { | |||||
| font-weight: 600; | |||||
| letter-spacing: 0.03em; | |||||
| } | |||||
| main.routekit-main { | |||||
| padding-top: 1.5rem; | |||||
| padding-bottom: 2rem; | |||||
| } | |||||
| </style> | |||||
| </head> | |||||
| <body> | |||||
| <!-- Top navbar --> | |||||
| <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> | |||||
| <div class="container-fluid"> | |||||
| <a class="navbar-brand rk-navbar-brand" href="/"> | |||||
| RouteKit | |||||
| <span class="text-secondary small">Classic ASP</span> | |||||
| </a> | |||||
| <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#rkMainNav" aria-controls="rkMainNav" aria-expanded="false" aria-label="Toggle navigation"> | |||||
| <span class="navbar-toggler-icon"></span> | |||||
| </button> | |||||
| <div class="collapse navbar-collapse" id="rkMainNav"> | |||||
| <ul class="navbar-nav me-auto mb-2 mb-lg-0"> | |||||
| <li class="nav-item"> | |||||
| <a class="nav-link" href="/">Home</a> | |||||
| </li> | |||||
| <!-- Add more shared nav items here --> | |||||
| ' <li class="nav-item"><a class="nav-link" href="/docs">Docs</a></li> | |||||
| </ul> | |||||
| ' Right-side area (e.g., user info / login) | |||||
| ' You can wire this up to your auth later. | |||||
| <ul class="navbar-nav mb-2 mb-lg-0"> | |||||
| ' <li class="nav-item"> | |||||
| ' <a class="nav-link" href="/login">Login</a> | |||||
| ' </li> | |||||
| </ul> | |||||
| </div> | |||||
| </div> | |||||
| </nav> | |||||
| <!-- Main container for views --> | |||||
| <main class="routekit-main"> | |||||
| <div class="container"> | |||||
| <% Flash().ShowErrorsIfPresent : Flash().ShowSuccessIfPresent %> | |||||
| @@ -0,0 +1,21 @@ | |||||
| <!--#include file="../Core/helpers.asp"--> | |||||
| <!--#include file="../Core/lib.ErrorHandler.asp"--> | |||||
| <!--#include file="../Core/lib.ControllerRegistry.asp"--> | |||||
| <!--#include file="../Core/mvc.asp"--> | |||||
| <!--#include file="../Core/lib.DAL.asp"--> | |||||
| <!--#include file="../Core/lib.Data.asp"--> | |||||
| <!--#include file="../Core/lib.Migrations.asp"--> | |||||
| <% Dim router : Set router = GetObject("script:"& Server.MapPath("../Core/router.wsc") & "") %> | |||||
| <!--#include file="../Core/lib.collections.asp"--> | |||||
| <!--#include file="../Core/lib.HTML.asp"--> | |||||
| <!--#include file="../Core/lib.Routes.asp"--> | |||||
| <!--#include file="../Core/lib.Flash.asp"--> | |||||
| <!--#include file="../Core/lib.FormCache.asp"--> | |||||
| <!--#include file="../Core/lib.HTML.Security.asp"--> | |||||
| <!--#include file="../Core/lib.AutoMapper.asp"--> | |||||
| <!--#include file="../Core/lib.CDOEmail.asp"--> | |||||
| <!--#include file="../Core/lib.Upload.asp"--> | |||||
| <!--#include file="../Core/lib.json.asp"--> | |||||
| <!--#include file="../Core/lib.helpers.asp"--> | |||||
| <!--#include file="../Core/lib.crypto.helper.asp"--> | |||||
| <!--#include file="../Core/lib.Enumerable.asp"--> | |||||
| @@ -0,0 +1,536 @@ | |||||
| <% | |||||
| Function QuoteValue(val) | |||||
| if IsWrappedInSingleQuotes(val) then | |||||
| QuoteValue = val | |||||
| Exit Function | |||||
| end if | |||||
| Select Case VarType(val) | |||||
| Case vbString | |||||
| QuoteValue = "'" & Replace(val, "'", "''") & "'" | |||||
| Case vbDate | |||||
| if conn.Provider = "Microsoft.Jet.OLEDB.4.0" or conn.Provider = "Microsoft.ACE.OLEDB.12.0" then | |||||
| QuoteValue = "#" & FormatDateTime(val, 0) & "#" | |||||
| else | |||||
| ' SQL Server | |||||
| QuoteValue = "'" & FormatDateTime(val, 0) & "'" | |||||
| end if | |||||
| Case vbNull, vbEmpty | |||||
| QuoteValue = "Null" | |||||
| Case vbBoolean | |||||
| ' Return boolean values without quotes | |||||
| QuoteValue = "'" & CStr(val) & "'" | |||||
| Case Else | |||||
| If IsNumeric(val) Then | |||||
| QuoteValue = CLng(val) | |||||
| Else | |||||
| QuoteValue = CStr(val) | |||||
| End If | |||||
| End Select | |||||
| End Function | |||||
| Public Function GetAppSetting(key) | |||||
| Dim cacheKey, xml, nodes, node, i | |||||
| cacheKey = "AppSetting_" & key | |||||
| ' Check Application cache first for performance | |||||
| If Not IsEmpty(Application(cacheKey)) Then | |||||
| GetAppSetting = Application(cacheKey) | |||||
| Exit Function | |||||
| End If | |||||
| ' Load from web.config only if not cached | |||||
| Set xml = Server.CreateObject("Microsoft.XMLDOM") | |||||
| xml.Load(Server.MapPath("web.config")) | |||||
| Set nodes = xml.selectNodes("//appSettings/add") | |||||
| For i = 0 To nodes.Length - 1 | |||||
| Set node = nodes.Item(i) | |||||
| If node.getAttribute("key") = key Then | |||||
| GetAppSetting = node.getAttribute("value") | |||||
| ' Cache the value for subsequent requests | |||||
| Application.Lock | |||||
| Application(cacheKey) = GetAppSetting | |||||
| Application.Unlock | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| GetAppSetting = "nothing" | |||||
| End Function | |||||
| Public Sub ShowServerVariables | |||||
| Dim varName, htmlTable | |||||
| htmlTable = "<table border='1' cellspacing='0' cellpadding='5'>" | |||||
| htmlTable = htmlTable & "<thead><tr><th>Variable Name</th><th>Value</th></tr></thead><tbody>" | |||||
| ' Loop through all server variables | |||||
| For Each varName In Request.ServerVariables | |||||
| htmlTable = htmlTable & "<tr>" | |||||
| htmlTable = htmlTable & "<td>" & Server.HTMLEncode(varName) & "</td>" | |||||
| htmlTable = htmlTable & "<td>" & Server.HTMLEncode(Request.ServerVariables(varName)) & "</td>" | |||||
| htmlTable = htmlTable & "</tr>" | |||||
| Next | |||||
| htmlTable = htmlTable & "</tbody></table>" | |||||
| ' Output the HTML table | |||||
| Response.Write(htmlTable) | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------ | |||||
| ' Utility: IIf Function for VBScript | |||||
| ' Usage: result = IIf(condition, trueValue, falseValue) | |||||
| '------------------------------------------------------------------------------ | |||||
| Function IIf(condition, trueValue, falseValue) | |||||
| On Error Resume Next | |||||
| If CBool(condition) Then | |||||
| IIf = trueValue | |||||
| Else | |||||
| IIf = falseValue | |||||
| End If | |||||
| If Err.Number <> 0 Then | |||||
| ' Optional: handle or log error in conversion/evaluation | |||||
| Err.Clear | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Function | |||||
| '----------------------------- | |||||
| ' Utility: Generic Error Reporter | |||||
| '----------------------------- | |||||
| Public Sub ErrorCheck(context) | |||||
| If Err.Number <> 0 Then | |||||
| Dim errHtml | |||||
| errHtml = "<div style='padding:10px; border:2px solid red; background:#fdd; font-family:Verdana; font-size:12px;'>" | |||||
| errHtml = errHtml & "<strong>Error occurred" & IIf(Not IsEmpty(context) And context <> "", ": " & context, "") & "</strong><br />" | |||||
| errHtml = errHtml & "<em>Time:</em> " & Now() & "<br />" | |||||
| errHtml = errHtml & "<em>Number:</em> " & Err.Number & "<br />" | |||||
| errHtml = errHtml & "<em>Description:</em> " & Server.HTMLEncode(Err.Description) & "<br />" | |||||
| If Len(Err.Source) > 0 Then | |||||
| errHtml = errHtml & "<em>Source:</em> " & Server.HTMLEncode(Err.Source) & "<br />" | |||||
| End If | |||||
| errHtml = errHtml & "</div>" | |||||
| Response.Write errHtml | |||||
| Err.Clear | |||||
| End If | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------ | |||||
| ' Utility: TrimQueryParams | |||||
| ' Removes everything from the first "?" or "&" onward. | |||||
| ' Usage: | |||||
| ' CleanPath = TrimQueryParams(rawPath) | |||||
| '------------------------------------------------------------------------------ | |||||
| Function TrimQueryParams(rawPath) | |||||
| Dim posQ, posA, cutPos | |||||
| ' find the first occurrences of "?" and "&" | |||||
| posQ = InStr(rawPath, "?") | |||||
| posA = InStr(rawPath, "&") | |||||
| ' determine the earliest cut position (>0) | |||||
| If posQ > 0 And posA > 0 Then | |||||
| cutPos = IIf(posQ < posA, posQ, posA) | |||||
| ElseIf posQ > 0 Then | |||||
| cutPos = posQ | |||||
| ElseIf posA > 0 Then | |||||
| cutPos = posA | |||||
| Else | |||||
| cutPos = 0 | |||||
| End If | |||||
| ' if found, return up to just before that char | |||||
| If cutPos > 0 Then | |||||
| TrimQueryParams = Left(rawPath, cutPos - 1) | |||||
| Else | |||||
| TrimQueryParams = rawPath | |||||
| End If | |||||
| End Function | |||||
| Sub Destroy(o) | |||||
| if isobject(o) then | |||||
| if not o is nothing then | |||||
| on error resume next | |||||
| o.close | |||||
| on error goto 0 | |||||
| set o = nothing | |||||
| end if | |||||
| end if | |||||
| End Sub | |||||
| 'prepends indents | |||||
| Private Sub puti(v) | |||||
| put Spaces(m_indent) & v | |||||
| End Sub | |||||
| Sub put(v) | |||||
| Select Case typename(v) | |||||
| Case "LinkedList_Class" : response.write join(v.TO_Array, ", ") | |||||
| Case "DynamicArray_Class" : response.write JoinList(v) | |||||
| Case "Variant()" : response.write join(v, ", ") | |||||
| Case else : response.write v | |||||
| End Select | |||||
| End Sub | |||||
| Sub put_ | |||||
| put "<br>" | |||||
| End Sub | |||||
| Sub putl(v) | |||||
| put v | |||||
| put_ | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Wrapper for Server.HTMLEncode() -- makes it easier on the eyes when reading the HTML code | |||||
| Function H(s) | |||||
| If Not IsEmpty(s) and Not IsNull(s) then | |||||
| H = Server.HTMLEncode(s) | |||||
| Else | |||||
| H = "" | |||||
| End If | |||||
| End Function | |||||
| '======================================================================================================================= | |||||
| ' Adapted from Tolerable library | |||||
| '======================================================================================================================= | |||||
| ' This subroutine allows us to ignore the difference | |||||
| ' between object and primitive assignments. This is | |||||
| ' essential for many parts of the engine. | |||||
| Public Sub Assign(ByRef var, ByVal val) | |||||
| If IsObject(val) Then | |||||
| Set var = val | |||||
| Else | |||||
| var = val | |||||
| End If | |||||
| End Sub | |||||
| ' This is similar to the ? : operator of other languages. | |||||
| ' Unfortunately, both the if_true and if_false "branches" | |||||
| ' will be evalauted before the condition is even checked. So, | |||||
| ' you'll only want to use this for simple expressions. | |||||
| Public Function Choice(ByVal cond, ByVal if_true, ByVal if_false) | |||||
| If cond Then | |||||
| Assign Choice, if_true | |||||
| Else | |||||
| Assign Choice, if_false | |||||
| End If | |||||
| End Function | |||||
| ' Allows single-quotes to be used in place of double-quotes. | |||||
| ' Basically, this is a cheap trick that can make it easier | |||||
| ' to specify Lambdas. | |||||
| Public Function Q(ByVal input) | |||||
| Q = Replace(input, "'", """") | |||||
| End Function | |||||
| Function SurroundString(inputVar) | |||||
| If VarType(inputVar) = vbString Then | |||||
| SurroundString = """" & inputVar & """" | |||||
| Else | |||||
| SurroundString = inputVar | |||||
| End If | |||||
| End Function | |||||
| Function SurroundStringInArray(arr) | |||||
| Dim i | |||||
| For i = LBound(arr) To UBound(arr) | |||||
| If IsString(arr(i)) Then | |||||
| arr(i) = """" & arr(i) & """" | |||||
| End If | |||||
| Next | |||||
| SurroundStringInArray = arr | |||||
| End Function | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| 'Boolean type checkers | |||||
| 'Don't forget IsArray is built-in! | |||||
| Function IsString(value) | |||||
| IsString = Choice(typename(value) = "String", true, false) | |||||
| End Function | |||||
| Function IsDict(value) | |||||
| IsDict = Choice(typename(value) = "Dictionary", true, false) | |||||
| End Function | |||||
| Function IsRecordset(value) | |||||
| IsRecordset = Choice(typename(value) = "Recordset", true, false) | |||||
| End Function | |||||
| Function IsLinkedList(value) | |||||
| IsLinkedList = Choice(typename(value) = "LinkedList_Class", true, false) | |||||
| End Function | |||||
| Function IsArray(value) | |||||
| IsArray = Choice(typename(value) = "Variant()", true, false) | |||||
| End Function | |||||
| '-------------------------------------------------------------------- | |||||
| ' Returns True when the named key is present in Session.Contents | |||||
| ' • Handles scalars (String, Integer, etc.), objects, Empty, and Null | |||||
| '-------------------------------------------------------------------- | |||||
| Function SessionHasKey(keyName) | |||||
| 'Loop over the existing keys—Session.Contents is like a dictionary | |||||
| Dim k | |||||
| For Each k In Session.Contents | |||||
| If StrComp(k, keyName, vbTextCompare) = 0 Then | |||||
| SessionHasKey = True | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| SessionHasKey = False 'not found | |||||
| End Function | |||||
| Function RenderObjectsAsTable(arr,boolUseTabulator) | |||||
| Dim html, propNames, i, j, obj, val, pkName, isPk | |||||
| If IsEmpty(arr) Or Not IsArray(arr) Then | |||||
| RenderObjectsAsTable = "<!-- no data -->" | |||||
| Exit Function | |||||
| End If | |||||
| Set obj = arr(0) | |||||
| On Error Resume Next | |||||
| propNames = obj.Properties | |||||
| pkName = obj.PrimaryKey | |||||
| On Error GoTo 0 | |||||
| If IsEmpty(propNames) Or Len(pkName) = 0 Then | |||||
| RenderObjectsAsTable = "<!-- missing properties or primary key -->" | |||||
| Exit Function | |||||
| End If | |||||
| html = "<div class='table-wrapper'>" & vbCrLf | |||||
| html = html & "<table class='pobo-table' id='pobo-table'>" & vbCrLf | |||||
| html = html & " <thead><tr>" & vbCrLf | |||||
| For i = 0 To UBound(propNames) | |||||
| html = html & " <th>" & Server.HTMLEncode(propNames(i)) & "</th>" & vbCrLf | |||||
| Next | |||||
| html = html & " </tr></thead>" & vbCrLf | |||||
| html = html & " <tbody>" & vbCrLf | |||||
| For j = 0 To UBound(arr) | |||||
| Set obj = arr(j) | |||||
| html = html & " <tr>" & vbCrLf | |||||
| For i = 0 To UBound(propNames) | |||||
| val = GetDynamicProperty(obj, propNames(i)) | |||||
| isPk = (StrComp(propNames(i), pkName, vbTextCompare) = 0) | |||||
| If IsNull(val) Or IsEmpty(val) Then | |||||
| val = " " | |||||
| ElseIf IsDate(val) Then | |||||
| val = FormatDateTime(val, vbShortDate) | |||||
| ElseIf VarType(val) = vbBoolean Then | |||||
| val = IIf(val, "True", "False") | |||||
| Else | |||||
| val = CStr(val) | |||||
| Dim maxLen : maxLen = CInt(GetAppSetting("TableCellMaxLength")) | |||||
| If maxLen <= 0 Then maxLen = 90 | |||||
| If Len(val) > maxLen Then | |||||
| val = Left(val, maxLen - 3) & "..." | |||||
| End If | |||||
| val = Server.HTMLEncode(val) | |||||
| End If | |||||
| If isPk and boolUseTabulator = False Then | |||||
| val = "<a href=""" & obj.Tablename & "/edit/" & GetDynamicProperty(obj, pkName) & """ class=""table-link"">" & val & "</a>" | |||||
| End If | |||||
| html = html & " <td>" & val & "</td>" & vbCrLf | |||||
| Next | |||||
| html = html & " </tr>" & vbCrLf | |||||
| Next | |||||
| html = html & " </tbody>" & vbCrLf & "</table>" & vbCrLf & "</div>" | |||||
| RenderObjectsAsTable = html | |||||
| End Function | |||||
| Function RenderFormFromObject(obj) | |||||
| Dim html, propNames, i, name, val, inputType | |||||
| Dim pkName, tableName, checkedAttr | |||||
| On Error Resume Next | |||||
| propNames = obj.Properties | |||||
| pkName = obj.PrimaryKey | |||||
| tableName = obj.TableName | |||||
| On Error GoTo 0 | |||||
| If IsEmpty(propNames) Or Len(pkName) = 0 Then | |||||
| RenderFormFromObject = "<!-- Invalid object -->" | |||||
| Exit Function | |||||
| End If | |||||
| html = "<form method='post' action='/" & tableName & "/save' class='article-content'>" & vbCrLf | |||||
| For i = 0 To UBound(propNames) | |||||
| name = propNames(i) | |||||
| val = GetDynamicProperty(obj, name) | |||||
| ' Handle nulls | |||||
| If IsNull(val) Then val = "" | |||||
| ' Primary key → hidden input | |||||
| If StrComp(name, pkName, vbTextCompare) = 0 Then | |||||
| html = html & " <input type='hidden' name='" & name & "' value='" & Server.HTMLEncode(val) & "' />" & vbCrLf | |||||
| 'Continue For | |||||
| End If | |||||
| html = html & " <div class='form-group'>" & vbCrLf | |||||
| html = html & " <label for='" & name & "'>" & name & "</label>" & vbCrLf | |||||
| Select Case True | |||||
| Case VarType(val) = vbBoolean | |||||
| checkedAttr = "" | |||||
| If val = True Then checkedAttr = " checked" | |||||
| html = html & " <input type='checkbox' class='form-check-input' name='" & name & "' id='" & name & "' value='true'" & checkedAttr & " />" & vbCrLf | |||||
| Case IsDate(val) | |||||
| html = html & " <input type='date' class='form-control' name='" & name & "' id='" & name & "' value='" & FormatDateForInput(val) & "' />" & vbCrLf | |||||
| Case IsNumeric(val) | |||||
| html = html & " <input type='number' class='form-control' name='" & name & "' id='" & name & "' value='" & val & "' />" & vbCrLf | |||||
| Case Len(val) > CInt(GetAppSetting("FormTextareaThreshold")) | |||||
| html = html & " <textarea class='form-control' name='" & name & "' id='" & name & "' rows='6'>" & Server.HTMLEncode(val) & "</textarea>" & vbCrLf | |||||
| Case Else | |||||
| html = html & " <input type='text' class='form-control' name='" & name & "' id='" & name & "' value='" & Server.HTMLEncode(val) & "' />" & vbCrLf | |||||
| End Select | |||||
| html = html & " </div>" & vbCrLf | |||||
| Next | |||||
| html = html & " <button type='submit' class='btn btn-primary btn-lg'>Save</button>" & vbCrLf | |||||
| html = html & "</form>" & vbCrLf | |||||
| RenderFormFromObject = html | |||||
| End Function | |||||
| Function GetDynamicProperty(obj, propName) | |||||
| On Error Resume Next | |||||
| Dim result | |||||
| Execute "result = obj." & propName | |||||
| If Err.Number <> 0 Then | |||||
| result = "" | |||||
| Err.Clear | |||||
| End If | |||||
| GetDynamicProperty = result | |||||
| On Error GoTo 0 | |||||
| End Function | |||||
| Function FormatDateForInput(val) | |||||
| If IsDate(val) Then | |||||
| Dim yyyy, mm, dd | |||||
| yyyy = Year(val) | |||||
| mm = Right("0" & Month(val), 2) | |||||
| dd = Right("0" & Day(val), 2) | |||||
| FormatDateForInput = yyyy & "-" & mm & "-" & dd | |||||
| Else | |||||
| FormatDateForInput = "" | |||||
| End If | |||||
| End Function | |||||
| '------------------------------------------------------------- | |||||
| ' Returns obj.<propName> for any public VBScript class property | |||||
| '------------------------------------------------------------- | |||||
| Function GetObjProp(o, pName) | |||||
| Dim tmp | |||||
| ' Build a tiny statement like: tmp = o.UserID | |||||
| Execute "tmp = o." & pName | |||||
| GetObjProp = tmp | |||||
| End Function | |||||
| Function GenerateSlug(title) | |||||
| Dim slug | |||||
| slug = LCase(title) ' Convert to lowercase | |||||
| slug = Replace(slug, "&", "and") ' Replace ampersands | |||||
| slug = Replace(slug, "'", "") ' Remove apostrophes | |||||
| slug = Replace(slug, """", "") ' Remove quotes | |||||
| slug = Replace(slug, "–", "-") ' Replace en dash | |||||
| slug = Replace(slug, "—", "-") ' Replace em dash | |||||
| slug = Replace(slug, "/", "-") ' Replace slashes | |||||
| slug = Replace(slug, "\", "-") ' Replace backslashes | |||||
| ' Remove all non-alphanumeric and non-hyphen/space characters | |||||
| Dim i, ch, clean | |||||
| clean = "" | |||||
| For i = 1 To Len(slug) | |||||
| ch = Mid(slug, i, 1) | |||||
| If (ch >= "a" And ch <= "z") Or (ch >= "0" And ch <= "9") Or ch = " " Or ch = "-" Then | |||||
| clean = clean & ch | |||||
| End If | |||||
| Next | |||||
| ' Replace multiple spaces or hyphens with single hyphen | |||||
| Do While InStr(clean, " ") > 0 | |||||
| clean = Replace(clean, " ", " ") | |||||
| Loop | |||||
| clean = Replace(clean, " ", "-") | |||||
| Do While InStr(clean, "--") > 0 | |||||
| clean = Replace(clean, "--", "-") | |||||
| Loop | |||||
| ' Trim leading/trailing hyphens | |||||
| Do While Left(clean, 1) = "-" | |||||
| clean = Mid(clean, 2) | |||||
| Loop | |||||
| Do While Right(clean, 1) = "-" | |||||
| clean = Left(clean, Len(clean) - 1) | |||||
| Loop | |||||
| GenerateSlug = clean | |||||
| End Function | |||||
| Function GetRawJsonFromRequest() | |||||
| Dim stream, rawJson | |||||
| Set stream = Server.CreateObject("ADODB.Stream") | |||||
| stream.Type = 1 ' adTypeBinary | |||||
| stream.Open | |||||
| stream.Write Request.BinaryRead(Request.TotalBytes) | |||||
| stream.Position = 0 | |||||
| stream.Type = 2 ' adTypeText | |||||
| stream.Charset = "utf-8" | |||||
| rawJson = stream.ReadText | |||||
| stream.Close | |||||
| Set stream = Nothing | |||||
| GetRawJsonFromRequest = rawJson | |||||
| End Function | |||||
| Function Active(controllerName) | |||||
| On Error Resume Next | |||||
| If Replace(Lcase(router.Resolve(Request.ServerVariables("REQUEST_METHOD"), TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL")))(0)),"controller","") = LCase(controllerName) Then | |||||
| Active = "active" | |||||
| Else | |||||
| Active = "" | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Function | |||||
| '==================================================================== | |||||
| ' FormatDateForSql | |||||
| ' Converts a VBScript Date to a SQL Server-compatible string | |||||
| ' Output: 'YYYY-MM-DD HH:MM:SS' | |||||
| '==================================================================== | |||||
| Function FormatDateForSql(vbDate) | |||||
| If IsNull(vbDate) Or vbDate = "" Then | |||||
| FormatDateForSql = "NULL" | |||||
| Exit Function | |||||
| End If | |||||
| ' Ensure vbDate is a valid date | |||||
| If Not IsDate(vbDate) Then | |||||
| Err.Raise vbObjectError + 1000, "FormatDateForSql", "Invalid date: " & vbDate | |||||
| End If | |||||
| Dim yyyy, mm, dd, hh, nn, ss | |||||
| yyyy = Year(vbDate) | |||||
| mm = Right("0" & Month(vbDate), 2) | |||||
| dd = Right("0" & Day(vbDate), 2) | |||||
| hh = Right("0" & Hour(vbDate), 2) | |||||
| nn = Right("0" & Minute(vbDate), 2) | |||||
| ss = Right("0" & Second(vbDate), 2) | |||||
| ' Construct SQL Server datetime literal | |||||
| FormatDateForSql = "'" & yyyy & "-" & mm & "-" & dd & " " & hh & ":" & nn & ":" & ss & "'" | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,159 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' AUTOMAPPER CLASS | |||||
| '======================================================================================================================= | |||||
| 'Side Effects: Since src and target are passed ByRef to reduce unnecessary copying, if src is a recordset then the | |||||
| ' current record pointer is modified using src.MoveFirst and src.MoveNext. The end result is the current | |||||
| ' record pointer ends the operation at src.EOF. | |||||
| Class Automapper_Class | |||||
| Private m_src | |||||
| Private m_target | |||||
| Private m_statements | |||||
| Private m_statements_count | |||||
| Private Property Get Src : set Src = m_src : End Property | |||||
| Private Property Get Target : set Target = m_target : End Property | |||||
| Private Sub Class_Initialize | |||||
| m_statements_count = -1 | |||||
| ReDim m_statements(-1) | |||||
| End Sub | |||||
| Private Sub ResetState | |||||
| m_statements_count = -1 | |||||
| ReDim m_statements(-1) | |||||
| Set m_src = Nothing | |||||
| Set m_target = Nothing | |||||
| End Sub | |||||
| 'Maps all rs or object fields to corresponding fields in the specified class. | |||||
| Public Function AutoMap(src_obj, target_obj) | |||||
| Set AutoMap = FlexMap(src_obj, target_obj, empty) | |||||
| End Function | |||||
| 'Only maps fields specified in the field_names array (array of strings). | |||||
| 'If field_names is empty, attempts to map all fields from the passed rs or object. | |||||
| Public Function FlexMap(src_obj, target_obj, field_names) | |||||
| Set FlexMap = DynMap(src_obj, target_obj, field_names, empty) | |||||
| End Function | |||||
| 'Only maps fields specified in the field_names array (array of strings). | |||||
| 'If field_names is empty then src MUST be a recordset as it attempts to map all fields from the recordset. | |||||
| 'Since there is no reflection in vbscript, there is no way around this short of pseudo-reflection. | |||||
| Public Function DynMap(src_obj, target_obj, field_names, exprs) | |||||
| SetSource src_obj | |||||
| SetTarget target_obj | |||||
| dim field_name | |||||
| dim field_idx 'loop counter | |||||
| if IsEmpty(field_names) then 'map everything | |||||
| if typename(src_obj) = "Recordset" then | |||||
| for field_idx = 0 to src_obj.Fields.Count - 1 | |||||
| field_name = src_obj.Fields.Item(field_idx).Name | |||||
| 'AddStatement field_name | |||||
| AddStatement BuildStatement(field_name) | |||||
| next | |||||
| elseif InStr(typename(src_obj), "Dictionary") > 0 then 'enables Scripting.Dictionary and IRequestDictionary for Request.Querystring and Request.Form | |||||
| for each field_name in src_obj | |||||
| AddStatement BuildStatement(field_name) | |||||
| next | |||||
| elseif not IsEmpty(src_obj.Class_Get_Properties) then | |||||
| dim props : props = src_obj.Class_Get_Properties | |||||
| for field_idx = 0 to ubound(props) | |||||
| field_name = props(field_idx) | |||||
| 'AddStatement field_name | |||||
| AddStatement BuildStatement(field_name) | |||||
| next | |||||
| else 'some invalid type of object | |||||
| Err.Raise 9, "Automapper.DynMap", "Cannot automatically map this source object. Expected recordset or object implementing Class_Get_Properties reflection, got: " & typename(src_obj) | |||||
| end if | |||||
| else 'map only specified fields | |||||
| for field_idx = lbound(field_names) to ubound(field_names) | |||||
| field_name = field_names(field_idx) | |||||
| 'AddStatement field_name | |||||
| AddStatement BuildStatement(field_name) | |||||
| next | |||||
| end if | |||||
| dim exprs_idx | |||||
| if not IsEmpty(exprs) then | |||||
| if typename(exprs) = "Variant()" then | |||||
| for exprs_idx = lbound(exprs) to ubound(exprs) | |||||
| 'field_name = exprs(exprs_idx) | |||||
| 'AddStatement field_name | |||||
| AddStatement exprs(exprs_idx) | |||||
| next | |||||
| else 'assume string or string-like default value | |||||
| AddStatement exprs | |||||
| end if | |||||
| end if | |||||
| 'Can't pre-join the statements because if one fails the rest of them fail too... :( | |||||
| 'dim joined_statements : joined_statements = Join(m_statements, " : ") | |||||
| 'put joined_statements | |||||
| 'suspend errors to prevent failing when attempting to map a field that does not exist in the class | |||||
| on error resume next | |||||
| dim stmt_idx | |||||
| for stmt_idx = 0 to ubound(m_statements) | |||||
| Execute m_statements(stmt_idx) | |||||
| next | |||||
| on error goto 0 | |||||
| set DynMap = m_target | |||||
| ResetState | |||||
| End Function | |||||
| Private Sub SetSource(ByVal src_obj) | |||||
| set m_src = src_obj | |||||
| End Sub | |||||
| Private Sub SetTarget(ByVal target_obj) | |||||
| if typename(target_obj) = "String" then | |||||
| set m_target = eval("new " & target_obj) | |||||
| else | |||||
| set m_target = target_obj | |||||
| end if | |||||
| End Sub | |||||
| 'Builds a statement and adds it to the internal statements array | |||||
| Private Sub AddStatement(ByVal stmt) | |||||
| m_statements_count = m_statements_count + 1 | |||||
| ReDim Preserve m_statements(m_statements_count) | |||||
| m_statements(m_statements_count) = stmt | |||||
| End Sub | |||||
| Private Function BuildStatement(ByVal field_name) | |||||
| dim result | |||||
| if typename(m_src) = "Recordset" or InStr(typename(m_src), "Dictionary") > 0 then | |||||
| result = "m_target." & field_name & " = m_src(""" & field_name & """)" | |||||
| else | |||||
| 'Funky magic... | |||||
| 'If src.field_name is an object, ensure the set statement is used | |||||
| if IsObject(eval("m_src." & field_name)) then | |||||
| result = "set " | |||||
| else | |||||
| 'result = "m_target." & field_name & " = m_src." & field_name | |||||
| end if | |||||
| result = result & " m_target." & field_name & " = m_src." & field_name | |||||
| end if | |||||
| BuildStatement = result | |||||
| End Function | |||||
| End Class | |||||
| Function Automapper() | |||||
| Set Automapper = new Automapper_Class | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,155 @@ | |||||
| <% | |||||
| ' Class: CDOEmail | |||||
| ' Handles email creation and sending via CDO in VBScript | |||||
| Class CDOEmail_Class | |||||
| ' Public properties | |||||
| Public From | |||||
| Public Subject | |||||
| Public Body | |||||
| Public IsBodyHTML | |||||
| Public SMTPServer | |||||
| Public SMTPPort | |||||
| Public SMTPUsername | |||||
| Public SMTPPassword | |||||
| Public SMTPUseSSL | |||||
| ' Private members | |||||
| Private cfg | |||||
| Private msg | |||||
| Private dictRecipients | |||||
| Private arrAttachments | |||||
| ' Initialize default values and objects | |||||
| Private Sub Class_Initialize() | |||||
| ' Create CDO configuration and message objects | |||||
| Set cfg = Server.CreateObject("CDO.Configuration") | |||||
| Set msg = Server.CreateObject("CDO.Message") | |||||
| ' Default SMTP settings | |||||
| SMTPServer = "localhost" | |||||
| SMTPPort = 25 | |||||
| SMTPUsername = "" | |||||
| SMTPPassword = "" | |||||
| SMTPUseSSL = False | |||||
| ' Initialize recipient collections | |||||
| Set dictRecipients = Server.CreateObject("Scripting.Dictionary") | |||||
| dictRecipients.Add "TO", Array() | |||||
| dictRecipients.Add "CC", Array() | |||||
| dictRecipients.Add "BCC", Array() | |||||
| ' Initialize attachments dynamic array | |||||
| ReDim arrAttachments(-1) | |||||
| ' Default message settings | |||||
| From = "" | |||||
| Subject = "" | |||||
| Body = "" | |||||
| IsBodyHTML = False | |||||
| End Sub | |||||
| ' Add a recipient by type: "To", "Cc", or "Bcc" | |||||
| Public Sub AddRecipient(recipientType, address) | |||||
| Dim key, tmp | |||||
| key = UCase(recipientType) | |||||
| If Not dictRecipients.Exists(key) Then | |||||
| Err.Raise vbObjectError + 1000, "CDOEmail", "Invalid recipient type: " & recipientType | |||||
| End If | |||||
| tmp = dictRecipients(key) | |||||
| If UBound(tmp) < LBound(tmp) Then | |||||
| ReDim tmp(0) | |||||
| Else | |||||
| ReDim Preserve tmp(UBound(tmp) + 1) | |||||
| End If | |||||
| tmp(UBound(tmp)) = address | |||||
| dictRecipients(key) = tmp | |||||
| End Sub | |||||
| ' Add a file attachment | |||||
| Public Sub AddAttachment(filePath) | |||||
| If UBound(arrAttachments) < LBound(arrAttachments) Then | |||||
| ReDim arrAttachments(0) | |||||
| Else | |||||
| ReDim Preserve arrAttachments(UBound(arrAttachments) + 1) | |||||
| End If | |||||
| arrAttachments(UBound(arrAttachments)) = filePath | |||||
| End Sub | |||||
| ' Send the email, returning True on success, False on failure | |||||
| Public Function Send() | |||||
| ' Configure SMTP settings | |||||
| With cfg.Fields | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2 ' cdoSendUsingPort | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = SMTPServer | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = SMTPPort | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = Choice(Len(SMTPUsername) > 0, 1, 0) ' cdoBasic or cdoAnonymous | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = SMTPUsername | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = SMTPPassword | |||||
| .Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = SMTPUseSSL | |||||
| .Update | |||||
| End With | |||||
| ' Apply configuration to message | |||||
| Set msg.Configuration = cfg | |||||
| ' Populate message fields | |||||
| msg.From = From | |||||
| msg.Subject = Subject | |||||
| If dictRecipients.Exists("TO") And UBound(dictRecipients("TO")) >= LBound(dictRecipients("TO")) Then | |||||
| msg.To = Join(dictRecipients("TO"), ";") | |||||
| End If | |||||
| If dictRecipients.Exists("CC") And UBound(dictRecipients("CC")) >= LBound(dictRecipients("CC")) Then | |||||
| msg.CC = Join(dictRecipients("CC"), ";") | |||||
| End If | |||||
| If dictRecipients.Exists("BCC") And UBound(dictRecipients("BCC")) >= LBound(dictRecipients("BCC")) Then | |||||
| msg.BCC = Join(dictRecipients("BCC"), ";") | |||||
| End If | |||||
| If IsBodyHTML Then | |||||
| msg.HTMLBody = Body | |||||
| Else | |||||
| msg.TextBody = Body | |||||
| End If | |||||
| ' Add attachments if any | |||||
| Dim i | |||||
| For i = LBound(arrAttachments) To UBound(arrAttachments) | |||||
| msg.AddAttachment arrAttachments(i) | |||||
| Next | |||||
| ' Send and handle errors | |||||
| On Error Resume Next | |||||
| msg.Send | |||||
| errNum = Err.Number | |||||
| errDesc = Err.Description | |||||
| On Error Goto 0 | |||||
| If Err.Number <> 0 Then | |||||
| Response.Write "CDO Error #" & errNum & ": " & errDesc & "<br/>" | |||||
| Send = False | |||||
| Err.Clear | |||||
| Else | |||||
| Send = True | |||||
| End If | |||||
| End Function | |||||
| ' Clean up objects | |||||
| Private Sub Class_Terminate() | |||||
| On Error Resume Next | |||||
| Set msg = Nothing | |||||
| Set cfg = Nothing | |||||
| Set dictRecipients = Nothing | |||||
| Erase arrAttachments | |||||
| End Sub | |||||
| End Class | |||||
| dim CDOEmail_Class__Singleton | |||||
| Function CDOEmail() | |||||
| if IsEmpty(CDOEmail_Class__Singleton) then | |||||
| set CDOEmail_Class__Singleton = new CDOEmail_Class | |||||
| end if | |||||
| set CDOEmail = CDOEmail_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,914 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' KVArray | |||||
| ' Relatively painless implementation of key/value pair arrays without requiring a full Scripting.Dictionary COM instance. | |||||
| ' A KVArray is a standard array where element i is the key and element i+1 is the value. Loops must step by 2. | |||||
| '======================================================================================================================= | |||||
| 'given a KVArray and key index, returns the key and value | |||||
| 'pre: kv_array has at least key_idx and key_idx + 1 values | |||||
| 'post: key and val are populated | |||||
| Sub KeyVal(kv_array, key_idx, ByRef key, ByRef val) | |||||
| if (key_idx + 1 > ubound(kv_array)) then err.raise 1, "KeyVal", "expected key_idx < " & ubound(kv_array) - 1 & ", got: " & key_idx | |||||
| key = kv_array(key_idx) | |||||
| val = kv_array(key_idx + 1) | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Given a KVArray, a key and a value, appends the key and value to the end of the KVArray | |||||
| Sub KVAppend(ByRef kv_array, key, val) | |||||
| dim i : i = ubound(kv_array) | |||||
| redim preserve kv_array(i + 2) | |||||
| kv_array(i + 1) = key | |||||
| kv_array(i + 2) = val | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| 'Given a KVArray and two variants, populates the first variant with all keys and the second variant with all values. | |||||
| 'If | |||||
| 'Pre: kv_array has at least key_idx and key_idx + 1 values | |||||
| 'Post: key_array contains all keys in kvarray. | |||||
| ' val_array contains all values in kvarray. | |||||
| ' key_array and val_array values are in corresponding order, i.e. key_array(i) corresponds to val_array(i). | |||||
| Sub KVUnzip(kv_array, key_array, val_array) | |||||
| dim kv_array_size : kv_array_size = ubound(kv_array) | |||||
| dim num_pairs : num_pairs = (kv_array_size + 1) / 2 | |||||
| dim result_array_size : result_array_size = num_pairs - 1 | |||||
| 'Extend existing key_array or create new array to hold the keys | |||||
| If IsArray(key_array) then | |||||
| redim preserve key_array(ubound(key_array) + result_array_size) | |||||
| Else | |||||
| key_array = Array() | |||||
| redim key_array(result_array_size) | |||||
| End If | |||||
| 'Extend existing val array or create new array to hold the values | |||||
| If IsArray(val_array) then | |||||
| redim preserve val_array(ubound(val_array) + result_array_size) | |||||
| Else | |||||
| val_array = Array() | |||||
| redim val_array(num_pairs - 1) | |||||
| End If | |||||
| 'Unzip the KVArray into the two output arrays | |||||
| dim i, key, val | |||||
| dim key_val_arrays_idx : key_val_arrays_idx = 0 ' used to sync loading the key_array and val_array | |||||
| For i = 0 to ubound(kv_array) step 2 | |||||
| KeyVal kv_array, i, key, val | |||||
| key_array(key_val_arrays_idx) = key | |||||
| val_array(key_val_arrays_idx) = val | |||||
| key_val_arrays_idx = key_val_arrays_idx + 1 ' increment by 1 because loop goes to next pair in kv_array | |||||
| Next | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Given a KVArray, dumps it to the screen. Useful for debugging purposes. | |||||
| Sub DumpKVArray(kv_array) | |||||
| dim i, key, val | |||||
| For i = 0 to ubound(kv_array) step 2 | |||||
| KeyVal kv_array, i, key, val | |||||
| put key & " => " & val & "<br>" | |||||
| Next | |||||
| End Sub | |||||
| '======================================================================================================================= | |||||
| ' Pair Class | |||||
| ' Holds a pair of values, i.e. a key value pair, recordset field name/value pair, etc. | |||||
| ' Similar to the C++ STL std::pair class. Useful for some iteration and the like. | |||||
| ' | |||||
| ' This was an interesting idea but so far has not really been used, oh well...... | |||||
| '======================================================================================================================= | |||||
| Class Pair_Class | |||||
| Private m_first, m_second | |||||
| Public Property Get First : First = m_first : End Property | |||||
| Public Property Get [Second] : [Second] = m_second : End Property | |||||
| Public Default Property Get TO_String | |||||
| TO_String = First & " " & [Second] | |||||
| End Property | |||||
| Public Sub Initialize(ByVal firstval, ByVal secondval) | |||||
| Assign m_first, firstval | |||||
| Assign m_second, secondval | |||||
| End Sub | |||||
| 'Swaps the two values | |||||
| Public Sub Swap | |||||
| dim tmp | |||||
| Assign tmp, m_second | |||||
| Assign m_second, m_first | |||||
| Assign m_first, tmp | |||||
| End Sub | |||||
| End Class | |||||
| Function MakePair(ByVal firstval, ByVal secondval) | |||||
| dim P : set P = new Pair_Class | |||||
| P.Initialize firstval, secondval | |||||
| set MakePair = P | |||||
| End Function | |||||
| '======================================================================================================================= | |||||
| ' Linked List - From the Tolerable lib | |||||
| '======================================================================================================================= | |||||
| ' This is just here for reference | |||||
| Class Iterator_Class | |||||
| Public Function HasNext() | |||||
| End Function | |||||
| Public Function PeekNext() | |||||
| End Function | |||||
| Public Function GetNext() | |||||
| End Function | |||||
| Public Function HasPrev() | |||||
| End Function | |||||
| Public Function PeekPrev() | |||||
| End Function | |||||
| Public Function GetPrev() | |||||
| End Function | |||||
| End Class | |||||
| Class Enumerator_Source_Iterator_Class | |||||
| Private m_iter | |||||
| Public Sub Initialize(ByVal iter) | |||||
| Set m_iter = iter | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Set m_iter = Nothing | |||||
| End Sub | |||||
| Public Sub GetNext(ByRef retval, ByRef successful) | |||||
| If m_iter.HasNext Then | |||||
| Assign retval, m_iter.GetNext | |||||
| successful = True | |||||
| Else | |||||
| successful = False | |||||
| End If | |||||
| End Sub | |||||
| End Class | |||||
| Public Function En_Iterator(ByVal iter) | |||||
| Dim retval | |||||
| Set retval = New Enumerator_Source_Iterator_Class | |||||
| retval.Initialize iter | |||||
| Set En_Iterator = Enumerator(retval) | |||||
| End Function | |||||
| Class LinkedList_Node_Class | |||||
| Public m_prev | |||||
| Public m_next | |||||
| Public m_value | |||||
| Private Sub Class_Initialize() | |||||
| Set m_prev = Nothing | |||||
| Set m_next = Nothing | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Set m_prev = Nothing | |||||
| Set m_next = Nothing | |||||
| Set m_value = Nothing | |||||
| End Sub | |||||
| Public Sub SetValue(ByVal value) | |||||
| Assign m_value, value | |||||
| End Sub | |||||
| End Class | |||||
| Class Iterator_LinkedList_Class | |||||
| Private m_left | |||||
| Private m_right | |||||
| Public Sub Initialize(ByVal r) | |||||
| Set m_left = Nothing | |||||
| Set m_right = r | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Set m_Left = Nothing | |||||
| Set m_Right = Nothing | |||||
| End Sub | |||||
| Public Function HasNext() | |||||
| HasNext = Not(m_right Is Nothing) | |||||
| End Function | |||||
| Public Function PeekNext() | |||||
| Assign PeekNext, m_right.m_value | |||||
| End Function | |||||
| Public Function GetNext() | |||||
| Assign GetNext, m_right.m_value | |||||
| Set m_left = m_right | |||||
| Set m_right = m_right.m_next | |||||
| End Function | |||||
| Public Function HasPrev() | |||||
| HasPrev = Not(m_left Is Nothing) | |||||
| End Function | |||||
| Public Function PeekPrev() | |||||
| Assign PeekPrev, m_left.m_value | |||||
| End Function | |||||
| Public Function GetPrev() | |||||
| Assign GetPrev, m_left.m_value | |||||
| Set m_right = m_left | |||||
| Set m_left = m_left.m_prev | |||||
| End Function | |||||
| End Class | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class LinkedList_Class | |||||
| Private m_first | |||||
| Private m_last | |||||
| Private m_size | |||||
| Private Sub Class_Initialize() | |||||
| Me.Reset | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Me.Reset | |||||
| End Sub | |||||
| Public Function Clear() | |||||
| Set m_first = Nothing | |||||
| Set m_last = Nothing | |||||
| m_size = 0 | |||||
| Set Clear = Me | |||||
| End Function | |||||
| Private Function NewNode(ByVal value) | |||||
| Dim retval | |||||
| Set retval = New LinkedList_Node_Class | |||||
| retval.SetValue value | |||||
| Set NewNode = retval | |||||
| End Function | |||||
| Public Sub Reset() | |||||
| Set m_first = Nothing | |||||
| Set m_last = Nothing | |||||
| m_size = 0 | |||||
| End Sub | |||||
| Public Function IsEmpty() | |||||
| IsEmpty = (m_last Is Nothing) | |||||
| End Function | |||||
| Public Property Get Count | |||||
| Count = m_size | |||||
| End Property | |||||
| 'I just like .Size better than .Count sometimes, sue me | |||||
| Public Property Get Size | |||||
| Size = m_size | |||||
| End Property | |||||
| Public Function Iterator() | |||||
| Dim retval | |||||
| Set retval = New Iterator_LinkedList_Class | |||||
| retval.Initialize m_first | |||||
| Set Iterator = retval | |||||
| End Function | |||||
| Public Function Push(ByVal value) | |||||
| Dim temp | |||||
| Set temp = NewNode(value) | |||||
| If Me.IsEmpty Then | |||||
| Set m_first = temp | |||||
| Set m_last = temp | |||||
| Else | |||||
| Set temp.m_prev = m_last | |||||
| Set m_last.m_next = temp | |||||
| Set m_last = temp | |||||
| End If | |||||
| m_size = m_size + 1 | |||||
| Set Push = Me | |||||
| End Function | |||||
| Public Function Peek() | |||||
| ' TODO: Error handling | |||||
| Assign Peek, m_last.m_value | |||||
| End Function | |||||
| ' Alias for Peek | |||||
| Public Function Back() | |||||
| ' TODO: Error handling | |||||
| Assign Back, m_last.m_value | |||||
| End Function | |||||
| Public Function Pop() | |||||
| Dim temp | |||||
| ' TODO: Error Handling | |||||
| Assign Pop, m_last.m_value | |||||
| Set temp = m_last | |||||
| Set m_last = temp.m_prev | |||||
| Set temp.m_prev = Nothing | |||||
| If m_last Is Nothing Then | |||||
| Set m_first = Nothing | |||||
| Else | |||||
| Set m_last.m_next = Nothing | |||||
| End If | |||||
| m_size = m_size - 1 | |||||
| End Function | |||||
| Public Function Unshift(ByVal value) | |||||
| Dim temp | |||||
| Set temp = NewNode(value) | |||||
| If Me.IsEmpty Then | |||||
| Set m_first = temp | |||||
| Set m_last = temp | |||||
| Else | |||||
| Set temp.m_next = m_first | |||||
| Set m_first.m_prev = temp | |||||
| Set m_first = temp | |||||
| End If | |||||
| m_size = m_size + 1 | |||||
| Set Unshift = Me | |||||
| End Function | |||||
| ' Alias for Peek | |||||
| Public Function Front() | |||||
| ' TODO: Error handling | |||||
| Assign Front, m_first.m_value | |||||
| End Function | |||||
| Public Function Shift() | |||||
| Dim temp | |||||
| ' TODO: Error Handling | |||||
| Assign Shift, m_first.m_value | |||||
| Set temp = m_first | |||||
| Set m_first = temp.m_next | |||||
| Set temp.m_next = Nothing | |||||
| If m_first Is Nothing Then | |||||
| Set m_last = Nothing | |||||
| Else | |||||
| Set m_first.m_prev = Nothing | |||||
| End If | |||||
| m_size = m_size - 1 | |||||
| End Function | |||||
| Public Function TO_Array() | |||||
| Dim i, iter | |||||
| ReDim retval(Me.Count - 1) | |||||
| i = 0 | |||||
| Set iter = Me.Iterator | |||||
| While iter.HasNext | |||||
| assign retval(i),iter.GetNext | |||||
| i = i + 1 | |||||
| Wend | |||||
| TO_Array = retval | |||||
| End Function | |||||
| Public Function TO_En() | |||||
| Set TO_En = En_Iterator(Iterator) | |||||
| End Function | |||||
| End Class | |||||
| '======================================================================================================================= | |||||
| ' Dynamic Array - From the Tolerable lib | |||||
| '======================================================================================================================= | |||||
| Class DynamicArray_Class | |||||
| Private m_data | |||||
| Private m_size | |||||
| Public Sub Initialize(ByVal d, ByVal s) | |||||
| m_data = d | |||||
| m_size = s | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Set m_data = Nothing | |||||
| End Sub | |||||
| Public Property Get Capacity | |||||
| Capacity = UBOUND(m_data) + 1 | |||||
| End Property | |||||
| Public Property Get Count | |||||
| Count = m_size | |||||
| End Property | |||||
| ' Alias for Count | |||||
| Public Property Get Size | |||||
| Size = m_size | |||||
| End Property | |||||
| Public Function IsEmpty() | |||||
| IsEmpty = (m_size = 0) | |||||
| End Function | |||||
| Public Function Clear() | |||||
| m_size = 0 | |||||
| Set Clear = Me | |||||
| End Function | |||||
| Private Sub Grow | |||||
| ' TODO: There's probably a better way to | |||||
| ' do this. Doubling might be excessive | |||||
| ReDim Preserve m_data(UBOUND(m_data) * 2) | |||||
| End Sub | |||||
| Public Function Push(ByVal val) | |||||
| If m_size >= UBOUND(m_data) Then | |||||
| Grow | |||||
| End If | |||||
| Assign m_data(m_size), val | |||||
| m_size = m_size + 1 | |||||
| Set Push = Me | |||||
| End Function | |||||
| ' Look at the last element | |||||
| Public Function Peek() | |||||
| Assign Peek, m_data(m_size - 1) | |||||
| End Function | |||||
| ' Look at the last element and | |||||
| ' pop it off of the list | |||||
| Public Function Pop() | |||||
| Assign Pop, m_data(m_size - 1) | |||||
| m_size = m_size - 1 | |||||
| End Function | |||||
| ' If pseudo_index < 0, then we assume we're counting | |||||
| ' from the back of the Array. | |||||
| Private Function CalculateIndex(ByVal pseudo_index) | |||||
| If pseudo_index >= 0 Then | |||||
| CalculateIndex = pseudo_index | |||||
| Else | |||||
| CalculateIndex = m_size + pseudo_index | |||||
| End If | |||||
| End Function | |||||
| Public Default Function Item(ByVal i) | |||||
| Assign Item, m_data(CalculateIndex(i)) | |||||
| End Function | |||||
| ' This does not treat negative indices as wrap-around. | |||||
| ' Thus, it is slightly faster. | |||||
| Public Function FastItem(ByVal i) | |||||
| Assign FastItem, m_data(i) | |||||
| End Function | |||||
| Public Function Slice(ByVal s, ByVal e) | |||||
| s = CalculateIndex(s) | |||||
| e = CalculateIndex(e) | |||||
| If e < s Then | |||||
| Set Slice = DynamicArray() | |||||
| Else | |||||
| ReDim retval(e - s) | |||||
| Dim i, j | |||||
| j = 0 | |||||
| For i = s to e | |||||
| Assign retval(j), m_data(i) | |||||
| j = j + 1 | |||||
| Next | |||||
| Set Slice = DynamicArray1(retval) | |||||
| End If | |||||
| End Function | |||||
| Public Function Iterator() | |||||
| Dim retval | |||||
| Set retval = New Iterator_DynamicArray_Class | |||||
| retval.Initialize Me | |||||
| Set Iterator = retval | |||||
| End Function | |||||
| Public Function TO_En() | |||||
| Set TO_En = En_Iterator(Me.Iterator) | |||||
| End Function | |||||
| Public Function TO_Array() | |||||
| Dim i | |||||
| ReDim retval(m_size - 1) | |||||
| For i = 0 to UBOUND(retval) | |||||
| Assign retval(i), m_data(i) | |||||
| Next | |||||
| TO_Array = retval | |||||
| End Function | |||||
| End Class | |||||
| Public Function DynamicArray() | |||||
| ReDim data(3) | |||||
| Set DynamicArray = DynamicArray2(data, 0) | |||||
| End Function | |||||
| Public Function DynamicArray1(ByVal data) | |||||
| Set DynamicArray1 = DynamicArray2(data, UBOUND(data) + 1) | |||||
| End Function | |||||
| Private Function DynamicArray2(ByVal data, ByVal size) | |||||
| Dim retval | |||||
| Set retval = New DynamicArray_Class | |||||
| retval.Initialize data, size | |||||
| Set DynamicArray2 = retval | |||||
| End Function | |||||
| Class Iterator_DynamicArray_Class | |||||
| Private m_dynamic_array | |||||
| Private m_index | |||||
| Public Sub Initialize(ByVal dynamic_array) | |||||
| Set m_dynamic_array = dynamic_array | |||||
| m_index = 0 | |||||
| End Sub | |||||
| Private Sub Class_Terminate | |||||
| Set m_dynamic_array = Nothing | |||||
| End Sub | |||||
| Public Function HasNext() | |||||
| HasNext = (m_index < m_dynamic_array.Size) | |||||
| End Function | |||||
| Public Function PeekNext() | |||||
| Assign PeekNext, m_dynamic_array.FastItem(m_index) | |||||
| End Function | |||||
| Public Function GetNext() | |||||
| Assign GetNext, m_dynamic_array.FastItem(m_index) | |||||
| m_index = m_index + 1 | |||||
| End Function | |||||
| Public Function HasPrev() | |||||
| HasPrev = (m_index > 0) | |||||
| End Function | |||||
| Public Function PeekPrev() | |||||
| Assign PeekPrev, m_dynamic_array.FastItem(m_index - 1) | |||||
| End Function | |||||
| Public Function GetPrev() | |||||
| Assign GetPrev, m_dynamic_array.FastItem(m_index - 1) | |||||
| m_index = m_index - 1 | |||||
| End Function | |||||
| End Class | |||||
| '======================================================================================================================= | |||||
| ' Other Iterators | |||||
| '======================================================================================================================= | |||||
| '!!! EXPERIMENTAL !!! May not be very useful, oh well... | |||||
| Class Iterator_Recordset_Class | |||||
| Private m_rs | |||||
| Private m_record_count | |||||
| Private m_current_index | |||||
| Private m_field_names 'cached array | |||||
| Public Sub Initialize(ByVal rs) | |||||
| Set m_rs = rs | |||||
| m_rs.MoveFirst | |||||
| m_rs.MovePrevious | |||||
| m_record_count = rs.RecordCount | |||||
| m_current_index = 0 | |||||
| 'cache field names | |||||
| m_field_names = array() | |||||
| redim m_field_names(m_rs.Fields.Count) | |||||
| dim field | |||||
| dim i : i = 0 | |||||
| for each field in m_rs.Fields | |||||
| m_field_names(i) = field.Name | |||||
| next | |||||
| End Sub | |||||
| Private Sub Class_Terminate | |||||
| Set m_rs = Nothing | |||||
| End Sub | |||||
| Public Function HasNext() | |||||
| HasNext = (m_current_index < m_record_count) | |||||
| put "m_current_index := " & m_current_index | |||||
| put "m_record_count := " & m_record_count | |||||
| End Function | |||||
| Public Function PeekNext | |||||
| if HasNext then | |||||
| m_rs.MoveNext | |||||
| Assign PeekNext, GetPairs | |||||
| m_rs.MovePrevious | |||||
| else | |||||
| set PeekNext = Nothing | |||||
| end if | |||||
| End Function | |||||
| Private Function GetPairs | |||||
| End Function | |||||
| Public Function GetNext | |||||
| if m_current_index < m_record_count then | |||||
| Assign GetNext, m_rs | |||||
| m_rs.MoveNext | |||||
| m_current_index = m_current_index + 1 | |||||
| else | |||||
| set GetNext = Nothing | |||||
| end if | |||||
| End Function | |||||
| Public Function HasPrev() | |||||
| if m_rs.BOF then | |||||
| HasPrev = false | |||||
| else | |||||
| m_rs.MovePrevious | |||||
| HasPrev = Choice(m_rs.BOF, false, true) | |||||
| m_rs.MoveNext | |||||
| end if | |||||
| End Function | |||||
| Public Function PeekPrev | |||||
| m_rs.MovePrevious | |||||
| if m_rs.BOF then | |||||
| set PeekPrev = Nothing | |||||
| else | |||||
| Assign PeekPrev, m_rs | |||||
| end if | |||||
| m_rs.MoveNext | |||||
| End Function | |||||
| Public Function GetPrev | |||||
| m_rs.MovePrevious | |||||
| if m_rs.BOF then | |||||
| set GetPrev = Nothing | |||||
| else | |||||
| Assign GetPrev, m_rs | |||||
| end if | |||||
| End Function | |||||
| End Class | |||||
| Class Iterator_Dictionary_Class | |||||
| Private m_dic | |||||
| Private m_keys 'array | |||||
| Private m_idx 'current array index | |||||
| Private m_keys_ubound 'cached ubound(m_keys) | |||||
| Public Sub Initialize(ByVal dic) | |||||
| set m_dic = dic | |||||
| m_keys = m_dic.Keys() | |||||
| m_idx = -1 | |||||
| m_keys_ubound = ubound(m_keys) | |||||
| End Sub | |||||
| Private Sub Class_Terminate | |||||
| set m_dic = Nothing | |||||
| End Sub | |||||
| Public Function HasNext() | |||||
| HasNext = (m_idx < m_keys_ubound) | |||||
| End Function | |||||
| Public Function PeekNext() | |||||
| Assign PeekNext, m_dic(m_keys(m_idx + 1)) | |||||
| End Function | |||||
| Public Function GetNext() | |||||
| Assign GetNext, m_dic(m_keys(m_idx + 1)) | |||||
| m_idx = m_idx + 1 | |||||
| End Function | |||||
| Public Function HasPrev() | |||||
| HasPrev = (m_idx > 0) | |||||
| End Function | |||||
| Public Function PeekPrev() | |||||
| Assign PeekPrev, m_dic(m_keys(m_idx - 1)) | |||||
| End Function | |||||
| Public Function GetPrev() | |||||
| Assign GetPrev, m_dic(m_keys(m_idx - 1)) | |||||
| m_idx = m_idx - 1 | |||||
| End Function | |||||
| End Class | |||||
| '======================================================================================================================= | |||||
| ' Iterator Factory | |||||
| '======================================================================================================================= | |||||
| 'Returns the appropriate iterator for the passed-in collection. Errors if unknown collection. | |||||
| Function IteratorFor(col) | |||||
| dim result | |||||
| select case typename(col) | |||||
| case "LinkedList_Class" : set result = new Iterator_LinkedList_Class | |||||
| case "Dictionary" : set result = new Iterator_Dictionary_Class | |||||
| case "Recordset" : set result = new Iterator_Recordset_Class | |||||
| end select | |||||
| result.Initialize col | |||||
| set IteratorFor = result | |||||
| End Function | |||||
| Class List | |||||
| Private items | |||||
| Private P_count | |||||
| Private Sub Class_Initialize() | |||||
| ReDim items(-1) | |||||
| P_count = 0 | |||||
| End Sub | |||||
| Public Sub Add(value) | |||||
| ReDim Preserve items(P_count) | |||||
| If IsObject(value) Then | |||||
| Set items(P_count) = value | |||||
| Else | |||||
| items(P_count) = value | |||||
| End If | |||||
| P_count = P_count + 1 | |||||
| End Sub | |||||
| Public Sub AddRange(arrayToAdd) | |||||
| Dim i | |||||
| For i = 0 To UBound(arrayToAdd) | |||||
| Me.Add arrayToAdd(i) | |||||
| Next | |||||
| End Sub | |||||
| Public Property Get Count() | |||||
| Count = P_count | |||||
| End Property | |||||
| Public Property Get Item(index) | |||||
| If index < 0 Or index >= P_count Then | |||||
| Err.Raise 9, "List", "Index out of bounds" | |||||
| End If | |||||
| If IsObject(items(index)) Then | |||||
| Set Item = items(index) | |||||
| Else | |||||
| Item = items(index) | |||||
| End If | |||||
| End Property | |||||
| Public Property Let Item(index, value) | |||||
| If index < 0 Or index >= P_count Then | |||||
| Err.Raise 9, "List", "Index out of bounds" | |||||
| End If | |||||
| If IsObject(value) Then | |||||
| Set items(index) = value | |||||
| Else | |||||
| items(index) = value | |||||
| End If | |||||
| End Property | |||||
| Public Function Contains(ByRef value) | |||||
| Dim i | |||||
| For i = 0 To P_count - 1 | |||||
| If IsObject(items(i)) And IsObject(value) Then | |||||
| If (items(i) Is value) Then | |||||
| Contains = True | |||||
| Exit Function | |||||
| End If | |||||
| Else | |||||
| If items(i) = value Then | |||||
| Contains = True | |||||
| Exit Function | |||||
| End If | |||||
| End If | |||||
| Next | |||||
| Contains = False | |||||
| End Function | |||||
| Public Function IndexOf(value) | |||||
| Dim i | |||||
| For i = 0 To P_count - 1 | |||||
| If IsObject(items(i)) And IsObject(value) Then | |||||
| If items(i) Is value Then | |||||
| IndexOf = i | |||||
| Exit Function | |||||
| End If | |||||
| Else | |||||
| If items(i) = value Then | |||||
| IndexOf = i | |||||
| Exit Function | |||||
| End If | |||||
| End If | |||||
| Next | |||||
| IndexOf = -1 | |||||
| End Function | |||||
| Public Sub Remove(value) | |||||
| Dim i | |||||
| i = Me.IndexOf(value) | |||||
| If i <> -1 Then | |||||
| Me.RemoveAt i | |||||
| End If | |||||
| End Sub | |||||
| Public Sub RemoveAt(index) | |||||
| Dim i | |||||
| If index < 0 Or index >= P_count Then | |||||
| Err.Raise 9, "List", "Index out of bounds" | |||||
| End If | |||||
| For i = index To P_count - 2 | |||||
| If IsObject(items(i + 1)) Then | |||||
| Set items(i) = items(i + 1) | |||||
| Else | |||||
| items(i) = items(i + 1) | |||||
| End If | |||||
| Next | |||||
| P_count = P_count - 1 | |||||
| If P_count <= 0 Then | |||||
| ReDim items(-1) | |||||
| Else | |||||
| ReDim Preserve items(P_count - 1) | |||||
| End If | |||||
| End Sub | |||||
| Public Sub Clear() | |||||
| ReDim items(-1) | |||||
| P_count = 0 | |||||
| End Sub | |||||
| Public Sub Sort() | |||||
| Dim i, j, tmp | |||||
| For i = 0 To P_count - 2 | |||||
| For j = i + 1 To P_count - 1 | |||||
| If CStr(items(i)) > CStr(items(j)) Then | |||||
| Set tmp = items(i) | |||||
| Set items(i) = items(j) | |||||
| Set items(j) = tmp | |||||
| End If | |||||
| Next | |||||
| Next | |||||
| End Sub | |||||
| ' Note: this will NOT be used automatically by VBScript For Each, | |||||
| ' but you can use it manually. | |||||
| Public Function New_Enum() | |||||
| Set New_Enum = New ListEnumerator | |||||
| New_Enum.Init items, P_count | |||||
| End Function | |||||
| End Class | |||||
| Class ListEnumerator | |||||
| Private items, index, max | |||||
| Public Sub Init(arr, cnt) | |||||
| items = arr | |||||
| index = -1 | |||||
| max = cnt | |||||
| End Sub | |||||
| Public Function MoveNext() | |||||
| index = index + 1 | |||||
| MoveNext = (index < max) | |||||
| End Function | |||||
| Public Property Get Current() | |||||
| Set Current = items(index) | |||||
| End Property | |||||
| Public Sub Reset() | |||||
| index = -1 | |||||
| End Sub | |||||
| End Class | |||||
| %> | |||||
| @@ -0,0 +1,110 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' Controller Registry | |||||
| ' Provides a whitelist of valid controllers to prevent code injection attacks | |||||
| '======================================================================================================================= | |||||
| Class ControllerRegistry_Class | |||||
| Private m_controllers | |||||
| Private Sub Class_Initialize() | |||||
| Set m_controllers = Server.CreateObject("Scripting.Dictionary") | |||||
| m_controllers.CompareMode = 1 ' vbTextCompare for case-insensitive | |||||
| ' Register all valid controllers here | |||||
| ' Format: m_controllers.Add "controllername", True | |||||
| RegisterController "homecontroller" | |||||
| RegisterController "errorcontroller" | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Set m_controllers = Nothing | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Register a controller as valid | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub RegisterController(controllerName) | |||||
| Dim key : key = LCase(Trim(controllerName)) | |||||
| If Not m_controllers.Exists(key) Then | |||||
| m_controllers.Add key, True | |||||
| End If | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Check if a controller is registered (valid) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function IsValidController(controllerName) | |||||
| Dim key : key = LCase(Trim(controllerName)) | |||||
| IsValidController = m_controllers.Exists(key) | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Get list of all registered controllers | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function GetRegisteredControllers() | |||||
| GetRegisteredControllers = m_controllers.Keys() | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Validate controller name format (alphanumeric and underscore only) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function IsValidControllerFormat(controllerName) | |||||
| If IsEmpty(controllerName) Or Len(controllerName) = 0 Then | |||||
| IsValidControllerFormat = False | |||||
| Exit Function | |||||
| End If | |||||
| Dim i, ch | |||||
| For i = 1 To Len(controllerName) | |||||
| ch = Mid(controllerName, i, 1) | |||||
| ' Allow a-z, A-Z, 0-9, and underscore | |||||
| If Not ((ch >= "a" And ch <= "z") Or _ | |||||
| (ch >= "A" And ch <= "Z") Or _ | |||||
| (ch >= "0" And ch <= "9") Or _ | |||||
| ch = "_") Then | |||||
| IsValidControllerFormat = False | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| IsValidControllerFormat = True | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Validate action name format (alphanumeric and underscore only) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function IsValidActionFormat(actionName) | |||||
| If IsEmpty(actionName) Or Len(actionName) = 0 Then | |||||
| IsValidActionFormat = False | |||||
| Exit Function | |||||
| End If | |||||
| Dim i, ch | |||||
| For i = 1 To Len(actionName) | |||||
| ch = Mid(actionName, i, 1) | |||||
| ' Allow a-z, A-Z, 0-9, and underscore | |||||
| If Not ((ch >= "a" And ch <= "z") Or _ | |||||
| (ch >= "A" And ch <= "Z") Or _ | |||||
| (ch >= "0" And ch <= "9") Or _ | |||||
| ch = "_") Then | |||||
| IsValidActionFormat = False | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| IsValidActionFormat = True | |||||
| End Function | |||||
| End Class | |||||
| ' Singleton instance | |||||
| Dim ControllerRegistry_Class__Singleton | |||||
| Function ControllerRegistry() | |||||
| If IsEmpty(ControllerRegistry_Class__Singleton) Then | |||||
| Set ControllerRegistry_Class__Singleton = New ControllerRegistry_Class | |||||
| End If | |||||
| Set ControllerRegistry = ControllerRegistry_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,29 @@ | |||||
| <% | |||||
| ' This class encapsulates database access into one location, isolating database details from the rest of the app. | |||||
| ' Multiple databases can be handled in one of two ways: | |||||
| ' | |||||
| ' Option 1. Use a single DAL_Class instance with separate public properties for each database. | |||||
| ' Ex: To access Orders use DAL.Orders and to access Employees use DAL.Employees. | |||||
| ' | |||||
| ' Option 2. Use a separate DAL_Class instance for each database. | |||||
| ' Ex: | |||||
| ' dim OrdersDAL : set OrdersDAL = new DAL_Class | |||||
| ' OrdersDAL.ConnectionString = "..." <-- you would have to create this property to use this approach | |||||
| ' | |||||
| ' If you only access one database it is easier to just set the global DAL singleton to an instance of the | |||||
| ' Database_Class and use it directly. See the example project for details. | |||||
| '======================================================================================================================= | |||||
| ' DATA ACCESS LAYER Class | |||||
| '======================================================================================================================= | |||||
| dim DAL__Singleton : set DAL__Singleton = Nothing | |||||
| Function DAL() | |||||
| If DAL__Singleton is Nothing then | |||||
| set DAL__Singleton = new Database_Class | |||||
| DAL__Singleton.Initialize GetAppSetting("ConnectionString") | |||||
| End If | |||||
| set DAL = DAL__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,113 @@ | |||||
| <% | |||||
| Class Database_Class | |||||
| Private m_connection | |||||
| Private m_connection_string | |||||
| Private m_trace_enabled | |||||
| Public Sub set_trace(bool) : m_trace_enabled = bool : End Sub | |||||
| Public Property Get is_trace_enabled : is_trace_enabled = m_trace_enabled : End Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub Initialize(connection_string) | |||||
| m_connection_string = connection_string | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function ShapeQuery(sql,params) | |||||
| dim shapeConn : set shapeConn = server.createobject("adodb.connection") | |||||
| shapeConn.ConnectionString = "Provider=MSDataShape;Data " & m_connection_string | |||||
| dim cmd : set cmd = server.createobject("adodb.command") | |||||
| shapeConn.open | |||||
| set cmd.ActiveConnection = shapeConn | |||||
| cmd.CommandText = sql | |||||
| dim rs | |||||
| If IsArray(params) then | |||||
| set rs = cmd.Execute(, params) | |||||
| ElseIf Not IsEmpty(params) then ' one parameter | |||||
| set rs = cmd.Execute(, Array(params)) | |||||
| Else | |||||
| set rs = cmd.Execute() | |||||
| End If | |||||
| set ShapeQuery = rs | |||||
| End Function | |||||
| Public Function Query(sql, params) | |||||
| dim cmd : set cmd = server.createobject("adodb.command") | |||||
| set cmd.ActiveConnection = Connection | |||||
| cmd.CommandText = sql | |||||
| dim rs | |||||
| If IsArray(params) then | |||||
| set rs = cmd.Execute(, params) | |||||
| ElseIf Not IsEmpty(params) then ' one parameter | |||||
| set rs = cmd.Execute(, Array(params)) | |||||
| Else | |||||
| set rs = cmd.Execute() | |||||
| End If | |||||
| set Query = rs | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function PagedQuery(sql, params, per_page, page_num) | |||||
| dim cmd : set cmd = server.createobject("adodb.command") | |||||
| set cmd.ActiveConnection = Connection | |||||
| cmd.CommandText = sql | |||||
| cmd.CommandType = 1 'adCmdText | |||||
| cmd.ActiveConnection.CursorLocation = 3 'adUseClient | |||||
| dim rs | |||||
| If IsArray(params) then | |||||
| set rs = cmd.Execute(, params) | |||||
| ElseIf Not IsEmpty(params) then ' one parameter | |||||
| set rs = cmd.Execute(, Array(params)) | |||||
| Else | |||||
| set rs = cmd.Execute() | |||||
| End If | |||||
| If Not rs.EOF then | |||||
| rs.PageSize = 1 | |||||
| rs.CacheSize = 1 | |||||
| rs.AbsolutePage = 1 | |||||
| End If | |||||
| set PagedQuery = rs | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub [Execute](sql, params) | |||||
| me.query sql, params | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub BeginTransaction | |||||
| Connection.BeginTrans | |||||
| End Sub | |||||
| Public Sub RollbackTransaction | |||||
| Connection.RollbackTrans | |||||
| End Sub | |||||
| Public Sub CommitTransaction | |||||
| Connection.CommitTrans | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Private Methods | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Private Sub Class_terminate | |||||
| Destroy m_connection | |||||
| End Sub | |||||
| Public Function Connection | |||||
| if not isobject(m_connection) then | |||||
| set m_connection = Server.CreateObject("adodb.connection") | |||||
| m_connection.open m_connection_string | |||||
| end if | |||||
| set Connection = m_connection | |||||
| End Function | |||||
| end Class | |||||
| %> | |||||
| @@ -0,0 +1,202 @@ | |||||
| <% | |||||
| Class EnumerableHelper_Class | |||||
| Private m_list | |||||
| Public Sub Init(list) | |||||
| set m_list = list | |||||
| End Sub | |||||
| Public Sub Class_Terminate | |||||
| set m_list = Nothing | |||||
| End Sub | |||||
| Public Default Function Data() | |||||
| set Data = m_list | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Convenience wrappers | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function Count() | |||||
| Count = m_list.Count() | |||||
| End Function | |||||
| Public Function First() | |||||
| Assign First, m_list.Front() | |||||
| End Function | |||||
| Public Function Last() | |||||
| Assign Last, m_list.Back() | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Methods that return a single value | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'true if all elements of the list satisfy the condition | |||||
| Public Function All(condition) | |||||
| dim item_, all_matched : all_matched = true | |||||
| dim it : set it = m_list.Iterator | |||||
| Do While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| If "String" = typename(condition) then | |||||
| If Not eval(condition) then | |||||
| all_matched = false | |||||
| End If | |||||
| Else | |||||
| If Not condition(item_) then | |||||
| all_matched = false | |||||
| End If | |||||
| End If | |||||
| If Not all_matched then Exit Do | |||||
| Loop | |||||
| All = all_matched | |||||
| End Function | |||||
| 'true if any element of the list satisfies the condition | |||||
| Public Function Any(condition) | |||||
| Any = Not All("Not " & condition) | |||||
| End Function | |||||
| Public Function Max(expr) | |||||
| dim V_, item_, maxval | |||||
| dim it : set it = m_list.Iterator | |||||
| If "String" = typename(expr) then | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| Assign V_, eval(expr) | |||||
| If V_ > maxval then maxval = V_ | |||||
| Wend | |||||
| Else | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| Assign V_, expr(item_) | |||||
| If V_ > maxval then maxval = V_ | |||||
| Wend | |||||
| End If | |||||
| Max = maxval | |||||
| End Function | |||||
| Public Function Min(expr) | |||||
| dim V_, item_, minval | |||||
| dim it : set it = m_list.Iterator | |||||
| If "String" = typename(expr) then | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| If IsEmpty(minval) then ' empty is always less than everything so set it on first pass | |||||
| Assign minval, item_ | |||||
| End If | |||||
| Assign V_, eval(expr) | |||||
| If V_ < minval then minval = V_ | |||||
| Wend | |||||
| Else | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| If IsEmpty(minval) then | |||||
| Assign minval, item_ | |||||
| End If | |||||
| V_ = expr(item_) | |||||
| If V_ < minval then minval = V_ | |||||
| Wend | |||||
| End If | |||||
| Min = minval | |||||
| End Function | |||||
| Public Function Sum(expr) | |||||
| dim V_, item_ | |||||
| dim it : set it = m_list.Iterator | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| execute "V_ = V_ + " & expr | |||||
| Wend | |||||
| Sum = V_ | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Methods that return a new instance of this class | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'returns a list that results from running lambda_or_proc once for every element in the list | |||||
| Public Function Map(lambda_or_proc) | |||||
| dim list2 : set list2 = new LinkedList_Class | |||||
| dim it : set it = m_list.Iterator | |||||
| dim item_ | |||||
| If "String" = typename(lambda_or_proc) then | |||||
| dim V_ | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| execute lambda_or_proc | |||||
| list2.Push V_ | |||||
| Wend | |||||
| Else | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| list2.Push lambda_or_proc(item_) | |||||
| Wend | |||||
| End If | |||||
| set Map = Enumerable(list2) | |||||
| End Function | |||||
| 'alias to match IEnumerable for convenience | |||||
| Public Function [Select](lambda_or_proc) | |||||
| set [Select] = Map(lambda_or_proc) | |||||
| End Function | |||||
| 'returns list containing first n items | |||||
| Public Function Take(n) | |||||
| dim list2 : set list2 = new LinkedList_Class | |||||
| dim it : set it = m_list.Iterator | |||||
| dim i : i = 1 | |||||
| While it.HasNext And i <= n | |||||
| list2.Push it.GetNext() | |||||
| i = i + 1 | |||||
| Wend | |||||
| set Take = Enumerable(list2) | |||||
| End Function | |||||
| 'returns list containing elements as long as the condition is true, and skips the remaining elements | |||||
| Public Function TakeWhile(condition) | |||||
| dim list2 : set list2 = new LinkedList_Class | |||||
| dim item_, V_, bln | |||||
| dim it : set it = m_list.Iterator | |||||
| Do While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| If "String" = typename(condition) then | |||||
| 'execute condition | |||||
| If Not eval(condition) then Exit Do | |||||
| Else | |||||
| If Not condition(item_) then Exit Do | |||||
| End If | |||||
| list2.Push item_ | |||||
| Loop | |||||
| set TakeWhile = Enumerable(list2) | |||||
| End Function | |||||
| 'returns a list containing only elements that satisfy the condition | |||||
| Public Function Where(condition) | |||||
| dim list2 : set list2 = new LinkedList_Class | |||||
| dim it : set it = m_list.Iterator | |||||
| dim item_ | |||||
| While it.HasNext | |||||
| Assign item_, it.GetNext() | |||||
| If "String" = typename(condition) then | |||||
| If eval(condition) then list2.Push item_ | |||||
| Else | |||||
| If condition(item_) then list2.Push item_ | |||||
| End If | |||||
| Wend | |||||
| set Where = Enumerable(list2) | |||||
| End Function | |||||
| End Class | |||||
| Function Enumerable(list) | |||||
| dim E : set E = new EnumerableHelper_Class | |||||
| E.Init list | |||||
| set Enumerable = E | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,178 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' Error Handler Library | |||||
| ' Provides centralized error handling and logging functionality | |||||
| '======================================================================================================================= | |||||
| Class ErrorHandler_Class | |||||
| Private m_log_to_file | |||||
| Private m_log_file_path | |||||
| Private Sub Class_Initialize() | |||||
| m_log_to_file = False | |||||
| m_log_file_path = "" | |||||
| End Sub | |||||
| ' Configure whether to log errors to file | |||||
| Public Property Let LogToFile(value) | |||||
| m_log_to_file = value | |||||
| End Property | |||||
| Public Property Get LogToFile() | |||||
| LogToFile = m_log_to_file | |||||
| End Property | |||||
| ' Set the log file path | |||||
| Public Property Let LogFilePath(value) | |||||
| m_log_file_path = value | |||||
| End Property | |||||
| Public Property Get LogFilePath() | |||||
| LogFilePath = m_log_file_path | |||||
| End Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Main error handling method | |||||
| ' context: String describing where the error occurred | |||||
| ' errObj: VBScript Err object (optional, uses global Err if not provided) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub HandleError(context, errObj) | |||||
| Dim isDevelopment | |||||
| isDevelopment = (LCase(GetAppSetting("Environment")) = "development") | |||||
| If IsEmpty(errObj) Or Not IsObject(errObj) Then | |||||
| ' Use global Err object if none provided | |||||
| If Err.Number <> 0 Then | |||||
| If isDevelopment Then | |||||
| ShowDetailedError context, Err | |||||
| Else | |||||
| ShowGenericError | |||||
| End If | |||||
| LogError context, Err | |||||
| End If | |||||
| Else | |||||
| If errObj.Number <> 0 Then | |||||
| If isDevelopment Then | |||||
| ShowDetailedError context, errObj | |||||
| Else | |||||
| ShowGenericError | |||||
| End If | |||||
| LogError context, errObj | |||||
| End If | |||||
| End If | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Display detailed error information (Development mode only) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Private Sub ShowDetailedError(context, errObj) | |||||
| Dim errHtml | |||||
| errHtml = "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; font-family:Verdana,sans-serif; font-size:14px; border-radius:4px;'>" | |||||
| errHtml = errHtml & "<strong>Error Occurred</strong><br>" | |||||
| If Not IsEmpty(context) And Len(context) > 0 Then | |||||
| errHtml = errHtml & "<strong>Context:</strong> " & Server.HTMLEncode(context) & "<br>" | |||||
| End If | |||||
| errHtml = errHtml & "<strong>Time:</strong> " & Now() & "<br>" | |||||
| errHtml = errHtml & "<strong>Number:</strong> " & errObj.Number & "<br>" | |||||
| errHtml = errHtml & "<strong>Description:</strong> " & Server.HTMLEncode(errObj.Description) & "<br>" | |||||
| If Len(errObj.Source) > 0 Then | |||||
| errHtml = errHtml & "<strong>Source:</strong> " & Server.HTMLEncode(errObj.Source) & "<br>" | |||||
| End If | |||||
| errHtml = errHtml & "</div>" | |||||
| Response.Write errHtml | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Display generic error message (Production mode) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Private Sub ShowGenericError() | |||||
| Dim errHtml | |||||
| errHtml = "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; font-family:Verdana,sans-serif; font-size:14px; border-radius:4px;'>" | |||||
| errHtml = errHtml & "<strong>An error occurred</strong><br>" | |||||
| errHtml = errHtml & "We apologize for the inconvenience. The error has been logged and will be investigated.<br>" | |||||
| errHtml = errHtml & "Please try again later or contact support if the problem persists." | |||||
| errHtml = errHtml & "</div>" | |||||
| Response.Write errHtml | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Log error to file (if enabled) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Private Sub LogError(context, errObj) | |||||
| If Not m_log_to_file Or Len(m_log_file_path) = 0 Then Exit Sub | |||||
| On Error Resume Next | |||||
| Dim fso, logFile, logEntry | |||||
| Set fso = Server.CreateObject("Scripting.FileSystemObject") | |||||
| ' Create log entry | |||||
| logEntry = "[" & Now() & "] " | |||||
| If Len(context) > 0 Then | |||||
| logEntry = logEntry & "Context: " & context & " | " | |||||
| End If | |||||
| logEntry = logEntry & "Error #" & errObj.Number & ": " & errObj.Description | |||||
| If Len(errObj.Source) > 0 Then | |||||
| logEntry = logEntry & " | Source: " & errObj.Source | |||||
| End If | |||||
| logEntry = logEntry & vbCrLf | |||||
| ' Append to log file | |||||
| If fso.FileExists(m_log_file_path) Then | |||||
| Set logFile = fso.OpenTextFile(m_log_file_path, 8, False) ' 8 = ForAppending | |||||
| Else | |||||
| Set logFile = fso.CreateTextFile(m_log_file_path, True) | |||||
| End If | |||||
| logFile.Write logEntry | |||||
| logFile.Close | |||||
| Set logFile = Nothing | |||||
| Set fso = Nothing | |||||
| On Error GoTo 0 | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Quick check if an error exists | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function HasError() | |||||
| HasError = (Err.Number <> 0) | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Clear the current error | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub ClearError() | |||||
| Err.Clear | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Check and handle error in one call | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function CheckAndHandle(context) | |||||
| If Err.Number <> 0 Then | |||||
| HandleError context, Err | |||||
| CheckAndHandle = True | |||||
| Else | |||||
| CheckAndHandle = False | |||||
| End If | |||||
| End Function | |||||
| End Class | |||||
| ' Singleton instance | |||||
| Dim ErrorHandler_Class__Singleton | |||||
| Function ErrorHandler() | |||||
| If IsEmpty(ErrorHandler_Class__Singleton) Then | |||||
| Set ErrorHandler_Class__Singleton = New ErrorHandler_Class | |||||
| End If | |||||
| Set ErrorHandler = ErrorHandler_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,151 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' Flash Message Class | |||||
| '======================================================================================================================= | |||||
| Class Flash_Class | |||||
| Private m_errors_key | |||||
| Private m_success_key | |||||
| Private Sub Class_Initialize | |||||
| ' Use constants to avoid typos | |||||
| m_errors_key = "mvc.flash.errors_array" | |||||
| m_success_key = "mvc.flash.success_message" | |||||
| End Sub | |||||
| 'helper methods to avoid if..then statements in views | |||||
| Public Sub ShowErrorsIfPresent | |||||
| if HasErrors then ShowErrors | |||||
| End Sub | |||||
| Public Sub ShowSuccessIfPresent | |||||
| if HasSuccess then ShowSuccess | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Errors | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Property Get HasErrors | |||||
| HasErrors = (Not IsEmpty(Session(m_errors_key))) | |||||
| End Property | |||||
| Public Property Get Errors | |||||
| Errors = Session(m_errors_key) | |||||
| End Property | |||||
| Public Property Let Errors(ary) | |||||
| Session(m_errors_key) = ary | |||||
| End Property | |||||
| Public Sub AddError(msg) | |||||
| dim ary | |||||
| if IsEmpty(Session(m_errors_key)) then | |||||
| ary = Array() | |||||
| redim ary(-1) | |||||
| else | |||||
| ary = Session(m_errors_key) | |||||
| end if | |||||
| redim preserve ary(ubound(ary) + 1) | |||||
| ary(ubound(ary)) = msg | |||||
| Session(m_errors_key) = ary | |||||
| End Sub | |||||
| 'Public Sub ShowErrors | |||||
| ' ClearErrors | |||||
| 'End Sub | |||||
| Public Sub ShowErrors | |||||
| if HasErrors then | |||||
| %> | |||||
| <div id="flashErrorBox" class="alert alert-danger alert-dismissible fade show" role="alert"> | |||||
| <strong>Error!</strong> | |||||
| <ul class="mb-0 mt-2"> | |||||
| <% | |||||
| dim ary, i | |||||
| ary = Errors | |||||
| for i = 0 to ubound(ary) | |||||
| put "<li>" | |||||
| put H(ary(i)) | |||||
| put "</li>" | |||||
| next | |||||
| %> | |||||
| </ul> | |||||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |||||
| </div> | |||||
| <script> | |||||
| (function() { | |||||
| var timeout = <%= GetAppSetting("FlashMessageTimeout") %>; | |||||
| if (isNaN(timeout) || timeout <= 0) timeout = 3000; | |||||
| setTimeout(function() { | |||||
| var alertEl = document.getElementById("flashErrorBox"); | |||||
| if (alertEl && typeof bootstrap !== 'undefined') { | |||||
| var bsAlert = bootstrap.Alert.getOrCreateInstance(alertEl); | |||||
| bsAlert.close(); | |||||
| } else if (alertEl) { | |||||
| alertEl.style.display = "none"; | |||||
| } | |||||
| }, timeout); | |||||
| })(); | |||||
| </script> | |||||
| <% | |||||
| ClearErrors | |||||
| end if | |||||
| End Sub | |||||
| Public Sub ClearErrors | |||||
| Session.Contents.Remove(m_errors_key) | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Success | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Property Get HasSuccess | |||||
| HasSuccess = (Not IsEmpty(Session(m_success_key))) | |||||
| End Property | |||||
| Public Property Get Success | |||||
| Success = Session(m_success_key) | |||||
| End Property | |||||
| Public Property Let Success(msg) | |||||
| Session(m_success_key) = msg | |||||
| End Property | |||||
| Public Sub ShowSuccess | |||||
| if HasSuccess then | |||||
| %> | |||||
| <div id="flashSuccessBox" class="alert alert-success alert-dismissible fade show" role="alert"> | |||||
| <%= H(Success) %> | |||||
| <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> | |||||
| </div> | |||||
| <script> | |||||
| (function() { | |||||
| var timeout = <%= GetAppSetting("FlashMessageTimeout") %>; | |||||
| if (isNaN(timeout) || timeout <= 0) timeout = 3000; | |||||
| setTimeout(function() { | |||||
| var alertEl = document.getElementById("flashSuccessBox"); | |||||
| if (alertEl && typeof bootstrap !== 'undefined') { | |||||
| var bsAlert = bootstrap.Alert.getOrCreateInstance(alertEl); | |||||
| bsAlert.close(); | |||||
| } else if (alertEl) { | |||||
| alertEl.style.display = "none"; | |||||
| } | |||||
| }, timeout); | |||||
| })(); | |||||
| </script> | |||||
| <% | |||||
| ClearSuccess | |||||
| end if | |||||
| End Sub | |||||
| Public Sub ClearSuccess | |||||
| Session.Contents.Remove(m_success_key) | |||||
| End Sub | |||||
| End Class | |||||
| Function Flash() | |||||
| set Flash = new Flash_Class | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,79 @@ | |||||
| <% | |||||
| Class FormCache_Class | |||||
| 'given a form name and IRequestDictionary params (request.form) object, caches form values | |||||
| Public Sub SerializeForm(form_name, params) | |||||
| dim form_key, form_val, serialized_key | |||||
| For Each form_key in params | |||||
| form_val = params(form_key) | |||||
| serialized_key = CachedFormKeyName(form_name, form_key) | |||||
| 'put "serialize<br>" | |||||
| 'put "--form_key := " & form_key & "<br>" | |||||
| 'put "--form_val := " & form_val & "<br>" | |||||
| 'put "--serialized_key := " & serialized_key & "<br>" | |||||
| Session(serialized_key) = form_val | |||||
| Next | |||||
| End Sub | |||||
| 'given a form name, returns a dict with the form's stored values | |||||
| Public Function DeserializeForm(form_name) | |||||
| dim dict : set dict = Nothing | |||||
| dim serialized_key, serialized_val, form_key, form_val | |||||
| For Each serialized_key in Session.Contents | |||||
| 'put "serialized_key: " & serialized_key & "<br>" | |||||
| If InStr(serialized_key, "mvc.form." & form_name) > 0 then | |||||
| 'put "--match" & "<br>" | |||||
| If dict Is Nothing then | |||||
| set dict = Server.CreateObject("Scripting.Dictionary") | |||||
| 'put "dict created<br>" | |||||
| End If | |||||
| form_val = Session(serialized_key) | |||||
| form_key = Replace(serialized_key, "mvc.form." & form_name & ".", "") | |||||
| dict(form_key) = form_val | |||||
| 'Session.Contents.Remove serialized_key | |||||
| 'put "--serialized_val: " & serialized_val & "<br>" | |||||
| 'put "--form_val: " & form_val & "<br>" | |||||
| End If | |||||
| Next | |||||
| set DeserializeForm = dict | |||||
| End Function | |||||
| 'given a form name, clears the keys for that form | |||||
| Public Sub ClearForm(form_name) | |||||
| Dim key, prefix, keysToRemove(), idx | |||||
| prefix = "mvc.form." & form_name & "." | |||||
| ReDim keysToRemove(-1) | |||||
| ' First collect the keys to remove | |||||
| For Each key In Session.Contents | |||||
| If Left(CStr(key), Len(prefix)) = prefix Then | |||||
| ReDim Preserve keysToRemove(UBound(keysToRemove) + 1) | |||||
| keysToRemove(UBound(keysToRemove)) = key | |||||
| End If | |||||
| Next | |||||
| ' Then remove them | |||||
| For idx = 0 To UBound(keysToRemove) | |||||
| Session.Contents.Remove keysToRemove(idx) | |||||
| Next | |||||
| End Sub | |||||
| Private Function CachedFormKeyName(form_name, key) | |||||
| CachedFormKeyName = "mvc.form." & form_name & "." & key | |||||
| End Function | |||||
| End Class | |||||
| dim FormCache__Singleton | |||||
| Function FormCache() | |||||
| if IsEmpty(FormCache__Singleton) then | |||||
| set FormCache__Singleton = new FormCache_Class | |||||
| end if | |||||
| set FormCache = FormCache__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,77 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' HTML SECURITY HELPER | |||||
| '======================================================================================================================= | |||||
| Class HTML_Security_Helper_Class | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Uses Scriptlet.TypeLib to generate a GUID. There may be a better/faster way than this to generate a nonce. | |||||
| Public Function Nonce() | |||||
| dim TL : set TL = CreateObject("Scriptlet.TypeLib") | |||||
| Nonce = Left(CStr(TL.Guid), 38) 'avoids issue w/ strings appended after this token not being displayed on screen, MSFT bug | |||||
| set TL = Nothing | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Name is probably the combined ControllerName and ActionName of the form generator by convention | |||||
| Public Sub SetAntiCSRFToken(name) | |||||
| Session(name & ".anti_csrf_token") = Nonce() | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Returns the CSRF token nonce from the session corresponding to the passed name | |||||
| Public Function GetAntiCSRFToken(name) | |||||
| dim token : token = Session(name & ".anti_csrf_token") | |||||
| If Len(token) = 0 or IsEmpty(token) then | |||||
| SetAntiCSRFToken name | |||||
| End If | |||||
| GetAntiCSRFToken = token | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Removes the current CSRF token nonce for the passed name | |||||
| Public Sub ClearAntiCSRFToken(name) | |||||
| Session.Contents.Remove(name & ".anti_csrf_token") | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Returns true if passed nonce matches the stored CSRF token nonce for the specified name, false if not | |||||
| Public Function IsValidAntiCSRFToken(name, nonce) | |||||
| IsValidAntiCSRFToken = (GetAntiCSRFToken(name) = nonce) | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'If an invalid CSRF nonce is passed, sets the flash and redirects using the appropriate MVC.Redirect* method. | |||||
| 'If a valid CSRF nonce is passed, clears it from the cache to reset the state to the beginning. | |||||
| Public Sub OnInvalidAntiCSRFTokenRedirectToAction(token_name, token, action_name) | |||||
| OnInvalidAntiCSRFTokenRedirectToExt token_name, token, MVC.ControllerName, action_name, empty | |||||
| End Sub | |||||
| Public Sub OnInvalidAntiCSRFTokenRedirectToActionExt(token_name, token, action_name, params) | |||||
| OnInvalidAntiCSRFTokenRedirectToExt token_name, token, MVC.ControllerName, action_name, params | |||||
| End Sub | |||||
| Public Sub OnInvalidAntiCSRFTokenRedirectTo(token_name, token, controller_name, action_name) | |||||
| OnInvalidAntiCSRFTokenRedirectToExt token_name, token, controller_name, action_name | |||||
| End Sub | |||||
| Public Sub OnInvalidAntiCSRFTokenRedirectToExt(token_name, token, controller_name, action_name, params) | |||||
| If IsValidAntiCSRFToken(token_name, token) then | |||||
| ClearAntiCSRFToken token_name | |||||
| Else | |||||
| ClearAntiCSRFToken token_name | |||||
| Flash.AddError "Invalid form state. Please try again." | |||||
| MVC.RedirectToExt controller_name, action_name, params | |||||
| End If | |||||
| End Sub | |||||
| End Class | |||||
| dim HTML_Security_Helper__Singleton | |||||
| Function HTMLSecurity() | |||||
| If IsEmpty(HTML_Security_Helper__Singleton) Then | |||||
| set HTML_Security_Helper__Singleton = new HTML_Security_Helper_Class | |||||
| End If | |||||
| set HTMLSecurity = HTML_Security_Helper__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,282 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' HTML HELPER | |||||
| '======================================================================================================================= | |||||
| Class HTML_Helper_Class | |||||
| 'Duplicate of Routes.NoCacheToken, copied to avoid extra lookup into the Routes object for such a trivial function. | |||||
| 'Allows caller to reference HTML.NoCacheToken in cases where it seems to feel right. | |||||
| Public Property Get NoCacheToken | |||||
| NoCacheToken = Timer() * 100 | |||||
| End Property | |||||
| 'Ensures safe output | |||||
| Public Function Encode(ByVal value) | |||||
| If Not IsEmpty(value) and Not IsNull(value) then | |||||
| Encode = Server.HtmlEncode(value) | |||||
| End If | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'LinkTo and its relatives DO NOT HTMLEncode the link_text! This allows use of HTML within the link, especially | |||||
| 'useful for Bootstrap icons and the like. | |||||
| ' | |||||
| 'Bottom Line: If you need to HTMLEncode the link text YOU MUST DO IT YOURSELF! The H() method makes this easy! | |||||
| Public Function LinkTo(link_text, controller_name, action_name) | |||||
| LinkTo = LinkToExt(link_text, controller_name, action_name, empty, empty) | |||||
| End Function | |||||
| Public Function LinkToExt(link_text, controller_name, action_name, params_array, attribs_array) | |||||
| LinkToExt = "<a href='" & Encode(Routes.UrlTo(controller_name, action_name, params_array)) & "'" &_ | |||||
| HtmlAttribs(attribs_array) & ">" & link_text & "</a>" & vbCR | |||||
| End Function | |||||
| Public Function LinkToIf(condition, link_text, controller_name, action_name) | |||||
| if condition then | |||||
| LinkToIf = LinkToExt(link_text, controller_name, action_name, empty, empty) | |||||
| end if | |||||
| End Function | |||||
| Public Function LinkToExtIf(condition, link_text, controller_name, action_name, params_array, attribs_array) | |||||
| if condition then | |||||
| LinkToExtIf = LinkToExt(link_text, controller_name, action_name, params_array, attribs_array) | |||||
| end if | |||||
| End Function | |||||
| Public Function LinkToUnless(condition, link_text, controller_name, action_name) | |||||
| if not condition then | |||||
| LinkToIf = LinkToExt(link_text, controller_name, action_name, empty, empty) | |||||
| end if | |||||
| End Function | |||||
| Public Function LinkToExtUnless(condition, link_text, controller_name, action_name, params_array, attribs_array) | |||||
| if not condition then | |||||
| LinkToExtUnless = LinkToExt(link_text, controller_name, action_name, params_array, attribs_array) | |||||
| end if | |||||
| End Function | |||||
| '' | |||||
| ' Creates a form button and a hidden form to enforce POST submissions. Params are in hidden fields. | |||||
| '' | |||||
| 'Public Function PostButtonLinkTo(controller_name, action_name, params) | |||||
| ' dim id : id = "post_button__" & controller_name & action_name | |||||
| ' dim s | |||||
| ' s = "<form id='" & id & "' action='" & Routes.UrlTo(controller_name, action_name, empty) & "' method='POST'>" | |||||
| ' dim i, key, val | |||||
| ' for i = 0 to ubound(params) step 2 | |||||
| ' KeyVal params, i, key, val | |||||
| ' s = s & "<input type='hidden' name='" & key & "' value='" & val & "'>" | |||||
| ' next | |||||
| ' s = s & "<input type='submit' value='>>'>" | |||||
| ' s = s & "</form>" | |||||
| ' PostButtonLinkTo = s | |||||
| 'End Function | |||||
| Public Function PostButtonTo(button_contents, controller_name, action_name, form_fields) | |||||
| PostButtonTo = PostButtonToExt(button_contents, controller_name, action_name, form_fields, empty) | |||||
| End Function | |||||
| Public Function PostButtonToExt(button_contents, controller_name, action_name, form_fields, url_params) | |||||
| dim s : s = "<form action='" & Routes.UrlTo(controller_name, action_name, url_params) & "' method='POST' style='margin: 0;'>" | |||||
| dim i, key, val | |||||
| for i = 0 to ubound(form_fields) step 2 | |||||
| KeyVal form_fields, i, key, val | |||||
| s = s & HTML.Hidden(key, val) | |||||
| next | |||||
| s = s & HTML.SubmitButton(button_contents) | |||||
| s = s & "</form>" & vbCR | |||||
| PostButtonToExt = s | |||||
| End Function | |||||
| Public Function AppStylesheetTag | |||||
| AppStylesheetTag = StylesheetTag(Routes.StylesheetsURL & "App.css") | |||||
| End Function | |||||
| Public Function ControllerStylesheetTag | |||||
| ControllerStylesheetTag = StylesheetTag(Routes.StylesheetsUrl & MVC.ControllerName & "Controller.css") | |||||
| End Function | |||||
| Public Function StylesheetTag(url) | |||||
| StylesheetTag = "<link rel='stylesheet' href='" & Encode(url) & "?" & Year(now) & Month(now) & Day(now) & Hour(now) & Minute(now) & Second(now) & "'>" & vbCR | |||||
| End Function | |||||
| Public Function JSTag(url) | |||||
| JSTag = "<script type='text/javascript' src='" & Encode(url) & "'></script>" & vbCR | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Form Helpers | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function FormTag(controller_name, action_name, route_attribs, form_attribs) | |||||
| FormTag = "<form action='" & Routes.UrlTo(controller_name, action_name, route_attribs) & "' method='POST' " & HtmlAttribs(form_attribs) & ">" & vbCR | |||||
| End Function | |||||
| Public Function Label(name, for_name) | |||||
| Label = LabelExt(name, for_name, empty) | |||||
| End Function | |||||
| Public Function LabelExt(name, for_name, attribs) | |||||
| LabelExt = "<label for='" & Encode(for_name) & "' " & HtmlAttribs(attribs) & ">" & Encode(name) & "</label>" & vbCR | |||||
| End Function | |||||
| Public Function Hidden(id, value) | |||||
| Hidden = HiddenExt(id, value, empty) | |||||
| End Function | |||||
| Public Function HiddenExt(id, value, attribs) | |||||
| HiddenExt = "<input type='hidden' id='" & Encode(id) & "' name='" & Encode(id) & "' value='" & Encode(value) & "' " & HtmlAttribs(attribs) & " >" & vbCR | |||||
| End Function | |||||
| Public Function TextBox(id, value) | |||||
| TextBox = TextBoxExt(id, value, empty) | |||||
| End Function | |||||
| Public Function TextBoxExt(id, value, attribs) | |||||
| TextBoxExt = "<input type='text' id='" & Encode(id) & "' name='" & Encode(id) & "' value='" & Encode(value) & "' " & HtmlAttribs(attribs) & " >" & vbCR | |||||
| End Function | |||||
| Public Function TextArea(id, value, rows, cols) | |||||
| TextArea = TextAreaExt(id, value, rows, cols, empty) | |||||
| End Function | |||||
| Public Function TextAreaExt(id, value, rows, cols, attribs) | |||||
| TextAreaExt = "<textarea id='" & Encode(id) & "' name='" & Encode(id) & "' cols='" & Encode(cols) & "' rows='" & Encode(rows) & "' " & HtmlAttribs(attribs) & " >" &_ | |||||
| Encode(value) & "</textarea>" & vbCR | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'If list is a recordset then option_value_field and option_text_field are required. | |||||
| 'If list is an array the method assumes it is a KVArray and those parameters are ignored. | |||||
| Public Function DropDownList(id, selected_value, list, option_value_field, option_text_field) | |||||
| DropDownList = DropDownListExt(id, selected_value, list, option_value_field, option_text_field, empty) | |||||
| End Function | |||||
| Public Function DropDownListExt(id, selected_value, list, option_value_field, option_text_field, attribs) | |||||
| If IsNull(selected_value) then | |||||
| selected_value = "" | |||||
| Else | |||||
| selected_value = CStr(selected_value) | |||||
| End If | |||||
| dim item, options, opt_val, opt_txt | |||||
| options = "<option value=''>" ' first value is "non-selected" blank state | |||||
| select case typename(list) | |||||
| case "Recordset" | |||||
| do until list.EOF | |||||
| If IsNull(list(option_value_field)) then | |||||
| opt_val = "" | |||||
| Else | |||||
| opt_val = CStr(list(option_value_field)) | |||||
| End If | |||||
| opt_txt = list(option_text_field) | |||||
| If Not IsNull(opt_val) And Not IsEmpty(opt_val) then | |||||
| options = options & "<option value='" & Encode(opt_val) & "' " & Choice((CStr(opt_val) = CStr(selected_value)), "selected='selected'", "") & ">" & Encode(opt_txt) & "</option>" & vbCR | |||||
| End If | |||||
| list.MoveNext | |||||
| loop | |||||
| case "Variant()" 'assumes KVArray | |||||
| dim i | |||||
| for i = 0 to ubound(list) step 2 | |||||
| KeyVal list, i, opt_val, opt_txt | |||||
| options = options & "<option value='" & Encode(opt_val) & "' " & Choice((CStr(opt_val) = CStr(selected_value)), "selected='selected'", "") & ">" & Encode(opt_txt) & "</option>" & vbCR | |||||
| next | |||||
| end select | |||||
| DropDownListExt = "<select id='" & Encode(id) & "' name='" & Encode(id) & "' " & HtmlAttribs(attribs) & " >" & vbCR & options & "</select>" & vbCR | |||||
| End Function | |||||
| Public Function Checkbox(id, value) | |||||
| Checkbox = CheckboxExt(id, value, empty) | |||||
| End Function | |||||
| Public Function CheckboxExt(id, value, attribs) | |||||
| CheckBoxExt = "<input type='checkbox' id='" & Encode(id) & "' name='" & Encode(id) & "' " & Choice( (value = 1) or (value = true) or (LCase(value) = "true") or (LCase(value) = "on"), "checked='checked'", "") & " " & HtmlAttribs(attribs) & ">" & vbCR | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| 'Button text IS NOT ENCODED! As with LinkTo, this allows use of Bootstrap icons and other arbitrary HTML in the | |||||
| 'button. If you need to HTMLEncode the text you MUST do it yourself! | |||||
| Public Function SubmitButton(text) | |||||
| SubmitButton = "<button type='submit' class='btn'>" & text & "</button>" & vbCR | |||||
| End Function | |||||
| Public Function Button(button_type, text, class_name) | |||||
| Button = "<button type='" & Encode(button_type) & "' class='btn " & Encode(class_name) & "'>" & text & "</button>" & vbCR | |||||
| End Function | |||||
| Public Function ButtonExt(button_type, text, attribs_array) | |||||
| ButtonExt = "<button type='" & Encode(button_type) & "' " & HtmlAttribs(attribs_array) & ">" & text & "</button>" & vbCR | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function Tag(Tag_name, attribs_array) | |||||
| Tag = "<" & Encode(tag_name) & " " & HtmlAttribs(attribs_array) & ">" | |||||
| End Function | |||||
| Public Function Tag_(Tag_name) | |||||
| Tag_ = "</" & Encode(tag_name) & ">" | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function HtmlAttribs(attribs) | |||||
| dim result : result = "" | |||||
| if not IsEmpty(attribs) then | |||||
| if IsArray(attribs) then | |||||
| dim idx | |||||
| for idx = lbound(attribs) to ubound(attribs) step 2 | |||||
| result = result & " " & HtmlAttrib(attribs, idx) & " " | |||||
| next | |||||
| else ' assume string or string-like default value | |||||
| result = attribs | |||||
| end if | |||||
| end if | |||||
| HtmlAttribs = result | |||||
| End Function | |||||
| Public Function HtmlAttrib(attribs_array, key_idx) | |||||
| dim key, val | |||||
| KeyVal attribs_array, key_idx, key, val | |||||
| HtmlAttrib = Encode(key) & "='" & Encode(val) & "'" | |||||
| End Function | |||||
| Function checkboxfor(fieldName, isChecked) | |||||
| Dim idSuffix, checkboxId, html | |||||
| ' Decide on the id suffix and checked attribute | |||||
| If isChecked Then | |||||
| idSuffix = "Checked" | |||||
| Else | |||||
| idSuffix = "Default" | |||||
| End If | |||||
| checkboxId = "flexSwitchCheck" & idSuffix | |||||
| ' Build the HTML string | |||||
| html = "<div class='form-check form-switch'>" & vbCrLf | |||||
| html = html & " <input class='form-check-input form-control' type='checkbox' id='" & checkboxId & "' name='" & fieldName & "'" | |||||
| If isChecked Then html = html & " checked" | |||||
| html = html & ">" & vbCrLf | |||||
| html = html & " <label class='form-check-label' for='" & checkboxId & "'>" & fieldName & "</label>" & vbCrLf | |||||
| html = html & "</div>" | |||||
| checkboxfor = html | |||||
| End Function | |||||
| End Class | |||||
| dim HTML_Helper__Singleton : set HTML_Helper__Singleton = Nothing | |||||
| Function HTML() | |||||
| if HTML_Helper__Singleton Is Nothing then | |||||
| set HTML_Helper__Singleton = new HTML_Helper_Class | |||||
| End if | |||||
| set HTML = HTML_Helper__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,439 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION SYSTEM | |||||
| '======================================================================================================================= | |||||
| ' Provides database migration capabilities for version-controlled schema changes. | |||||
| ' | |||||
| ' Features: | |||||
| ' - Sequential migration versioning (timestamp-based) | |||||
| ' - Up/Down migration support | |||||
| ' - Migration tracking in schema_migrations table | |||||
| ' - Transaction support for atomic migrations | |||||
| ' - Migration status checking | |||||
| ' | |||||
| ' Usage: | |||||
| ' Set migrator = Migrator() | |||||
| ' migrator.ApplyMigration "20260109120000_create_users_table.asp" | |||||
| ' migrator.RollbackMigration "20260109120000_create_users_table.asp" | |||||
| ' pending = migrator.GetPendingMigrations() | |||||
| ' applied = migrator.GetAppliedMigrations() | |||||
| ' | |||||
| Class Migrator_Class | |||||
| Private m_db | |||||
| Private m_migrations_path | |||||
| Private m_schema_table | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Private Sub Class_Initialize() | |||||
| Set m_db = DAL() | |||||
| m_schema_table = "schema_migrations" | |||||
| m_migrations_path = Server.MapPath("../db/migrations/") | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Ensure the schema_migrations table exists | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub EnsureSchemaMigrationsTable() | |||||
| On Error Resume Next | |||||
| ' Try to query the table - if it doesn't exist, create it | |||||
| Dim rs | |||||
| Set rs = m_db.Query("SELECT TOP 1 version FROM " & m_schema_table, empty) | |||||
| If Err.Number <> 0 Then | |||||
| ' Table doesn't exist, create it | |||||
| Err.Clear | |||||
| On Error GoTo 0 | |||||
| Dim createSQL | |||||
| createSQL = "CREATE TABLE " & m_schema_table & " (" & _ | |||||
| "version VARCHAR(14) PRIMARY KEY, " & _ | |||||
| "applied_at DATETIME NOT NULL)" | |||||
| m_db.Execute createSQL, empty | |||||
| Else | |||||
| If Not rs Is Nothing Then | |||||
| rs.Close | |||||
| Set rs = Nothing | |||||
| End If | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Get all applied migration versions | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function GetAppliedMigrations() | |||||
| EnsureSchemaMigrationsTable | |||||
| Dim rs, versions, version | |||||
| Set versions = Server.CreateObject("Scripting.Dictionary") | |||||
| Set rs = m_db.Query("SELECT version FROM " & m_schema_table & " ORDER BY version", empty) | |||||
| Do While Not rs.EOF | |||||
| version = Trim(rs("version")) | |||||
| versions.Add version, True | |||||
| rs.MoveNext | |||||
| Loop | |||||
| rs.Close | |||||
| Set rs = Nothing | |||||
| Set GetAppliedMigrations = versions | |||||
| End Function | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Get all available migration files | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function GetAvailableMigrations() | |||||
| Dim fso, folder, files, file | |||||
| Set fso = Server.CreateObject("Scripting.FileSystemObject") | |||||
| If Not fso.FolderExists(m_migrations_path) Then | |||||
| Set GetAvailableMigrations = Server.CreateObject("Scripting.Dictionary") | |||||
| Exit Function | |||||
| End If | |||||
| Set folder = fso.GetFolder(m_migrations_path) | |||||
| Set files = folder.Files | |||||
| Dim migrations | |||||
| Set migrations = Server.CreateObject("Scripting.Dictionary") | |||||
| For Each file In files | |||||
| If LCase(fso.GetExtensionName(file.Name)) = "asp" Then | |||||
| Dim version | |||||
| version = GetVersionFromFilename(file.Name) | |||||
| If version <> "" Then | |||||
| migrations.Add version, file.Name | |||||
| End If | |||||
| End If | |||||
| Next | |||||
| Set GetAvailableMigrations = migrations | |||||
| End Function | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Get pending migrations (available but not applied) | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function GetPendingMigrations() | |||||
| Dim applied, available, pending, version | |||||
| Set applied = GetAppliedMigrations() | |||||
| Set available = GetAvailableMigrations() | |||||
| Set pending = Server.CreateObject("Scripting.Dictionary") | |||||
| For Each version In available.Keys | |||||
| If Not applied.Exists(version) Then | |||||
| pending.Add version, available(version) | |||||
| End If | |||||
| Next | |||||
| Set GetPendingMigrations = pending | |||||
| End Function | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Extract version from migration filename | |||||
| ' Expected format: YYYYMMDDHHMMSS_description.asp | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Private Function GetVersionFromFilename(filename) | |||||
| Dim parts | |||||
| parts = Split(filename, "_") | |||||
| If UBound(parts) >= 0 Then | |||||
| Dim version | |||||
| version = parts(0) | |||||
| ' Validate it's a 14-digit timestamp | |||||
| If Len(version) = 14 And IsNumeric(version) Then | |||||
| GetVersionFromFilename = version | |||||
| Exit Function | |||||
| End If | |||||
| End If | |||||
| GetVersionFromFilename = "" | |||||
| End Function | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Apply a migration (run the Up method) | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub ApplyMigration(filename) | |||||
| Dim version | |||||
| version = GetVersionFromFilename(filename) | |||||
| If version = "" Then | |||||
| Err.Raise vbObjectError + 1, "Migrator", "Invalid migration filename format: " & filename | |||||
| End If | |||||
| ' Check if already applied | |||||
| Dim applied | |||||
| Set applied = GetAppliedMigrations() | |||||
| If applied.Exists(version) Then | |||||
| Response.Write "Migration " & version & " already applied. Skipping." & vbCrLf | |||||
| Exit Sub | |||||
| End If | |||||
| Response.Write "Applying migration: " & filename & "..." & vbCrLf | |||||
| ' Begin transaction | |||||
| m_db.BeginTransaction | |||||
| On Error Resume Next | |||||
| ' Execute the migration file | |||||
| ExecuteMigrationFile filename, "Up" | |||||
| If Err.Number <> 0 Then | |||||
| Dim errMsg | |||||
| errMsg = "Migration failed: " & Err.Description | |||||
| m_db.RollbackTransaction | |||||
| Err.Raise vbObjectError + 2, "Migrator", errMsg | |||||
| End If | |||||
| ' Record the migration | |||||
| m_db.Execute "INSERT INTO " & m_schema_table & " (version, applied_at) VALUES (?, ?)", _ | |||||
| Array(version, Now()) | |||||
| If Err.Number <> 0 Then | |||||
| Dim recordErr | |||||
| recordErr = "Failed to record migration: " & Err.Description | |||||
| m_db.RollbackTransaction | |||||
| Err.Raise vbObjectError + 3, "Migrator", recordErr | |||||
| End If | |||||
| ' Commit transaction | |||||
| m_db.CommitTransaction | |||||
| On Error GoTo 0 | |||||
| Response.Write "Migration " & version & " applied successfully." & vbCrLf | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Rollback a migration (run the Down method) | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub RollbackMigration(filename) | |||||
| Dim version | |||||
| version = GetVersionFromFilename(filename) | |||||
| If version = "" Then | |||||
| Err.Raise vbObjectError + 1, "Migrator", "Invalid migration filename format: " & filename | |||||
| End If | |||||
| ' Check if applied | |||||
| Dim applied | |||||
| Set applied = GetAppliedMigrations() | |||||
| If Not applied.Exists(version) Then | |||||
| Response.Write "Migration " & version & " not applied. Skipping." & vbCrLf | |||||
| Exit Sub | |||||
| End If | |||||
| Response.Write "Rolling back migration: " & filename & "..." & vbCrLf | |||||
| ' Begin transaction | |||||
| m_db.BeginTransaction | |||||
| On Error Resume Next | |||||
| ' Execute the migration file | |||||
| ExecuteMigrationFile filename, "Down" | |||||
| If Err.Number <> 0 Then | |||||
| Dim errMsg | |||||
| errMsg = "Rollback failed: " & Err.Description | |||||
| m_db.RollbackTransaction | |||||
| Err.Raise vbObjectError + 4, "Migrator", errMsg | |||||
| End If | |||||
| ' Remove the migration record | |||||
| m_db.Execute "DELETE FROM " & m_schema_table & " WHERE version = ?", version | |||||
| If Err.Number <> 0 Then | |||||
| Dim recordErr | |||||
| recordErr = "Failed to remove migration record: " & Err.Description | |||||
| m_db.RollbackTransaction | |||||
| Err.Raise vbObjectError + 5, "Migrator", recordErr | |||||
| End If | |||||
| ' Commit transaction | |||||
| m_db.CommitTransaction | |||||
| On Error GoTo 0 | |||||
| Response.Write "Migration " & version & " rolled back successfully." & vbCrLf | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Execute a migration file's Up or Down method | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Private Sub ExecuteMigrationFile(filename, direction) | |||||
| Dim migrationPath | |||||
| migrationPath = m_migrations_path & filename | |||||
| Dim fso | |||||
| Set fso = Server.CreateObject("Scripting.FileSystemObject") | |||||
| If Not fso.FileExists(migrationPath) Then | |||||
| Err.Raise vbObjectError + 6, "Migrator", "Migration file not found: " & migrationPath | |||||
| End If | |||||
| ' Create a migration context that the file can use | |||||
| Dim migration | |||||
| Set migration = New MigrationContext_Class | |||||
| Set migration.DB = m_db | |||||
| ' Include and execute the migration file | |||||
| Server.Execute(migrationPath) | |||||
| ' Call the appropriate method (Up or Down) | |||||
| If direction = "Up" Then | |||||
| Execute "Call Migration_Up(migration)" | |||||
| ElseIf direction = "Down" Then | |||||
| Execute "Call Migration_Down(migration)" | |||||
| End If | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Apply all pending migrations | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub ApplyAllPending() | |||||
| Dim pending, version, versions() | |||||
| Set pending = GetPendingMigrations() | |||||
| If pending.Count = 0 Then | |||||
| Response.Write "No pending migrations." & vbCrLf | |||||
| Exit Sub | |||||
| End If | |||||
| ' Sort versions | |||||
| ReDim versions(pending.Count - 1) | |||||
| Dim i : i = 0 | |||||
| For Each version In pending.Keys | |||||
| versions(i) = version | |||||
| i = i + 1 | |||||
| Next | |||||
| ' Simple bubble sort for versions | |||||
| Dim j, temp | |||||
| For i = 0 To UBound(versions) - 1 | |||||
| For j = i + 1 To UBound(versions) | |||||
| If CLng(versions(i)) > CLng(versions(j)) Then | |||||
| temp = versions(i) | |||||
| versions(i) = versions(j) | |||||
| versions(j) = temp | |||||
| End If | |||||
| Next | |||||
| Next | |||||
| ' Apply in order | |||||
| For i = 0 To UBound(versions) | |||||
| ApplyMigration pending(versions(i)) | |||||
| Next | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Rollback the last applied migration | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub RollbackLast() | |||||
| Dim applied, version, lastVersion | |||||
| Set applied = GetAppliedMigrations() | |||||
| If applied.Count = 0 Then | |||||
| Response.Write "No migrations to rollback." & vbCrLf | |||||
| Exit Sub | |||||
| End If | |||||
| ' Find the last version | |||||
| lastVersion = "" | |||||
| For Each version In applied.Keys | |||||
| If lastVersion = "" Or CLng(version) > CLng(lastVersion) Then | |||||
| lastVersion = version | |||||
| End If | |||||
| Next | |||||
| ' Find the filename | |||||
| Dim available | |||||
| Set available = GetAvailableMigrations() | |||||
| If available.Exists(lastVersion) Then | |||||
| RollbackMigration available(lastVersion) | |||||
| Else | |||||
| Err.Raise vbObjectError + 7, "Migrator", "Migration file not found for version: " & lastVersion | |||||
| End If | |||||
| End Sub | |||||
| End Class | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION CONTEXT | |||||
| '======================================================================================================================= | |||||
| ' Provides helper methods for use within migration files | |||||
| ' | |||||
| Class MigrationContext_Class | |||||
| Public DB ' Reference to DAL | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Execute raw SQL | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub ExecuteSQL(sql) | |||||
| DB.Execute sql, empty | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Create a table | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub CreateTable(tableName, columns) | |||||
| Dim sql | |||||
| sql = "CREATE TABLE " & tableName & " (" & columns & ")" | |||||
| ExecuteSQL sql | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Drop a table | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub DropTable(tableName) | |||||
| ExecuteSQL "DROP TABLE " & tableName | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Add a column to a table | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub AddColumn(tableName, columnName, columnType) | |||||
| ExecuteSQL "ALTER TABLE " & tableName & " ADD COLUMN " & columnName & " " & columnType | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Drop a column from a table | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub DropColumn(tableName, columnName) | |||||
| ExecuteSQL "ALTER TABLE " & tableName & " DROP COLUMN " & columnName | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Create an index | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub CreateIndex(indexName, tableName, columns) | |||||
| ExecuteSQL "CREATE INDEX " & indexName & " ON " & tableName & " (" & columns & ")" | |||||
| End Sub | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| ' Drop an index | |||||
| '------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub DropIndex(indexName, tableName) | |||||
| ExecuteSQL "DROP INDEX " & indexName & " ON " & tableName | |||||
| End Sub | |||||
| End Class | |||||
| '======================================================================================================================= | |||||
| ' SINGLETON | |||||
| '======================================================================================================================= | |||||
| Dim Migrator__Singleton : Set Migrator__Singleton = Nothing | |||||
| Function Migrator() | |||||
| If Migrator__Singleton Is Nothing Then | |||||
| Set Migrator__Singleton = New Migrator_Class | |||||
| End If | |||||
| Set Migrator = Migrator__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,262 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' ROUTING HELPER | |||||
| ' Provides URL generation for RouteKit framework | |||||
| '======================================================================================================================= | |||||
| Class Route_Helper_Class | |||||
| Private m_app_url | |||||
| Private m_content_url | |||||
| Private m_stylesheets_url | |||||
| Private m_js_url | |||||
| Private m_images_url | |||||
| Private m_enable_cache_busting | |||||
| Private m_cache_bust_param_name | |||||
| Private Sub Class_Initialize() | |||||
| ' Auto-detect application URL from current request | |||||
| Dim protocol, host, appPath | |||||
| protocol = IIf(Request.ServerVariables("HTTPS") = "on", "https://", "http://") | |||||
| host = Request.ServerVariables("HTTP_HOST") | |||||
| appPath = Request.ServerVariables("APPL_MD_PATH") | |||||
| ' Extract virtual directory from IIS path | |||||
| Dim vdir | |||||
| vdir = "" | |||||
| If InStr(appPath, "/LM/W3SVC/") > 0 Then | |||||
| vdir = Mid(appPath, InStrRev(appPath, "/") + 1) | |||||
| If vdir <> "ROOT" Then | |||||
| vdir = "/" & vdir | |||||
| Else | |||||
| vdir = "" | |||||
| End If | |||||
| End If | |||||
| m_app_url = protocol & host & vdir & "/" | |||||
| m_content_url = m_app_url & "content/" | |||||
| m_stylesheets_url = m_app_url & "css/" | |||||
| m_js_url = m_app_url & "js/" | |||||
| m_images_url = m_app_url & "images/" | |||||
| ' Load cache-busting configuration | |||||
| Dim cacheBustSetting | |||||
| cacheBustSetting = GetAppSetting("EnableCacheBusting") | |||||
| m_enable_cache_busting = (cacheBustSetting = "true" Or cacheBustSetting = "True") | |||||
| ' Get cache-bust parameter name (default: "v") | |||||
| m_cache_bust_param_name = GetAppSetting("CacheBustParamName") | |||||
| If m_cache_bust_param_name = "nothing" Or Len(m_cache_bust_param_name) = 0 Then | |||||
| m_cache_bust_param_name = "v" | |||||
| End If | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Cache-busting token (timestamp-based for uniqueness) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Property Get NoCacheToken | |||||
| NoCacheToken = CLng(Timer() * 100) | |||||
| End Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Check if cache-busting is globally enabled | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Property Get CacheBustingEnabled | |||||
| CacheBustingEnabled = m_enable_cache_busting | |||||
| End Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Get/Set cache-busting enabled (can override config at runtime) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Property Let CacheBustingEnabled(value) | |||||
| m_enable_cache_busting = value | |||||
| End Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' URL Properties | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Property Get AppURL | |||||
| AppURL = m_app_url | |||||
| End Property | |||||
| Public Property Get ContentURL | |||||
| ContentURL = m_content_url | |||||
| End Property | |||||
| Public Property Get StylesheetsURL | |||||
| StylesheetsURL = m_stylesheets_url | |||||
| End Property | |||||
| Public Property Get JsURL | |||||
| JsURL = m_js_url | |||||
| End Property | |||||
| Public Property Get ImagesURL | |||||
| ImagesURL = m_images_url | |||||
| End Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Generate clean URL for controller/action (RouteKit style) | |||||
| ' | |||||
| ' @param controller_name String Name of controller (without "Controller" suffix) | |||||
| ' @param action_name String Name of action method | |||||
| ' @param params_array KV Array Optional key/value pair array for query string | |||||
| ' @returns String Clean URL like "/controller/action?key=val" | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function UrlTo(controller_name, action_name, params_array) | |||||
| Dim url, qs | |||||
| ' Build clean URL: /controller/action | |||||
| url = m_app_url & LCase(controller_name) & "/" & LCase(action_name) | |||||
| ' Add query string parameters if provided | |||||
| qs = TO_Querystring(params_array) | |||||
| If Len(qs) > 0 Then | |||||
| url = url & "?" & qs | |||||
| End If | |||||
| UrlTo = url | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Generate URL with route parameters (e.g., /users/show/123) | |||||
| ' | |||||
| ' @param controller_name String Name of controller | |||||
| ' @param action_name String Name of action method | |||||
| ' @param route_params Array Route parameters (e.g., Array(123) for ID) | |||||
| ' @param query_params KV Array Optional query string parameters | |||||
| ' @returns String URL with route params | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function UrlToWithParams(controller_name, action_name, route_params, query_params) | |||||
| Dim url, qs, i | |||||
| ' Build base URL | |||||
| url = m_app_url & LCase(controller_name) & "/" & LCase(action_name) | |||||
| ' Append route parameters | |||||
| If IsArray(route_params) Then | |||||
| For i = 0 To UBound(route_params) | |||||
| url = url & "/" & Server.URLEncode(CStr(route_params(i))) | |||||
| Next | |||||
| ElseIf Not IsEmpty(route_params) Then | |||||
| url = url & "/" & Server.URLEncode(CStr(route_params)) | |||||
| End If | |||||
| ' Add query string parameters if provided | |||||
| qs = TO_Querystring(query_params) | |||||
| If Len(qs) > 0 Then | |||||
| url = url & "?" & qs | |||||
| End If | |||||
| UrlToWithParams = url | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Generate URL for static asset with cache-busting | |||||
| ' | |||||
| ' @param asset_path String Relative path to asset (e.g., "css/site.css") | |||||
| ' @param use_cache_bust Variant Boolean or Empty. If Empty, uses global setting. If True/False, overrides. | |||||
| ' @returns String Full URL to asset | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function AssetUrl(asset_path, use_cache_bust) | |||||
| Dim url, shouldBust | |||||
| url = m_app_url & asset_path | |||||
| ' Determine if we should cache-bust | |||||
| If IsEmpty(use_cache_bust) Then | |||||
| shouldBust = m_enable_cache_busting | |||||
| Else | |||||
| shouldBust = CBool(use_cache_bust) | |||||
| End If | |||||
| If shouldBust Then | |||||
| url = AppendCacheBustParam(url) | |||||
| End If | |||||
| AssetUrl = url | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Generate URL for route with optional cache-busting | |||||
| ' | |||||
| ' @param controller_name String Name of controller | |||||
| ' @param action_name String Name of action | |||||
| ' @param params_array KV Array Optional query params | |||||
| ' @param use_cache_bust Variant Boolean or Empty. If Empty, uses global setting | |||||
| ' @returns String URL with optional cache-busting | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Function UrlToWithCacheBust(controller_name, action_name, params_array, use_cache_bust) | |||||
| Dim url | |||||
| url = UrlTo(controller_name, action_name, params_array) | |||||
| ' Determine if we should cache-bust | |||||
| Dim shouldBust | |||||
| If IsEmpty(use_cache_bust) Then | |||||
| shouldBust = m_enable_cache_busting | |||||
| Else | |||||
| shouldBust = CBool(use_cache_bust) | |||||
| End If | |||||
| If shouldBust Then | |||||
| url = AppendCacheBustParam(url) | |||||
| End If | |||||
| UrlToWithCacheBust = url | |||||
| End Function | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' PRIVATE HELPER METHODS | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Private Function TO_Querystring(the_array) | |||||
| Dim result, idx | |||||
| result = "" | |||||
| If Not IsEmpty(the_array) And IsArray(the_array) Then | |||||
| ' Process key-value pairs (array with even number of elements) | |||||
| For idx = LBound(the_array) To UBound(the_array) Step 2 | |||||
| If idx + 1 <= UBound(the_array) Then | |||||
| result = result & GetParam(the_array, idx) | |||||
| ' Append & between parameters, but not on the last parameter | |||||
| If Not (idx >= UBound(the_array) - 1) Then | |||||
| result = result & "&" | |||||
| End If | |||||
| End If | |||||
| Next | |||||
| End If | |||||
| TO_Querystring = result | |||||
| End Function | |||||
| Private Function GetParam(params_array, key_idx) | |||||
| Dim key, val | |||||
| key = Server.URLEncode(CStr(params_array(key_idx))) | |||||
| val = Server.URLEncode(CStr(params_array(key_idx + 1))) | |||||
| GetParam = key & "=" & val | |||||
| End Function | |||||
| Private Function AppendCacheBustParam(url) | |||||
| Dim separator | |||||
| ' Determine if we need ? or & | |||||
| If InStr(url, "?") > 0 Then | |||||
| separator = "&" | |||||
| Else | |||||
| separator = "?" | |||||
| End If | |||||
| AppendCacheBustParam = url & separator & m_cache_bust_param_name & "=" & NoCacheToken | |||||
| End Function | |||||
| End Class | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Singleton accessor | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Dim Route_Helper__Singleton | |||||
| Set Route_Helper__Singleton = Nothing | |||||
| Function Routes() | |||||
| If Route_Helper__Singleton Is Nothing Then | |||||
| Set Route_Helper__Singleton = New Route_Helper_Class | |||||
| End If | |||||
| Set Routes = Route_Helper__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,73 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' StringBuilder Class | |||||
| '======================================================================================================================= | |||||
| Class StringBuilder_Class | |||||
| dim m_array | |||||
| dim m_array_size | |||||
| dim m_cur_pos | |||||
| Private Sub Class_Initialize | |||||
| m_array = Array | |||||
| m_array_size = 100 | |||||
| redim m_array(m_array_size) | |||||
| m_cur_pos = -1 | |||||
| End Sub | |||||
| Private Sub Extend | |||||
| m_array_size = m_array_size + 100 | |||||
| redim preserve m_array(m_array_size) | |||||
| End Sub | |||||
| Public Sub Add(s) | |||||
| m_cur_pos = m_cur_pos + 1 | |||||
| m_array(m_cur_pos) = s | |||||
| if m_cur_pos = m_array_size then Extend | |||||
| End Sub | |||||
| Public Function [Get](delim) | |||||
| 'have to create a new array containing only the slots actually used, otherwise Join() happily adds delim | |||||
| 'for *every* slot even the unused ones... | |||||
| dim new_array : new_array = Array() | |||||
| redim new_array(m_cur_pos) | |||||
| dim i | |||||
| for i = 0 to m_cur_pos | |||||
| new_array(i) = m_array(i) | |||||
| next | |||||
| [Get] = Join(new_array, delim) | |||||
| End Function | |||||
| Public Default Property Get TO_String | |||||
| TO_String = Join(m_array, "") | |||||
| End Property | |||||
| End Class | |||||
| Function StringBuilder() | |||||
| set StringBuilder = new StringBuilder_Class | |||||
| End Function | |||||
| '======================================================================================================================= | |||||
| ' Misc | |||||
| '======================================================================================================================= | |||||
| Function Excerpt(text, length) | |||||
| Excerpt = Left(text, length) & " ..." | |||||
| End Function | |||||
| Function IsBlank(text) | |||||
| If IsObject(text) then | |||||
| If text Is Nothing then | |||||
| IsBlank = true | |||||
| Else | |||||
| IsBlank = false | |||||
| End If | |||||
| Else | |||||
| If IsEmpty(text) or IsNull(text) or Len(text) = 0 then | |||||
| IsBlank = true | |||||
| Else | |||||
| IsBlank = false | |||||
| End If | |||||
| End If | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,405 @@ | |||||
| <!--METADATA | |||||
| TYPE="TypeLib" | |||||
| NAME="Microsoft ActiveX Data Objects 2.5 Library" | |||||
| UUID="{00000205-0000-0010-8000-00AA006D2EA4}" | |||||
| VERSION="2.5" | |||||
| --> | |||||
| <% | |||||
| ' For examples, documentation, and your own free copy, go to: | |||||
| ' http://www.freeaspupload.net | |||||
| ' Note: You can copy and use this script for free and you can make changes | |||||
| ' to the code, but you cannot remove the above comment. | |||||
| 'Changes: | |||||
| 'Aug 2, 2005: Add support for checkboxes and other input elements with multiple values | |||||
| 'Jan 6, 2009: Lars added ASP_CHUNK_SIZE | |||||
| const DEFAULT_ASP_CHUNK_SIZE = 200000 | |||||
| Class FreeASPUpload | |||||
| Public UploadedFiles | |||||
| Public FormElements | |||||
| Private VarArrayBinRequest | |||||
| Private StreamRequest | |||||
| Private uploadedYet | |||||
| Private internalChunkSize | |||||
| Private Sub Class_Initialize() | |||||
| Set UploadedFiles = Server.CreateObject("Scripting.Dictionary") | |||||
| Set FormElements = Server.CreateObject("Scripting.Dictionary") | |||||
| Set StreamRequest = Server.CreateObject("ADODB.Stream") | |||||
| StreamRequest.Type = 2 ' adTypeText | |||||
| StreamRequest.Open | |||||
| uploadedYet = false | |||||
| internalChunkSize = DEFAULT_ASP_CHUNK_SIZE | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| If IsObject(UploadedFiles) Then | |||||
| UploadedFiles.RemoveAll() | |||||
| Set UploadedFiles = Nothing | |||||
| End If | |||||
| If IsObject(FormElements) Then | |||||
| FormElements.RemoveAll() | |||||
| Set FormElements = Nothing | |||||
| End If | |||||
| StreamRequest.Close | |||||
| Set StreamRequest = Nothing | |||||
| End Sub | |||||
| Public Property Get Form(sIndex) | |||||
| Form = "" | |||||
| If FormElements.Exists(LCase(sIndex)) Then Form = FormElements.Item(LCase(sIndex)) | |||||
| End Property | |||||
| Public Property Get Files() | |||||
| Files = UploadedFiles.Items | |||||
| End Property | |||||
| Public Property Get Exists(sIndex) | |||||
| Exists = false | |||||
| If FormElements.Exists(LCase(sIndex)) Then Exists = true | |||||
| End Property | |||||
| Public Property Get FileExists(sIndex) | |||||
| FileExists = false | |||||
| if UploadedFiles.Exists(LCase(sIndex)) then FileExists = true | |||||
| End Property | |||||
| Public Property Get chunkSize() | |||||
| chunkSize = internalChunkSize | |||||
| End Property | |||||
| Public Property Let chunkSize(sz) | |||||
| internalChunkSize = sz | |||||
| End Property | |||||
| 'Calls Upload to extract the data from the binary request and then saves the uploaded files | |||||
| Public Sub Save(path) | |||||
| Dim streamFile, fileItem | |||||
| if Right(path, 1) <> "\" then path = path & "\" | |||||
| if not uploadedYet then Upload | |||||
| For Each fileItem In UploadedFiles.Items | |||||
| Set streamFile = Server.CreateObject("ADODB.Stream") | |||||
| streamFile.Type = 1 | |||||
| streamFile.Open | |||||
| StreamRequest.Position=fileItem.Start | |||||
| StreamRequest.CopyTo streamFile, fileItem.Length | |||||
| streamFile.SaveToFile path & fileItem.FileName, 2 | |||||
| streamFile.close | |||||
| Set streamFile = Nothing | |||||
| fileItem.Path = path & fileItem.FileName | |||||
| Next | |||||
| End Sub | |||||
| public sub SaveOne(path, num, byref outFileName, byref outLocalFileName) | |||||
| Dim streamFile, fileItems, fileItem, fs | |||||
| set fs = Server.CreateObject("Scripting.FileSystemObject") | |||||
| if Right(path, 1) <> "\" then path = path & "\" | |||||
| if not uploadedYet then Upload | |||||
| if UploadedFiles.Count > 0 then | |||||
| fileItems = UploadedFiles.Items | |||||
| set fileItem = fileItems(num) | |||||
| outFileName = fileItem.FileName | |||||
| outLocalFileName = GetFileName(path, outFileName) | |||||
| Set streamFile = Server.CreateObject("ADODB.Stream") | |||||
| streamFile.Type = 1 | |||||
| streamFile.Open | |||||
| StreamRequest.Position = fileItem.Start | |||||
| StreamRequest.CopyTo streamFile, fileItem.Length | |||||
| streamFile.SaveToFile path & outLocalFileName, 2 | |||||
| streamFile.close | |||||
| Set streamFile = Nothing | |||||
| fileItem.Path = path & filename | |||||
| end if | |||||
| end sub | |||||
| Public Function SaveBinRequest(path) ' For debugging purposes | |||||
| StreamRequest.SaveToFile path & "\debugStream.bin", 2 | |||||
| End Function | |||||
| Public Sub DumpData() 'only works if files are plain text | |||||
| Dim i, aKeys, f | |||||
| response.write "Form Items:<br>" | |||||
| aKeys = FormElements.Keys | |||||
| For i = 0 To FormElements.Count -1 ' Iterate the array | |||||
| response.write aKeys(i) & " = " & FormElements.Item(aKeys(i)) & "<BR>" | |||||
| Next | |||||
| response.write "Uploaded Files:<br>" | |||||
| For Each f In UploadedFiles.Items | |||||
| response.write "Name: " & f.FileName & "<br>" | |||||
| response.write "Type: " & f.ContentType & "<br>" | |||||
| response.write "Start: " & f.Start & "<br>" | |||||
| response.write "Size: " & f.Length & "<br>" | |||||
| Next | |||||
| End Sub | |||||
| Public Sub Upload() | |||||
| Dim nCurPos, nDataBoundPos, nLastSepPos | |||||
| Dim nPosFile, nPosBound | |||||
| Dim sFieldName, osPathSep, auxStr | |||||
| Dim readBytes, readLoop, tmpBinRequest | |||||
| 'RFC1867 Tokens | |||||
| Dim vDataSep | |||||
| Dim tNewLine, tDoubleQuotes, tTerm, tFilename, tName, tContentDisp, tContentType | |||||
| tNewLine = String2Byte(Chr(13)) | |||||
| tDoubleQuotes = String2Byte(Chr(34)) | |||||
| tTerm = String2Byte("--") | |||||
| tFilename = String2Byte("filename=""") | |||||
| tName = String2Byte("name=""") | |||||
| tContentDisp = String2Byte("Content-Disposition") | |||||
| tContentType = String2Byte("Content-Type:") | |||||
| uploadedYet = true | |||||
| on error resume next | |||||
| readBytes = internalChunkSize | |||||
| VarArrayBinRequest = Request.BinaryRead(readBytes) | |||||
| VarArrayBinRequest = midb(VarArrayBinRequest, 1, lenb(VarArrayBinRequest)) | |||||
| for readLoop = 0 to 300000 | |||||
| tmpBinRequest = Request.BinaryRead(readBytes) | |||||
| if readBytes < 1 then exit for | |||||
| VarArrayBinRequest = VarArrayBinRequest & midb(tmpBinRequest, 1, lenb(tmpBinRequest)) | |||||
| next | |||||
| if Err.Number <> 0 then | |||||
| response.write "<br><br><B>System reported this error:</B><p>" | |||||
| response.write Err.Description & "<p>" | |||||
| response.write "The most likely cause for this error is the incorrect setup of AspMaxRequestEntityAllowed in IIS MetaBase. Please see instructions in the <A HREF='http://www.freeaspupload.net/freeaspupload/requirements.asp'>requirements page of freeaspupload.net</A>.<p>" | |||||
| Exit Sub | |||||
| end if | |||||
| on error goto 0 'reset error handling | |||||
| nCurPos = FindToken(tNewLine,1) 'Note: nCurPos is 1-based (and so is InstrB, MidB, etc) | |||||
| If nCurPos <= 1 Then Exit Sub | |||||
| 'vDataSep is a separator like -----------------------------21763138716045 | |||||
| vDataSep = MidB(VarArrayBinRequest, 1, nCurPos-1) | |||||
| 'Start of current separator | |||||
| nDataBoundPos = 1 | |||||
| 'Beginning of last line | |||||
| nLastSepPos = FindToken(vDataSep & tTerm, 1) | |||||
| Do Until nDataBoundPos = nLastSepPos | |||||
| nCurPos = SkipToken(tContentDisp, nDataBoundPos) | |||||
| nCurPos = SkipToken(tName, nCurPos) | |||||
| sFieldName = ExtractField(tDoubleQuotes, nCurPos) | |||||
| nPosFile = FindToken(tFilename, nCurPos) | |||||
| nPosBound = FindToken(vDataSep, nCurPos) | |||||
| If nPosFile <> 0 And nPosFile < nPosBound Then | |||||
| Dim oUploadFile | |||||
| Set oUploadFile = New UploadedFile | |||||
| nCurPos = SkipToken(tFilename, nCurPos) | |||||
| auxStr = ExtractField(tDoubleQuotes, nCurPos) | |||||
| ' We are interested only in the name of the file, not the whole path | |||||
| ' Path separator is \ in windows, / in UNIX | |||||
| ' While IE seems to put the whole pathname in the stream, Mozilla seem to | |||||
| ' only put the actual file name, so UNIX paths may be rare. But not impossible. | |||||
| osPathSep = "\" | |||||
| if InStr(auxStr, osPathSep) = 0 then osPathSep = "/" | |||||
| oUploadFile.FileName = Right(auxStr, Len(auxStr)-InStrRev(auxStr, osPathSep)) | |||||
| if (Len(oUploadFile.FileName) > 0) then 'File field not left empty | |||||
| nCurPos = SkipToken(tContentType, nCurPos) | |||||
| auxStr = ExtractField(tNewLine, nCurPos) | |||||
| ' NN on UNIX puts things like this in the stream: | |||||
| ' ?? python py type=?? python application/x-python | |||||
| oUploadFile.ContentType = Right(auxStr, Len(auxStr)-InStrRev(auxStr, " ")) | |||||
| nCurPos = FindToken(tNewLine, nCurPos) + 4 'skip empty line | |||||
| oUploadFile.Start = nCurPos+1 | |||||
| oUploadFile.Length = FindToken(vDataSep, nCurPos) - 2 - nCurPos | |||||
| If oUploadFile.Length > 0 Then UploadedFiles.Add LCase(sFieldName), oUploadFile | |||||
| End If | |||||
| Else | |||||
| Dim nEndOfData | |||||
| nCurPos = FindToken(tNewLine, nCurPos) + 4 'skip empty line | |||||
| nEndOfData = FindToken(vDataSep, nCurPos) - 2 | |||||
| If Not FormElements.Exists(LCase(sFieldName)) Then | |||||
| FormElements.Add LCase(sFieldName), Byte2String(MidB(VarArrayBinRequest, nCurPos, nEndOfData-nCurPos)) | |||||
| else | |||||
| FormElements.Item(LCase(sFieldName))= FormElements.Item(LCase(sFieldName)) & ", " & Byte2String(MidB(VarArrayBinRequest, nCurPos, nEndOfData-nCurPos)) | |||||
| end if | |||||
| End If | |||||
| 'Advance to next separator | |||||
| nDataBoundPos = FindToken(vDataSep, nCurPos) | |||||
| Loop | |||||
| StreamRequest.WriteText(VarArrayBinRequest) | |||||
| End Sub | |||||
| Private Function SkipToken(sToken, nStart) | |||||
| SkipToken = InstrB(nStart, VarArrayBinRequest, sToken) | |||||
| If SkipToken = 0 then | |||||
| Response.write "Error in parsing uploaded binary request. The most likely cause for this error is the incorrect setup of AspMaxRequestEntityAllowed in IIS MetaBase. Please see instructions in the <A HREF='http://www.freeaspupload.net/freeaspupload/requirements.asp'>requirements page of freeaspupload.net</A>.<p>" | |||||
| Response.End | |||||
| end if | |||||
| SkipToken = SkipToken + LenB(sToken) | |||||
| End Function | |||||
| Private Function FindToken(sToken, nStart) | |||||
| FindToken = InstrB(nStart, VarArrayBinRequest, sToken) | |||||
| End Function | |||||
| Private Function ExtractField(sToken, nStart) | |||||
| Dim nEnd | |||||
| nEnd = InstrB(nStart, VarArrayBinRequest, sToken) | |||||
| If nEnd = 0 then | |||||
| Response.write "Error in parsing uploaded binary request." | |||||
| Response.End | |||||
| end if | |||||
| ExtractField = Byte2String(MidB(VarArrayBinRequest, nStart, nEnd-nStart)) | |||||
| End Function | |||||
| 'String to byte string conversion | |||||
| Private Function String2Byte(sString) | |||||
| Dim i | |||||
| For i = 1 to Len(sString) | |||||
| String2Byte = String2Byte & ChrB(AscB(Mid(sString,i,1))) | |||||
| Next | |||||
| End Function | |||||
| 'Byte string to string conversion | |||||
| Private Function Byte2String(bsString) | |||||
| Dim i | |||||
| dim b1, b2, b3, b4 | |||||
| Byte2String ="" | |||||
| For i = 1 to LenB(bsString) | |||||
| if AscB(MidB(bsString,i,1)) < 128 then | |||||
| ' One byte | |||||
| Byte2String = Byte2String & ChrW(AscB(MidB(bsString,i,1))) | |||||
| elseif AscB(MidB(bsString,i,1)) < 224 then | |||||
| ' Two bytes | |||||
| b1 = AscB(MidB(bsString,i,1)) | |||||
| b2 = AscB(MidB(bsString,i+1,1)) | |||||
| Byte2String = Byte2String & ChrW((((b1 AND 28) / 4) * 256 + (b1 AND 3) * 64 + (b2 AND 63))) | |||||
| i = i + 1 | |||||
| elseif AscB(MidB(bsString,i,1)) < 240 then | |||||
| ' Three bytes | |||||
| b1 = AscB(MidB(bsString,i,1)) | |||||
| b2 = AscB(MidB(bsString,i+1,1)) | |||||
| b3 = AscB(MidB(bsString,i+2,1)) | |||||
| Byte2String = Byte2String & ChrW(((b1 AND 15) * 16 + (b2 AND 60)) * 256 + (b2 AND 3) * 64 + (b3 AND 63)) | |||||
| i = i + 2 | |||||
| else | |||||
| ' Four bytes | |||||
| b1 = AscB(MidB(bsString,i,1)) | |||||
| b2 = AscB(MidB(bsString,i+1,1)) | |||||
| b3 = AscB(MidB(bsString,i+2,1)) | |||||
| b4 = AscB(MidB(bsString,i+3,1)) | |||||
| ' Don't know how to handle this, I believe Microsoft doesn't support these characters so I replace them with a "^" | |||||
| 'Byte2String = Byte2String & ChrW(((b1 AND 3) * 64 + (b2 AND 63)) & "," & (((b1 AND 28) / 4) * 256 + (b1 AND 3) * 64 + (b2 AND 63))) | |||||
| Byte2String = Byte2String & "^" | |||||
| i = i + 3 | |||||
| end if | |||||
| Next | |||||
| End Function | |||||
| End Class | |||||
| Class UploadedFile | |||||
| Public ContentType | |||||
| Public Start | |||||
| Public Length | |||||
| Public Path | |||||
| Private nameOfFile | |||||
| ' Need to remove characters that are valid in UNIX, but not in Windows | |||||
| Public Property Let FileName(fN) | |||||
| nameOfFile = fN | |||||
| nameOfFile = SubstNoReg(nameOfFile, "\", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, "/", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, ":", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, "*", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, "?", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, """", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, "<", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, ">", "_") | |||||
| nameOfFile = SubstNoReg(nameOfFile, "|", "_") | |||||
| End Property | |||||
| Public Property Get FileName() | |||||
| FileName = nameOfFile | |||||
| End Property | |||||
| 'Public Property Get FileN()ame | |||||
| End Class | |||||
| ' Does not depend on RegEx, which is not available on older VBScript | |||||
| ' Is not recursive, which means it will not run out of stack space | |||||
| Function SubstNoReg(initialStr, oldStr, newStr) | |||||
| Dim currentPos, oldStrPos, skip | |||||
| If IsNull(initialStr) Or Len(initialStr) = 0 Then | |||||
| SubstNoReg = "" | |||||
| ElseIf IsNull(oldStr) Or Len(oldStr) = 0 Then | |||||
| SubstNoReg = initialStr | |||||
| Else | |||||
| If IsNull(newStr) Then newStr = "" | |||||
| currentPos = 1 | |||||
| oldStrPos = 0 | |||||
| SubstNoReg = "" | |||||
| skip = Len(oldStr) | |||||
| Do While currentPos <= Len(initialStr) | |||||
| oldStrPos = InStr(currentPos, initialStr, oldStr) | |||||
| If oldStrPos = 0 Then | |||||
| SubstNoReg = SubstNoReg & Mid(initialStr, currentPos, Len(initialStr) - currentPos + 1) | |||||
| currentPos = Len(initialStr) + 1 | |||||
| Else | |||||
| SubstNoReg = SubstNoReg & Mid(initialStr, currentPos, oldStrPos - currentPos) & newStr | |||||
| currentPos = oldStrPos + skip | |||||
| End If | |||||
| Loop | |||||
| End If | |||||
| End Function | |||||
| Function GetFileName(strSaveToPath, FileName) | |||||
| 'This function is used when saving a file to check there is not already a file with the same name so that you don't overwrite it. | |||||
| 'It adds numbers to the filename e.g. file.gif becomes file1.gif becomes file2.gif and so on. | |||||
| 'It keeps going until it returns a filename that does not exist. | |||||
| 'You could just create a filename from the ID field but that means writing the record - and it still might exist! | |||||
| 'N.B. Requires strSaveToPath variable to be available - and containing the path to save to | |||||
| Dim Counter | |||||
| Dim Flag | |||||
| Dim strTempFileName | |||||
| Dim FileExt | |||||
| Dim NewFullPath | |||||
| dim objFSO, p | |||||
| Set objFSO = CreateObject("Scripting.FileSystemObject") | |||||
| Counter = 0 | |||||
| p = instrrev(FileName, ".") | |||||
| FileExt = mid(FileName, p+1) | |||||
| strTempFileName = left(FileName, p-1) | |||||
| NewFullPath = strSaveToPath & "\" & FileName | |||||
| Flag = False | |||||
| Do Until Flag = True | |||||
| If objFSO.FileExists(NewFullPath) = False Then | |||||
| Flag = True | |||||
| GetFileName = Mid(NewFullPath, InstrRev(NewFullPath, "\") + 1) | |||||
| Else | |||||
| Counter = Counter + 1 | |||||
| NewFullPath = strSaveToPath & "\" & strTempFileName & Counter & "." & FileExt | |||||
| End If | |||||
| Loop | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,250 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' Validation Classes | |||||
| '======================================================================================================================= | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' Exists Validation | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class ExistsValidation_Class | |||||
| Private m_instance | |||||
| Private m_field_name | |||||
| Private m_message | |||||
| Private m_ok | |||||
| Public Function Initialize(instance, field_name, message) | |||||
| set m_instance = instance | |||||
| m_field_name = field_name | |||||
| m_message = message | |||||
| m_ok = true | |||||
| set Initialize = Me | |||||
| End Function | |||||
| Public Sub Check | |||||
| If Len(eval("m_instance." & m_field_name)) = 0 then | |||||
| m_ok = false | |||||
| End If | |||||
| End Sub | |||||
| Public Property Get OK | |||||
| OK = m_ok | |||||
| End Property | |||||
| Public Property Get Message | |||||
| Message = m_message | |||||
| End Property | |||||
| End Class | |||||
| Sub ValidateExists(instance, field_name, message) | |||||
| if not IsObject(instance.Validator) then set instance.Validator = new Validator_Class | |||||
| instance.Validator.AddValidation new ExistsValidation_Class.Initialize(instance, field_name, message) | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' Minimum Length Validation | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class MinLengthValidation_Class | |||||
| Private m_instance | |||||
| Private m_field_name | |||||
| Private m_size | |||||
| Private m_message | |||||
| Private m_ok | |||||
| Public Function Initialize(instance, field_name, size, message) | |||||
| set m_instance = instance | |||||
| m_field_name = field_name | |||||
| m_size = size | |||||
| m_message = message | |||||
| m_ok = true | |||||
| set Initialize = Me | |||||
| End Function | |||||
| Public Sub Check | |||||
| If Len(eval("m_instance." & m_field_name)) < m_size then m_ok = false | |||||
| End Sub | |||||
| Public Property Get OK | |||||
| OK = m_ok | |||||
| End Property | |||||
| Public Property Get Message | |||||
| Message = m_message | |||||
| End Property | |||||
| End Class | |||||
| Sub ValidateMinLength(instance, field_name, size, message) | |||||
| if not IsObject(instance.Validator) then set instance.Validator = new Validator_Class | |||||
| instance.Validator.AddValidation new MinLengthValidation_Class.Initialize(instance, field_name, size, message) | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' Max Length Validation | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class MaxLengthValidation_Class | |||||
| Private m_instance | |||||
| Private m_field_name | |||||
| Private m_size | |||||
| Private m_message | |||||
| Private m_ok | |||||
| Public Function Initialize(instance, field_name, size, message) | |||||
| set m_instance = instance | |||||
| m_field_name = field_name | |||||
| m_size = size | |||||
| m_message = message | |||||
| m_ok = true | |||||
| set Initialize = Me | |||||
| End Function | |||||
| Public Sub Check | |||||
| If Len(eval("m_instance." & m_field_name)) > m_size then m_ok = false | |||||
| End Sub | |||||
| Public Property Get OK | |||||
| OK = m_ok | |||||
| End Property | |||||
| Public Property Get Message | |||||
| Message = m_message | |||||
| End Property | |||||
| End Class | |||||
| Sub ValidateMaxLength(instance, field_name, size, message) | |||||
| if not IsObject(instance.Validator) then set instance.Validator = new Validator_Class | |||||
| instance.Validator.AddValidation new MaxLengthValidation_Class.Initialize(instance, field_name, size, message) | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' Numeric Validation | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class NumericValidation_Class | |||||
| Private m_instance | |||||
| Private m_field_name | |||||
| Private m_message | |||||
| Private m_ok | |||||
| Public Function Initialize(instance, field_name, message) | |||||
| set m_instance = instance | |||||
| m_field_name = field_name | |||||
| m_message = message | |||||
| m_ok = true | |||||
| set Initialize = Me | |||||
| End Function | |||||
| Public Sub Check | |||||
| If Not IsNumeric(eval("m_instance." & m_field_name)) then m_ok = false | |||||
| End Sub | |||||
| Public Property Get OK | |||||
| OK = m_ok | |||||
| End Property | |||||
| Public Property Get Message | |||||
| Message = m_message | |||||
| End Property | |||||
| End Class | |||||
| Sub ValidateNumeric(instance, field_name, message) | |||||
| if not IsObject(instance.Validator) then set instance.Validator = new Validator_Class | |||||
| instance.Validator.AddValidation new NumericValidation_Class.Initialize(instance, field_name, message) | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' Regular Expression Pattern Validation | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class PatternValidation_Class | |||||
| Private m_instance | |||||
| Private m_field_name | |||||
| Private m_pattern | |||||
| Private m_message | |||||
| Private m_ok | |||||
| Public Function Initialize(instance, field_name, pattern, message) | |||||
| set m_instance = instance | |||||
| m_field_name = field_name | |||||
| m_pattern = pattern | |||||
| m_message = message | |||||
| m_ok = true | |||||
| set Initialize = Me | |||||
| End Function | |||||
| Public Sub Check | |||||
| dim re : set re = new RegExp | |||||
| With re | |||||
| .Pattern = m_pattern | |||||
| .Global = true | |||||
| .IgnoreCase = true | |||||
| End With | |||||
| dim matches : set matches = re.Execute(eval("m_instance." & m_field_name)) | |||||
| if matches.Count = 0 then | |||||
| m_ok = false | |||||
| end if | |||||
| End Sub | |||||
| Public Property Get OK | |||||
| OK = m_ok | |||||
| End Property | |||||
| Public Property Get Message | |||||
| Message = m_message | |||||
| End Property | |||||
| End Class | |||||
| Sub ValidatePattern(instance, field_name, pattern, message) | |||||
| if not IsObject(instance.Validator) then set instance.Validator = new Validator_Class | |||||
| instance.Validator.AddValidation new PatternValidation_Class.Initialize(instance, field_name, pattern, message) | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' Validator Class | |||||
| ' This class is not intended to be used directly. Models should use the Validate* subs instead. | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Class Validator_Class | |||||
| Private m_validations | |||||
| Private m_errors | |||||
| Private Sub Class_Initialize | |||||
| m_validations = Array() | |||||
| redim m_validations(-1) | |||||
| m_errors = Array() | |||||
| redim m_errors(-1) | |||||
| End Sub | |||||
| Public Property Get Errors | |||||
| Errors = m_errors | |||||
| End Property | |||||
| Public Sub AddValidation(validation) | |||||
| dim n : n = ubound(m_validations) + 1 | |||||
| redim preserve m_validations(n) | |||||
| set m_validations(n) = validation | |||||
| End Sub | |||||
| Public Sub Validate | |||||
| dim n : n = ubound(m_validations) | |||||
| dim i, V | |||||
| for i = 0 to n | |||||
| set V = m_validations(i) | |||||
| V.Check | |||||
| if not V.OK then | |||||
| AddError V.Message | |||||
| end if | |||||
| next | |||||
| End Sub | |||||
| Public Property Get HasErrors | |||||
| HasErrors = (ubound(m_errors) > -1) | |||||
| End Property | |||||
| 'Public to allow other errors to be added by the controller for circumstances not accounted for by the validators | |||||
| Public Sub AddError(msg) | |||||
| redim preserve m_errors(ubound(m_errors) + 1) | |||||
| m_errors(ubound(m_errors)) = msg | |||||
| End Sub | |||||
| End Class | |||||
| %> | |||||
| @@ -0,0 +1,45 @@ | |||||
| <% | |||||
| Function HashPassword(password) | |||||
| Dim shell, command, execObj, outputLine, result | |||||
| ' Create Shell Object | |||||
| Set shell = CreateObject("WScript.Shell") | |||||
| ' Construct PowerShell Command | |||||
| command = "cmd /c powershell -ExecutionPolicy Bypass -NoLogo -NoProfile -File """ & Server.MapPath(".") & "..\Core\hash_sha256.ps1"" -password " & password | |||||
| ' Execute Command | |||||
| Set execObj = shell.Exec(command) | |||||
| ' Read Output | |||||
| Do While Not execObj.StdOut.AtEndOfStream | |||||
| outputLine = Trim(execObj.StdOut.ReadAll()) | |||||
| If outputLine <> "" Then | |||||
| result = outputLine ' Capture the hash | |||||
| End If | |||||
| Loop | |||||
| ' Cleanup | |||||
| Set shell = Nothing | |||||
| Set execObj = Nothing | |||||
| ' Return the hash or error message | |||||
| If result = "" Or Left(result, 5) = "ERROR" Then | |||||
| HashPassword = result ' "ERROR: Hash not generated" | |||||
| Else | |||||
| HashPassword = result | |||||
| End If | |||||
| End Function | |||||
| Function CheckPassword(username, password) | |||||
| Dim user,UsersRepository | |||||
| Set UsersRepository = CreateRepository(conn, "Users", "UserId") | |||||
| ' Find User | |||||
| Set User = UsersRepository.Find(Array("UserName", user), Empty) | |||||
| If user Is Nothing Then Exit Function ' Implicitly returns False | |||||
| ' Compare Hashed Password | |||||
| CheckPassword = (HashPassword(password) = user.PasswordHash) | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,115 @@ | |||||
| <% | |||||
| 'protocol = IIf(LCase(Request.ServerVariables("HTTPS")) = "1", "https", "http") | |||||
| protocol = "https" | |||||
| Dim timeZones | |||||
| timeZones = Array( _ | |||||
| Array("America/New_York", "Eastern (New York)"), _ | |||||
| Array("America/Chicago", "Central (Chicago)"), _ | |||||
| Array("America/Denver", "Mountain (Denver)"), _ | |||||
| Array("America/Los_Angeles", "Pacific (Los Angeles)"), _ | |||||
| Array("Europe/London", "London (UK)") _ | |||||
| ) | |||||
| Function RequestBinary() | |||||
| On Error Resume Next | |||||
| Dim stream, rawData | |||||
| If Request.TotalBytes = 0 Then | |||||
| RequestBinary = "" | |||||
| Exit Function | |||||
| End If | |||||
| Set stream = Server.CreateObject("ADODB.Stream") | |||||
| stream.Type = 1 ' adTypeBinary | |||||
| stream.Open | |||||
| stream.Write Request.BinaryRead(Request.TotalBytes) | |||||
| stream.Position = 0 | |||||
| stream.Type = 2 ' adTypeText | |||||
| stream.Charset = "utf-8" | |||||
| rawData = stream.ReadText | |||||
| stream.Close | |||||
| Set stream = Nothing | |||||
| If Err.Number <> 0 Then | |||||
| rawData = "" | |||||
| Err.Clear | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| RequestBinary = rawData | |||||
| End Function | |||||
| Function ArrayContains(arr, val) | |||||
| Dim i | |||||
| ArrayContains = False | |||||
| If IsArray(arr) Then | |||||
| For i = LBound(arr) To UBound(arr) | |||||
| If arr(i) = val Then | |||||
| ArrayContains = True | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| End If | |||||
| End Function | |||||
| Function RenderPOBOArrayAsJson(pobo) | |||||
| Dim i, j, obj, propList, propName, json, propVal, line,poboArray | |||||
| if IsLinkedList(pobo) Then | |||||
| poboArray = pobo.TO_Array() | |||||
| End If | |||||
| json = "[" | |||||
| If IsEmpty(poboArray) Then | |||||
| RenderPOBOArrayAsJson = "[]" | |||||
| Exit Function | |||||
| End If | |||||
| For i = 0 To UBound(poboArray) | |||||
| Set obj = poboArray(i) | |||||
| propList = obj.Properties | |||||
| line = "{" | |||||
| For j = 0 To UBound(propList) | |||||
| propName = propList(j) | |||||
| ' Dynamically get value using Execute | |||||
| Execute "propVal = obj." & propName | |||||
| Select Case VarType(propVal) | |||||
| Case vbString | |||||
| line = line & """" & propName & """:""" & JsonEscape(propVal) & """" | |||||
| Case vbBoolean | |||||
| line = line & """" & propName & """:" & LCase(CStr(propVal)) | |||||
| Case vbDate | |||||
| line = line & """" & propName & """:""" & Replace(CStr(propVal), """", "") & """" | |||||
| Case vbNull | |||||
| line = line & """" & propName & """:null" | |||||
| Case Else | |||||
| line = line & """" & propName & """:" & CStr(propVal) | |||||
| End Select | |||||
| If j < UBound(propList) Then | |||||
| line = line & "," | |||||
| End If | |||||
| Next | |||||
| line = line & "}" | |||||
| If i < UBound(poboArray) Then line = line & "," | |||||
| json = json & line | |||||
| Next | |||||
| json = json & "]" | |||||
| RenderPOBOArrayAsJson = json | |||||
| End Function | |||||
| Function JsonEscape(str) | |||||
| str = Replace(str, "\", "\\") | |||||
| str = Replace(str, """", "\""") | |||||
| str = Replace(str, vbCrLf, "\n") | |||||
| str = Replace(str, vbLf, "\n") | |||||
| str = Replace(str, vbCr, "\n") | |||||
| str = Replace(str, Chr(9), "\t") | |||||
| JsonEscape = str | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,284 @@ | |||||
| <% | |||||
| 'January 2021 - Version 1.1 by Gerrit van Kuipers | |||||
| Class aspJSON | |||||
| Public data | |||||
| Private p_JSONstring | |||||
| Private aj_in_string, aj_in_escape, aj_i_tmp, aj_char_tmp, aj_s_tmp, aj_line_tmp, aj_line, aj_lines, aj_currentlevel, aj_currentkey, aj_currentvalue, aj_newlabel, aj_XmlHttp, aj_RegExp, aj_colonfound | |||||
| Private Sub Class_Initialize() | |||||
| Set data = Collection() | |||||
| Set aj_RegExp = New regexp | |||||
| aj_RegExp.Pattern = "\s{0,}(\S{1}[\s,\S]*\S{1})\s{0,}" | |||||
| aj_RegExp.Global = False | |||||
| aj_RegExp.IgnoreCase = True | |||||
| aj_RegExp.Multiline = True | |||||
| End Sub | |||||
| Private Sub Class_Terminate() | |||||
| Set data = Nothing | |||||
| Set aj_RegExp = Nothing | |||||
| End Sub | |||||
| Public Sub loadJSON(inputsource) | |||||
| inputsource = aj_MultilineTrim(inputsource) | |||||
| If Len(inputsource) = 0 Then Err.Raise 1, "loadJSON Error", "No data to load." | |||||
| Select Case Left(inputsource, 1) | |||||
| Case "{", "[" | |||||
| Case Else | |||||
| Set aj_XmlHttp = Server.CreateObject("Msxml2.ServerXMLHTTP") | |||||
| aj_XmlHttp.open "POST", inputsource, False | |||||
| aj_XmlHttp.setRequestHeader "Content-Type", "text/json" | |||||
| aj_XmlHttp.setRequestHeader "CharSet", "UTF-8" | |||||
| aj_XmlHttp.Send | |||||
| inputsource = aj_XmlHttp.responseText | |||||
| Set aj_XmlHttp = Nothing | |||||
| End Select | |||||
| p_JSONstring = CleanUpJSONstring(inputsource) | |||||
| aj_lines = Split(p_JSONstring, Chr(13) & Chr(10)) | |||||
| Dim level(99) | |||||
| aj_currentlevel = 1 | |||||
| Set level(aj_currentlevel) = data | |||||
| For Each aj_line In aj_lines | |||||
| aj_currentkey = "" | |||||
| aj_currentvalue = "" | |||||
| If Instr(aj_line, ":") > 0 Then | |||||
| aj_in_string = False | |||||
| aj_in_escape = False | |||||
| aj_colonfound = False | |||||
| For aj_i_tmp = 1 To Len(aj_line) | |||||
| If aj_in_escape Then | |||||
| aj_in_escape = False | |||||
| Else | |||||
| Select Case Mid(aj_line, aj_i_tmp, 1) | |||||
| Case """" | |||||
| aj_in_string = Not aj_in_string | |||||
| Case ":" | |||||
| If Not aj_in_escape And Not aj_in_string Then | |||||
| aj_currentkey = Left(aj_line, aj_i_tmp - 1) | |||||
| aj_currentvalue = Mid(aj_line, aj_i_tmp + 1) | |||||
| aj_colonfound = True | |||||
| Exit For | |||||
| End If | |||||
| Case "\" | |||||
| aj_in_escape = True | |||||
| End Select | |||||
| End If | |||||
| Next | |||||
| if aj_colonfound then | |||||
| aj_currentkey = aj_Strip(aj_JSONDecode(aj_currentkey), """") | |||||
| If Not level(aj_currentlevel).exists(aj_currentkey) Then level(aj_currentlevel).Add aj_currentkey, "" | |||||
| end if | |||||
| End If | |||||
| If right(aj_line,1) = "{" Or right(aj_line,1) = "[" Then | |||||
| If Len(aj_currentkey) = 0 Then aj_currentkey = level(aj_currentlevel).Count | |||||
| Set level(aj_currentlevel).Item(aj_currentkey) = Collection() | |||||
| Set level(aj_currentlevel + 1) = level(aj_currentlevel).Item(aj_currentkey) | |||||
| aj_currentlevel = aj_currentlevel + 1 | |||||
| aj_currentkey = "" | |||||
| ElseIf right(aj_line,1) = "}" Or right(aj_line,1) = "]" or right(aj_line,2) = "}," Or right(aj_line,2) = "]," Then | |||||
| aj_currentlevel = aj_currentlevel - 1 | |||||
| ElseIf Len(Trim(aj_line)) > 0 Then | |||||
| If Len(aj_currentvalue) = 0 Then aj_currentvalue = aj_line | |||||
| aj_currentvalue = getJSONValue(aj_currentvalue) | |||||
| If Len(aj_currentkey) = 0 Then aj_currentkey = level(aj_currentlevel).Count | |||||
| level(aj_currentlevel).Item(aj_currentkey) = aj_currentvalue | |||||
| End If | |||||
| Next | |||||
| End Sub | |||||
| Public Function Collection() | |||||
| Set Collection = Server.CreateObject("Scripting.Dictionary") | |||||
| End Function | |||||
| Public Function AddToCollection(dictobj) | |||||
| If TypeName(dictobj) <> "Dictionary" Then Err.Raise 1, "AddToCollection Error", "Not a collection." | |||||
| aj_newlabel = dictobj.Count | |||||
| dictobj.Add aj_newlabel, Collection() | |||||
| Set AddToCollection = dictobj.item(aj_newlabel) | |||||
| end function | |||||
| Private Function CleanUpJSONstring(aj_originalstring) | |||||
| aj_originalstring = Replace(aj_originalstring, Chr(13) & Chr(10), "") | |||||
| aj_originalstring = Mid(aj_originalstring, 2, Len(aj_originalstring) - 2) | |||||
| aj_in_string = False : aj_in_escape = False : aj_s_tmp = "" | |||||
| For aj_i_tmp = 1 To Len(aj_originalstring) | |||||
| aj_char_tmp = Mid(aj_originalstring, aj_i_tmp, 1) | |||||
| If aj_in_escape Then | |||||
| aj_in_escape = False | |||||
| aj_s_tmp = aj_s_tmp & aj_char_tmp | |||||
| Else | |||||
| Select Case aj_char_tmp | |||||
| Case "\" : aj_s_tmp = aj_s_tmp & aj_char_tmp : aj_in_escape = True | |||||
| Case """" : aj_s_tmp = aj_s_tmp & aj_char_tmp : aj_in_string = Not aj_in_string | |||||
| Case "{", "[" | |||||
| aj_s_tmp = aj_s_tmp & aj_char_tmp & aj_InlineIf(aj_in_string, "", Chr(13) & Chr(10)) | |||||
| Case "}", "]" | |||||
| aj_s_tmp = aj_s_tmp & aj_InlineIf(aj_in_string, "", Chr(13) & Chr(10)) & aj_char_tmp | |||||
| Case "," : aj_s_tmp = aj_s_tmp & aj_char_tmp & aj_InlineIf(aj_in_string, "", Chr(13) & Chr(10)) | |||||
| Case Else : aj_s_tmp = aj_s_tmp & aj_char_tmp | |||||
| End Select | |||||
| End If | |||||
| Next | |||||
| CleanUpJSONstring = "" | |||||
| aj_s_tmp = Split(aj_s_tmp, Chr(13) & Chr(10)) | |||||
| For Each aj_line_tmp In aj_s_tmp | |||||
| aj_line_tmp = Replace(Replace(aj_line_tmp, Chr(10), ""), Chr(13), "") | |||||
| CleanUpJSONstring = CleanUpJSONstring & aj_Trim(aj_line_tmp) & Chr(13) & Chr(10) | |||||
| Next | |||||
| End Function | |||||
| Private Function getJSONValue(ByVal val) | |||||
| val = Trim(val) | |||||
| If Left(val,1) = ":" Then val = Mid(val, 2) | |||||
| If Right(val,1) = "," Then val = Left(val, Len(val) - 1) | |||||
| val = Trim(val) | |||||
| Select Case val | |||||
| Case "true" : getJSONValue = True | |||||
| Case "false" : getJSONValue = False | |||||
| Case "null" : getJSONValue = Null | |||||
| Case Else | |||||
| If (Instr(val, """") = 0) Then | |||||
| If IsNumeric(val) Then | |||||
| getJSONValue = aj_ReadNumericValue(val) | |||||
| Else | |||||
| getJSONValue = val | |||||
| End If | |||||
| Else | |||||
| If Left(val,1) = """" Then val = Mid(val, 2) | |||||
| If Right(val,1) = """" Then val = Left(val, Len(val) - 1) | |||||
| getJSONValue = aj_JSONDecode(Trim(val)) | |||||
| End If | |||||
| End Select | |||||
| End Function | |||||
| Private JSONoutput_level | |||||
| Public Function JSONoutput() | |||||
| Dim wrap_dicttype, aj_label | |||||
| JSONoutput_level = 1 | |||||
| wrap_dicttype = "[]" | |||||
| For Each aj_label In data | |||||
| If Not aj_IsInt(aj_label) Then wrap_dicttype = "{}" | |||||
| Next | |||||
| JSONoutput = Left(wrap_dicttype, 1) & Chr(13) & Chr(10) & GetDict(data) & Right(wrap_dicttype, 1) | |||||
| End Function | |||||
| Private Function GetDict(objDict) | |||||
| Dim aj_item, aj_keyvals, aj_label, aj_dicttype | |||||
| For Each aj_item In objDict | |||||
| Select Case TypeName(objDict.Item(aj_item)) | |||||
| Case "Dictionary" | |||||
| GetDict = GetDict & Space(JSONoutput_level * 4) | |||||
| aj_dicttype = "[]" | |||||
| For Each aj_label In objDict.Item(aj_item).Keys | |||||
| If Not aj_IsInt(aj_label) Then aj_dicttype = "{}" | |||||
| Next | |||||
| If aj_IsInt(aj_item) Then | |||||
| GetDict = GetDict & (Left(aj_dicttype,1) & Chr(13) & Chr(10)) | |||||
| Else | |||||
| GetDict = GetDict & ("""" & aj_JSONEncode(aj_item) & """" & ": " & Left(aj_dicttype,1) & Chr(13) & Chr(10)) | |||||
| End If | |||||
| JSONoutput_level = JSONoutput_level + 1 | |||||
| aj_keyvals = objDict.Keys | |||||
| GetDict = GetDict & (GetSubDict(objDict.Item(aj_item)) & Space(JSONoutput_level * 4) & Right(aj_dicttype,1) & aj_InlineIf(aj_item = aj_keyvals(objDict.Count - 1),"" , ",") & Chr(13) & Chr(10)) | |||||
| Case Else | |||||
| aj_keyvals = objDict.Keys | |||||
| GetDict = GetDict & (Space(JSONoutput_level * 4) & aj_InlineIf(aj_IsInt(aj_item), "", """" & aj_JSONEncode(aj_item) & """: ") & WriteValue(objDict.Item(aj_item)) & aj_InlineIf(aj_item = aj_keyvals(objDict.Count - 1),"" , ",") & Chr(13) & Chr(10)) | |||||
| End Select | |||||
| Next | |||||
| End Function | |||||
| Private Function aj_IsInt(val) | |||||
| aj_IsInt = (TypeName(val) = "Integer" Or TypeName(val) = "Long") | |||||
| End Function | |||||
| Private Function GetSubDict(objSubDict) | |||||
| GetSubDict = GetDict(objSubDict) | |||||
| JSONoutput_level= JSONoutput_level -1 | |||||
| End Function | |||||
| Private Function WriteValue(ByVal val) | |||||
| Select Case TypeName(val) | |||||
| Case "Double", "Integer", "Long": WriteValue = replace(val, ",", ".") | |||||
| Case "Null" : WriteValue = "null" | |||||
| Case "Boolean" : WriteValue = aj_InlineIf(val, "true", "false") | |||||
| Case Else : WriteValue = """" & aj_JSONEncode(val) & """" | |||||
| End Select | |||||
| End Function | |||||
| Private Function aj_JSONEncode(ByVal val) | |||||
| val = Replace(val, "\", "\\") | |||||
| val = Replace(val, """", "\""") | |||||
| 'val = Replace(val, "/", "\/") | |||||
| val = Replace(val, Chr(8), "\b") | |||||
| val = Replace(val, Chr(12), "\f") | |||||
| val = Replace(val, Chr(10), "\n") | |||||
| val = Replace(val, Chr(13), "\r") | |||||
| val = Replace(val, Chr(9), "\t") | |||||
| aj_JSONEncode = Trim(val) | |||||
| End Function | |||||
| Private Function aj_JSONDecode(ByVal val) | |||||
| val = Replace(val, "\""", """") | |||||
| val = Replace(val, "\\", "\") | |||||
| val = Replace(val, "\/", "/") | |||||
| val = Replace(val, "\b", Chr(8)) | |||||
| val = Replace(val, "\f", Chr(12)) | |||||
| val = Replace(val, "\n", Chr(10)) | |||||
| val = Replace(val, "\r", Chr(13)) | |||||
| val = Replace(val, "\t", Chr(9)) | |||||
| aj_JSONDecode = Trim(val) | |||||
| End Function | |||||
| Private Function aj_InlineIf(condition, returntrue, returnfalse) | |||||
| If condition Then aj_InlineIf = returntrue Else aj_InlineIf = returnfalse | |||||
| End Function | |||||
| Private Function aj_Strip(ByVal val, stripper) | |||||
| If Left(val, 1) = stripper Then val = Mid(val, 2) | |||||
| If Right(val, 1) = stripper Then val = Left(val, Len(val) - 1) | |||||
| aj_Strip = val | |||||
| End Function | |||||
| Private Function aj_MultilineTrim(TextData) | |||||
| aj_MultilineTrim = aj_RegExp.Replace(TextData, "$1") | |||||
| End Function | |||||
| Private Function aj_Trim(val) | |||||
| aj_Trim = Trim(val) | |||||
| Do While Left(aj_Trim, 1) = Chr(9) : aj_Trim = Mid(aj_Trim, 2) : Loop | |||||
| Do While Right(aj_Trim, 1) = Chr(9) : aj_Trim = Left(aj_Trim, Len(aj_Trim) - 1) : Loop | |||||
| aj_Trim = Trim(aj_Trim) | |||||
| End Function | |||||
| Private Function aj_ReadNumericValue(ByVal val) | |||||
| If Instr(val, ".") > 0 Then | |||||
| numdecimals = Len(val) - Instr(val, ".") | |||||
| val = Clng(Replace(val, ".", "")) | |||||
| val = val / (10 ^ numdecimals) | |||||
| aj_ReadNumericValue = val | |||||
| Else | |||||
| aj_ReadNumericValue = Clng(val) | |||||
| End If | |||||
| End Function | |||||
| End Class | |||||
| dim json_Class__Singleton | |||||
| Function json() | |||||
| if IsEmpty(json_Class__Singleton) then | |||||
| set json_Class__Singleton = new aspJSON | |||||
| end if | |||||
| set json = json_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,166 @@ | |||||
| <!--#include file="../app/Controllers/autoload_controllers.asp" --> | |||||
| <% | |||||
| ' Set cache expiration from configuration | |||||
| Dim cacheYear : cacheYear = GetAppSetting("CacheExpirationYear") | |||||
| If cacheYear = "nothing" Then cacheYear = "2030" | |||||
| Response.ExpiresAbsolute = "01/01/" & cacheYear | |||||
| Response.AddHeader "pragma", "no-cache" | |||||
| Response.AddHeader "cache-control", "private, no-cache, must-revalidate" | |||||
| '======================================================================================================================= | |||||
| ' MVC Dispatcher | |||||
| '======================================================================================================================= | |||||
| Class MVC_Dispatcher_Class | |||||
| dim CurrentController | |||||
| Public Property Get ControllerName | |||||
| ControllerName = CurrentController | |||||
| end Property | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Convenience method to resolve route and dispatch in one call | |||||
| ' method: HTTP method (GET, POST, etc.) | |||||
| ' path: Request path (already cleaned of query params) | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub DispatchRequest(method, path) | |||||
| Dim routeArray | |||||
| routeArray = router.Resolve(method, path) | |||||
| Dispatch routeArray | |||||
| End Sub | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| ' Main dispatch method - executes a resolved route | |||||
| ' RouteArray: Array(controller, action, params) from router.Resolve() | |||||
| '--------------------------------------------------------------------------------------------------------------------- | |||||
| Public Sub Dispatch(RouteArray) | |||||
| On Error Resume Next | |||||
| Dim controllerName, actionName, hasParams, paramsArray | |||||
| controllerName = RouteArray(0) | |||||
| actionName = RouteArray(1) | |||||
| ' Security: Validate controller and action names | |||||
| If Not ControllerRegistry.IsValidControllerFormat(controllerName) Then | |||||
| Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>" | |||||
| Response.Write "<strong>Security Error:</strong> Invalid controller name format." | |||||
| Response.Write "</div>" | |||||
| Exit Sub | |||||
| End If | |||||
| If Not ControllerRegistry.IsValidActionFormat(actionName) Then | |||||
| Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>" | |||||
| Response.Write "<strong>Security Error:</strong> Invalid action name format." | |||||
| Response.Write "</div>" | |||||
| Exit Sub | |||||
| End If | |||||
| ' Security: Check controller whitelist | |||||
| If Not ControllerRegistry.IsValidController(controllerName) Then | |||||
| Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>" | |||||
| Response.Write "<strong>Security Error:</strong> Controller '" & Server.HTMLEncode(controllerName) & "' is not registered." | |||||
| Response.Write "</div>" | |||||
| Exit Sub | |||||
| End If | |||||
| ' Initialize current controller | |||||
| Dim controllerAssignment : controllerAssignment = "Set CurrentController = " & controllerName & "()" | |||||
| Execute controllerAssignment | |||||
| ' Check if layout should be used | |||||
| hasParams = (UBound(RouteArray) >= 2) | |||||
| If eval(controllerName & ".useLayout") Then | |||||
| %> <!-- #include file="../app/views/Shared/Header.asp" --> <% | |||||
| End If | |||||
| ' Prepare parameters | |||||
| If hasParams Then | |||||
| paramsArray = SurroundStringInArray(RouteArray(2)) | |||||
| Else | |||||
| paramsArray = Empty | |||||
| End If | |||||
| ' Execute controller action | |||||
| ExecuteControllerAction controllerName, actionName, paramsArray | |||||
| ' Include footer if layout is used | |||||
| If eval(controllerName & ".useLayout") Then | |||||
| %> <!-- #include file="../app/views/Shared/Footer.asp" --> <% | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Sub | |||||
| ' Helper method to execute controller actions (eliminates code duplication) | |||||
| Private Sub ExecuteControllerAction(controllerName, actionName, paramsArray) | |||||
| On Error Resume Next | |||||
| Dim callString | |||||
| ' Build the call string based on whether we have parameters | |||||
| If Not IsEmpty(paramsArray) And IsArray(paramsArray) And UBound(paramsArray) >= 0 Then | |||||
| callString = "Call " & controllerName & "." & actionName & "(" & Join(paramsArray, ",") & ")" | |||||
| Else | |||||
| callString = "Call " & controllerName & "." & actionName & "()" | |||||
| End If | |||||
| ' Execute the action | |||||
| Execute callString | |||||
| ' Handle errors | |||||
| If Err.Number <> 0 Then | |||||
| HandleDispatchError actionName, Err.Description, Err.Number | |||||
| Err.Clear | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Sub | |||||
| ' Centralized error handling for dispatch errors | |||||
| Private Sub HandleDispatchError(actionName, errorDesc, errorNum) | |||||
| Dim isDevelopment | |||||
| isDevelopment = (LCase(GetAppSetting("Environment")) = "development") | |||||
| If isDevelopment Then | |||||
| Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>" | |||||
| Response.Write "<strong>Controller Action Error</strong><br>" | |||||
| Response.Write "Action: <code>" & Server.HTMLEncode(actionName) & "</code><br>" | |||||
| Response.Write "Error: " & Server.HTMLEncode(errorDesc) & "<br>" | |||||
| Response.Write "Error Number: " & errorNum | |||||
| Response.Write "</div>" | |||||
| Else | |||||
| Response.Write "<div style='padding:15px; margin:10px; border:2px solid #dc3545; background:#f8d7da; color:#721c24; border-radius:4px;'>" | |||||
| Response.Write "<strong>An error occurred</strong><br>" | |||||
| Response.Write "Please contact the system administrator if the problem persists." | |||||
| Response.Write "</div>" | |||||
| End If | |||||
| End Sub | |||||
| Public Sub RequirePost | |||||
| If Request.Form.Count = 0 Then MVC.RedirectToExt "NotValid","",empty:End If | |||||
| End Sub | |||||
| ' Shortcut for RedirectToActionExt that does not require passing a parameters argument. | |||||
| Public Sub RedirectToAction(ByVal action_name) | |||||
| RedirectToActionExt action_name, empty | |||||
| End Sub | |||||
| Public Sub RedirectTo(controller_name, action_name) | |||||
| RedirectToExt controller_name, action_name, empty | |||||
| End Sub | |||||
| ' Redirects the browser to the specified action on the specified controller with the specified querystring parameters. | |||||
| ' params is a KVArray of querystring parameters. | |||||
| Public Sub RedirectToExt(controller_name, action_name, params) | |||||
| Response.Redirect Routes.UrlTo(controller_name, action_name, params) | |||||
| End Sub | |||||
| Public Sub RedirectToActionExt(ByVal action_name, ByVal params) | |||||
| RedirectToExt ControllerName, action_name, params | |||||
| End Sub | |||||
| End Class | |||||
| dim MVC_Dispatcher_Class__Singleton | |||||
| Function MVC() | |||||
| if IsEmpty(MVC_Dispatcher_Class__Singleton) then | |||||
| set MVC_Dispatcher_Class__Singleton = new MVC_Dispatcher_Class | |||||
| end if | |||||
| set MVC = MVC_Dispatcher_Class__Singleton | |||||
| End Function | |||||
| %> | |||||
| @@ -0,0 +1,147 @@ | |||||
| <?xml version="1.0"?> | |||||
| <!-- RouterComponent.wsc --> | |||||
| <component> | |||||
| <!-- COM registration --> | |||||
| <registration | |||||
| description = "Classic ASP Router Component" | |||||
| progid = "App.Router" | |||||
| version = "1.0" | |||||
| classid = "{A1FC6EA8-519E-4E34-AC08-77788E3E5E44}" /> | |||||
| <!-- Public interface --> | |||||
| <public> | |||||
| <method name="AddRoute"/> | |||||
| <method name="Resolve"/> | |||||
| </public> | |||||
| <!-- Give the component ASP intrinsic objects (Request, Response, Server …) --> | |||||
| <implements type="ASP"/> | |||||
| <!-- Implementation --> | |||||
| <script language="VBScript"> | |||||
| <![CDATA[ | |||||
| Option Explicit | |||||
| '------------------------------------------------------------ | |||||
| ' Private state | |||||
| '------------------------------------------------------------ | |||||
| Dim routes : Set routes = CreateObject("Scripting.Dictionary") | |||||
| '------------------------------------------------------------ | |||||
| ' METHOD AddRoute(method, path, controller, action) | |||||
| '------------------------------------------------------------ | |||||
| Public Sub AddRoute(method, path, controller, action) | |||||
| ' Input validation | |||||
| If IsEmpty(method) Or Len(Trim(method)) = 0 Then | |||||
| Err.Raise 5, "Router.AddRoute", "HTTP method parameter is required and cannot be empty" | |||||
| End If | |||||
| If IsEmpty(path) Then | |||||
| Err.Raise 5, "Router.AddRoute", "Path parameter is required" | |||||
| End If | |||||
| If IsEmpty(controller) Or Len(Trim(controller)) = 0 Then | |||||
| Err.Raise 5, "Router.AddRoute", "Controller parameter is required and cannot be empty" | |||||
| End If | |||||
| If IsEmpty(action) Or Len(Trim(action)) = 0 Then | |||||
| Err.Raise 5, "Router.AddRoute", "Action parameter is required and cannot be empty" | |||||
| End If | |||||
| ' Validate HTTP method (allow common methods) | |||||
| Dim validMethods, methodUpper, i, isValidMethod | |||||
| validMethods = Array("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS") | |||||
| methodUpper = UCase(Trim(method)) | |||||
| isValidMethod = False | |||||
| For i = 0 To UBound(validMethods) | |||||
| If validMethods(i) = methodUpper Then | |||||
| isValidMethod = True | |||||
| Exit For | |||||
| End If | |||||
| Next | |||||
| If Not isValidMethod Then | |||||
| Err.Raise 5, "Router.AddRoute", "Invalid HTTP method: " & method & ". Allowed: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS" | |||||
| End If | |||||
| Dim routeKey | |||||
| routeKey = methodUpper & ":" & LCase(Trim(path)) | |||||
| If Not routes.Exists(routeKey) Then | |||||
| routes.Add routeKey, Array(Trim(controller), Trim(action)) | |||||
| End If | |||||
| End Sub | |||||
| '------------------------------------------------------------ | |||||
| ' METHOD Resolve(method, path) -> Array(controller, action, params()) | |||||
| '------------------------------------------------------------ | |||||
| Public Function Resolve(method, path) | |||||
| Dim routeKey, routeValue, values | |||||
| routeKey = UCase(method) & ":" & LCase(path) | |||||
| ' Always return a params array (empty by default) | |||||
| Dim emptyParams() : ReDim emptyParams(-1) | |||||
| ' Exact match first | |||||
| If routes.Exists(routeKey) Then | |||||
| routeValue = routes(routeKey) | |||||
| Resolve = Array(routeValue(0), routeValue(1), emptyParams) | |||||
| Exit Function | |||||
| End If | |||||
| ' Dynamic routes (e.g. /users/:id) | |||||
| Dim r, routeMethod, routePattern | |||||
| For Each r In routes.Keys | |||||
| routeMethod = Split(r, ":")(0) | |||||
| routePattern = Mid(r, Len(routeMethod) + 2) ' strip "METHOD:" | |||||
| If UCase(routeMethod) = UCase(method) Then | |||||
| If IsMatch(path, routePattern, values) Then | |||||
| routeValue = routes(r) | |||||
| Resolve = Array(routeValue(0), routeValue(1), values) | |||||
| Exit Function | |||||
| End If | |||||
| End If | |||||
| Next | |||||
| ' 404 fallback | |||||
| Resolve = Array("ErrorController", "NotFound", emptyParams) | |||||
| End Function | |||||
| '------------------------------------------------------------ | |||||
| ' INTERNAL IsMatch(requestPath, routePattern, values()) | |||||
| ' Returns True/False and fills values() with parameters | |||||
| '------------------------------------------------------------ | |||||
| Private Function IsMatch(requestPath, routePattern, values) | |||||
| Dim reqParts, routeParts, i, paramCount | |||||
| reqParts = Split(requestPath, "/") | |||||
| routeParts = Split(routePattern, "/") | |||||
| If UBound(reqParts) <> UBound(routeParts) Then | |||||
| IsMatch = False : Exit Function | |||||
| End If | |||||
| paramCount = 0 : ReDim values(-1) | |||||
| For i = 0 To UBound(reqParts) | |||||
| If Left(routeParts(i), 1) = ":" Then | |||||
| ReDim Preserve values(paramCount) | |||||
| values(paramCount) = reqParts(i) | |||||
| paramCount = paramCount + 1 | |||||
| ElseIf LCase(routeParts(i)) <> LCase(reqParts(i)) Then | |||||
| IsMatch = False : Exit Function | |||||
| End If | |||||
| Next | |||||
| If paramCount = 0 Then ReDim values(-1) | |||||
| IsMatch = True | |||||
| End Function | |||||
| '------------------------------------------------------------ | |||||
| ' Optional lifecycle hooks | |||||
| '------------------------------------------------------------ | |||||
| Private Sub Class_Terminate() | |||||
| Set routes = Nothing | |||||
| End Sub | |||||
| ]]> | |||||
| </script> | |||||
| </component> | |||||
| @@ -0,0 +1,13 @@ | |||||
| <!--#include file="..\core\autoload_core.asp" --> | |||||
| <% | |||||
| ' Define application routes | |||||
| router.AddRoute "GET", "/home", "homeController", "Index" | |||||
| router.AddRoute "GET", "/", "homeController", "Index" | |||||
| router.AddRoute "GET", "", "homeController", "Index" | |||||
| router.AddRoute "GET", "/404", "ErrorController", "NotFound" | |||||
| ' Dispatch the request (resolves route and executes controller action) | |||||
| MVC.DispatchRequest Request.ServerVariables("REQUEST_METHOD"), _ | |||||
| TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL")) | |||||
| %> | |||||
| @@ -0,0 +1,70 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <configuration> | |||||
| <appSettings> | |||||
| <!-- | |||||
| Access connection string. | |||||
| IMPORTANT: Change Data Source to the real physical path | |||||
| to your webdata.accdb file. | |||||
| --> | |||||
| <add key="ConnectionString" | |||||
| value="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\YourApp\db\webdata.accdb;Persist Security Info=False;" /> | |||||
| <!-- Environment flag (Development / Staging / Production) --> | |||||
| <add key="Environment" value="Development" /> | |||||
| <!-- Flash message display duration in milliseconds --> | |||||
| <add key="FlashMessageTimeout" value="3000" /> | |||||
| <!-- 404 error page redirect countdown in seconds --> | |||||
| <add key="Error404RedirectSeconds" value="5" /> | |||||
| <!-- Cache expiration year for static content --> | |||||
| <add key="CacheExpirationYear" value="2030" /> | |||||
| <!-- Maximum characters to display in table cells before truncating --> | |||||
| <add key="TableCellMaxLength" value="90" /> | |||||
| <!-- Character threshold for textarea vs input field in forms --> | |||||
| <add key="FormTextareaThreshold" value="100" /> | |||||
| <!-- Enable error logging to file (true/false) --> | |||||
| <add key="EnableErrorLogging" value="false" /> | |||||
| <!-- Error log file path (if EnableErrorLogging is true) --> | |||||
| <add key="ErrorLogPath" value="C:\YourApp\logs\errors.log" /> | |||||
| <!-- Enable cache-busting for URLs and assets (true/false) --> | |||||
| <add key="EnableCacheBusting" value="false" /> | |||||
| <!-- Cache-bust parameter name (default: "v") --> | |||||
| <add key="CacheBustParamName" value="v" /> | |||||
| </appSettings> | |||||
| <system.webServer> | |||||
| <!-- Default document for the site root --> | |||||
| <defaultDocument> | |||||
| <files> | |||||
| <clear /> | |||||
| <add value="Default.asp" /> | |||||
| </files> | |||||
| </defaultDocument> | |||||
| <!-- URL Rewrite: send everything through Default.asp except static assets --> | |||||
| <rewrite> | |||||
| <rules> | |||||
| <rule name="Rewrite to Default.asp" stopProcessing="true"> | |||||
| <match url="^(?!Default\.asp$|css/|js/|images/|aspunit/|favicon\.ico$).*$" /> | |||||
| <conditions> | |||||
| <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> | |||||
| <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> | |||||
| </conditions> | |||||
| <action type="Rewrite" url="/Default.asp" /> | |||||
| </rule> | |||||
| </rules> | |||||
| </rewrite> | |||||
| </system.webServer> | |||||
| </configuration> | |||||
| @@ -0,0 +1,637 @@ | |||||
| '============================================================== | |||||
| ' GeneratePOBOAndRepository.vbs | |||||
| ' | |||||
| ' Usage: | |||||
| ' cscript //nologo GeneratePOBOAndRepository.vbs /table:Orders /pk:OrderID [/cs:MyConnString] [/config:C:\path\web.config] | |||||
| ' | |||||
| ' Reads connection string from web.config <appSettings>, | |||||
| ' discovers [table] schema, and generates: | |||||
| ' � POBO_<Table>.asp (Classic ASP class with typed Property Lets) | |||||
| ' � <Table>Repository.asp (parameterized CRUD + paging + search) | |||||
| ' | |||||
| ' Safe for Access or SQL Server connection strings. | |||||
| '============================================================== | |||||
| Option Explicit | |||||
| '---------------- Args & paths ---------------- | |||||
| Dim fso, args, i, arg, parts | |||||
| Dim tableName, primaryKey, csKey, configPath, thisFolder | |||||
| Set fso = CreateObject("Scripting.FileSystemObject") | |||||
| Set args = WScript.Arguments | |||||
| csKey = "ConnectionString" | |||||
| thisFolder = fso.GetParentFolderName(WScript.ScriptFullName) | |||||
| configPath = fso.GetParentFolderName(fso.GetParentFolderName(WScript.ScriptFullName)) & "\public\web.config" | |||||
| For i = 0 To args.Count - 1 | |||||
| arg = args(i) | |||||
| If InStr(arg, ":") > 0 Then | |||||
| parts = Split(arg, ":", 2) | |||||
| Select Case LCase(Replace(parts(0), "/", "")) | |||||
| Case "table": tableName = parts(1) | |||||
| Case "pk": primaryKey = parts(1) | |||||
| Case "cs": csKey = parts(1) | |||||
| Case "config": configPath = parts(1) | |||||
| End Select | |||||
| End If | |||||
| Next | |||||
| If Len(tableName) = 0 Or Len(primaryKey) = 0 Then | |||||
| WScript.Echo "Usage: cscript //nologo GeneratePOBOAndRepository.vbs /table:<TableName> /pk:<PrimaryKey> [/cs:<ConnStringKey>] [/config:<PathToWebConfig>]" | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Validate table name format (alphanumeric, underscore only - prevent SQL injection) | |||||
| If Not IsValidIdentifier(tableName) Then | |||||
| WScript.Echo "Error: Invalid table name format. Only letters, numbers, and underscores are allowed." | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Validate primary key format | |||||
| If Not IsValidIdentifier(primaryKey) Then | |||||
| WScript.Echo "Error: Invalid primary key format. Only letters, numbers, and underscores are allowed." | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| '---------------- Config & connection ---------------- | |||||
| Dim connStr | |||||
| connStr = GetAppSetting(csKey, configPath) | |||||
| If connStr = "nothing" Then | |||||
| WScript.Echo "Error: Key '" & csKey & "' not found in " & configPath | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| Dim conn | |||||
| Set conn = CreateObject("ADODB.Connection") | |||||
| On Error Resume Next | |||||
| conn.Open connStr | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "DB connection failed: " & Err.Description | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| '---------------- Discover schema (portable) ---------------- | |||||
| ' Strategy: | |||||
| ' 1) Try OpenSchema(adSchemaColumns) for rich metadata. | |||||
| ' 2) Fallback to "SELECT * WHERE 1=0" to at least get names/types. | |||||
| Const adSchemaColumns = 4 | |||||
| Dim cols(), types(), nullable(), ordinals(), fld, idx | |||||
| Dim haveSchema : haveSchema = False | |||||
| On Error Resume Next | |||||
| Dim rsCols | |||||
| Set rsCols = conn.OpenSchema(adSchemaColumns, Array(Empty, Empty, tableName, Empty)) | |||||
| If Err.Number = 0 Then | |||||
| idx = -1 | |||||
| Do Until rsCols.EOF | |||||
| idx = idx + 1 | |||||
| ReDim Preserve cols(idx), types(idx), nullable(idx), ordinals(idx) | |||||
| cols(idx) = CStr(rsCols("COLUMN_NAME")) | |||||
| types(idx) = Nz(rsCols("DATA_TYPE"), 0) ' ADO type enum | |||||
| nullable(idx) = LCase(CStr(Nz(rsCols("IS_NULLABLE"), "YES"))) = "yes" | |||||
| ordinals(idx) = CLng(Nz(rsCols("ORDINAL_POSITION"), idx+1)) | |||||
| rsCols.MoveNext | |||||
| Loop | |||||
| rsCols.Close : Set rsCols = Nothing | |||||
| haveSchema = (idx >= 0) | |||||
| Else | |||||
| Err.Clear | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| If Not haveSchema Then | |||||
| ' Fallback: get names/types from zero-row select | |||||
| On Error Resume Next | |||||
| Dim rsProbe | |||||
| Set rsProbe = conn.Execute("SELECT * FROM " & QI(tableName) & " WHERE 1=0") | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "Error querying table [" & tableName & "]: " & Err.Description | |||||
| conn.Close : Set conn = Nothing | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| idx = -1 | |||||
| For Each fld In rsProbe.Fields | |||||
| idx = idx + 1 | |||||
| ReDim Preserve cols(idx), types(idx), nullable(idx), ordinals(idx) | |||||
| cols(idx) = fld.Name | |||||
| types(idx) = fld.Type | |||||
| nullable(idx) = True ' Unknown; assume nullable | |||||
| ordinals(idx) = idx + 1 | |||||
| Next | |||||
| rsProbe.Close : Set rsProbe = Nothing | |||||
| End If | |||||
| If idx < 0 Then | |||||
| WScript.Echo "No columns found for table [" & tableName & "]." | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Sort metadata by ordinal (defensive) | |||||
| Call ArraySortByParallel(ordinals, Array(cols, types, nullable)) | |||||
| ' Validate PK exists | |||||
| If Not InArrayInsensitive(cols, primaryKey) Then | |||||
| WScript.Echo "Primary key '" & primaryKey & "' not found in table [" & tableName & "]." | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| '---------------- Generate POBO_<Table>.asp ---------------- | |||||
| Dim poboOut | |||||
| poboOut = BuildPOBO(cols, types, tableName, primaryKey) | |||||
| Dim poboPath, tf | |||||
| poboPath = fso.BuildPath(thisFolder, "POBO_" & tableName & ".asp") | |||||
| Set tf = fso.CreateTextFile(poboPath, True, False) | |||||
| tf.Write poboOut | |||||
| tf.Close | |||||
| Set tf = Nothing | |||||
| WScript.Echo "POBO class written to: " & poboPath | |||||
| '---------------- Generate <Table>Repository.asp ---------------- | |||||
| Dim repoOut | |||||
| repoOut = BuildRepository(cols, tableName, primaryKey) | |||||
| Dim repoPath | |||||
| repoPath = fso.BuildPath(thisFolder, tableName & "Repository.asp") | |||||
| Set tf = fso.CreateTextFile(repoPath, True, False) | |||||
| tf.Write repoOut | |||||
| tf.Close | |||||
| Set tf = Nothing | |||||
| WScript.Echo "Repository written to: " & repoPath | |||||
| '---------------- Cleanup ---------------- | |||||
| conn.Close : Set conn = Nothing | |||||
| Set fso = Nothing | |||||
| '============================================================== | |||||
| ' POBO generator (your example, integrated) | |||||
| '============================================================== | |||||
| Function BuildPOBO(byRef colsArr, byRef typesArr, ByVal tName, ByVal pk) | |||||
| Dim classDef, idxLocal, name, ftype | |||||
| Dim parts() | |||||
| ReDim parts(1000) ' Pre-allocate for performance | |||||
| Dim partIdx : partIdx = 0 | |||||
| ' Build using array for performance (StringBuilder pattern) | |||||
| parts(partIdx) = "<%" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "' Auto-generated POBO for table [" & tName & "]" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "' Generated on " & Now() & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "' Generator: GenerateRepo.vbs v1.0" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "'" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "' Dependencies: core/helpers.asp (QuoteValue function)" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "Class POBO_" & tName & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " ' Public array of all property names" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " Public Properties" & vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| ' Private backing fields | |||||
| For idxLocal = 0 To UBound(colsArr) | |||||
| parts(partIdx) = " Private p" & colsArr(idxLocal) & vbCrLf : partIdx = partIdx + 1 | |||||
| Next | |||||
| ' Initializer with defaults | |||||
| parts(partIdx) = vbCrLf & " Private Sub Class_Initialize()" & vbCrLf : partIdx = partIdx + 1 | |||||
| For idxLocal = 0 To UBound(colsArr) | |||||
| Select Case typesArr(idxLocal) | |||||
| Case 200,201,202,203 ' adChar, adVarChar, adVarWChar, adWChar (strings) | |||||
| parts(partIdx) = " p" & colsArr(idxLocal) & " = """"" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case 7,133,135 ' adDate, adDBDate, adDBTimeStamp (dates) | |||||
| parts(partIdx) = " p" & colsArr(idxLocal) & " = #1/1/1970#" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case 2,3,4,5,6,14,131 ' adSmallInt, adInteger, adSingle, adDouble, adCurrency, adDecimal, adNumeric | |||||
| parts(partIdx) = " p" & colsArr(idxLocal) & " = 0" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case 11 ' adBoolean | |||||
| parts(partIdx) = " p" & colsArr(idxLocal) & " = False" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case Else | |||||
| parts(partIdx) = " p" & colsArr(idxLocal) & " = Null" & vbCrLf : partIdx = partIdx + 1 | |||||
| End Select | |||||
| Next | |||||
| parts(partIdx) = " Properties = Array(""" & Join(colsArr, """,""") & """)" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End Sub" & vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " Public Property Get PrimaryKey()" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " PrimaryKey = """ & pk & """" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End Property" & vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " Public Property Get TableName()" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " TableName = """ & tName & """" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End Property" & vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| ' Get/Let with coercion | |||||
| For idxLocal = 0 To UBound(colsArr) | |||||
| name = colsArr(idxLocal) | |||||
| ftype = typesArr(idxLocal) | |||||
| parts(partIdx) = " Public Property Get " & name & "()" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " " & name & " = p" & name & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End Property" & vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " Public Property Let " & name & "(val)" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " On Error Resume Next" & vbCrLf : partIdx = partIdx + 1 | |||||
| Select Case ftype | |||||
| Case 200,201,202,203 ' Strings | |||||
| parts(partIdx) = " p" & name & " = CStr(val)" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case 7 ' Dates | |||||
| parts(partIdx) = " p" & name & " = CDate(val)" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case 133,135 ' Timestamps | |||||
| parts(partIdx) = " p" & name & " = QuoteValue(val)" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case 11 ' Boolean | |||||
| parts(partIdx) = " p" & name & " = CBool(val)" & vbCrLf : partIdx = partIdx + 1 | |||||
| Case Else ' Numeric or other | |||||
| parts(partIdx) = " If IsNumeric(val) Then" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " p" & name & " = CDbl(val)" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " Else" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " p" & name & " = val" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End If" & vbCrLf : partIdx = partIdx + 1 | |||||
| End Select | |||||
| parts(partIdx) = " If Err.Number <> 0 Then" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " Err.Raise Err.Number, ""POBO_" & tName & "." & name & """, ""Invalid value for " & name & ": "" & Err.Description" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End If" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " On Error GoTo 0" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = " End Property" & vbCrLf & vbCrLf : partIdx = partIdx + 1 | |||||
| Next | |||||
| parts(partIdx) = "End Class" & vbCrLf : partIdx = partIdx + 1 | |||||
| parts(partIdx) = "%>" & vbCrLf : partIdx = partIdx + 1 | |||||
| ' Join array for performance | |||||
| ReDim Preserve parts(partIdx - 1) | |||||
| BuildPOBO = Join(parts, "") | |||||
| End Function | |||||
| '============================================================== | |||||
| ' Repository generator (parameterized; DAL.* expected) | |||||
| '============================================================== | |||||
| Function BuildRepository(byRef colsArr, ByVal tName, ByVal pk) | |||||
| Dim insertCols(), updateCols(), allCols(), idCol | |||||
| Dim iLocal, cName | |||||
| idCol = pk | |||||
| ' Copy columns (no array-to-array assignment in VBScript) | |||||
| ReDim allCols(UBound(colsArr)) | |||||
| For iLocal = 0 To UBound(colsArr) | |||||
| allCols(iLocal) = colsArr(iLocal) | |||||
| Next | |||||
| ' Insert/Update sets: exclude PK | |||||
| Dim insIdx : insIdx = -1 | |||||
| For Each cName In colsArr | |||||
| If Not StrEqualCI(cName, idCol) Then | |||||
| insIdx = insIdx + 1 | |||||
| ReDim Preserve insertCols(insIdx) | |||||
| insertCols(insIdx) = cName | |||||
| End If | |||||
| Next | |||||
| Dim updIdx : updIdx = -1 | |||||
| For Each cName In colsArr | |||||
| If Not StrEqualCI(cName, idCol) Then | |||||
| updIdx = updIdx + 1 | |||||
| ReDim Preserve updateCols(updIdx) | |||||
| updateCols(updIdx) = cName | |||||
| End If | |||||
| Next | |||||
| Dim QTable, QPK, selectList, selectBase | |||||
| QTable = QI(tName) | |||||
| QPK = QI(idCol) | |||||
| selectList = JoinQI(allCols, ", ") | |||||
| selectBase = "Select " & selectList & " FROM " & QTable | |||||
| Dim out | |||||
| out = out & "<%" & vbCrLf | |||||
| out = out & "' Auto-generated Repository for table [" & tName & "]" & vbCrLf | |||||
| out = out & "' Generated on " & Now() & vbCrLf | |||||
| out = out & "' Generator: GenerateRepo.vbs v1.0" & vbCrLf | |||||
| out = out & "'" & vbCrLf | |||||
| out = out & "' Dependencies:" & vbCrLf | |||||
| out = out & "' - core/lib.DAL.asp (DAL singleton for database access)" & vbCrLf | |||||
| out = out & "' - core/lib.AutoMapper.asp (Automapper for object mapping)" & vbCrLf | |||||
| out = out & "' - core/lib.Collections.asp (LinkedList_Class)" & vbCrLf | |||||
| out = out & "' - core/lib.helpers.asp (KVUnzip, BuildOrderBy, QI, Destroy)" & vbCrLf | |||||
| out = out & vbCrLf & vbCrLf | |||||
| Dim className : className = tName & "Repository_Class" | |||||
| out = out & "Class " & className & vbCrLf & vbCrLf | |||||
| ' FindByID | |||||
| out = out & " Public Function FindByID(id)" & vbCrLf | |||||
| out = out & " Dim sql : sql = """ & selectBase & " WHERE " & QPK & " = ?""" & vbCrLf | |||||
| out = out & " Dim rs : Set rs = DAL.Query(sql, Array(id))" & vbCrLf | |||||
| out = out & " If rs.EOF Then" & vbCrLf | |||||
| out = out & " Err.Raise 1, """ & className & """, RecordNotFoundException(""" & idCol & """, id)" & vbCrLf | |||||
| out = out & " Else" & vbCrLf | |||||
| out = out & " Set FindByID = Automapper.AutoMap(rs, ""POBO_" & tName & """)" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " Destroy rs" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| ' GetAll -> Find | |||||
| out = out & " Public Function GetAll(orderBy)" & vbCrLf | |||||
| out = out & " Set GetAll = Find(Empty, orderBy)" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| ' Find | |||||
| out = out & " Public Function Find(where_kvarray, order_string_or_array)" & vbCrLf | |||||
| out = out & " Dim sql : sql = """ & selectBase & """" & vbCrLf | |||||
| out = out & " Dim where_keys, where_values, i" & vbCrLf | |||||
| out = out & " If Not IsEmpty(where_kvarray) Then" & vbCrLf | |||||
| out = out & " KVUnzip where_kvarray, where_keys, where_values" & vbCrLf | |||||
| out = out & " If Not IsEmpty(where_keys) Then" & vbCrLf | |||||
| out = out & " sql = sql & "" WHERE """ & vbCrLf | |||||
| out = out & " For i = 0 To UBound(where_keys)" & vbCrLf | |||||
| out = out & " If i > 0 Then sql = sql & "" AND """ & vbCrLf | |||||
| out = out & " sql = sql & "" "" & QI(where_keys(i)) & "" = ?""" & vbCrLf | |||||
| out = out & " Next" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " sql = sql & BuildOrderBy(order_string_or_array, """ & QPK & """)" & vbCrLf | |||||
| out = out & " Dim rs : Set rs = DAL.Query(sql, where_values)" & vbCrLf | |||||
| out = out & " Dim list : Set list = new LinkedList_Class" & vbCrLf | |||||
| out = out & " Do Until rs.EOF" & vbCrLf | |||||
| out = out & " list.Push Automapper.AutoMap(rs, ""POBO_" & tName & """)" & vbCrLf | |||||
| out = out & " rs.MoveNext" & vbCrLf | |||||
| out = out & " Loop" & vbCrLf | |||||
| out = out & " Set Find = list" & vbCrLf | |||||
| out = out & " Destroy rs" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| ' FindPaged | |||||
| out = out & " Public Function FindPaged(where_kvarray, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count)" & vbCrLf | |||||
| out = out & " Dim sql : sql = """ & selectBase & """" & vbCrLf | |||||
| out = out & " Dim where_keys, where_values, i" & vbCrLf | |||||
| out = out & " If Not IsEmpty(where_kvarray) Then" & vbCrLf | |||||
| out = out & " KVUnzip where_kvarray, where_keys, where_values" & vbCrLf | |||||
| out = out & " If Not IsEmpty(where_keys) Then" & vbCrLf | |||||
| out = out & " sql = sql & "" WHERE """ & vbCrLf | |||||
| out = out & " For i = 0 To UBound(where_keys)" & vbCrLf | |||||
| out = out & " If i > 0 Then sql = sql & "" AND """ & vbCrLf | |||||
| out = out & " sql = sql & "" "" & QI(where_keys(i)) & "" = ?""" & vbCrLf | |||||
| out = out & " Next" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " sql = sql & BuildOrderBy(order_string_or_array, """ & QPK & """)" & vbCrLf | |||||
| out = out & " Dim rs : Set rs = DAL.PagedQuery(sql, where_values, per_page, page_num)" & vbCrLf | |||||
| out = out & " If Not rs.EOF Then" & vbCrLf | |||||
| out = out & " rs.PageSize = per_page" & vbCrLf | |||||
| out = out & " rs.AbsolutePage = page_num" & vbCrLf | |||||
| out = out & " page_count = rs.PageCount" & vbCrLf | |||||
| out = out & " record_count = rs.RecordCount" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " Set FindPaged = PagedList(rs, per_page)" & vbCrLf | |||||
| out = out & " Destroy rs" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| ' SearchTablePaged (OR col LIKE ?) | |||||
| out = out & " Public Function SearchTablePaged(columns_array, search_value, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count)" & vbCrLf | |||||
| out = out & " Dim sql : sql = """ & selectBase & """" & vbCrLf | |||||
| out = out & " Dim i, params()" & vbCrLf | |||||
| out = out & " If IsArray(columns_array) And UBound(columns_array) >= 0 Then" & vbCrLf | |||||
| out = out & " sql = sql & "" WHERE """ & vbCrLf | |||||
| out = out & " ReDim params(UBound(columns_array))" & vbCrLf | |||||
| out = out & " For i = 0 To UBound(columns_array)" & vbCrLf | |||||
| out = out & " If i > 0 Then sql = sql & "" OR """ & vbCrLf | |||||
| out = out & " sql = sql & "" "" & QI(columns_array(i)) & "" LIKE ?""" & vbCrLf | |||||
| out = out & " params(i) = ""%"" & search_value & ""%""" & vbCrLf | |||||
| out = out & " Next" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " sql = sql & BuildOrderBy(order_string_or_array, """ & QPK & """)" & vbCrLf | |||||
| out = out & " Dim rs : Set rs = DAL.PagedQuery(sql, params, per_page, page_num)" & vbCrLf | |||||
| out = out & " If Not rs.EOF Then" & vbCrLf | |||||
| out = out & " rs.PageSize = per_page" & vbCrLf | |||||
| out = out & " rs.AbsolutePage = page_num" & vbCrLf | |||||
| out = out & " page_count = rs.PageCount" & vbCrLf | |||||
| out = out & " record_count = rs.RecordCount" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " Set SearchTablePaged = PagedList(rs, per_page)" & vbCrLf | |||||
| out = out & " Destroy rs" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| ' PagedList helper | |||||
| out = out & " Private Function PagedList(rs, per_page)" & vbCrLf | |||||
| out = out & " Dim list : Set list = new LinkedList_Class" & vbCrLf | |||||
| out = out & " Dim x : x = 0" & vbCrLf | |||||
| out = out & " Do While (per_page <= 0 Or x < per_page) And Not rs.EOF" & vbCrLf | |||||
| out = out & " list.Push Automapper.AutoMap(rs, ""POBO_" & tName & """)" & vbCrLf | |||||
| out = out & " x = x + 1" & vbCrLf | |||||
| out = out & " rs.MoveNext" & vbCrLf | |||||
| out = out & " Loop" & vbCrLf | |||||
| out = out & " Set PagedList = list" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| ' AddNew | |||||
| out = out & " Public Sub AddNew(ByRef model)" & vbCrLf | |||||
| out = out & " Dim sql : sql = ""INSERT INTO " & QTable & " (" & JoinQI(insertCols, ", ") & ") VALUES (" & Placeholders(UBound(insertCols)+1) & ")""" & vbCrLf | |||||
| out = out & " DAL.Execute sql, " & BuildModelParamsArray("model", insertCols) & vbCrLf | |||||
| out = out & " " & vbCrLf | |||||
| out = out & " ' Retrieve the newly inserted ID" & vbCrLf | |||||
| out = out & " On Error Resume Next" & vbCrLf | |||||
| out = out & " Dim rsId : Set rsId = DAL.Query(""SELECT @@IDENTITY AS NewID"", Empty)" & vbCrLf | |||||
| out = out & " If Err.Number <> 0 Then" & vbCrLf | |||||
| out = out & " ' Fallback for Access databases" & vbCrLf | |||||
| out = out & " Err.Clear" & vbCrLf | |||||
| out = out & " Set rsId = DAL.Query(""SELECT TOP 1 " & QPK & " FROM " & QTable & " ORDER BY " & QPK & " DESC"", Empty)" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " On Error GoTo 0" & vbCrLf | |||||
| out = out & " " & vbCrLf | |||||
| out = out & " If Not rsId.EOF Then" & vbCrLf | |||||
| out = out & " If Not IsNull(rsId(0)) Then model." & idCol & " = rsId(0)" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " Destroy rsId" & vbCrLf | |||||
| out = out & " End Sub" & vbCrLf & vbCrLf | |||||
| ' Update | |||||
| out = out & " Public Sub Update(model)" & vbCrLf | |||||
| out = out & " Dim sql : sql = ""UPDATE " & QTable & " SET " & JoinSetters(updateCols) & " WHERE " & QPK & " = ?""" & vbCrLf | |||||
| out = out & " DAL.Execute sql, " & BuildModelParamsArrayWithPK("model", updateCols, idCol) & vbCrLf | |||||
| out = out & " End Sub" & vbCrLf & vbCrLf | |||||
| ' Delete | |||||
| out = out & " Public Sub Delete(id)" & vbCrLf | |||||
| out = out & " Dim sql : sql = ""DELETE FROM " & QTable & " WHERE " & QPK & " = ?""" & vbCrLf | |||||
| out = out & " DAL.Execute sql, Array(id)" & vbCrLf | |||||
| out = out & " End Sub" & vbCrLf & vbCrLf | |||||
| ' Exceptions & helpers | |||||
| out = out & " Private Function RecordNotFoundException(ByVal field_name, ByVal field_val)" & vbCrLf | |||||
| out = out & " RecordNotFoundException = """ & tName & " record was not found with "" & field_name & "" = '"" & field_val & ""'.""" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| out = out & " Private Function QI(name)" & vbCrLf | |||||
| out = out & " QI = ""["" & Replace(CStr(name), ""]"", ""]]"") & ""]""" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf & vbCrLf | |||||
| out = out & " Private Function BuildOrderBy(orderArg, defaultCol)" & vbCrLf | |||||
| out = out & " Dim s : s = """"" & vbCrLf | |||||
| out = out & " If IsEmpty(orderArg) Or IsNull(orderArg) Or orderArg = """" Then" & vbCrLf | |||||
| out = out & " s = "" ORDER BY "" & defaultCol & "" ASC""" & vbCrLf | |||||
| out = out & " ElseIf IsArray(orderArg) Then" & vbCrLf | |||||
| out = out & " Dim i : s = "" ORDER BY """ & vbCrLf | |||||
| out = out & " For i = 0 To UBound(orderArg)" & vbCrLf | |||||
| out = out & " If i > 0 Then s = s & "", """ & vbCrLf | |||||
| out = out & " s = s & QI(orderArg(i))" & vbCrLf | |||||
| out = out & " Next" & vbCrLf | |||||
| out = out & " Else" & vbCrLf | |||||
| out = out & " s = "" ORDER BY "" & QI(orderArg)" & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " BuildOrderBy = s" & vbCrLf | |||||
| out = out & " End Function" & vbCrLf | |||||
| out = out & "End Class" & vbCrLf & vbCrLf | |||||
| out = out & "Dim " & tName & "Repository__Singleton" & vbCrLf | |||||
| out = out & "Function " & tName & "Repository()" & vbCrLf | |||||
| out = out & " If IsEmpty(" & tName & "Repository__Singleton) Then" & vbCrLf | |||||
| out = out & " Set " & tName & "Repository__Singleton = new " & className & vbCrLf | |||||
| out = out & " End If" & vbCrLf | |||||
| out = out & " Set " & tName & "Repository = " & tName & "Repository__Singleton" & vbCrLf | |||||
| out = out & "End Function" & vbCrLf | |||||
| out = out & "%>" & vbCrLf | |||||
| BuildRepository = out | |||||
| End Function | |||||
| '============================================================== | |||||
| ' Helpers (generator side) | |||||
| '============================================================== | |||||
| Function GetAppSetting(key, configFilePath) | |||||
| Dim xml, nodes, node, j, localFso | |||||
| Set localFso = CreateObject("Scripting.FileSystemObject") | |||||
| Set xml = CreateObject("Microsoft.XMLDOM") | |||||
| If Not localFso.FileExists(configFilePath) Then | |||||
| GetAppSetting = "nothing" : Exit Function | |||||
| End If | |||||
| xml.Async = False | |||||
| xml.Load(configFilePath) | |||||
| If xml.ParseError.ErrorCode <> 0 Then | |||||
| GetAppSetting = "nothing" : Exit Function | |||||
| End If | |||||
| Set nodes = xml.selectNodes("//appSettings/add") | |||||
| For j = 0 To nodes.Length - 1 | |||||
| Set node = nodes.Item(j) | |||||
| If node.getAttribute("key") = key Then | |||||
| GetAppSetting = node.getAttribute("value") | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| GetAppSetting = "nothing" | |||||
| End Function | |||||
| Function Nz(val, defaultVal) | |||||
| If IsNull(val) Or IsEmpty(val) Then | |||||
| Nz = defaultVal | |||||
| Else | |||||
| Nz = val | |||||
| End If | |||||
| End Function | |||||
| Sub ArraySortByParallel(keys, arrs) | |||||
| Dim iLocal, jLocal, kLocal, tmpKey, tmp | |||||
| For iLocal = 0 To UBound(keys) - 1 | |||||
| For jLocal = iLocal + 1 To UBound(keys) | |||||
| If keys(jLocal) < keys(iLocal) Then | |||||
| tmpKey = keys(iLocal) : keys(iLocal) = keys(jLocal) : keys(jLocal) = tmpKey | |||||
| For kLocal = 0 To UBound(arrs) | |||||
| tmp = arrs(kLocal)(iLocal) | |||||
| arrs(kLocal)(iLocal) = arrs(kLocal)(jLocal) | |||||
| arrs(kLocal)(jLocal) = tmp | |||||
| Next | |||||
| End If | |||||
| Next | |||||
| Next | |||||
| End Sub | |||||
| Function InArrayInsensitive(a, val) | |||||
| Dim z | |||||
| InArrayInsensitive = False | |||||
| For z = 0 To UBound(a) | |||||
| If StrEqualCI(a(z), val) Then InArrayInsensitive = True : Exit Function | |||||
| Next | |||||
| End Function | |||||
| Function StrEqualCI(a, b) | |||||
| StrEqualCI = (LCase(CStr(a)) = LCase(CStr(b))) | |||||
| End Function | |||||
| Function QI(name) | |||||
| QI = "[" & Replace(CStr(name), "]", "]]") & "]" | |||||
| End Function | |||||
| Function JoinQI(a, sep) | |||||
| Dim j, s : s = "" | |||||
| For j = 0 To UBound(a) | |||||
| If j > 0 Then s = s & sep | |||||
| s = s & QI(a(j)) | |||||
| Next | |||||
| JoinQI = s | |||||
| End Function | |||||
| Function Placeholders(n) | |||||
| Dim j, s : s = "" | |||||
| For j = 1 To n | |||||
| If j > 1 Then s = s & ", " | |||||
| s = s & "?" | |||||
| Next | |||||
| Placeholders = s | |||||
| End Function | |||||
| Function JoinSetters(a) | |||||
| Dim j, s : s = "" | |||||
| For j = 0 To UBound(a) | |||||
| If j > 0 Then s = s & ", " | |||||
| s = s & QI(a(j)) & " = ?" | |||||
| Next | |||||
| JoinSetters = s | |||||
| End Function | |||||
| Function BuildModelParamsArray(modelName, a) | |||||
| Dim j, s : s = "Array(" | |||||
| For j = 0 To UBound(a) | |||||
| If j > 0 Then s = s & ", " | |||||
| s = s & modelName & "." & a(j) | |||||
| Next | |||||
| s = s & ")" | |||||
| BuildModelParamsArray = s | |||||
| End Function | |||||
| Function BuildModelParamsArrayWithPK(modelName, a, pk) | |||||
| Dim j, s : s = "Array(" | |||||
| For j = 0 To UBound(a) | |||||
| If j > 0 Then s = s & ", " | |||||
| s = s & modelName & "." & a(j) | |||||
| Next | |||||
| s = s & ", " & modelName & "." & pk & ")" | |||||
| BuildModelParamsArrayWithPK = s | |||||
| End Function | |||||
| ' Validate identifier (table name, column name, etc.) | |||||
| Function IsValidIdentifier(name) | |||||
| If IsEmpty(name) Or Len(name) = 0 Then | |||||
| IsValidIdentifier = False | |||||
| Exit Function | |||||
| End If | |||||
| Dim i, ch | |||||
| For i = 1 To Len(name) | |||||
| ch = Mid(name, i, 1) | |||||
| ' Allow a-z, A-Z, 0-9, and underscore only | |||||
| If Not ((ch >= "a" And ch <= "z") Or _ | |||||
| (ch >= "A" And ch <= "Z") Or _ | |||||
| (ch >= "0" And ch <= "9") Or _ | |||||
| ch = "_") Then | |||||
| IsValidIdentifier = False | |||||
| Exit Function | |||||
| End If | |||||
| Next | |||||
| IsValidIdentifier = True | |||||
| End Function | |||||
| @@ -0,0 +1,162 @@ | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION GENERATOR | |||||
| '======================================================================================================================= | |||||
| ' Generates a new migration file with timestamp-based versioning. | |||||
| ' | |||||
| ' Usage: | |||||
| ' cscript //nologo scripts\generateMigration.vbs migration_name | |||||
| ' | |||||
| ' Example: | |||||
| ' cscript //nologo scripts\generateMigration.vbs create_users_table | |||||
| ' Creates: db/migrations/20260109153045_create_users_table.asp | |||||
| ' | |||||
| Option Explicit | |||||
| Dim fso, migrationName, timestamp, filename, filepath, content | |||||
| Set fso = CreateObject("Scripting.FileSystemObject") | |||||
| ' Check arguments | |||||
| If WScript.Arguments.Count < 1 Then | |||||
| WScript.Echo "Usage: cscript //nologo scripts\generateMigration.vbs migration_name" | |||||
| WScript.Echo "" | |||||
| WScript.Echo "Example:" | |||||
| WScript.Echo " cscript //nologo scripts\generateMigration.vbs create_users_table" | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| migrationName = WScript.Arguments(0) | |||||
| ' Validate migration name (alphanumeric and underscore only) | |||||
| If Not IsValidMigrationName(migrationName) Then | |||||
| WScript.Echo "Error: Migration name must contain only letters, numbers, and underscores" | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Generate timestamp (YYYYMMDDHHMMSS) | |||||
| timestamp = GetTimestamp() | |||||
| ' Create filename | |||||
| filename = timestamp & "_" & migrationName & ".asp" | |||||
| ' Ensure migrations directory exists | |||||
| Dim migrationsDir | |||||
| migrationsDir = fso.GetParentFolderName(WScript.ScriptFullName) & "\..\db\migrations" | |||||
| migrationsDir = fso.GetAbsolutePathName(migrationsDir) | |||||
| If Not fso.FolderExists(migrationsDir) Then | |||||
| CreateDirectoryPath migrationsDir | |||||
| End If | |||||
| filepath = migrationsDir & "\" & filename | |||||
| ' Check if file already exists | |||||
| If fso.FileExists(filepath) Then | |||||
| WScript.Echo "Error: Migration file already exists: " & filename | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Generate migration content | |||||
| content = GenerateMigrationContent(migrationName) | |||||
| ' Write the file | |||||
| Dim file | |||||
| Set file = fso.CreateTextFile(filepath, True) | |||||
| file.Write content | |||||
| file.Close | |||||
| WScript.Echo "Migration created: " & filename | |||||
| WScript.Echo "Path: " & filepath | |||||
| WScript.Echo "" | |||||
| WScript.Echo "Edit the file to add your migration logic, then run:" | |||||
| WScript.Echo " cscript //nologo scripts\runMigrations.vbs" | |||||
| WScript.Quit 0 | |||||
| '======================================================================================================================= | |||||
| ' HELPER FUNCTIONS | |||||
| '======================================================================================================================= | |||||
| Function GetTimestamp() | |||||
| Dim dtNow, sYear, sMonth, sDay, sHour, sMinute, sSecond | |||||
| dtNow = Now() | |||||
| sYear = Year(dtNow) | |||||
| sMonth = Right("0" & Month(dtNow), 2) | |||||
| sDay = Right("0" & Day(dtNow), 2) | |||||
| sHour = Right("0" & Hour(dtNow), 2) | |||||
| sMinute = Right("0" & Minute(dtNow), 2) | |||||
| sSecond = Right("0" & Second(dtNow), 2) | |||||
| GetTimestamp = sYear & sMonth & sDay & sHour & sMinute & sSecond | |||||
| End Function | |||||
| Function IsValidMigrationName(name) | |||||
| Dim regex | |||||
| Set regex = New RegExp | |||||
| regex.Pattern = "^[a-zA-Z0-9_]+$" | |||||
| IsValidMigrationName = regex.Test(name) | |||||
| End Function | |||||
| Function GenerateMigrationContent(name) | |||||
| Dim template | |||||
| template = "<%"& vbCrLf | |||||
| template = template & "'======================================================================================================================="& vbCrLf | |||||
| template = template & "' MIGRATION: " & name & vbCrLf | |||||
| template = template & "'======================================================================================================================="& vbCrLf | |||||
| template = template & "' This migration was auto-generated. Add your migration logic below."& vbCrLf | |||||
| template = template & "'"& vbCrLf | |||||
| template = template & "' The migration object provides these helper methods:"& vbCrLf | |||||
| template = template & "' - migration.ExecuteSQL(sql) - Execute raw SQL"& vbCrLf | |||||
| template = template & "' - migration.CreateTable(name, columns) - Create a table"& vbCrLf | |||||
| template = template & "' - migration.DropTable(name) - Drop a table"& vbCrLf | |||||
| template = template & "' - migration.AddColumn(table, column, type) - Add a column"& vbCrLf | |||||
| template = template & "' - migration.DropColumn(table, column) - Drop a column"& vbCrLf | |||||
| template = template & "' - migration.CreateIndex(name, table, columns) - Create an index"& vbCrLf | |||||
| template = template & "' - migration.DropIndex(name, table) - Drop an index"& vbCrLf | |||||
| template = template & "'"& vbCrLf | |||||
| template = template & "' For complex operations, use migration.DB to access the database directly:"& vbCrLf | |||||
| template = template & "' migration.DB.Execute ""INSERT INTO users (name) VALUES (?)"", Array(""John"")"& vbCrLf | |||||
| template = template & "'"& vbCrLf | |||||
| template = template & ""& vbCrLf | |||||
| template = template & "'-----------------------------------------------------------------------------------------------------------------------"& vbCrLf | |||||
| template = template & "' UP - Apply the migration"& vbCrLf | |||||
| template = template & "'-----------------------------------------------------------------------------------------------------------------------"& vbCrLf | |||||
| template = template & "Sub Migration_Up(migration)"& vbCrLf | |||||
| template = template & " ' Example: Create a table"& vbCrLf | |||||
| template = template & " ' migration.CreateTable ""users"", ""id AUTOINCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255), created_at DATETIME"""& vbCrLf | |||||
| template = template & " "& vbCrLf | |||||
| template = template & " ' Example: Add an index"& vbCrLf | |||||
| template = template & " ' migration.CreateIndex ""idx_users_email"", ""users"", ""email"""& vbCrLf | |||||
| template = template & " "& vbCrLf | |||||
| template = template & " ' TODO: Add your migration logic here"& vbCrLf | |||||
| template = template & " "& vbCrLf | |||||
| template = template & "End Sub"& vbCrLf | |||||
| template = template & ""& vbCrLf | |||||
| template = template & "'-----------------------------------------------------------------------------------------------------------------------"& vbCrLf | |||||
| template = template & "' DOWN - Rollback the migration"& vbCrLf | |||||
| template = template & "'-----------------------------------------------------------------------------------------------------------------------"& vbCrLf | |||||
| template = template & "Sub Migration_Down(migration)"& vbCrLf | |||||
| template = template & " ' Example: Drop the table"& vbCrLf | |||||
| template = template & " ' migration.DropTable ""users"""& vbCrLf | |||||
| template = template & " "& vbCrLf | |||||
| template = template & " ' TODO: Add your rollback logic here (reverse the Up migration)"& vbCrLf | |||||
| template = template & " "& vbCrLf | |||||
| template = template & "End Sub"& vbCrLf | |||||
| template = template & "%>"& vbCrLf | |||||
| GenerateMigrationContent = template | |||||
| End Function | |||||
| Sub CreateDirectoryPath(path) | |||||
| Dim parentPath | |||||
| If Not fso.FolderExists(path) Then | |||||
| parentPath = fso.GetParentFolderName(path) | |||||
| If parentPath <> "" Then | |||||
| CreateDirectoryPath parentPath | |||||
| End If | |||||
| fso.CreateFolder path | |||||
| End If | |||||
| End Sub | |||||
| @@ -0,0 +1,614 @@ | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION RUNNER (Standalone VBScript) | |||||
| '======================================================================================================================= | |||||
| ' Runs database migrations directly via VBScript without requiring IIS/ASP. | |||||
| ' | |||||
| ' Usage: | |||||
| ' cscript //nologo scripts\runMigrations.vbs [command] | |||||
| ' | |||||
| ' Commands: | |||||
| ' up - Apply all pending migrations (default) | |||||
| ' down - Rollback the last migration | |||||
| ' status - Show migration status | |||||
| ' apply <file> - Apply a specific migration file | |||||
| ' rollback <file> - Rollback a specific migration file | |||||
| ' | |||||
| ' Examples: | |||||
| ' cscript //nologo scripts\runMigrations.vbs | |||||
| ' cscript //nologo scripts\runMigrations.vbs up | |||||
| ' cscript //nologo scripts\runMigrations.vbs down | |||||
| ' cscript //nologo scripts\runMigrations.vbs status | |||||
| ' | |||||
| Option Explicit | |||||
| Dim fso, scriptDir, projectRoot, webConfigPath, migrationsPath | |||||
| Dim connectionString, command, argument | |||||
| Set fso = CreateObject("Scripting.FileSystemObject") | |||||
| ' Get paths | |||||
| scriptDir = fso.GetParentFolderName(WScript.ScriptFullName) | |||||
| projectRoot = fso.GetAbsolutePathName(scriptDir & "\..") | |||||
| webConfigPath = projectRoot & "\public\web.config" | |||||
| migrationsPath = projectRoot & "\db\migrations" | |||||
| ' Parse arguments | |||||
| command = "up" ' default command | |||||
| If WScript.Arguments.Count > 0 Then | |||||
| command = LCase(WScript.Arguments(0)) | |||||
| End If | |||||
| If WScript.Arguments.Count > 1 Then | |||||
| argument = WScript.Arguments(1) | |||||
| End If | |||||
| ' Validate command | |||||
| Select Case command | |||||
| Case "up", "down", "status", "apply", "rollback" | |||||
| ' Valid command | |||||
| Case Else | |||||
| ShowUsage | |||||
| WScript.Quit 1 | |||||
| End Select | |||||
| ' Load connection string from web.config | |||||
| connectionString = GetConnectionString(webConfigPath) | |||||
| If connectionString = "" Then | |||||
| WScript.Echo "Error: Could not read connection string from web.config" | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Execute the command | |||||
| On Error Resume Next | |||||
| Select Case command | |||||
| Case "up" | |||||
| ApplyAllPending | |||||
| Case "down" | |||||
| RollbackLast | |||||
| Case "status" | |||||
| ShowStatus | |||||
| Case "apply" | |||||
| If argument = "" Then | |||||
| WScript.Echo "Error: No migration file specified" | |||||
| WScript.Echo "Usage: cscript //nologo scripts\runMigrations.vbs apply <filename>" | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ApplyMigration argument | |||||
| Case "rollback" | |||||
| If argument = "" Then | |||||
| WScript.Echo "Error: No migration file specified" | |||||
| WScript.Echo "Usage: cscript //nologo scripts\runMigrations.vbs rollback <filename>" | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| RollbackMigration argument | |||||
| End Select | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "" | |||||
| WScript.Echo "ERROR: " & Err.Description | |||||
| WScript.Echo "Number: " & Err.Number | |||||
| WScript.Echo "Source: " & Err.Source | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| WScript.Quit 0 | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION FUNCTIONS | |||||
| '======================================================================================================================= | |||||
| Sub ApplyAllPending() | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "APPLYING PENDING MIGRATIONS" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "" | |||||
| EnsureSchemaMigrationsTable | |||||
| Dim pending, version, versions(), i, j, temp | |||||
| Set pending = GetPendingMigrations() | |||||
| If pending.Count = 0 Then | |||||
| WScript.Echo "No pending migrations." | |||||
| WScript.Echo "" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "DONE" | |||||
| WScript.Echo "==============================================================" | |||||
| Exit Sub | |||||
| End If | |||||
| ' Sort versions | |||||
| ReDim versions(pending.Count - 1) | |||||
| i = 0 | |||||
| For Each version In pending.Keys | |||||
| versions(i) = version | |||||
| i = i + 1 | |||||
| Next | |||||
| ' Simple bubble sort (string comparison works for YYYYMMDDHHMMSS format) | |||||
| For i = 0 To UBound(versions) - 1 | |||||
| For j = i + 1 To UBound(versions) | |||||
| If versions(i) > versions(j) Then | |||||
| temp = versions(i) | |||||
| versions(i) = versions(j) | |||||
| versions(j) = temp | |||||
| End If | |||||
| Next | |||||
| Next | |||||
| ' Apply in order | |||||
| For i = 0 To UBound(versions) | |||||
| ApplyMigration pending(versions(i)) | |||||
| Next | |||||
| WScript.Echo "" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "DONE" | |||||
| WScript.Echo "==============================================================" | |||||
| End Sub | |||||
| Sub RollbackLast() | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "ROLLING BACK LAST MIGRATION" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "" | |||||
| EnsureSchemaMigrationsTable | |||||
| Dim applied, available, version, lastVersion, filename | |||||
| Set applied = GetAppliedMigrations() | |||||
| If applied.Count = 0 Then | |||||
| WScript.Echo "No migrations to rollback." | |||||
| WScript.Echo "" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "DONE" | |||||
| WScript.Echo "==============================================================" | |||||
| Exit Sub | |||||
| End If | |||||
| ' Find the last version (string comparison works for YYYYMMDDHHMMSS format) | |||||
| lastVersion = "" | |||||
| For Each version In applied.Keys | |||||
| If lastVersion = "" Or version > lastVersion Then | |||||
| lastVersion = version | |||||
| End If | |||||
| Next | |||||
| ' Find the filename | |||||
| Set available = GetAvailableMigrations() | |||||
| If available.Exists(lastVersion) Then | |||||
| filename = available(lastVersion) | |||||
| RollbackMigration filename | |||||
| Else | |||||
| WScript.Echo "Error: Migration file not found for version: " & lastVersion | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| WScript.Echo "" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "DONE" | |||||
| WScript.Echo "==============================================================" | |||||
| End Sub | |||||
| Sub ShowStatus() | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "MIGRATION STATUS" | |||||
| WScript.Echo "==============================================================" | |||||
| WScript.Echo "" | |||||
| EnsureSchemaMigrationsTable | |||||
| Dim applied, pending, available, version | |||||
| Set applied = GetAppliedMigrations() | |||||
| Set pending = GetPendingMigrations() | |||||
| Set available = GetAvailableMigrations() | |||||
| WScript.Echo "Applied migrations: " & applied.Count | |||||
| If applied.Count > 0 Then | |||||
| For Each version In applied.Keys | |||||
| If available.Exists(version) Then | |||||
| WScript.Echo " [X] " & available(version) | |||||
| Else | |||||
| WScript.Echo " [X] " & version & " (file not found)" | |||||
| End If | |||||
| Next | |||||
| End If | |||||
| WScript.Echo "" | |||||
| WScript.Echo "Pending migrations: " & pending.Count | |||||
| If pending.Count > 0 Then | |||||
| For Each version In pending.Keys | |||||
| WScript.Echo " [ ] " & pending(version) | |||||
| Next | |||||
| End If | |||||
| WScript.Echo "" | |||||
| WScript.Echo "==============================================================" | |||||
| End Sub | |||||
| Sub ApplyMigration(filename) | |||||
| Dim version | |||||
| version = GetVersionFromFilename(filename) | |||||
| If version = "" Then | |||||
| Err.Raise vbObjectError + 1, "Migrator", "Invalid migration filename format: " & filename | |||||
| End If | |||||
| ' Check if already applied | |||||
| Dim applied | |||||
| Set applied = GetAppliedMigrations() | |||||
| If applied.Exists(version) Then | |||||
| WScript.Echo "Migration " & version & " already applied. Skipping." | |||||
| Exit Sub | |||||
| End If | |||||
| WScript.Echo "Applying migration: " & filename & "..." | |||||
| ' Execute the migration | |||||
| ' NOTE: Access/Jet does NOT support DDL (CREATE TABLE, etc.) inside transactions | |||||
| ' So we run without transaction wrapper and rely on error checking instead | |||||
| Dim conn, migrationSuccess | |||||
| Set conn = GetConnection() | |||||
| On Error Resume Next | |||||
| migrationSuccess = ExecuteMigrationFile(filename, "Up", conn) | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "ERROR: Migration failed - " & Err.Description | |||||
| WScript.Echo "Error Number: " & Err.Number | |||||
| conn.Close | |||||
| Err.Raise vbObjectError + 2, "Migrator", "Migration failed: " & Err.Description | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| ' Check if migration had SQL errors | |||||
| If Not migrationSuccess Then | |||||
| WScript.Echo "" | |||||
| WScript.Echo "ERROR: Migration failed due to SQL errors (see above)." | |||||
| WScript.Echo "Migration NOT recorded. Please fix the migration and try again." | |||||
| conn.Close | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Record the migration | |||||
| On Error Resume Next | |||||
| Dim cmd | |||||
| Set cmd = CreateObject("ADODB.Command") | |||||
| Set cmd.ActiveConnection = conn | |||||
| cmd.CommandText = "INSERT INTO schema_migrations (version, applied_at) VALUES (?, ?)" | |||||
| cmd.Execute , Array(version, Now()) | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "ERROR: Failed to record migration - " & Err.Description | |||||
| conn.Close | |||||
| Err.Raise vbObjectError + 3, "Migrator", "Failed to record migration: " & Err.Description | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| conn.Close | |||||
| WScript.Echo "Migration " & version & " applied successfully." | |||||
| End Sub | |||||
| Sub RollbackMigration(filename) | |||||
| Dim version | |||||
| version = GetVersionFromFilename(filename) | |||||
| If version = "" Then | |||||
| Err.Raise vbObjectError + 1, "Migrator", "Invalid migration filename format: " & filename | |||||
| End If | |||||
| ' Check if applied | |||||
| Dim applied | |||||
| Set applied = GetAppliedMigrations() | |||||
| If Not applied.Exists(version) Then | |||||
| WScript.Echo "Migration " & version & " not applied. Skipping." | |||||
| Exit Sub | |||||
| End If | |||||
| WScript.Echo "Rolling back migration: " & filename & "..." | |||||
| ' Execute the migration | |||||
| ' NOTE: Access/Jet does NOT support DDL (DROP TABLE, etc.) inside transactions | |||||
| Dim conn, rollbackSuccess | |||||
| Set conn = GetConnection() | |||||
| On Error Resume Next | |||||
| rollbackSuccess = ExecuteMigrationFile(filename, "Down", conn) | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "ERROR: Rollback failed - " & Err.Description | |||||
| WScript.Echo "Error Number: " & Err.Number | |||||
| conn.Close | |||||
| Err.Raise vbObjectError + 4, "Migrator", "Rollback failed: " & Err.Description | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| ' Check if rollback had SQL errors | |||||
| If Not rollbackSuccess Then | |||||
| WScript.Echo "" | |||||
| WScript.Echo "ERROR: Rollback failed due to SQL errors (see above)." | |||||
| WScript.Echo "Migration record NOT removed. Please fix the issue manually." | |||||
| conn.Close | |||||
| WScript.Quit 1 | |||||
| End If | |||||
| ' Remove migration record | |||||
| On Error Resume Next | |||||
| Dim cmd | |||||
| Set cmd = CreateObject("ADODB.Command") | |||||
| Set cmd.ActiveConnection = conn | |||||
| cmd.CommandText = "DELETE FROM schema_migrations WHERE version = ?" | |||||
| cmd.Execute , Array(version) | |||||
| If Err.Number <> 0 Then | |||||
| WScript.Echo "ERROR: Failed to remove migration record - " & Err.Description | |||||
| conn.Close | |||||
| Err.Raise vbObjectError + 5, "Migrator", "Failed to remove migration record: " & Err.Description | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| conn.Close | |||||
| WScript.Echo "Migration " & version & " rolled back successfully." | |||||
| End Sub | |||||
| '======================================================================================================================= | |||||
| ' HELPER FUNCTIONS | |||||
| '======================================================================================================================= | |||||
| Sub ShowUsage() | |||||
| WScript.Echo "Usage: cscript //nologo scripts\runMigrations.vbs [command]" | |||||
| WScript.Echo "" | |||||
| WScript.Echo "Commands:" | |||||
| WScript.Echo " up - Apply all pending migrations (default)" | |||||
| WScript.Echo " down - Rollback the last migration" | |||||
| WScript.Echo " status - Show migration status" | |||||
| WScript.Echo " apply <file> - Apply a specific migration file" | |||||
| WScript.Echo " rollback <file> - Rollback a specific migration file" | |||||
| WScript.Echo "" | |||||
| WScript.Echo "Examples:" | |||||
| WScript.Echo " cscript //nologo scripts\runMigrations.vbs" | |||||
| WScript.Echo " cscript //nologo scripts\runMigrations.vbs up" | |||||
| WScript.Echo " cscript //nologo scripts\runMigrations.vbs down" | |||||
| WScript.Echo " cscript //nologo scripts\runMigrations.vbs status" | |||||
| End Sub | |||||
| Function GetConnectionString(configPath) | |||||
| If Not fso.FileExists(configPath) Then | |||||
| GetConnectionString = "" | |||||
| Exit Function | |||||
| End If | |||||
| Dim xmlDoc, node | |||||
| Set xmlDoc = CreateObject("Microsoft.XMLDOM") | |||||
| xmlDoc.async = False | |||||
| xmlDoc.load configPath | |||||
| Set node = xmlDoc.selectSingleNode("//appSettings/add[@key='ConnectionString']/@value") | |||||
| If node Is Nothing Then | |||||
| GetConnectionString = "" | |||||
| Else | |||||
| GetConnectionString = node.text | |||||
| End If | |||||
| End Function | |||||
| Function GetConnection() | |||||
| Dim conn | |||||
| Set conn = CreateObject("ADODB.Connection") | |||||
| conn.Open connectionString | |||||
| Set GetConnection = conn | |||||
| End Function | |||||
| Sub EnsureSchemaMigrationsTable() | |||||
| Dim conn, rs | |||||
| Set conn = GetConnection() | |||||
| On Error Resume Next | |||||
| Set rs = conn.Execute("SELECT TOP 1 version FROM schema_migrations") | |||||
| If Err.Number <> 0 Then | |||||
| ' Table doesn't exist, create it | |||||
| Err.Clear | |||||
| On Error GoTo 0 | |||||
| conn.Execute "CREATE TABLE schema_migrations (" & _ | |||||
| "version VARCHAR(14) PRIMARY KEY, " & _ | |||||
| "applied_at DATETIME NOT NULL)" | |||||
| Else | |||||
| If Not rs Is Nothing Then | |||||
| If Not rs.EOF Then rs.Close | |||||
| End If | |||||
| End If | |||||
| conn.Close | |||||
| On Error GoTo 0 | |||||
| End Sub | |||||
| Function GetAppliedMigrations() | |||||
| Dim conn, rs, versions, version | |||||
| Set conn = GetConnection() | |||||
| Set versions = CreateObject("Scripting.Dictionary") | |||||
| Set rs = conn.Execute("SELECT version FROM schema_migrations ORDER BY version") | |||||
| Do While Not rs.EOF | |||||
| version = Trim(rs("version")) | |||||
| versions.Add version, True | |||||
| rs.MoveNext | |||||
| Loop | |||||
| rs.Close | |||||
| conn.Close | |||||
| Set GetAppliedMigrations = versions | |||||
| End Function | |||||
| Function GetAvailableMigrations() | |||||
| Dim folder, files, file, migrations, version | |||||
| Set migrations = CreateObject("Scripting.Dictionary") | |||||
| If Not fso.FolderExists(migrationsPath) Then | |||||
| Set GetAvailableMigrations = migrations | |||||
| Exit Function | |||||
| End If | |||||
| Set folder = fso.GetFolder(migrationsPath) | |||||
| Set files = folder.Files | |||||
| For Each file In files | |||||
| If LCase(fso.GetExtensionName(file.Name)) = "asp" Then | |||||
| version = GetVersionFromFilename(file.Name) | |||||
| If version <> "" Then | |||||
| migrations.Add version, file.Name | |||||
| End If | |||||
| End If | |||||
| Next | |||||
| Set GetAvailableMigrations = migrations | |||||
| End Function | |||||
| Function GetPendingMigrations() | |||||
| Dim applied, available, pending, version | |||||
| Set applied = GetAppliedMigrations() | |||||
| Set available = GetAvailableMigrations() | |||||
| Set pending = CreateObject("Scripting.Dictionary") | |||||
| For Each version In available.Keys | |||||
| If Not applied.Exists(version) Then | |||||
| pending.Add version, available(version) | |||||
| End If | |||||
| Next | |||||
| Set GetPendingMigrations = pending | |||||
| End Function | |||||
| Function GetVersionFromFilename(filename) | |||||
| Dim parts | |||||
| parts = Split(filename, "_") | |||||
| If UBound(parts) >= 0 Then | |||||
| Dim version | |||||
| version = parts(0) | |||||
| ' Validate it's a 14-digit timestamp | |||||
| If Len(version) = 14 And IsNumeric(version) Then | |||||
| GetVersionFromFilename = version | |||||
| Exit Function | |||||
| End If | |||||
| End If | |||||
| GetVersionFromFilename = "" | |||||
| End Function | |||||
| ' Global variable to hold migration context for error checking | |||||
| Dim g_migrationContext | |||||
| Function ExecuteMigrationFile(filename, direction, conn) | |||||
| Dim migrationPath, fileContent, migration | |||||
| migrationPath = migrationsPath & "\" & filename | |||||
| If Not fso.FileExists(migrationPath) Then | |||||
| Err.Raise vbObjectError + 6, "Migrator", "Migration file not found: " & migrationPath | |||||
| End If | |||||
| ' Create migration context | |||||
| Set migration = New MigrationContext | |||||
| Set migration.Connection = conn | |||||
| Set g_migrationContext = migration ' Store globally for error checking | |||||
| ' Read and execute the migration file | |||||
| Dim stream | |||||
| Set stream = fso.OpenTextFile(migrationPath, 1) | |||||
| fileContent = stream.ReadAll | |||||
| stream.Close | |||||
| ' Remove ASP tags | |||||
| fileContent = Replace(fileContent, "<%", "") | |||||
| fileContent = Replace(fileContent, "%>", "") | |||||
| ' Execute the migration code | |||||
| ExecuteGlobal fileContent | |||||
| ' Call the appropriate method | |||||
| If direction = "Up" Then | |||||
| Migration_Up migration | |||||
| ElseIf direction = "Down" Then | |||||
| Migration_Down migration | |||||
| End If | |||||
| ' Return True if successful, False if error | |||||
| ExecuteMigrationFile = Not migration.HasError | |||||
| End Function | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION CONTEXT CLASS | |||||
| '======================================================================================================================= | |||||
| Class MigrationContext | |||||
| Public Connection | |||||
| Public HasError | |||||
| Public LastErrorMessage | |||||
| Public LastErrorNumber | |||||
| Private Sub Class_Initialize() | |||||
| HasError = False | |||||
| LastErrorMessage = "" | |||||
| LastErrorNumber = 0 | |||||
| End Sub | |||||
| Public Sub ExecuteSQL(sql) | |||||
| If HasError Then Exit Sub ' Skip if previous error occurred | |||||
| On Error Resume Next | |||||
| Connection.Execute sql | |||||
| If Err.Number <> 0 Then | |||||
| HasError = True | |||||
| LastErrorNumber = Err.Number | |||||
| LastErrorMessage = "SQL Error: " & Err.Description & " (Error " & Err.Number & ")" & vbCrLf & "SQL: " & sql | |||||
| WScript.Echo " ERROR executing SQL: " & Err.Description | |||||
| WScript.Echo " Error Number: " & Err.Number | |||||
| WScript.Echo " SQL: " & sql | |||||
| Err.Clear | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Sub | |||||
| Public Sub CreateTable(tableName, columns) | |||||
| If HasError Then Exit Sub | |||||
| WScript.Echo " Creating table: " & tableName | |||||
| ExecuteSQL "CREATE TABLE " & tableName & " (" & columns & ")" | |||||
| End Sub | |||||
| Public Sub DropTable(tableName) | |||||
| If HasError Then Exit Sub | |||||
| WScript.Echo " Dropping table: " & tableName | |||||
| ExecuteSQL "DROP TABLE " & tableName | |||||
| End Sub | |||||
| Public Sub AddColumn(tableName, columnName, columnType) | |||||
| If HasError Then Exit Sub | |||||
| WScript.Echo " Adding column: " & tableName & "." & columnName | |||||
| ExecuteSQL "ALTER TABLE " & tableName & " ADD COLUMN " & columnName & " " & columnType | |||||
| End Sub | |||||
| Public Sub DropColumn(tableName, columnName) | |||||
| If HasError Then Exit Sub | |||||
| WScript.Echo " Dropping column: " & tableName & "." & columnName | |||||
| ExecuteSQL "ALTER TABLE " & tableName & " DROP COLUMN " & columnName | |||||
| End Sub | |||||
| Public Sub CreateIndex(indexName, tableName, columns) | |||||
| If HasError Then Exit Sub | |||||
| WScript.Echo " Creating index: " & indexName & " on " & tableName | |||||
| ExecuteSQL "CREATE INDEX " & indexName & " ON " & tableName & " (" & columns & ")" | |||||
| End Sub | |||||
| Public Sub DropIndex(indexName, tableName) | |||||
| If HasError Then Exit Sub | |||||
| WScript.Echo " Dropping index: " & indexName & " on " & tableName | |||||
| ExecuteSQL "DROP INDEX " & indexName & " ON " & tableName | |||||
| End Sub | |||||
| Public Function DB() | |||||
| Set DB = Me | |||||
| End Function | |||||
| End Class | |||||
Powered by TurnKey Linux.