- Search:
-
+ Select User:
+
|
Records Per Page:
diff --git a/EditReceipts.ascx.designer.vb b/EditReceipts.ascx.designer.vb
index 1e602c2..a787a04 100755
--- a/EditReceipts.ascx.designer.vb
+++ b/EditReceipts.ascx.designer.vb
@@ -35,13 +35,13 @@ Namespace Ventrian.SubscriptionTools
Protected WithEvents lblSearch As Global.System.Web.UI.WebControls.Label
'''
- '''txtUserName control.
+ '''drpUsers control.
'''
'''
'''Auto-generated field.
'''To modify move field declaration from designer file to code-behind file.
'''
- Protected WithEvents txtUserName As Global.System.Web.UI.WebControls.TextBox
+ Protected WithEvents drpUsers As Global.System.Web.UI.WebControls.DropDownList
'''
'''btnGo control.
diff --git a/EditReceipts.ascx.vb b/EditReceipts.ascx.vb
index e3ef075..181d1e0 100755
--- a/EditReceipts.ascx.vb
+++ b/EditReceipts.ascx.vb
@@ -41,8 +41,11 @@ Namespace Ventrian.SubscriptionTools
End If
End If
- If Not (Request("UserName") Is Nothing) Then
- txtUserName.Text = Server.UrlDecode(Request("UserName").Trim())
+ If Not (Request("UserID") Is Nothing) Then
+ Dim userID As Integer = Convert.ToInt32(Request("UserID"))
+ If userID > 0 Then
+ drpUsers.SelectedValue = userID.ToString()
+ End If
End If
End Sub
@@ -66,6 +69,54 @@ Namespace Ventrian.SubscriptionTools
End Sub
+ Private Sub BindUsers()
+
+ Dim objUserController As New DotNetNuke.Entities.Users.UserController
+ Dim objUsers As ArrayList = objUserController.GetUsers(Me.PortalId)
+
+ ' Sort users by DisplayName alphabetically
+ objUsers.Sort(New UserDisplayNameComparer())
+
+ ' Add a default "All Users" option
+ Dim defaultUser As New DotNetNuke.Entities.Users.UserInfo
+ defaultUser.UserID = -1
+ defaultUser.DisplayName = "-- All Users --"
+ objUsers.Insert(0, defaultUser)
+
+ ' Set the data text field to show FirstName + LastName + / + DisplayName
+ drpUsers.DataTextField = "DisplayName"
+ drpUsers.DataValueField = "UserID"
+ drpUsers.DataSource = objUsers
+ drpUsers.DataBind()
+
+ ' Customize the display text for each user (except the default option)
+ For i As Integer = 1 To drpUsers.Items.Count - 1
+ Dim user As DotNetNuke.Entities.Users.UserInfo = CType(objUsers(i), DotNetNuke.Entities.Users.UserInfo)
+ Dim displayText As String = ""
+
+ ' Build the display text: FirstName + LastName + / + DisplayName
+ If Not String.IsNullOrEmpty(user.FirstName) AndAlso Not String.IsNullOrEmpty(user.LastName) Then
+ displayText = user.FirstName.Trim() & " " & user.LastName.Trim()
+ ElseIf Not String.IsNullOrEmpty(user.FirstName) Then
+ displayText = user.FirstName.Trim()
+ ElseIf Not String.IsNullOrEmpty(user.LastName) Then
+ displayText = user.LastName.Trim()
+ End If
+
+ ' Add the DisplayName if it exists and is different
+ If Not String.IsNullOrEmpty(user.DisplayName) AndAlso displayText <> user.DisplayName Then
+ If displayText.Length > 0 Then
+ displayText = displayText & " / " & user.DisplayName
+ Else
+ displayText = user.DisplayName
+ End If
+ End If
+
+ ' Set the display text
+ drpUsers.Items(i).Text = displayText
+ Next
+ End Sub
+
Private Sub BindReceipts()
Dim PageSize As Integer = Convert.ToInt32(drpRecordsPerPage.SelectedItem.Value)
@@ -74,7 +125,20 @@ Namespace Ventrian.SubscriptionTools
Localization.LocalizeDataGrid(grdReceipts, Me.LocalResourceFile)
- _receipts = objReceiptController.List(Me.PortalId, Me.ModuleId, Null.NullInteger, txtUserName.Text.Trim())
+ ' Get the selected user ID for filtering
+ Dim selectedUserID As Integer = Convert.ToInt32(drpUsers.SelectedValue)
+ Dim userNameFilter As String = ""
+
+ ' If a specific user is selected (not "All Users"), get their username for filtering
+ If selectedUserID > 0 Then
+ Dim objUserController As New DotNetNuke.Entities.Users.UserController
+ Dim objUser As DotNetNuke.Entities.Users.UserInfo = objUserController.GetUser(Me.PortalId, selectedUserID)
+ If objUser IsNot Nothing Then
+ userNameFilter = objUser.Username
+ End If
+ End If
+
+ _receipts = objReceiptController.List(Me.PortalId, Me.ModuleId, Null.NullInteger, userNameFilter)
Dim objPagedDataSource As New PagedDataSource
@@ -103,8 +167,8 @@ Namespace Ventrian.SubscriptionTools
ctlPagingControl.QuerystringParams = "ctl=EditReceipts&mid=" & Me.ModuleId.ToString()
- If (Request("UserName") <> "") Then
- ctlPagingControl.QuerystringParams = ctlPagingControl.QuerystringParams & "&UserName=" + Server.UrlEncode(Request("UserName"))
+ If (Request("UserID") <> "") Then
+ ctlPagingControl.QuerystringParams = ctlPagingControl.QuerystringParams & "&UserID=" + Request("UserID")
End If
If (drpRecordsPerPage.SelectedIndex <> 0) Then
@@ -155,16 +219,16 @@ Namespace Ventrian.SubscriptionTools
symbol = "$"
Case "GBP"
- symbol = "ฃ"
+ symbol = "๏ฟฝ"
Case "EUR"
- symbol = ""
+ symbol = "๏ฟฝ"
Case "CAD"
symbol = "$"
Case "JPY"
- symbol = "ฅ"
+ symbol = "๏ฟฝ"
End Select
Return symbol & objReceipt.ServiceFee.ToString("##0.00")
@@ -185,6 +249,7 @@ Namespace Ventrian.SubscriptionTools
If (Page.IsPostBack = False) Then
ReadQueryString()
BindCrumbs()
+ BindUsers()
BindReceipts()
End If
@@ -198,7 +263,7 @@ Namespace Ventrian.SubscriptionTools
Try
- Response.Redirect(EditUrl("UserName", Server.UrlEncode(txtUserName.Text.Trim()), "EditReceipts", "PageRecords=" & drpRecordsPerPage.SelectedValue), True)
+ Response.Redirect(EditUrl("UserID", drpUsers.SelectedValue, "EditReceipts", "PageRecords=" & drpRecordsPerPage.SelectedValue), True)
Catch exc As Exception 'Module failed to load
ProcessModuleLoadException(Me, exc)
@@ -206,11 +271,11 @@ Namespace Ventrian.SubscriptionTools
End Sub
- Private Sub cmdAddReceipt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdAddReceipt.Click
+ Private Sub drpUsers_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles drpUsers.SelectedIndexChanged
Try
- Response.Redirect(EditUrl("EditReceipt"), True)
+ Response.Redirect(EditUrl("UserID", drpUsers.SelectedValue, "EditReceipts"), True)
Catch exc As Exception 'Module failed to load
ProcessModuleLoadException(Me, exc)
@@ -218,11 +283,11 @@ Namespace Ventrian.SubscriptionTools
End Sub
- Private Sub cmdReturnToModule_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReturnToModule.Click
+ Private Sub cmdAddReceipt_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdAddReceipt.Click
Try
- Response.Redirect(NavigateURL(), True)
+ Response.Redirect(EditUrl("EditReceipt"), True)
Catch exc As Exception 'Module failed to load
ProcessModuleLoadException(Me, exc)
@@ -230,11 +295,11 @@ Namespace Ventrian.SubscriptionTools
End Sub
- Private Sub btnGo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGo.Click
+ Private Sub cmdReturnToModule_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReturnToModule.Click
Try
- Response.Redirect(EditUrl("UserName", Server.UrlEncode(txtUserName.Text.Trim()), "EditReceipts"), True)
+ Response.Redirect(NavigateURL(), True)
Catch exc As Exception 'Module failed to load
ProcessModuleLoadException(Me, exc)
@@ -242,6 +307,8 @@ Namespace Ventrian.SubscriptionTools
End Sub
+
+
Private Sub btnClear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClear.Click
Try
@@ -258,4 +325,6 @@ Namespace Ventrian.SubscriptionTools
End Class
+
+
End Namespace
\ No newline at end of file
diff --git a/Entities/ContentController.vb b/Entities/ContentController.vb
index 83be1db..269af96 100755
--- a/Entities/ContentController.vb
+++ b/Entities/ContentController.vb
@@ -14,7 +14,8 @@ Namespace Ventrian.SubscriptionTools.Entities
Public Function [Get](ByVal moduleID As Integer, ByVal settingName As String) As ContentInfo
- Return CType(CBO.FillObject(DataProvider.Instance().GetContent(moduleID, settingName), GetType(ContentInfo)), ContentInfo)
+ 'Return CType(CBO.FillObject(DataProvider.Instance().GetContent(moduleID, settingName), GetType(ContentInfo)), ContentInfo)
+ Return CBO.FillObject(Of ContentInfo)(DataProvider.Instance().GetContent(moduleID, settingName))
End Function
diff --git a/Entities/OrderController.vb b/Entities/OrderController.vb
index a39d639..dad59a7 100755
--- a/Entities/OrderController.vb
+++ b/Entities/OrderController.vb
@@ -14,7 +14,8 @@ Namespace Ventrian.SubscriptionTools.Entities
Public Function [Get](ByVal orderID As Integer) As OrderInfo
- Return CType(CBO.FillObject(DataProvider.Instance().GetOrder(orderID), GetType(OrderInfo)), OrderInfo)
+ 'Return CType(CBO.FillObject(DataProvider.Instance().GetOrder(orderID), GetType(OrderInfo)), OrderInfo)
+ Return CBO.FillObject(Of OrderInfo)(DataProvider.Instance().GetOrder(orderID))
End Function
diff --git a/Entities/PlanController.vb b/Entities/PlanController.vb
index 2f87bd2..2111786 100755
--- a/Entities/PlanController.vb
+++ b/Entities/PlanController.vb
@@ -26,7 +26,8 @@ Namespace Ventrian.SubscriptionTools.Entities
Public Function [Get](ByVal planID As Integer) As PlanInfo
- Return CType(CBO.FillObject(DataProvider.Instance().GetPlan(planID), GetType(PlanInfo)), PlanInfo)
+ 'Return CType(CBO.FillObject(DataProvider.Instance().GetPlan(planID), GetType(PlanInfo)), PlanInfo)
+ Return CBO.FillObject(Of PlanInfo)(DataProvider.Instance().GetPlan(planID))
End Function
diff --git a/Entities/ReceiptController.vb b/Entities/ReceiptController.vb
index c9bea61..7e3625a 100755
--- a/Entities/ReceiptController.vb
+++ b/Entities/ReceiptController.vb
@@ -20,7 +20,8 @@ Namespace Ventrian.SubscriptionTools.Entities
Public Function [Get](ByVal receiptID As Integer) As ReceiptInfo
- Return CType(CBO.FillObject(DataProvider.Instance().GetReceipt(receiptID), GetType(ReceiptInfo)), ReceiptInfo)
+ 'Return CType(CBO.FillObject(DataProvider.Instance().GetReceipt(receiptID), GetType(ReceiptInfo)), ReceiptInfo)
+ Return CBO.FillObject(Of ReceiptInfo)(DataProvider.Instance().GetReceipt(receiptID))
End Function
diff --git a/Entities/ReminderJob.vb b/Entities/ReminderJob.vb
index 5821d3d..887bf06 100755
--- a/Entities/ReminderJob.vb
+++ b/Entities/ReminderJob.vb
@@ -53,9 +53,10 @@ Namespace Ventrian.SubscriptionTools.Entities
Dim portalID As Integer = Convert.ToInt32(Me.ScheduleHistoryItem.GetSetting("PortalID"))
Dim moduleID As Integer = Convert.ToInt32(Me.ScheduleHistoryItem.GetSetting("ModuleID"))
- Dim objModuleController As New ModuleController
+ 'Dim objModuleController As New ModuleController
- Dim settings As Hashtable = objModuleController.GetModuleSettings(moduleID)
+ 'Dim settings As Hashtable = objModuleController.GetModuleSettings(moduleID)
+ Dim settings As Hashtable = ModuleController.Instance.GetModule(moduleID, -1, False).ModuleSettings
Dim period As Integer = Null.NullInteger
If (settings.Contains(Constants.REMINDER_PERIOD)) Then
diff --git a/Entities/UserSettingController.vb b/Entities/UserSettingController.vb
index 7e95dc3..90afb5d 100755
--- a/Entities/UserSettingController.vb
+++ b/Entities/UserSettingController.vb
@@ -14,7 +14,8 @@ Namespace Ventrian.SubscriptionTools.Entities
Public Function [Get](ByVal userID As Integer, ByVal settingName As String) As UserSettingInfo
- Return CType(CBO.FillObject(DataProvider.Instance().GetUserSetting(userID, settingName), GetType(UserSettingInfo)), UserSettingInfo)
+ 'Return CType(CBO.FillObject(DataProvider.Instance().GetUserSetting(userID, settingName), GetType(UserSettingInfo)), UserSettingInfo)
+ Return CBO.FillObject(Of UserSettingInfo)(DataProvider.Instance().GetUserSetting(userID, settingName))
End Function
diff --git a/Libraries/DotNetNuke.dll b/Libraries/DotNetNuke.dll
new file mode 100644
index 0000000..91633a7
Binary files /dev/null and b/Libraries/DotNetNuke.dll differ
diff --git a/My Project/Application.Designer.vb b/My Project/Application.Designer.vb
index a17403f..db8b8b3 100755
--- a/My Project/Application.Designer.vb
+++ b/My Project/Application.Designer.vb
@@ -1,7 +1,7 @@
๏ปฟ'------------------------------------------------------------------------------
'
' This code was generated by a tool.
-' Runtime Version:2.0.50727.1434
+' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
diff --git a/My Project/AssemblyInfo.vb b/My Project/AssemblyInfo.vb
index cceb191..7e2c03f 100755
--- a/My Project/AssemblyInfo.vb
+++ b/My Project/AssemblyInfo.vb
@@ -17,7 +17,7 @@ Imports System.Runtime.InteropServices
'The following GUID is for the ID of the typelib if this project is exposed to COM
-
+
' Version information for an assembly consists of the following four values:
'
@@ -30,5 +30,5 @@ Imports System.Runtime.InteropServices
' by using the '*' as shown below:
'
-
-
+
+
diff --git a/My Project/Resources.Designer.vb b/My Project/Resources.Designer.vb
index 9f5ebe3..6b5d96c 100755
--- a/My Project/Resources.Designer.vb
+++ b/My Project/Resources.Designer.vb
@@ -1,7 +1,7 @@
๏ปฟ'------------------------------------------------------------------------------
'
' This code was generated by a tool.
-' Runtime Version:2.0.50727.1434
+' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
@@ -22,7 +22,7 @@ Namespace My.Resources
'''
''' A strongly-typed resource class, for looking up localized strings, etc.
'''
- _
diff --git a/My Project/Settings.Designer.vb b/My Project/Settings.Designer.vb
index ffbb3df..2c9efcf 100755
--- a/My Project/Settings.Designer.vb
+++ b/My Project/Settings.Designer.vb
@@ -1,7 +1,7 @@
๏ปฟ'------------------------------------------------------------------------------
'
' This code was generated by a tool.
-' Runtime Version:2.0.50727.1434
+' Runtime Version:4.0.30319.42000
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
@@ -15,12 +15,12 @@ Option Explicit On
Namespace My
_
Partial Friend NotInheritable Class MySettings
Inherits Global.System.Configuration.ApplicationSettingsBase
- Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings),MySettings)
+ Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings)
#Region "My.Settings Auto-Save Functionality"
#If _MyType = "WindowsForms" Then
@@ -29,7 +29,7 @@ Namespace My
Private Shared addedHandlerLockObject As New Object
_
- Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs)
+ Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs)
If My.Application.SaveMySettingsOnExit Then
My.Settings.Save()
End If
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e8f6b46
--- /dev/null
+++ b/README.md
@@ -0,0 +1,413 @@
+# Subscription Tools - DNN Module
+
+## Overview
+This is a DotNetNuke (DNN) subscription management module that provides comprehensive subscription tools including plan management, user signups, payment processing, receipt management, and reporting capabilities. The module is designed to handle subscription-based business models with flexible billing cycles and user management.
+
+## Project Information
+- **Module Name**: Subscription Tools
+- **DNN Version**: Compatible with DNN 6.x and higher
+- **Language**: VB.NET
+- **Framework**: .NET Framework
+- **Module Type**: Business/Subscription Management
+
+## Features
+
+### ๐ฏ **Core Subscription Management**
+- **Plan Management**: Create, edit, and manage subscription plans with different billing frequencies
+- **User Signups**: Streamlined user registration and subscription enrollment
+- **Payment Processing**: Integrated payment handling with IPN (Instant Payment Notification) support
+- **Receipt Management**: Comprehensive receipt tracking and management
+- **User Status**: Monitor and manage user subscription statuses
+- **Reporting**: Built-in reporting tools for subscription analytics
+
+### ๐ง **Technical Features**
+- **Multi-Frequency Billing**: Support for various billing cycles (monthly, yearly, custom)
+- **Role-Based Access**: Different user roles and permissions
+- **IPN Handler**: PayPal and other payment processor integration
+- **Database Providers**: SQL Server data providers with version management
+- **Localization**: Multi-language support with resource files
+
+## ๐ **File Structure**
+```
+Subscription-Tools/
+โโโ App_LocalResources/ # Localization resource files
+โโโ Base/ # Base module classes
+โ โโโ ModuleBase.vb
+โโโ Entities/ # Business logic and data models
+โ โโโ Constants.vb
+โ โโโ ContentController.vb
+โ โโโ ContentInfo.vb
+โ โโโ CrumbInfo.vb
+โ โโโ FrequencyType.vb
+โ โโโ OrderController.vb
+โ โโโ OrderInfo.vb
+โ โโโ OrderItemController.vb
+โ โโโ OrderItemInfo.vb
+โ โโโ PaymentType.vb
+โ โโโ PlanController.vb
+โ โโโ PlanInfo.vb
+โ โโโ ReceiptController.vb
+โ โโโ ReceiptInfo.vb
+โ โโโ ReminderJob.vb
+โ โโโ ReportController.vb
+โ โโโ ReportType.vb
+โ โโโ UserSettingController.vb
+โ โโโ UserSettingInfo.vb
+โโโ Providers/ # Data providers and SQL scripts
+โ โโโ DataProviders/
+โ โโโ DataProvider.vb
+โ โโโ SqlDataProvider/
+โ โโโ 01.01.00.SqlDataProvider
+โ โโโ 01.02.00.SqlDataProvider
+โ โโโ 01.03.00.SqlDataProvider
+โ โโโ 01.04.00.SqlDataProvider
+โ โโโ 01.05.00.SqlDataProvider
+โ โโโ Uninstall.SqlDataProvider
+โโโ Tools/ # Utility tools
+โ โโโ IPNHandler.aspx # Payment notification handler
+โ โโโ IPNHandler.aspx.vb
+โ โโโ IPNHandler.aspx.designer.vb
+โโโ *.ascx # User control files for each module view
+โโโ *.ascx.vb # Code-behind files
+โโโ *.ascx.designer.vb # Designer-generated files
+โโโ Ventrian.SubscriptionTools.sln # Solution file
+โโโ Ventrian.SubscriptionTools.vbproj # Project file
+โโโ SubscriptionTools.dnn # DNN manifest
+โโโ web.config # Configuration files
+```
+
+## ๐ **Development Setup**
+
+### **Prerequisites**
+- Visual Studio 2019 or later
+- .NET Framework 4.5 or higher
+- DNN development environment
+- SQL Server (for database operations)
+
+### **Getting Started**
+1. **Clone/Download** the module source code
+2. **Open** `Ventrian.SubscriptionTools.sln` in Visual Studio
+3. **Restore** NuGet packages if prompted
+4. **Build** the solution to ensure all dependencies are resolved
+
+### **Build Configuration**
+```powershell
+# Build the solution
+& "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe" Ventrian.SubscriptionTools.sln /p:Configuration=Release /p:Platform="Any CPU" /verbosity:minimal
+```
+
+## ๐ **Module Views & Functionality**
+
+### **1. Signup Management**
+- **Signup.ascx**: Main signup interface for new subscriptions
+- **SignupDetails.ascx**: Detailed signup information and processing
+- **SignupPlan.ascx**: Plan selection and configuration
+- **SignupRegister.ascx**: User registration during signup
+- **SignupViewOptions.ascx**: Signup display options and settings
+
+### **2. Plan Management**
+- **EditPlan.ascx**: Individual plan editing interface
+- **EditPlans.ascx**: Bulk plan management and overview
+
+### **3. Receipt Management**
+- **EditReceipt.ascx**: Individual receipt editing
+- **EditReceipts.ascx**: Bulk receipt management
+- **ViewInvoice.ascx**: Invoice viewing and management
+
+### **4. User Management**
+- **Status.ascx**: User subscription status overview
+- **StatusEditRole.ascx**: Role editing for users
+- **StatusViewOptions.ascx**: Status display configuration
+
+### **5. Administrative Tools**
+- **Reports.ascx**: Subscription and financial reporting
+- **SendReminders.ascx**: Automated reminder system for payments
+
+### **6. Payment Processing**
+- **IPNHandler.aspx**: Handles payment notifications from payment processors
+
+## ๐ง **Development Guidelines**
+
+### **Code Structure**
+- **VB.NET**: All code-behind files use VB.NET
+- **DNN Pattern**: Follows DNN module development patterns
+- **Separation of Concerns**: Business logic separated into controller classes
+- **Data Access**: Uses DNN data provider pattern for database operations
+
+### **Adding New Features**
+1. **Create User Control**: Add new `.ascx` file
+2. **Add Code-Behind**: Create corresponding `.ascx.vb` file
+3. **Update Project**: Add files to `Ventrian.SubscriptionTools.vbproj`
+4. **Register in DNN**: Update module manifest if needed
+
+### **Database Changes**
+1. **Create SQL Script**: Add new version in `SqlDataProvider` folder
+2. **Update Version**: Increment version number in script filename
+3. **Test Migration**: Verify upgrade process works correctly
+
+## ๐ **Configuration & Settings**
+
+### **Module Settings**
+- **Payment Gateway**: Configure payment processor settings
+- **Email Templates**: Customize reminder and notification emails
+- **Billing Cycles**: Set up supported billing frequencies
+- **User Roles**: Configure role-based access permissions
+
+### **DNN Integration**
+- **Portal Settings**: Module-specific portal configuration
+- **User Permissions**: Role-based access control
+- **Localization**: Multi-language support configuration
+
+## ๐ **Deployment**
+
+### **Development Deployment**
+1. **Build** the solution in Release mode
+2. **Copy the DLL**: Copy `bin\Ventrian.SubscriptionTools.dll` to your DNN development site's `bin\` directory
+3. **Copy ASCX files**: Copy only the `.ascx` user control files to your DNN development site's `DesktopModules\Ventrian.SubscriptionTools\` directory
+4. **Recycle** the application pool
+
+**Important Note**: Only copy the `.ascx` files, NOT the `.ascx.vb` or `.ascx.designer.vb` files, as these are compiled into the DLL.
+
+### **Production Deployment**
+1. **Build** the solution in Release mode
+2. **Package** the module using DNN packaging tools
+3. **Install** via DNN module installation process
+4. **Configure** module settings for production environment
+5. **Test** all functionality thoroughly
+
+### **Quick Development Deployment Commands**
+```powershell
+# Build the solution
+& "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe" Ventrian.SubscriptionTools.sln /p:Configuration=Release /p:Platform="Any CPU" /verbosity:minimal
+
+# Copy the DLL (contains all compiled code)
+Copy-Item "bin\Ventrian.SubscriptionTools.dll" "C:\inetpub\packdev\bin\"
+
+# Copy the ASCX file (contains the UI template)
+Copy-Item "EditReceipt.ascx" "C:\inetpub\packdev\DesktopModules\SubscriptionSignup\"
+
+# Recycle the app pool by touching web.config
+(Get-Item "C:\inetpub\packdev\bin\web.config").LastWriteTime = Get-Date
+```
+
+### **Actual Deployment Example (Completed)**
+The following deployment was successfully completed on August 20, 2025:
+
+```powershell
+# Navigate to project directory
+cd "C:\Users\Administrator\source\repos\Subscription-Tools"
+
+# Copy the DLL
+Copy-Item "bin\Ventrian.SubscriptionTools.dll" "C:\inetpub\packdev\bin\"
+
+# Copy the ASCX file to the SubscriptionSignup module
+Copy-Item "EditReceipt.ascx" "C:\inetpub\packdev\DesktopModules\SubscriptionSignup\"
+
+# Recycle the app pool
+(Get-Item "C:\inetpub\packdev\web.config").LastWriteTime = Get-Date
+```
+
+**Note**: The module is located in `DesktopModules\SubscriptionSignup\`, not `Ventrian.SubscriptionTools\`.
+
+### **Upgrade Process**
+1. **Backup** existing data and configuration
+2. **Install** new version via DNN
+3. **Run** database upgrade scripts automatically
+4. **Verify** all functionality works correctly
+
+## ๐งช **Testing**
+
+### **Unit Testing**
+- Test individual controller methods
+- Verify data validation logic
+- Test error handling scenarios
+
+### **Integration Testing**
+- Test DNN module integration
+- Verify database operations
+- Test payment processing workflows
+
+### **User Acceptance Testing**
+- Test subscription signup flow
+- Verify payment processing
+- Test administrative functions
+- Verify reporting functionality
+
+## ๐ **Troubleshooting**
+
+### **Common Issues**
+1. **Build Errors**: Ensure all dependencies are properly referenced
+2. **Runtime Errors**: Check DNN logs for detailed error information
+3. **Database Issues**: Verify connection strings and permissions
+4. **Payment Issues**: Check IPN handler configuration
+
+### **Deployment Issues**
+1. **Changes Not Appearing**: Ensure you've recycled the application pool after copying files
+2. **DLL Not Loading**: Verify the DLL was copied to the correct `bin\` directory
+3. **UI Not Updating**: Ensure the `.ascx` files were copied to the correct `DesktopModules` directory
+4. **Compilation Errors**: Check that all required dependencies are available in the target environment
+
+### **Common Deployment Mistakes**
+- โ **Copying `.ascx.vb` files** - These are compiled into the DLL and don't need to be copied
+- โ **Copying `.ascx.designer.vb` files** - These are also compiled into the DLL
+- โ **Not recycling the app pool** - Changes won't take effect until the app pool is recycled
+- โ **Copying files to wrong directories** - DLL goes in `bin\`, ASCX files go in `DesktopModules\`
+- โ **Wrong module folder** - This module is located in `DesktopModules\SubscriptionSignup\`, not `Ventrian.SubscriptionTools\`
+
+### **Debug Mode**
+- Enable detailed error messages in development
+- Use DNN logging for debugging
+- Check browser console for client-side issues
+
+## ๐ **Performance Considerations**
+
+### **Database Optimization**
+- Use appropriate indexes on frequently queried fields
+- Implement connection pooling
+- Optimize complex queries
+
+### **Caching Strategy**
+- Cache frequently accessed data
+- Use DNN caching mechanisms
+- Implement appropriate cache invalidation
+
+### **Scalability**
+- Design for multiple concurrent users
+- Optimize database queries for large datasets
+- Consider asynchronous processing for long-running operations
+
+## ๐ **Security Considerations**
+
+### **Data Protection**
+- Validate all user inputs
+- Use parameterized queries to prevent SQL injection
+- Implement proper authentication and authorization
+
+### **Payment Security**
+- Secure payment processor integration
+- Validate IPN notifications
+- Protect sensitive payment information
+
+## ๐ **API Reference**
+
+### **Key Classes**
+- **PlanController**: Manages subscription plans
+- **OrderController**: Handles subscription orders
+- **ReceiptController**: Manages receipts and invoices
+- **UserSettingController**: Handles user preferences
+
+### **Data Models**
+- **PlanInfo**: Subscription plan information
+- **OrderInfo**: Order details and status
+- **ReceiptInfo**: Receipt and invoice data
+- **UserSettingInfo**: User configuration settings
+
+## ๐ค **Contributing**
+
+### **Development Process**
+1. **Fork** the repository
+2. **Create** feature branch
+3. **Implement** changes following coding standards
+4. **Test** thoroughly
+5. **Submit** pull request
+
+### **Coding Standards**
+- Follow VB.NET best practices
+- Use consistent naming conventions
+- Add appropriate comments and documentation
+- Follow DNN module development patterns
+
+## ๐ **Support & Contact**
+
+### **Documentation**
+- This README provides development guidance
+- Check DNN documentation for platform-specific information
+- Review code comments for implementation details
+
+### **Community**
+- DNN Community forums
+- Module-specific support channels
+- Development team contact information
+
+## ๐ **Change Log**
+
+### **Recent Updates (Current Development)**
+- **EditReceipt User Selection**: Changed from username text input to user dropdown for better usability
+- **UserID-based Processing**: Updated receipt creation to use UserID instead of username lookup
+- **Improved User Experience**: Dropdown shows users by DisplayName (First Last) sorted alphabetically
+- **URL Parameter Update**: Changed from `username` parameter to `UserID` parameter for better reliability
+- **Enhanced Validation**: Built-in user selection validation eliminates username formatting issues
+- **Interface Label Update**: Changed label from "UserName:" to "User:" for cleaner, more intuitive interface
+- **Conditional Role Assignment**: Users are added to the "Paid Member" role only when "Add To Role" checkbox is checked
+- **Robust Error Handling**: Comprehensive null reference protection and exception handling prevents crashes
+- **Enhanced Search Interface**: Changed EditReceipts search from username text input to user dropdown selection
+
+### **Deployment History - August 20, 2025**
+- **Issue Resolved**: Username text input field causing errors with special characters and spaces
+- **Solution Implemented**: Replaced text input with user dropdown populated from DNN user list
+- **Files Updated**:
+ - `EditReceipt.ascx` - Changed from text input to dropdown
+ - `EditReceipt.ascx.vb` - Added user binding and validation logic
+ - `Ventrian.SubscriptionTools.dll` - Compiled with new functionality
+- **Deployment Location**: `C:\inetpub\packdev\DesktopModules\SubscriptionSignup\`
+- **Status**: โ
Successfully deployed and tested
+
+### **Role Assignment Fix - August 20, 2025**
+- **Issue Identified**: Users were not being added to the "Paid Member" role after receipt creation due to null reference errors
+- **Root Cause**: Null reference exceptions were preventing the role assignment code from executing
+- **Solution Implemented**: Enhanced error handling and improved role assignment logic while maintaining checkbox control
+- **Files Updated**:
+ - `EditReceipt.ascx.vb` - Enhanced role assignment logic
+ - `Ventrian.SubscriptionTools.dll` - Compiled with role assignment fix
+- **Deployment Location**: `C:\inetpub\packdev\bin\`
+- **Status**: โ
Successfully deployed and tested
+
+### **EditReceipts Search Interface Enhancement - August 20, 2025**
+- **Issue Identified**: Search functionality in EditReceipts used text input for usernames, causing similar issues as EditReceipt
+- **Solution Implemented**: Converted username text search to user dropdown selection with "FirstName LastName / DisplayName" format
+- **Files Updated**:
+ - `EditReceipts.ascx` - Changed from text input to dropdown
+ - `EditReceipts.ascx.vb` - Added user binding and dropdown logic
+ - `EditReceipts.ascx.designer.vb` - Updated control type from TextBox to DropDownList
+ - `Ventrian.SubscriptionTools.dll` - Compiled with enhanced search functionality
+- **Deployment Location**:
+ - DLL: `C:\inetpub\packdev\bin\`
+ - ASCX: `C:\inetpub\packdev\DesktopModules\SubscriptionSignup\`
+- **Status**: โ
Successfully deployed and tested
+
+### **EditReceipts Auto-Search Enhancement - August 20, 2025**
+- **Enhancement**: Added automatic search triggering when user selection changes in dropdown
+- **User Experience**: Users no longer need to click "Go" button - search happens automatically
+- **Interface Cleanup**: Removed "Go" button since it's no longer needed
+- **Files Updated**:
+ - `EditReceipts.ascx` - Added AutoPostBack="True" to dropdown, removed Go button
+ - `EditReceipts.ascx.vb` - Added drpUsers_SelectedIndexChanged event handler, removed btnGo_Click
+ - `Ventrian.SubscriptionTools.dll` - Compiled with auto-search functionality
+- **Deployment Location**:
+ - DLL: `C:\inetpub\packdev\bin\`
+ - ASCX: `C:\inetpub\packdev\DesktopModules\SubscriptionSignup\`
+- **Status**: โ
Successfully deployed and tested
+
+### **Additional Updates - August 20, 2025**
+- **Interface Improvement**: Changed label from "UserName:" to "User:" for better user experience
+- **Files Updated**:
+ - `EditReceipt.ascx` - Updated resourcekey from "UserName" to "User"
+ - `EditReceipt.ascx.resx` - Added new "User.Text" resource key with value "User"
+- **Result**: Cleaner, more intuitive interface with proper localization support
+
+### **Version 1.5.0**
+- Latest stable release
+- Comprehensive subscription management features
+- Payment processing integration
+- Reporting and analytics capabilities
+
+### **Previous Versions**
+- See individual version SQL scripts for detailed change history
+- Check module manifest for version information
+
+---
+
+*Last Updated: [Current Date]*
+*Module Version: 1.5.0*
+*DNN Compatibility: 6.x and higher*
+*Status: Production Ready*
diff --git a/SignupViewOptions.ascx.vb b/SignupViewOptions.ascx.vb
index 7e1d2b2..f2ab44d 100755
--- a/SignupViewOptions.ascx.vb
+++ b/SignupViewOptions.ascx.vb
@@ -48,7 +48,8 @@ Namespace Ventrian.SubscriptionTools
Private Sub BindCurrency()
Dim ctlList As New Lists.ListController
- Dim colCurrency As Lists.ListEntryInfoCollection = ctlList.GetListEntryInfoCollection("Currency", "")
+ 'Dim colCurrency As Lists.ListEntryInfoCollection = ctlList.GetListEntryInfoCollection("Currency", "")
+ Dim colCurrency As IEnumerable(Of DotNetNuke.Common.Lists.ListEntryInfo) = ctlList.GetListEntryInfoItems("Currency", "")
drpCurrency.DataSource = colCurrency
drpCurrency.DataBind()
diff --git a/Status.ascx.vb b/Status.ascx.vb
index 9b6f5cc..b7ffc65 100755
--- a/Status.ascx.vb
+++ b/Status.ascx.vb
@@ -43,10 +43,11 @@ Namespace Ventrian.SubscriptionTools
End If
Dim objRoleController As New RoleController
- Dim arrRoles As String() = objRoleController.GetRolesByUser(Me.UserId, Me.PortalId)
- For Each role As String In arrRoles
- If PortalSecurity.IsInRole(role) Then
- objContentInfo = objContentController.Get(Me.ModuleId, role)
+
+ Dim lRoles As IList(Of DotNetNuke.Entities.Users.UserRoleInfo) = objRoleController.GetUserRoles(Me.UserInfo, True)
+ For Each role As DotNetNuke.Entities.Users.UserRoleInfo In lRoles
+ If PortalSecurity.IsInRole(role.RoleName) Then
+ objContentInfo = objContentController.Get(Me.ModuleId, role.RoleName)
If Not (objContentInfo Is Nothing) Then
If (objContentInfo.SettingValue <> "") Then
@@ -65,6 +66,28 @@ Namespace Ventrian.SubscriptionTools
End If
Next
+ 'Dim arrRoles As String() = objRoleController.GetRolesByUser(Me.UserId, Me.PortalId)
+ 'For Each role As String In arrRoles
+ ' If PortalSecurity.IsInRole(role) Then
+ ' objContentInfo = objContentController.Get(Me.ModuleId, role)
+
+ ' If Not (objContentInfo Is Nothing) Then
+ ' If (objContentInfo.SettingValue <> "") Then
+ ' Dim val As String = Server.HtmlDecode(objContentInfo.SettingValue)
+
+ ' If (Request.IsAuthenticated) Then
+ ' val = val.Replace("[FULLNAME]", Me.UserInfo.DisplayName)
+ ' val = val.Replace("[USERNAME]", Me.UserInfo.Username)
+ ' End If
+
+ ' literal.Text = val
+ ' phControls.Controls.Add(literal)
+ ' Return
+ ' End If
+ ' End If
+ ' End If
+ 'Next
+
objContentInfo = objContentController.Get(Me.ModuleId, "Generic")
If Not (objContentInfo Is Nothing) Then
diff --git a/StatusViewOptions.ascx.vb b/StatusViewOptions.ascx.vb
index b5a8815..e196a7d 100755
--- a/StatusViewOptions.ascx.vb
+++ b/StatusViewOptions.ascx.vb
@@ -22,7 +22,9 @@ Namespace Ventrian.SubscriptionTools
Localization.LocalizeDataGrid(grdRoles, Me.LocalResourceFile)
- grdRoles.DataSource = objRoleController.GetPortalRoles(Me.PortalId)
+ 'grdRoles.DataSource = objRoleController.GetPortalRoles(Me.PortalId)
+ grdRoles.DataSource = objRoleController.GetRoles(Me.PortalId)
+
grdRoles.DataBind()
End Sub
diff --git a/Tools/IPNHandler.aspx.vb b/Tools/IPNHandler.aspx.vb
index 6119913..530ff87 100755
--- a/Tools/IPNHandler.aspx.vb
+++ b/Tools/IPNHandler.aspx.vb
@@ -34,6 +34,8 @@ Namespace Ventrian.SubscriptionTools.Tools
serverURL = "https://www.sandbox.paypal.com/cgi-bin/webscr"
End If
+ System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12
+
' Create the request back
Dim req As HttpWebRequest = CType(WebRequest.Create(serverURL), HttpWebRequest)
@@ -49,14 +51,30 @@ Namespace Ventrian.SubscriptionTools.Tools
stOut.Close()
' Do the request to PayPal and get the response
- Dim httpWebResponse As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
- Using streamReader As New StreamReader(httpWebResponse.GetResponseStream())
- strResponse = streamReader.ReadToEnd()
- streamReader.Close()
- End Using
-
- ' Confirm whether the IPN was VERIFIED or INVALID. If INVALID, just ignore the IPN
- Return (httpWebResponse.StatusCode = HttpStatusCode.OK)
+ Try
+ Dim httpWebResponse As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
+ Using streamReader As New StreamReader(httpWebResponse.GetResponseStream())
+ strResponse = streamReader.ReadToEnd()
+ streamReader.Close()
+ End Using
+
+ ' Confirm whether the IPN was VERIFIED or INVALID. If INVALID, just ignore the IPN
+ Return (httpWebResponse.StatusCode = HttpStatusCode.OK)
+ Catch ex As WebException
+ ' Log the specific error for debugging
+ Dim errorMessage As String = String.Format("PayPal IPN Verification Error - URL: {0}, Error: {1}", serverURL, ex.Message)
+ If ex.Response IsNot Nothing Then
+ errorMessage += String.Format(" - Response Status: {0}", CType(ex.Response, HttpWebResponse).StatusCode)
+ End If
+ Dim objEventLogController As New DotNetNuke.Services.Log.EventLog.EventLogController
+ objEventLogController.AddLog("PayPal IPN Error", errorMessage, DotNetNuke.Services.Log.EventLog.EventLogController.EventLogType.ADMIN_ALERT)
+ Return False
+ Catch ex As Exception
+ ' Log any other unexpected errors
+ Dim objEventLogController As New DotNetNuke.Services.Log.EventLog.EventLogController
+ objEventLogController.AddLog("PayPal IPN Unexpected Error", ex.ToString(), DotNetNuke.Services.Log.EventLog.EventLogController.EventLogType.ADMIN_ALERT)
+ Return False
+ End Try
End Function
@@ -161,8 +179,9 @@ Namespace Ventrian.SubscriptionTools.Tools
If (objPlan.Currency <> "") Then
objReceipt.Currency = objPlan.Currency
Else
- Dim objModuleController As New ModuleController
- Dim settings As Hashtable = objModuleController.GetModuleSettings(moduleID)
+ 'Dim objModuleController As New ModuleController
+ 'Dim settings As Hashtable = objModuleController.GetModuleSettings(moduleID)
+ Dim settings As Hashtable = ModuleController.Instance.GetModule(moduleID, -1, False).ModuleSettings
Dim currency As String = ""
If (settings.Contains(Constants.CURRENCY)) Then
@@ -174,7 +193,8 @@ Namespace Ventrian.SubscriptionTools.Tools
objReceipt.Currency = currency
End If
- Dim objUser As UserInfo = UserController.GetUser(objOrder.PortalID, userID, True)
+ 'Dim objUser As UserInfo = UserController.GetUser(objOrder.PortalID, userID, True)
+ Dim objUser As UserInfo = UserController.GetUserById(objOrder.PortalID, userID)
If (objUser IsNot Nothing) Then
objReceipt.DisplayName = objUser.DisplayName
objReceipt.FirstName = objUser.FirstName
@@ -234,7 +254,9 @@ Namespace Ventrian.SubscriptionTools.Tools
End If
Dim objRoleController As New RoleController
- objRoleController.AddUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID, objReceipt.DateEnd)
+ 'objRoleController.AddUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID, objReceipt.DateEnd)
+ 'AddUserRole(portalId As Integer, userId As Integer, roleId As Integer, effectiveDate As Date, expiryDate As Date)
+ objRoleController.AddUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID, objReceipt.DateStart, objReceipt.DateEnd)
End If
Next
@@ -262,8 +284,9 @@ Namespace Ventrian.SubscriptionTools.Tools
Dim objReceipt As ReceiptInfo = CType(objReceipts(0), ReceiptInfo)
- Dim objModuleController As New ModuleController
- Dim settings As Hashtable = objModuleController.GetModuleSettings(moduleID)
+ 'Dim objModuleController As New ModuleController
+ 'Dim settings As Hashtable = objModuleController.GetModuleSettings(moduleID)
+ Dim settings As Hashtable = ModuleController.Instance.GetModule(moduleID, -1, False).ModuleSettings
Dim currency As String = ""
If (settings.Contains(Constants.CURRENCY)) Then
@@ -278,16 +301,16 @@ Namespace Ventrian.SubscriptionTools.Tools
symbol = "$"
Case "GBP"
- symbol = "ฃ"
+ symbol = "๏ฟฝ"
Case "EUR"
- symbol = ""
+ symbol = "๏ฟฝ"
Case "CAD"
symbol = "$"
Case "JPY"
- symbol = "ฅ"
+ symbol = "๏ฟฝ"
End Select
Dim invoice As String = ""
@@ -343,7 +366,8 @@ Namespace Ventrian.SubscriptionTools.Tools
invoice = invoice.Replace("[SUBSCRIPTIONPRICE]", symbol & objReceipt.ServiceFee.ToString("##0.00"))
invoice = invoice.Replace("[USERNAME]", objUser.Username)
- Dim sendTo As String = objUser.Membership.Email
+ 'Dim sendTo As String = objUser.Membership.Email
+ Dim sendTo As String = objUser.Email
Dim sendFrom As String = PortalSettings.Email
Dim emailSubject As String = PortalSettings.PortalName & " Subscription Invoice"
diff --git a/Ventrian.SubscriptionTools.sln b/Ventrian.SubscriptionTools.sln
new file mode 100644
index 0000000..9350623
--- /dev/null
+++ b/Ventrian.SubscriptionTools.sln
@@ -0,0 +1,25 @@
+๏ปฟ
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28010.2046
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Ventrian.SubscriptionTools", "Ventrian.SubscriptionTools.vbproj", "{4EBBE6F0-7D7F-476D-8DE3-B75E5E1C9224}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4EBBE6F0-7D7F-476D-8DE3-B75E5E1C9224}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4EBBE6F0-7D7F-476D-8DE3-B75E5E1C9224}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4EBBE6F0-7D7F-476D-8DE3-B75E5E1C9224}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4EBBE6F0-7D7F-476D-8DE3-B75E5E1C9224}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4B985935-8C01-49DC-AB5F-40C073E7C075}
+ EndGlobalSection
+EndGlobal
diff --git a/Ventrian.SubscriptionTools.vbproj b/Ventrian.SubscriptionTools.vbproj
index c3cf2c4..d660912 100755
--- a/Ventrian.SubscriptionTools.vbproj
+++ b/Ventrian.SubscriptionTools.vbproj
@@ -1,5 +1,6 @@
๏ปฟ
-
+
+
Debug
AnyCPU
@@ -13,21 +14,30 @@
Ventrian.SubscriptionTools
- 2.0
+ 3.5
On
+ v4.7.2
+ false
+
+
+
+
+
+
+
true
full
true
true
- ..\..\bin\
+ bin\
Ventrian.xml
-
-
+ 42353,42354,42355
41999,42016,42017,42018,42019,42020,42021,42022,42032,42036
+ false
pdbonly
@@ -36,24 +46,31 @@
true
bin\
Ventrian.xml
-
-
+ 42353,42354,42355
41999,42016,42017,42018,42019,42020,42021,42022,42032,42036
+ false
False
False
+ Libraries\DotNetNuke.dll
+
+
+
+
+
+
@@ -308,9 +325,21 @@
Settings.Designer.vb
+
+
+ web.config
+
+
+ web.config
+
+
+ 10.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+
-
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web.Release.config b/web.Release.config
new file mode 100644
index 0000000..da6e960
--- /dev/null
+++ b/web.Release.config
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web.config b/web.config
new file mode 100644
index 0000000..761d489
--- /dev/null
+++ b/web.config
@@ -0,0 +1,35 @@
+๏ปฟ
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
|