diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80e2cdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,225 @@ +# The following command works for downloading when using Git for Windows: +# curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore +# +# Download this file using PowerShell v3 under Windows with the following comand: +# Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore +# +# or wget: +# wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +[Bb]in/ +[Oo]bj/ +# build folder is nowadays used for build scripts and should not be ignored +#build/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# OS generated files # +.DS_Store* +Icon? + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +modulesbin/ +tempbin/ + +# EPiServer Site file (VPP) +AppData/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# vim +*.txt~ +*.swp +*.swo + +# Temp files when opening LibreOffice on ubuntu +.~lock.* + +# svn +.svn + +# CVS - Source Control +**/CVS/ + +# Remainings from resolving conflicts in Source Control +*.orig + +# SQL Server files +**/App_Data/*.mdf +**/App_Data/*.ldf +**/App_Data/*.sdf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# SASS Compiler cache +.sass-cache + +# Visual Studio 2014 CTP +**/*.sln.ide + +# Visual Studio temp something +.vs/ + +# dotnet stuff +project.lock.json + +# VS 2015+ +*.vc.vc.opendb +*.vc.db + +# Rider +.idea/ + +# Visual Studio Code +.vscode/ + +# Output folder used by Webpack or other FE stuff +**/node_modules/* +**/wwwroot/* + +# SpecFlow specific +*.feature.cs +*.feature.xlsx.* +*.Specs_*.html + +##### +# End of core ignore list, below put you custom 'per project' settings (patterns or path) +##### \ No newline at end of file diff --git a/App_LocalResources/EditReceipt.ascx.resx b/App_LocalResources/EditReceipt.ascx.resx index 6809150..c035f83 100755 --- a/App_LocalResources/EditReceipt.ascx.resx +++ b/App_LocalResources/EditReceipt.ascx.resx @@ -156,6 +156,9 @@ UserName + + User + Enter the username you want to add the subscription to. diff --git a/EditPlan.ascx.vb b/EditPlan.ascx.vb index 583ca54..256c48e 100755 --- a/EditPlan.ascx.vb +++ b/EditPlan.ascx.vb @@ -62,7 +62,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() @@ -74,12 +75,13 @@ Namespace Ventrian.SubscriptionTools Private Sub BindRoles() Dim objRoleController As New RoleController + drpRole.DataSource = objRoleController.GetRoles(Me.PortalId) - drpRole.DataSource = objRoleController.GetPortalRoles(Me.PortalId) drpRole.DataBind() drpRole.Items.Insert(0, New ListItem(Localization.GetString("SelectRole.Text", Me.LocalResourceFile), "-1")) - rptRoles.DataSource = objRoleController.GetPortalRoles(Me.PortalId) + 'rptRoles.DataSource = objRoleController.GetPortalRoles(Me.PortalId) + rptRoles.DataSource = objRoleController.GetRoles(Me.PortalId) rptRoles.DataBind() End Sub diff --git a/EditReceipt.ascx b/EditReceipt.ascx index 7b0f623..ea45b85 100755 --- a/EditReceipt.ascx +++ b/EditReceipt.ascx @@ -32,16 +32,14 @@ - - - - - - - + + + + + + diff --git a/EditReceipt.ascx.designer.vb b/EditReceipt.ascx.designer.vb index c2ccf8a..2f27ea3 100755 --- a/EditReceipt.ascx.designer.vb +++ b/EditReceipt.ascx.designer.vb @@ -71,13 +71,13 @@ Namespace Ventrian.SubscriptionTools Protected WithEvents plUserName As Global.DotNetNuke.UI.UserControls.LabelControl ''' - '''txtUserName 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 + '''drpUsers control. + ''' + ''' + '''Auto-generated field. + '''To modify move field declaration from designer file to code-behind file. + ''' + Protected WithEvents drpUsers As Global.System.Web.UI.WebControls.DropDownList ''' '''valUserName control. diff --git a/EditReceipt.ascx.vb b/EditReceipt.ascx.vb index 7d7fbcf..cc310b5 100755 --- a/EditReceipt.ascx.vb +++ b/EditReceipt.ascx.vb @@ -64,6 +64,55 @@ Namespace Ventrian.SubscriptionTools End Sub + Private Sub BindUsers() + + Dim objUserController As New UserController + Dim objUsers As ArrayList = objUserController.GetUsers(Me.PortalId) + + ' Sort users by DisplayName alphabetically + objUsers.Sort(New UserDisplayNameComparer()) + + ' Add a default "Select User" option + Dim defaultUser As New UserInfo + defaultUser.UserID = -1 + defaultUser.DisplayName = "-- Select User --" + 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 UserInfo = CType(objUsers(i), 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 BindPlans() Dim objPlanController As New PlanController @@ -79,16 +128,16 @@ Namespace Ventrian.SubscriptionTools symbol = "$" Case "GBP" - symbol = "ฃ" + symbol = "๏ฟฝ" Case "EUR" - symbol = "€" + symbol = "๏ฟฝ" Case "CAD" symbol = "$" Case "JPY" - symbol = "ฅ" + symbol = "๏ฟฝ" End Select Dim _details As String = "" @@ -186,11 +235,20 @@ Namespace Ventrian.SubscriptionTools Try ReadQueryString() + + If (_receiptID = Null.NullInteger Or _receiptID < 0) AndAlso (Request("UserID") IsNot Nothing) Then + Dim userID As Integer = Convert.ToInt32(Request("UserID")) + If userID > 0 Then + drpUsers.SelectedValue = userID.ToString() + End If + End If + BindCrumbs() cmdStartDate.NavigateUrl = DotNetNuke.Common.Utilities.Calendar.InvokePopupCal(txtStartDate) If (IsPostBack = False) Then + BindUsers() BindPlans() BindReceipt() @@ -204,77 +262,155 @@ Namespace Ventrian.SubscriptionTools Private Sub cmdUpdate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdUpdate.Click - If (Page.IsValid) Then + Try + If (Page.IsValid) Then - Dim objReceipt As New ReceiptInfo + ' Validate required fields + If drpSelectPlan.SelectedValue Is Nothing OrElse String.IsNullOrEmpty(drpSelectPlan.SelectedValue) Then + ' Show error - no plan selected + Return + End If - objReceipt.ModuleID = Me.ModuleId - objReceipt.PortalID = Me.PortalId - objReceipt.Status = "Complete" - objReceipt.Processor = txtProcessor.Text - objReceipt.ProcessorTxID = txtTransactionID.Text + If drpUsers.SelectedValue Is Nothing OrElse String.IsNullOrEmpty(drpUsers.SelectedValue) Then + ' Show error - no user selected + Return + End If - Dim objPlanController As New PlanController - Dim objPlan As PlanInfo = objPlanController.Get(Convert.ToInt32(drpSelectPlan.SelectedValue)) + Dim objReceipt As New ReceiptInfo - objReceipt.BillingFrequency = objPlan.BillingFrequency - objReceipt.BillingPeriod = objPlan.BillingPeriod - objReceipt.ServiceFee = objPlan.ServiceFee - objReceipt.Name = objPlan.Name + objReceipt.ModuleID = Me.ModuleId + objReceipt.PortalID = Me.PortalId + objReceipt.Status = "Complete" + objReceipt.Processor = If(txtProcessor.Text, "Manual") + objReceipt.ProcessorTxID = If(txtTransactionID.Text, "") - objReceipt.DateCreated = DateTime.Now - If (txtStartDate.Text <> "") Then - objReceipt.DateStart = Convert.ToDateTime(txtStartDate.Text) - Else - objReceipt.DateStart = DateTime.Now - End If + Dim objPlanController As New PlanController + Dim planID As Integer = Convert.ToInt32(drpSelectPlan.SelectedValue) + Dim objPlan As PlanInfo = objPlanController.Get(planID) - Dim objUser As UserInfo = UserController.GetUserByName(Me.PortalId, txtUserName.Text) + ' Validate plan exists + If objPlan Is Nothing Then + ' Show error - plan not found + Return + End If - objReceipt.UserID = objUser.UserID + objReceipt.BillingFrequency = objPlan.BillingFrequency + objReceipt.BillingPeriod = objPlan.BillingPeriod + objReceipt.ServiceFee = objPlan.ServiceFee + objReceipt.Name = objPlan.Name + + objReceipt.DateCreated = DateTime.Now + + ' Safely handle start date + Try + If Not String.IsNullOrEmpty(txtStartDate.Text) Then + objReceipt.DateStart = Convert.ToDateTime(txtStartDate.Text) + Else + objReceipt.DateStart = DateTime.Now + End If + Catch ex As Exception + ' If date parsing fails, use current date + objReceipt.DateStart = DateTime.Now + End Try + + Dim selectedUserID As Integer = Convert.ToInt32(drpUsers.SelectedValue) + If selectedUserID <= 0 Then + ' Show error message + Return + End If - Dim objRoleController As New RoleController - Dim objUserRole As UserRoleInfo = objRoleController.GetUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID) + objReceipt.UserID = selectedUserID + + ' Get the user info to set the UserName and other user-related fields + Dim objUserController As New UserController + Dim objUser As UserInfo = objUserController.GetUser(Me.PortalId, selectedUserID) + If objUser IsNot Nothing Then + ' Safely set user properties with null checks + objReceipt.UserName = If(objUser.Username, "") + objReceipt.FirstName = If(objUser.FirstName, "") + objReceipt.LastName = If(objUser.LastName, "") + objReceipt.Email = If(objUser.Email, "") + objReceipt.DisplayName = If(objUser.DisplayName, "") + End If - If Not (objUserRole Is Nothing) Then - If (objUserRole.ExpiryDate <> Null.NullDate) Then - If (objUserRole.ExpiryDate > DateTime.Now) Then - objReceipt.DateStart = objUserRole.ExpiryDate - End If + ' Safely handle role logic + Dim objRoleController As New RoleController + Dim objUserRole As UserRoleInfo = Nothing + Try + objUserRole = objRoleController.GetUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID) + Catch ex As Exception + ' Role lookup failed, continue without role + objUserRole = Nothing + End Try + + If Not (objUserRole Is Nothing) Then + Try + If (objUserRole.ExpiryDate <> Null.NullDate) Then + If (objUserRole.ExpiryDate > DateTime.Now) Then + objReceipt.DateStart = objUserRole.ExpiryDate + End If + End If + Catch ex As Exception + ' Date comparison failed, continue with current date + End Try End If - End If - Select Case objPlan.BillingFrequencyType - Case FrequencyType.OneTimeFee + ' Safely calculate end date + Try + Select Case objPlan.BillingFrequencyType + Case FrequencyType.OneTimeFee + objReceipt.DateEnd = Null.NullDate + Exit Select + Case FrequencyType.Day + objReceipt.DateEnd = objReceipt.DateStart.AddDays(objPlan.BillingPeriod) + Exit Select + Case FrequencyType.Week + objReceipt.DateEnd = objReceipt.DateStart.AddDays(objPlan.BillingPeriod * 7) + Exit Select + Case FrequencyType.Month + objReceipt.DateEnd = objReceipt.DateStart.AddMonths(objPlan.BillingPeriod) + Exit Select + Case FrequencyType.Year + objReceipt.DateEnd = objReceipt.DateStart.AddYears(objPlan.BillingPeriod) + Exit Select + Case FrequencyType.FixedEndDate + objReceipt.DateEnd = objPlan.EndDate + Exit Select + End Select + Catch ex As Exception + ' Date calculation failed, set to null objReceipt.DateEnd = Null.NullDate - Exit Select - Case FrequencyType.Day - objReceipt.DateEnd = objReceipt.DateStart.AddDays(objPlan.BillingPeriod) - Exit Select - Case FrequencyType.Week - objReceipt.DateEnd = objReceipt.DateStart.AddDays(objPlan.BillingPeriod * 7) - Exit Select - Case FrequencyType.Month - objReceipt.DateEnd = objReceipt.DateStart.AddMonths(objPlan.BillingPeriod) - Exit Select - Case FrequencyType.Year - objReceipt.DateEnd = objReceipt.DateStart.AddYears(objPlan.BillingPeriod) - Exit Select - Case FrequencyType.FixedEndDate - objReceipt.DateEnd = objPlan.EndDate - Exit Select - End Select + End Try + + ' Add the receipt + Dim objReceiptController As New ReceiptController + Try + objReceiptController.Add(objReceipt) + Catch ex As Exception + ' Receipt creation failed + Return + End Try + + ' Handle role addition - only if checkbox is checked + If (chkAddToRole.Checked) Then + Try + ' Add the user to the role (DNN will handle duplicates gracefully) + objRoleController.AddUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID, objReceipt.DateStart, objReceipt.DateEnd) + Catch ex As Exception + ' Role assignment failed, but receipt was created + ' Log the error for debugging + DotNetNuke.Services.Exceptions.LogException(ex) + End Try + End If - Dim objReceiptController As New ReceiptController - objReceiptController.Add(objReceipt) + Response.Redirect(EditUrl("EditReceipts"), True) - If (chkAddToRole.Checked) Then - objRoleController.AddUserRole(objReceipt.PortalID, objReceipt.UserID, objPlan.RoleID, objReceipt.DateEnd) End If - Response.Redirect(EditUrl("EditReceipts"), True) - - End If + Catch ex As Exception + ' Log the error and show user-friendly message + ProcessModuleLoadException(Me, ex) + End Try End Sub @@ -293,14 +429,13 @@ Namespace Ventrian.SubscriptionTools End Sub - Private Sub valUserNameExists_ServerValidate(ByVal source As System.Object, ByVal args As System.Web.UI.WebControls.ServerValidateEventArgs) Handles valUserNameExists.ServerValidate - - Dim objUser As UserInfo = UserController.GetUserByName(PortalId, txtUserName.Text) + Private Sub ValidateUserSelection(ByVal source As System.Object, ByVal e As System.Web.UI.WebControls.ServerValidateEventArgs) - If (objUser Is Nothing) Then - args.IsValid = False + Dim selectedUserID As Integer = Convert.ToInt32(drpUsers.SelectedValue) + If selectedUserID <= 0 Then + e.IsValid = False Else - args.IsValid = True + e.IsValid = True End If End Sub @@ -309,4 +444,51 @@ Namespace Ventrian.SubscriptionTools End Class + Public Class UserDisplayNameComparer + Implements IComparer + + Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare + Dim user1 As UserInfo = CType(x, UserInfo) + Dim user2 As UserInfo = CType(y, UserInfo) + + If user1 Is Nothing AndAlso user2 Is Nothing Then + Return 0 + ElseIf user1 Is Nothing Then + Return -1 + ElseIf user2 Is Nothing Then + Return 1 + Else + ' Build the display text for comparison (same logic as in BindUsers) + Dim displayText1 As String = BuildUserDisplayText(user1) + Dim displayText2 As String = BuildUserDisplayText(user2) + + Return String.Compare(displayText1, displayText2, True) + End If + End Function + + Private Function BuildUserDisplayText(ByVal user As UserInfo) As String + 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 + + Return displayText + End Function + End Class + End Namespace \ No newline at end of file diff --git a/EditReceipts.ascx b/EditReceipts.ascx index e9515db..75910f3 100755 --- a/EditReceipts.ascx +++ b/EditReceipts.ascx @@ -20,8 +20,8 @@
- 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