v1.2.11.2
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Saxonite.Net.Core.Web.Base.Procs.Data;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Saxonite.Net.Core.Web.Admin.Models;
|
||||
using System.Collections;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Proc.Excel;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using System.IO;
|
||||
using DocumentFormat.OpenXml.EMMA;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using Saxonite.Net.Core.Web.Admin.Controllers;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Admin.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxOrganisationExporter : SaxProcess_Admin
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
public enum ExportFormat
|
||||
{
|
||||
Unknown,
|
||||
Excel_Open, /* xlxs */
|
||||
}
|
||||
|
||||
// Process configuration.
|
||||
public new class Config : SaxProcess_Admin.Config
|
||||
{
|
||||
public Config(SaxSessionContext sessionContext, SaxOrganisation.Params prmOrganisation) : base(sessionContext)
|
||||
{
|
||||
PrmOrganisation = prmOrganisation;
|
||||
}
|
||||
// Properties.
|
||||
public SaxOrganisation.Params PrmOrganisation { get; }
|
||||
public ExportFormat Format { get; set; }
|
||||
public bool IncludeId { get; set; }
|
||||
public ushort MaxRows { get; set; } = 2000;
|
||||
}
|
||||
|
||||
// Processing control data.
|
||||
private class ProcessControlData
|
||||
{
|
||||
public Config Config { get; set; }
|
||||
public SaMsSpreadsheet Spreadsheet { get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private ProcessControlData control { get; } = new ProcessControlData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxOrganisationExporter(Config config) : base(config)
|
||||
{
|
||||
control.Config = config;
|
||||
}
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Spreadsheet.
|
||||
DisposeSpreadsheet();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
private void DisposeSpreadsheet()
|
||||
{
|
||||
if (control.Spreadsheet != null)
|
||||
control.Spreadsheet.Dispose();
|
||||
control.Spreadsheet = null;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
public override string ProcessDescription => "Organisation Export";
|
||||
public byte[] GetDocumentData()
|
||||
{
|
||||
switch (control.Config.Format)
|
||||
{
|
||||
case ExportFormat.Excel_Open:
|
||||
return control.Spreadsheet.GetDocumentData();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public String GetContentType()
|
||||
{
|
||||
switch (control.Config.Format)
|
||||
{
|
||||
case ExportFormat.Excel_Open: return SacsContentTypeHelper.ContentType_Excel_Open;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run the process.
|
||||
protected override bool Run()
|
||||
{
|
||||
String fn = GetType().Name + ".Run";
|
||||
|
||||
// Verify configuration.
|
||||
if (control.Config == null)
|
||||
{
|
||||
AddErrorMessage("Configuration must be supplied.");
|
||||
SaLogger.Warn(fn + " " + GetErrorMessage());
|
||||
return OK;
|
||||
}
|
||||
if (control.Config.PrmOrganisation == null)
|
||||
{
|
||||
AddErrorMessage("Organisation params must be supplied.");
|
||||
SaLogger.Warn(fn + " " + GetErrorMessage());
|
||||
return OK;
|
||||
}
|
||||
else if (control.Config.Format != ExportFormat.Excel_Open)
|
||||
{
|
||||
AddErrorMessage("Unsupported export format: " + control.Config.Format.ToString() + ".");
|
||||
SaLogger.Warn(fn + " " + GetErrorMessage());
|
||||
}
|
||||
AddInfoMessage("Export format: " + control.Config.Format.ToString());
|
||||
|
||||
if (OK) ExportOrganisations();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private bool ExportOrganisations()
|
||||
{
|
||||
if (!OK) return false;
|
||||
|
||||
DisposeSpreadsheet();
|
||||
control.Spreadsheet = new SaMsSpreadsheet_POI();
|
||||
control.Spreadsheet.CreateWorkbook();
|
||||
control.Spreadsheet.WorksheetName = "Organisations";
|
||||
control.Spreadsheet.ApplyDefaultStyle(GetExceldefaultWorkbookStyle());
|
||||
|
||||
// Columns.
|
||||
ColumnDfn colId = AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Id);
|
||||
colId.Alignment = ColumnAlignment.Center;
|
||||
|
||||
// Columns to output.
|
||||
List<ColumnDfn> columnsExport = new List<ColumnDfn>();
|
||||
if (control.Config.IncludeId)
|
||||
columnsExport.Add(colId);
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Name));
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationCategory_Desc));
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.OrganisationType_Desc));
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Sport_Name));
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Region_Name));
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.User_Count));
|
||||
columnsExport.Add(AccountMgr.GetOrganisationColumnDfn(SaxOrganisation.Column.Member_Count));
|
||||
|
||||
// Initialise spreadsheet column headings and formats etc
|
||||
SaMsExcelColumnHeading_Config configHeading = GetExcelColumnHeadingConfig();
|
||||
control.Spreadsheet.SetColumnHeadings(columnsExport, configHeading);
|
||||
|
||||
// Columns to query - ensure Id is in the list.
|
||||
List<ColumnDfn> columnsQuery = new List<ColumnDfn>(columnsExport);
|
||||
if (!columnsQuery.Exists(item => item.Id == (int)SaxOrganisation.Column.Id))
|
||||
columnsQuery.Add(colId);
|
||||
|
||||
// Organisation params.
|
||||
control.Config.PrmOrganisation.ResultsPaging.PageNo = 1;
|
||||
control.Config.PrmOrganisation.ResultsPaging.PageLength = (ushort)Math.Min(ushort.MaxValue, Math.Max(ushort.MinValue, control.Config.MaxRows));
|
||||
|
||||
// Query Organisations.
|
||||
uint dataRowPos = configHeading.FirstDataRowPos - 1;
|
||||
using (DbResultSet resultSet = AccountMgr.ExecuteOrganisationQuery(columnsQuery, control.Config.PrmOrganisation))
|
||||
{
|
||||
while (resultSet != null && resultSet.HasRows && resultSet.NextRecord())
|
||||
{
|
||||
dataRowPos++;
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
SysId organisationId = new SysId(values.GetLong(colId.DataName));
|
||||
int colIdx = -1;
|
||||
foreach (ColumnDfn col in columnsExport)
|
||||
{
|
||||
colIdx++;
|
||||
uint dataColPos = (uint)(configHeading.FirstHeadingColPos + colIdx);
|
||||
DataValue value = AccountMgr.GetOrganisationDataValue(col, values);
|
||||
if (col.ShowZeroValues || !col.IsNumeric || value.ToNumeric() != 0.0)
|
||||
control.Spreadsheet.SetCellValue(dataColPos, dataRowPos, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Sarand.Net.Standard.Proc.Excel;
|
||||
using Sarand.Net.Standard.Proc.Procs;
|
||||
using Saxonite.Net.Core.Web.Base.Procs.Data;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Procs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Admin.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public class SaxOrganisationUploader : SaxProcess_Admin
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
|
||||
// Process configuration.
|
||||
public new class Config : SaxProcess_Admin.Config
|
||||
{
|
||||
public Config(SaxSessionContext sessionContext, IFormFile dataFile) : base(sessionContext)
|
||||
{
|
||||
this.DataFile = new UploadFileData(dataFile);
|
||||
}
|
||||
// Properties.
|
||||
public UploadFileData DataFile { get; }
|
||||
public String SheetNameOrNumber { get; set; }
|
||||
public uint HeaderRowPos { get; set; }
|
||||
public bool ValidateOnly { get; set; }
|
||||
public bool AllowCreateOrganisation { get; set; }
|
||||
public bool RequireOrganisationIdForUpdate { get; set; }
|
||||
}
|
||||
|
||||
// Supported content types.
|
||||
private static HashSet<String> validContentTypes { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
|
||||
SacsContentTypeHelper.ContentType_Excel_Legacy, /* xls */
|
||||
SacsContentTypeHelper.ContentType_Excel_Open, /* xlsx */
|
||||
SacsContentTypeHelper.ContentType_Excel_MacroEnabled, /* xlsm */
|
||||
SacsContentTypeHelper.ContentType_Excel_BinaryMacroEnabled /* xlsb */
|
||||
};
|
||||
/// <summary>Allowed content types.</summary>
|
||||
public static List<String> ValidContentTypes { get { return new List<String>(validContentTypes); } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Organisation data profile.
|
||||
private enum OrganisationProperty
|
||||
{
|
||||
// Property columns.
|
||||
OrganisationId, OrganisationName, Sport, Region,
|
||||
// Address.
|
||||
AddressLine1, AddressLine2, AddressTown, AddressPostcode,
|
||||
// Contact personal.
|
||||
ContactName, ContactRole, ContactEmail,
|
||||
// Contact.
|
||||
ContactPhone, ContactWeb, ContactMap, ContactWiki, ContactFacebook, ContactInstagram, ContactTwitter,
|
||||
}
|
||||
private class OrganisationPropertyData
|
||||
{
|
||||
public OrganisationPropertyData(OrganisationProperty property, String columnHeaders)
|
||||
{
|
||||
this.Property = property;
|
||||
foreach (String value in Regex.Split(columnHeaders, "[\\s,]+", RegexOptions.None))
|
||||
if (!StringUtil.IsBlank(value)) this.ColumnHeaders.Add(value);
|
||||
}
|
||||
public OrganisationProperty Property { get; }
|
||||
public HashSet<String> ColumnHeaders { get; } = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
public uint ColumnPos { get; set; }
|
||||
public bool IsValid { get { return ColumnPos > 0; } }
|
||||
}
|
||||
private class OrganisationDataProfile
|
||||
{
|
||||
public OrganisationDataProfile()
|
||||
{
|
||||
// Property columns.
|
||||
AddProperty(OrganisationProperty.OrganisationId, "Id, Organisation Id");
|
||||
AddProperty(OrganisationProperty.OrganisationName, "Name, Organisation Name");
|
||||
AddProperty(OrganisationProperty.Sport, "Sport");
|
||||
AddProperty(OrganisationProperty.Region, "Region");
|
||||
// Address.
|
||||
AddProperty(OrganisationProperty.AddressLine1, "Address Line 1, Line 1, Street");
|
||||
AddProperty(OrganisationProperty.AddressLine2, "Address Line 2, Line 2");
|
||||
AddProperty(OrganisationProperty.AddressTown, "Town,City,Address Town,Address City");
|
||||
AddProperty(OrganisationProperty.AddressPostcode, "Postcode");
|
||||
// Contact personal.
|
||||
AddProperty(OrganisationProperty.ContactName, "Contact Name");
|
||||
AddProperty(OrganisationProperty.ContactRole, "Contact Role");
|
||||
AddProperty(OrganisationProperty.ContactEmail, "Contatc Email");
|
||||
// Contact.
|
||||
AddProperty(OrganisationProperty.ContactPhone, "Phone");
|
||||
AddProperty(OrganisationProperty.ContactWeb, "Web, Web Link, Website");
|
||||
AddProperty(OrganisationProperty.ContactMap, "Web Map, Map Link");
|
||||
AddProperty(OrganisationProperty.ContactWiki, "Wiki, Wikipedia, Wiki Link");
|
||||
AddProperty(OrganisationProperty.ContactFacebook, "Facebook");
|
||||
AddProperty(OrganisationProperty.ContactInstagram, "Instagram");
|
||||
AddProperty(OrganisationProperty.ContactTwitter, "X, Twitter");
|
||||
}
|
||||
public Dictionary<OrganisationProperty, OrganisationPropertyData> Properties { get; } = new Dictionary<OrganisationProperty, OrganisationPropertyData>();
|
||||
private void AddProperty(OrganisationProperty property, String columnHeaders)
|
||||
{
|
||||
Properties.Add(property, new OrganisationPropertyData(property, columnHeaders));
|
||||
}
|
||||
public bool IsValidProperty(OrganisationProperty property)
|
||||
{
|
||||
OrganisationPropertyData propertyData = Properties[property];
|
||||
return propertyData != null && propertyData.IsValid;
|
||||
}
|
||||
}
|
||||
|
||||
// Processing control data.
|
||||
private class ProcessControlData
|
||||
{
|
||||
public Config config { get; set; }
|
||||
public SaMsSpreadsheet spreadsheet { get; set; }
|
||||
public OrganisationDataProfile dataProfile { get; } = new OrganisationDataProfile();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private ProcessControlData control { get; } = new ProcessControlData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
public SaxOrganisationUploader(Config config) : base(config)
|
||||
{
|
||||
control.config = config;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Properties.
|
||||
|
||||
public override string ProcessDescription => "Organisation Upload";
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Run the process.
|
||||
protected override bool Run()
|
||||
{
|
||||
String fn = GetType().Name + ".Run";
|
||||
|
||||
// Verify file.
|
||||
if (control.config.DataFile == null)
|
||||
{
|
||||
AddErrorMessage("Data file must be supplied.");
|
||||
SaLogger.Warn(fn + " " + GetErrorMessage());
|
||||
return OK;
|
||||
}
|
||||
else if (!validContentTypes.Contains(control.config.DataFile.ContentType))
|
||||
{
|
||||
AddErrorMessage("Unsupported content type: " + control.config.DataFile.ContentType + ".");
|
||||
SaLogger.Warn(fn + " " + GetErrorMessage());
|
||||
}
|
||||
AddInfoMessage("Data file: " + control.config.DataFile.FileName);
|
||||
AddInfoMessage("Sheet: " + control.config.SheetNameOrNumber);
|
||||
|
||||
try
|
||||
{
|
||||
if (OK)
|
||||
{
|
||||
// Open spreadsheet.
|
||||
using (SaMsSpreadsheet_POI spreadsheet = new SaMsSpreadsheet_POI())
|
||||
using (Stream stream = new MemoryStream())
|
||||
{
|
||||
stream.Write(control.config.DataFile.FileData, 0, (int)control.config.DataFile.Length);
|
||||
if (!spreadsheet.OpenWorkbook(stream))
|
||||
{
|
||||
// Failed to open workbook.
|
||||
String msg = "Failed to open workbook - " + spreadsheet.ErrorStateMessage;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Warn(fn + " " + msg);
|
||||
}
|
||||
if (OK && !spreadsheet.OpenWorksheet(1))
|
||||
{
|
||||
// Failed to open workbook.
|
||||
String msg = "Failed to open worksheet - " + spreadsheet.ErrorStateMessage;
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Warn(fn + " " + msg);
|
||||
}
|
||||
if (OK)
|
||||
{
|
||||
control.spreadsheet = spreadsheet;
|
||||
PopulateDataProfile();
|
||||
}
|
||||
if (OK)
|
||||
{
|
||||
uint firstRowPos = spreadsheet.FirstRowPos;
|
||||
for (uint rowPos = firstRowPos; rowPos <= firstRowPos; rowPos++)
|
||||
{
|
||||
for (uint colPos = 1; colPos <= 5; colPos++)
|
||||
{
|
||||
SaLogger.Info("Row: " + rowPos + ", colPos: " + colPos + ": '" + spreadsheet.GetCellText(colPos, rowPos) + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clean-up.
|
||||
control.spreadsheet = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
String msg = exc.GetType().Name + " uploading organisation data.";
|
||||
AddErrorMessage(msg);
|
||||
SaLogger.Warn(fn + " " + msg + " " + exc.Message + ExceptionUtil.GetStackEntries(exc, 6).Concat("", "\n\t"));
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
private bool PopulateDataProfile()
|
||||
{
|
||||
String fn = GetType().Name + ".PopulateDataProfile";
|
||||
|
||||
// Identify header row.
|
||||
uint headerRowPos = control.config.HeaderRowPos;
|
||||
if (headerRowPos < 1)
|
||||
headerRowPos = control.spreadsheet.FirstRowPos;
|
||||
|
||||
// Column headings.
|
||||
List<String> columnHeadings = new List<string>();
|
||||
for (uint colPos = 1; colPos <= 100; colPos++)
|
||||
{
|
||||
columnHeadings.Add(control.spreadsheet.GetCellText(colPos, headerRowPos));
|
||||
}
|
||||
SaLogger.Info(fn + " Column headings: " + columnHeadings.Concat(", "));
|
||||
|
||||
// Populate header columns in profile.
|
||||
foreach (OrganisationPropertyData propertyData in control.dataProfile.Properties.Values)
|
||||
{
|
||||
for (uint colPos = 1; colPos <= columnHeadings.Count; colPos++)
|
||||
{
|
||||
String columnHeading = columnHeadings[(int)(colPos - 1)];
|
||||
if (propertyData.ColumnHeaders.ContainsCI(columnHeading))
|
||||
{
|
||||
if (control.dataProfile.Properties.Values.ToList().Exists(item => item.ColumnPos == colPos))
|
||||
AddWarningMessage("Multiple properties associated with column '" + columnHeading + "'.");
|
||||
propertyData.ColumnPos = colPos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!control.dataProfile.IsValidProperty(OrganisationProperty.OrganisationId) && !control.dataProfile.IsValidProperty(OrganisationProperty.OrganisationName))
|
||||
return AddErrorMessage("Organisation Id or Name column must be present");
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Sarand.Net.Standard.Proc.Excel;
|
||||
using Sarand.Net.Standard.Proc.Procs;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Procs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Admin.Procs
|
||||
{
|
||||
//=======================================================================
|
||||
public abstract class SaxProcess_Admin : SaxProcess_Base
|
||||
{
|
||||
//-------------------------------------------------------------------
|
||||
// Definitions.
|
||||
public new class Config : SaxProcess_Base.Config
|
||||
{
|
||||
protected Config(SaxSessionContext sessionContext) : base(sessionContext)
|
||||
{ }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data members.
|
||||
private Config config;
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Construction.
|
||||
protected SaxProcess_Admin(Config config) : base(config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Excel Spreadsheet utilities.
|
||||
//-------------------------------------------------------------------
|
||||
protected SaMsExcelColumnHeading_Config GetExcelColumnHeadingConfig()
|
||||
{
|
||||
SaMsExcelColumnHeading_Config configHeading = new SaMsExcelColumnHeading_Config();
|
||||
configHeading.FontAttr.Name = "Arial";
|
||||
configHeading.FontAttr.HeightInPoints = 10;
|
||||
configHeading.FontAttr.ColourIndex = SaXlColourIndex.DarkBlue;
|
||||
configHeading.FontAttr.Bold = true;
|
||||
configHeading.HeadingTextWrap = true;
|
||||
configHeading.ForegroundColourIndex = SaXlColourIndex.LightYellow;
|
||||
configHeading.BorderWeight = SaXlBorderWeight.Medium;
|
||||
return configHeading;
|
||||
}
|
||||
|
||||
protected SaMsExcelStyleAttr GetExceldefaultWorkbookStyle()
|
||||
{
|
||||
SaMsExcelStyleAttr styleAttr = new SaMsExcelStyleAttr();
|
||||
styleAttr.FontAttr.Name = "Arial";
|
||||
styleAttr.FontAttr.HeightInPoints = 9;
|
||||
styleAttr.FontAttr.ColourIndex = SaXlColourIndex.Black;
|
||||
styleAttr.HorizontalAlignment = (short)XlHAlign.xlHAlignLeft;
|
||||
styleAttr.VerticalAlignment = (short)XlVAlign.xlVAlignTop;
|
||||
styleAttr.WrapText = true;
|
||||
return styleAttr;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
}
|
||||
Reference in New Issue
Block a user