v1.3.0 Restructure Saxonite workspace and upgrade to .NET 10 (#1)
Move Saxonite’s projects into a dedicated `Workspace` folder and align the solution with .NET 10 and the current Common workspace. Update encryption integration, test project structure and coverage, and repository guidance and upgrade notes. - Relocate application and library projects while retaining Git rename history. - Split tests into Admin, Online, and Shared projects; add representative behavioral and serialization tests. - Update Common integration, application version, and repository documentation. Application version: 1.3.0 Reviewed-on: #1 Co-authored-by: andy <andy@sarand.com>
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Sacs.Web.Components;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Base.App;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Core.Web.Base.Controllers;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Standard.Base.Consoles;
|
||||
using Saxonite.Net.Standard.Base.Mgr;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Reference;
|
||||
using Saxonite.Net.Standard.Base.Mgr.Support;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using static Saxonite.Net.Standard.Base.App.Sax;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Base.Models
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxModel_Base : SaModel
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private SaxInternals internals;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Protected construction.
|
||||
protected SaxModel_Base(SaxLocaliser localiser)
|
||||
{
|
||||
Localiser = localiser;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Application properties.
|
||||
private SaxApplication_Web App { get { return SaxApplication_Web.WebApp; } }
|
||||
protected SaxAppConfig AppConfig { get { return App.AppConfig; } }
|
||||
protected SaxSystem_Web System { get { return App.System_Web; } }
|
||||
public String SessionCookieName { get { return App.SessionCookieName; } }
|
||||
public SaxLocaliser Localiser { get; }
|
||||
public override String GetText(String textKey, String defaultText)
|
||||
{
|
||||
String text = Localiser[textKey];
|
||||
if (StringUtil.IsBlank(text) || text.EqualsCI(textKey))
|
||||
return defaultText;
|
||||
return text;
|
||||
}
|
||||
// Session properties.
|
||||
public SaxSessionData SessionData { get; set; }
|
||||
public SaxSessionContext SessionContext { get { return SessionData?.SessionContext ?? App.SessionContext; } }
|
||||
public DbUpdateInfo DbUpdateInfo { get { return SessionData?.DbUpdateInfo ?? new DbUpdateInfo(null, "", SaxSystem.ComputerName); } }
|
||||
// Request properties.
|
||||
public SaxController_Base Controller { get; set; }
|
||||
public CultureInfo SessionCulture { get { return Controller?.SessionCulture ?? CultureInfo.InvariantCulture; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Page configuration.
|
||||
public String Version { get { return SaxSystem.DebugMode ? $"v{Sax.Version.GetFullVersion()}" : $"v{Sax.Version.GetAppVersion()}"; } }
|
||||
public String InstanceVersion { get { return (!StringUtil.IsEmpty(App.AppConfig.Instance) ? App.AppConfig.Instance + " " : "") + $"{Version}"; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Business components.
|
||||
public SaxInternals Internals { get { if (internals == null) internals = new SaxInternals(SessionContext); return internals; } }
|
||||
// Consoles.
|
||||
protected SaxDataCacheConsole DataCacheConsole { get { return Internals.DataCacheConsole; } }
|
||||
protected SaxSessionDataConsole SessionDataConsole { get { return Internals.SessionDataConsole; } }
|
||||
protected SaxActivityConsole ActivityConsole { get { return Internals.ActivityConsole; } }
|
||||
// Managers.
|
||||
public SaxReferenceMgr ReferenceMgr { get { return Internals.ReferenceMgr; } }
|
||||
public SaxAccountMgr AccountMgr { get { return Internals.AccountMgr; } }
|
||||
public SaxActivityMgr ActivityMgr { get { return Internals.ActivityMgr; } }
|
||||
public SaxItemDataMgr ItemDataMgr { get { return Internals.ItemDataMgr; } }
|
||||
public SaxReferenceGolfMgr ReferenceGolfMgr { get { return Internals.ReferenceGolfMgr; } }
|
||||
public SaxSupportMgr SupportMgr { get { return Internals.SupportMgr; } }
|
||||
// System codes.
|
||||
public List<TypeCodeDesc> DataItemTypeCodes { get { return Internals.DataItemTypeCodes; } }
|
||||
public List<TypeCodeDesc> YesNoCodes { get { return Internals.YesNoCodes; } }
|
||||
public List<TypeCodeDesc> SportGroupingCodes { get { return Internals.SportGroupingCodes; } }
|
||||
public List<TypeCodeDesc> SportSpecialityLabelCodes { get { return Internals.SportSpecialityLabelCodes; } }
|
||||
public List<TypeCodeDesc> SportContactTypeCodes { get { return Internals.SportContactTypeCodes; } }
|
||||
public List<TypeCodeDesc> SportParticipatingCodes { get { return Internals.SportParticipatingCodes; } }
|
||||
public List<TypeCodeDesc> SportPreparingCodes { get { return Internals.SportPreparingCodes; } }
|
||||
public List<TypeCodeDesc> SportOfficiatingCodes { get { return Internals.SportOfficiatingCodes; } }
|
||||
public List<TypeCodeDesc> RegionTypeCodes { get { return Internals.RegionTypeCodes; } }
|
||||
public List<TypeCodeDesc> EligibilityTypeCodes { get { return Internals.EligibilityTypeCodes; } }
|
||||
public List<TypeCodeDesc> UserRoleCodes { get { return Internals.UserRoleCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationTypeCodes { get { return Internals.OrganisationTypeCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationCategoryCodes { get { return Internals.OrganisationCategoryCodes; } }
|
||||
public List<TypeCodeDesc> OrganisationRoleCodes { get { return Internals.OrganisationRoleCodes; } }
|
||||
public List<TypeCodeDesc> AssociationTypeCodes { get { return Internals.AssociationTypeCodes; } }
|
||||
public List<TypeCodeDesc> AddressTypeCodes { get { return Internals.AddressTypeCodes; } }
|
||||
public List<TypeCodeDesc> ContactMethodCodes { get { return Internals.ContactMethodCodes; } }
|
||||
public List<TypeCodeDesc> EventTypeCodes { get { return Internals.EventTypeCodes; } }
|
||||
public List<TypeCodeDesc> OpportunityTypeCodes { get { return Internals.OpportunityTypeCodes; } }
|
||||
public List<TypeCodeDesc> ActivityReactionCodes { get { return Internals.ActivityReactionCodes; } }
|
||||
// Support.
|
||||
public List<TypeCodeDesc> UserReportTypeCodes { get { return Internals.UserReportTypeCodes; } }
|
||||
public List<TypeCodeDesc> SupportItemProcessTypeCodes { get { return Internals.SupportItemProcessTypeCodes; } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Common data components.
|
||||
// User Accounts.
|
||||
public SaxUserAccount GetUserAccount(SysId userAccountId)
|
||||
{
|
||||
return Internals.GetUserAccount(userAccountId);
|
||||
}
|
||||
public String GetUserAccountDisplayName(SysId userAccountId)
|
||||
{
|
||||
return Internals.GetUserAccountDisplayName(userAccountId);
|
||||
}
|
||||
// Organisations.
|
||||
public SaxOrganisation GetOrganisation(SysId organisationId)
|
||||
{
|
||||
return Internals.GetOrganisation(organisationId);
|
||||
}
|
||||
public String GetOrganisationName(SysId organisationId)
|
||||
{
|
||||
return Internals.GetOrganisationName(organisationId);
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Security.
|
||||
public bool ValidUserSession { get { return SessionData?.ValidUserSession ?? false; } }
|
||||
public bool AwaitingLogin { get { return !ValidUserSession; } }
|
||||
public bool KeepLoggedIn { get { return !AwaitingLogin && SessionData.User.KeepLoggedIn; } }
|
||||
/// <summary>Maximum length allowed for password entry: sign-up, change password etc.</summary>
|
||||
public int PasswordMaxLength { get; } = 32;
|
||||
|
||||
// Account actions.
|
||||
// NB The form proerty will never be used - account actions processed within page.
|
||||
public String AccountAction { get; set; }
|
||||
public IEnumerable<SelectListItem> GetAccountActions()
|
||||
{
|
||||
List<CodeDesc> items = new List<CodeDesc>();
|
||||
if (SessionData?.ValidUserSession ?? false)
|
||||
{
|
||||
items.Add("Profile", Localiser["label.view.profile"]);
|
||||
items.Add("ChangePassword", Localiser["label.change.password"]);
|
||||
items.Add("SignOut", Localiser["label.sign.out"]);
|
||||
}
|
||||
return new SelectList(items, "Code", "Desc");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Link URLs.
|
||||
public class LinksData
|
||||
{
|
||||
public String Facebook { get; } = "https://www.facebook.com/sportierre/";
|
||||
public String LinkedIn { get; } = "https://www.linkedin.com/company/sportierre/about/";
|
||||
}
|
||||
public LinksData Links { get; } = new LinksData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Copyright notice.
|
||||
public HtmlString CopyrightNotice
|
||||
{
|
||||
get
|
||||
{
|
||||
int initialYear = 2020;
|
||||
int currentYear = DateTime.Today.Year;
|
||||
return Localiser.GetHtmlText("copyright.symbol.year.html", (initialYear != currentYear ? initialYear + "-" : "") + currentYear.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>
|
||||
/// Form control value max length.
|
||||
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
|
||||
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
|
||||
/// </summary>
|
||||
public int GetControlMaxLength(SaxDbDfn_Core.ColumnIdx column)
|
||||
{
|
||||
int maxLength = (int)SaxDbDfn_Core.GetColTextLen(column);
|
||||
switch (column)
|
||||
{
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_EmailAddress:
|
||||
return Math.Min(maxLength, 60);
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_FirstName:
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_LastName:
|
||||
return Math.Min(maxLength, 40);
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_About:
|
||||
return Math.Min(maxLength, 2000);
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_Name:
|
||||
return Math.Min(maxLength, 80);
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_About:
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_Facilities:
|
||||
case SaxDbDfn_Core.ColumnIdx.Organisation_Notes:
|
||||
return Math.Min(maxLength, 2000);
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_PhoneMain:
|
||||
case SaxDbDfn_Core.ColumnIdx.UserAccount_PhoneMobile:
|
||||
return Math.Min(maxLength, 20);
|
||||
//case SaxDbDfn.ColumnIdx.UserAccount_Postcode:
|
||||
//case SaxDbDfn.ColumnIdx.ItemAddress_Postcode:
|
||||
// return Math.Min(maxLength, 16);
|
||||
case SaxDbDfn_Core.ColumnIdx.Article_Headline:
|
||||
return Math.Min(maxLength, 2000);
|
||||
case SaxDbDfn_Core.ColumnIdx.Article_Summary:
|
||||
return Math.Min(maxLength, 1000);
|
||||
}
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Form control value max length - support database.
|
||||
/// <para>In many cases the database columns allow plenty of extra space for the value, in case they need to be encrypted.</para>
|
||||
/// <para>Hence, several cases where the value entered in the control isn't allowed the full database length.</para>
|
||||
/// </summary>
|
||||
public int GetControlMaxLength(SaxDbDfn_Support.ColumnIdx column)
|
||||
{
|
||||
int maxLength = (int)SaxDbDfn_Support.GetColTextLen(column);
|
||||
switch (column)
|
||||
{
|
||||
case SaxDbDfn_Support.ColumnIdx.UserReport_Title:
|
||||
break;
|
||||
}
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Display data - Dates.
|
||||
public String FormatDateForDisplay(DateTime value)
|
||||
{
|
||||
return FormatDateForDisplay((DateTime?)value);
|
||||
}
|
||||
public String FormatDateForDisplay(DateTime? value)
|
||||
{
|
||||
return DateUtil.Format(value, "d-MMM-yy", SessionCulture);
|
||||
}
|
||||
public String FormatDateTimeForDisplay(DateTime value)
|
||||
{
|
||||
return FormatDateTimeForDisplay((DateTime?)value);
|
||||
}
|
||||
public String FormatDateTimeForDisplay(DateTime? value)
|
||||
{
|
||||
return StringUtil.TrimAll(DateUtil.Format(value, "d-MMM-yy HH:mm", SessionCulture));
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Form data - Dates.
|
||||
// Format may depend on locale, but as long as these methods are used consistently,
|
||||
// there should be consistent formatting, display and parsing of date values.
|
||||
public String CalendarDateFormat
|
||||
{
|
||||
get
|
||||
{
|
||||
// JQuery-UI DatePicker date format.
|
||||
// Format:
|
||||
// d Single digit day of month
|
||||
// dd Double digit day of month
|
||||
// D 3 letter day of week abbreviation.
|
||||
// DD Day of week.
|
||||
// m Single digit month.
|
||||
// mm Double digit month.
|
||||
// M 3 letter month abbreviation.
|
||||
// MM Month name.
|
||||
// yy 4 digit year.
|
||||
// Examples:
|
||||
// dd/mm/yy 07/04/2012
|
||||
// dd-M-yy 07-Apr-2012
|
||||
// d-M-yy 7-Apr-2012
|
||||
return "dd-M-yy";
|
||||
}
|
||||
}
|
||||
public String FormatDateForForm(DateTime? value)
|
||||
{
|
||||
return DateUtil.Format(value, "dd-MMM-yyyy", SessionCulture);
|
||||
}
|
||||
public DateTime ParseDateFromForm(String value)
|
||||
{
|
||||
DateTime date = DateUtil.ParseExact(value, "dd-MMM-yyyy", SessionCulture);
|
||||
if (!date.IsValid())
|
||||
date = DateUtil.ParseExact(value, "dd-MMM-yyyy", CultureInfo.CurrentUICulture);
|
||||
if (!date.IsValid())
|
||||
date = DateUtil.ParseExact(value, "dd-MMM-yyyy", CultureInfo.CurrentCulture);
|
||||
if (!date.IsValid())
|
||||
date = DateUtil.ParseExact(value, "dd-MMM-yyyy", CultureInfo.InvariantCulture);
|
||||
return date;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Form data.
|
||||
//-------------------------------------------------------------------
|
||||
// Common form values.
|
||||
public String FormAction { get; set; }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>List of Yes/No options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> YesNoOptionsForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc yesNo in YesNoCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetYesNoCaption((Sax.YesNo)yesNo.Type), yesNo.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Grouping for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportGroupingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportGrouping in SportGroupingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportGroupingCaption((Sax.SportGrouping)sportGrouping.Type), sportGrouping.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Speciality Labels for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportSpecialityLabelCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
values.Add(new SelectListItem("Default", ""));
|
||||
foreach (TypeCodeDesc sportSpecialityLabel in SportSpecialityLabelCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportSpecialityLabelCaption((Sax.SportSpecialityLabel)sportSpecialityLabel.Type), sportSpecialityLabel.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Participating options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportParticipatingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportParticipating in SportParticipatingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportParticipatingCaption((Sax.SportParticipating)sportParticipating.Type), sportParticipating.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Preparing options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportPreparingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportPreparing in SportPreparingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportPreparingCaption((Sax.SportPreparing)sportPreparing.Type), sportPreparing.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Sport Officiating options for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> SportOfficiatingCodesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc sportOfficiating in SportOfficiatingCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetSportOfficiatingCaption((Sax.SportOfficiating)sportOfficiating.Type), sportOfficiating.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Region Types for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> RegionTypesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationType in RegionTypeCodes.FindAll(item => item.Type != (int)Sax.RegionType.Universe && item.Type != (int)Sax.RegionType.Galaxy))
|
||||
values.Add(new SelectListItem(Localiser.GetRegionTypeCaption((Sax.RegionType)organisationType.Type), organisationType.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of titles for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> TitlesForSelect
|
||||
{
|
||||
get { return new SelectList(SaxSystem.GetCodesOfType(Sax.CodeType.Title), "Code", "Desc"); }
|
||||
}
|
||||
|
||||
/// <summary>List of eligibility types for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> EligibilityTypesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc eligibilityType in Internals.EligibilityTypeCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetEligibilityTypeCaption((Sax.EligibilityType)eligibilityType.Type), eligibilityType.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>List of Organisation Types for dropdown.</summary>
|
||||
public IEnumerable<SelectListItem> OrganisationTypesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationType in OrganisationTypeCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetOrganisationTypeCaption((Sax.OrganisationType)organisationType.Type), organisationType.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of Organisation Categories for dropdown.
|
||||
/// <para>NB Does not include 'Partner' or 'Other'.</para>
|
||||
/// </summary>
|
||||
public IEnumerable<SelectListItem> OrganisationCategoriesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes.FindAll(item => item.Type != (int)Sax.OrganisationCategory.Partner && item.Type != (int)Sax.OrganisationCategory.Other))
|
||||
values.Add(new SelectListItem(Localiser.GetOrganisationCategoryCaption((Sax.OrganisationCategory)organisationCategory.Type), organisationCategory.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List of Organisation Categories for Admin dropdown.
|
||||
/// <para>Includes complete set of categories.</para>
|
||||
/// </summary>
|
||||
public IEnumerable<SelectListItem> AdminOrganisationCategoriesForSelect
|
||||
{
|
||||
get
|
||||
{
|
||||
List<SelectListItem> values = new List<SelectListItem>();
|
||||
foreach (TypeCodeDesc organisationCategory in OrganisationCategoryCodes)
|
||||
values.Add(new SelectListItem(Localiser.GetOrganisationCategoryCaption((Sax.OrganisationCategory)organisationCategory.Type), organisationCategory.Code));
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user